From 68b10d413baa865c8471fc575f03428024842816 Mon Sep 17 00:00:00 2001 From: mikahjc Date: Tue, 14 Dec 2021 00:28:25 -0700 Subject: [PATCH] latest SDK --- foobar2000/SDK/abort_callback.cpp | 42 + foobar2000/SDK/abort_callback.h | 119 ++ foobar2000/SDK/advconfig.cpp | 47 + foobar2000/SDK/advconfig.h | 333 ++++ foobar2000/SDK/album_art.cpp | 185 ++ foobar2000/SDK/album_art.h | 260 +++ foobar2000/SDK/album_art_helpers.h | 157 ++ foobar2000/SDK/app_close_blocker.cpp | 30 + foobar2000/SDK/app_close_blocker.h | 88 + foobar2000/SDK/archive.h | 85 + foobar2000/SDK/audio_chunk.cpp | 702 ++++++++ foobar2000/SDK/audio_chunk.h | 383 ++++ foobar2000/SDK/audio_chunk_channel_config.cpp | 210 +++ foobar2000/SDK/audio_chunk_impl.h | 3 + foobar2000/SDK/audio_postprocessor.h | 27 + foobar2000/SDK/autoplaylist.h | 104 ++ foobar2000/SDK/cfg_var.cpp | 60 + foobar2000/SDK/cfg_var.h | 292 +++ foobar2000/SDK/chapterizer.cpp | 43 + foobar2000/SDK/chapterizer.h | 91 + foobar2000/SDK/commandline.cpp | 12 + foobar2000/SDK/commandline.h | 41 + foobar2000/SDK/commonObjects.h | 10 + foobar2000/SDK/completion_notify.cpp | 87 + foobar2000/SDK/completion_notify.h | 85 + foobar2000/SDK/component.h | 56 + foobar2000/SDK/component_client.h | 6 + foobar2000/SDK/components_menu.h | 7 + foobar2000/SDK/componentversion.cpp | 36 + foobar2000/SDK/componentversion.h | 93 + foobar2000/SDK/config_io_callback.cpp | 14 + foobar2000/SDK/config_io_callback.h | 43 + foobar2000/SDK/config_object.cpp | 210 +++ foobar2000/SDK/config_object.h | 85 + foobar2000/SDK/config_object_impl.h | 173 ++ foobar2000/SDK/console.cpp | 82 + foobar2000/SDK/console.h | 53 + foobar2000/SDK/contextmenu.h | 341 ++++ foobar2000/SDK/contextmenu_manager.h | 130 ++ foobar2000/SDK/core_api.h | 41 + foobar2000/SDK/coreversion.h | 39 + foobar2000/SDK/decode_postprocessor.h | 170 ++ foobar2000/SDK/dsp.cpp | 556 ++++++ foobar2000/SDK/dsp.h | 558 ++++++ foobar2000/SDK/dsp_manager.cpp | 209 +++ foobar2000/SDK/dsp_manager.h | 105 ++ foobar2000/SDK/event_logger.h | 50 + foobar2000/SDK/exceptions.h | 13 + foobar2000/SDK/file_cached_impl.cpp | 387 ++++ foobar2000/SDK/file_format_sanitizer.h | 27 + foobar2000/SDK/file_info.cpp | 769 ++++++++ foobar2000/SDK/file_info.h | 287 +++ foobar2000/SDK/file_info_filter.h | 46 + foobar2000/SDK/file_info_filter_impl.h | 33 + foobar2000/SDK/file_info_impl.cpp | 243 +++ foobar2000/SDK/file_info_impl.h | 141 ++ foobar2000/SDK/file_info_merge.cpp | 190 ++ foobar2000/SDK/file_lock_manager.h | 77 + foobar2000/SDK/file_operation_callback.cpp | 131 ++ foobar2000/SDK/file_operation_callback.h | 62 + foobar2000/SDK/filesystem.cpp | 1599 +++++++++++++++++ foobar2000/SDK/filesystem.h | 837 +++++++++ foobar2000/SDK/filesystem_helper.cpp | 259 +++ foobar2000/SDK/filesystem_helper.h | 616 +++++++ foobar2000/SDK/filesystem_transacted.h | 44 + foobar2000/SDK/foobar2000-dsp.h | 5 + foobar2000/SDK/foobar2000-pfc.h | 14 + foobar2000/SDK/foobar2000-winver.h | 15 + foobar2000/SDK/foobar2000.h | 130 ++ foobar2000/SDK/foobar2000_SDK.vcxproj | 292 +++ foobar2000/SDK/foobar2000_SDK.vcxproj.filters | 488 +++++ foobar2000/SDK/foosort.cpp | 154 ++ foobar2000/SDK/foosort.h | 10 + foobar2000/SDK/genrand.h | 42 + foobar2000/SDK/guids.cpp | 1413 +++++++++++++++ foobar2000/SDK/hasher_md5.cpp | 43 + foobar2000/SDK/hasher_md5.h | 94 + foobar2000/SDK/http_client.h | 57 + foobar2000/SDK/icon_remap.h | 28 + foobar2000/SDK/imageLoaderLite.h | 52 + foobar2000/SDK/imageViewer.h | 15 + foobar2000/SDK/info_lookup_handler.h | 32 + foobar2000/SDK/initquit.h | 41 + foobar2000/SDK/input.cpp | 450 +++++ foobar2000/SDK/input.h | 545 ++++++ foobar2000/SDK/input_file_type.cpp | 130 ++ foobar2000/SDK/input_file_type.h | 109 ++ foobar2000/SDK/input_impl.h | 450 +++++ foobar2000/SDK/library_manager.h | 206 +++ foobar2000/SDK/link_resolver.cpp | 17 + foobar2000/SDK/link_resolver.h | 33 + foobar2000/SDK/main_thread_callback.cpp | 37 + foobar2000/SDK/main_thread_callback.h | 187 ++ foobar2000/SDK/mainmenu.cpp | 57 + foobar2000/SDK/mem_block_container.cpp | 12 + foobar2000/SDK/mem_block_container.h | 96 + foobar2000/SDK/menu.h | 225 +++ foobar2000/SDK/menu_helpers.cpp | 295 +++ foobar2000/SDK/menu_helpers.h | 183 ++ foobar2000/SDK/menu_item.cpp | 61 + foobar2000/SDK/menu_manager.cpp | 416 +++++ foobar2000/SDK/message_loop.h | 91 + foobar2000/SDK/metadb.cpp | 112 ++ foobar2000/SDK/metadb.h | 447 +++++ foobar2000/SDK/metadb_handle.cpp | 128 ++ foobar2000/SDK/metadb_handle.h | 262 +++ foobar2000/SDK/metadb_handle_list.cpp | 419 +++++ foobar2000/SDK/modeless_dialog.h | 17 + foobar2000/SDK/ole_interaction.h | 148 ++ foobar2000/SDK/output.cpp | 231 +++ foobar2000/SDK/output.h | 414 +++++ foobar2000/SDK/packet_decoder.cpp | 63 + foobar2000/SDK/packet_decoder.h | 159 ++ foobar2000/SDK/play_callback.h | 152 ++ foobar2000/SDK/playable_location.cpp | 76 + foobar2000/SDK/playable_location.h | 92 + foobar2000/SDK/playback_control.cpp | 139 ++ foobar2000/SDK/playback_control.h | 194 ++ foobar2000/SDK/playback_stream_capture.h | 25 + foobar2000/SDK/playlist.cpp | 931 ++++++++++ foobar2000/SDK/playlist.h | 897 +++++++++ foobar2000/SDK/playlist_loader.cpp | 362 ++++ foobar2000/SDK/playlist_loader.h | 132 ++ foobar2000/SDK/popup_message.cpp | 137 ++ foobar2000/SDK/popup_message.h | 141 ++ foobar2000/SDK/preferences_page.cpp | 10 + foobar2000/SDK/preferences_page.h | 150 ++ foobar2000/SDK/progress_meter.h | 20 + foobar2000/SDK/replaygain.cpp | 226 +++ foobar2000/SDK/replaygain.h | 89 + foobar2000/SDK/replaygain_info.cpp | 161 ++ foobar2000/SDK/replaygain_scanner.h | 112 ++ foobar2000/SDK/resampler.h | 75 + foobar2000/SDK/search_tools.cpp | 7 + foobar2000/SDK/search_tools.h | 71 + foobar2000/SDK/service.cpp | 68 + foobar2000/SDK/service.h | 845 +++++++++ foobar2000/SDK/service_by_guid.h | 104 ++ foobar2000/SDK/service_compat.h | 38 + foobar2000/SDK/service_impl.h | 124 ++ foobar2000/SDK/shortcut_actions.h | 1 + foobar2000/SDK/stdafx.cpp | 2 + foobar2000/SDK/system_time_keeper.h | 47 + foobar2000/SDK/tag_processor.cpp | 180 ++ foobar2000/SDK/tag_processor.h | 104 ++ foobar2000/SDK/tag_processor_id3v2.cpp | 112 ++ foobar2000/SDK/threaded_process.cpp | 118 ++ foobar2000/SDK/threaded_process.h | 153 ++ foobar2000/SDK/titleformat.cpp | 187 ++ foobar2000/SDK/titleformat.h | 243 +++ foobar2000/SDK/track_property.cpp | 70 + foobar2000/SDK/track_property.h | 98 + foobar2000/SDK/tracks.h | 34 + foobar2000/SDK/ui.cpp | 102 ++ foobar2000/SDK/ui.h | 256 +++ foobar2000/SDK/ui_edit_context.h | 109 ++ foobar2000/SDK/ui_element.cpp | 223 +++ foobar2000/SDK/ui_element.h | 577 ++++++ .../SDK/ui_element_typable_window_manager.h | 14 + foobar2000/SDK/unpack.h | 22 + foobar2000/SDK/utility.cpp | 77 + foobar2000/SDK/vis.h | 77 + .../foo_input_validator.dll | Bin 0 -> 194048 bytes foobar2000/foo_input_validator/readme.txt | 32 + foobar2000/foo_sample/IO.cpp | 329 ++++ foobar2000/foo_sample/PCH.cpp | 3 + foobar2000/foo_sample/contextmenu.cpp | 174 ++ foobar2000/foo_sample/decode.cpp | 77 + foobar2000/foo_sample/dsp.cpp | 154 ++ foobar2000/foo_sample/foo_sample.rc | 227 +++ foobar2000/foo_sample/foo_sample.sln | 55 + foobar2000/foo_sample/foo_sample.vcxproj | 159 ++ .../foo_sample/foo_sample.vcxproj.filters | 103 ++ foobar2000/foo_sample/initquit.cpp | 15 + foobar2000/foo_sample/input_raw.cpp | 103 ++ .../foo_sample/listcontrol-advanced.cpp | 323 ++++ .../foo_sample/listcontrol-ownerdata.cpp | 204 +++ foobar2000/foo_sample/listcontrol-simple.cpp | 163 ++ foobar2000/foo_sample/main.cpp | 11 + foobar2000/foo_sample/mainmenu-dynamic.cpp | 120 ++ foobar2000/foo_sample/mainmenu.cpp | 120 ++ foobar2000/foo_sample/playback_state.cpp | 155 ++ .../foo_sample/playback_stream_capture.cpp | 121 ++ .../foo_sample/playback_stream_capture.h | 4 + foobar2000/foo_sample/preferences.cpp | 111 ++ foobar2000/foo_sample/rating.cpp | 592 ++++++ foobar2000/foo_sample/readme.txt | 32 + foobar2000/foo_sample/resource.h | 46 + foobar2000/foo_sample/stdafx.h | 2 + foobar2000/foo_sample/ui_and_threads.cpp | 286 +++ foobar2000/foo_sample/ui_element.cpp | 82 + foobar2000/foo_sample/ui_element_dialog.cpp | 178 ++ .../component_client.cpp | 123 ++ .../foobar2000_component_client.vcxproj | 102 ++ foobar2000/helpers/AutoComplete.cpp | 49 + foobar2000/helpers/AutoComplete.h | 7 + foobar2000/helpers/BumpableElem.h | 73 + foobar2000/helpers/CDialogResizeHelper.h | 24 + foobar2000/helpers/COM_utils.h | 6 + foobar2000/helpers/CPropVariant.h | 3 + foobar2000/helpers/CSingleThreadWrapper.h | 91 + .../helpers/CTableEditHelper-Legacy.cpp | 80 + foobar2000/helpers/CTableEditHelper-Legacy.h | 35 + foobar2000/helpers/CallForwarder.h | 60 + foobar2000/helpers/CmdThread.h | 132 ++ foobar2000/helpers/ProcessUtils.h | 279 +++ foobar2000/helpers/ProfileCache.h | 39 + foobar2000/helpers/StdAfx.cpp | 6 + foobar2000/helpers/StdAfx.h | 20 + foobar2000/helpers/ThreadUtils.cpp | 139 ++ foobar2000/helpers/ThreadUtils.h | 57 + foobar2000/helpers/VisUtils.cpp | 38 + foobar2000/helpers/VisUtils.h | 10 + foobar2000/helpers/VolumeMap.cpp | 22 + foobar2000/helpers/VolumeMap.h | 7 + foobar2000/helpers/WindowPositionUtils.h | 391 ++++ foobar2000/helpers/advconfig_impl.h | 3 + foobar2000/helpers/advconfig_runtime.h | 45 + foobar2000/helpers/album_art_helpers.h | 2 + foobar2000/helpers/atl-misc.h | 291 +++ foobar2000/helpers/bitreader_helper.h | 154 ++ foobar2000/helpers/cfg_guidlist.h | 31 + .../helpers/create_directory_helper.cpp | 194 ++ foobar2000/helpers/create_directory_helper.h | 31 + foobar2000/helpers/cue_creator.cpp | 202 +++ foobar2000/helpers/cue_creator.h | 24 + foobar2000/helpers/cue_parser.cpp | 856 +++++++++ foobar2000/helpers/cue_parser.h | 360 ++++ foobar2000/helpers/cue_parser_embedding.cpp | 383 ++++ foobar2000/helpers/cuesheet_index_list.cpp | 145 ++ foobar2000/helpers/cuesheet_index_list.h | 35 + foobar2000/helpers/dialog_resize_helper.cpp | 164 ++ foobar2000/helpers/dialog_resize_helper.h | 40 + foobar2000/helpers/dropdown_helper.cpp | 174 ++ foobar2000/helpers/dropdown_helper.h | 56 + foobar2000/helpers/duration_counter.h | 94 + foobar2000/helpers/dynamic_bitrate_helper.cpp | 76 + foobar2000/helpers/dynamic_bitrate_helper.h | 17 + foobar2000/helpers/fb2k_threads.h | 88 + foobar2000/helpers/fb2k_wfx.h | 38 + foobar2000/helpers/fileReadAhead.h | 4 + foobar2000/helpers/file_cached.h | 1 + foobar2000/helpers/file_info_const_impl.cpp | 287 +++ foobar2000/helpers/file_info_const_impl.h | 80 + foobar2000/helpers/file_list_helper.cpp | 79 + foobar2000/helpers/file_list_helper.h | 28 + foobar2000/helpers/file_move_helper.cpp | 40 + foobar2000/helpers/file_move_helper.h | 10 + foobar2000/helpers/file_readonly.h | 4 + foobar2000/helpers/file_win32_wrapper.cpp | 281 +++ foobar2000/helpers/file_win32_wrapper.h | 254 +++ foobar2000/helpers/filetimetools.cpp | 103 ++ foobar2000/helpers/filetimetools.h | 25 + foobar2000/helpers/foobar2000+atl.h | 16 + .../helpers/foobar2000_sdk_helpers.vcxproj | 250 +++ .../foobar2000_sdk_helpers.vcxproj.filters | 350 ++++ foobar2000/helpers/fullFileBuffer.h | 18 + foobar2000/helpers/helpers.h | 41 + foobar2000/helpers/icon_remapping_wildcard.h | 15 + foobar2000/helpers/image_load_save.cpp | 88 + foobar2000/helpers/image_load_save.h | 13 + foobar2000/helpers/inplace_edit.cpp | 25 + foobar2000/helpers/inplace_edit.h | 13 + foobar2000/helpers/input_fix_seeking.h | 65 + foobar2000/helpers/input_helper_cue.cpp | 221 +++ foobar2000/helpers/input_helper_cue.h | 33 + foobar2000/helpers/input_helpers.cpp | 561 ++++++ foobar2000/helpers/input_helpers.h | 130 ++ foobar2000/helpers/input_logging.h | 35 + foobar2000/helpers/input_stream_info_reader.h | 35 + foobar2000/helpers/meta_table_builder.h | 143 ++ foobar2000/helpers/metadb_handle_set.h | 71 + foobar2000/helpers/metadb_io_hintlist.h | 33 + foobar2000/helpers/mp3_utils.cpp | 276 +++ foobar2000/helpers/mp3_utils.h | 75 + .../helpers/packet_decoder_aac_common.cpp | 247 +++ .../helpers/packet_decoder_aac_common.h | 37 + .../helpers/packet_decoder_mp3_common.cpp | 44 + .../helpers/packet_decoder_mp3_common.h | 84 + .../playlist_position_reference_tracker.h | 75 + .../helpers/reader_pretend_nonseekable.h | 61 + foobar2000/helpers/readers.cpp | 383 ++++ foobar2000/helpers/readers.h | 314 ++++ foobar2000/helpers/rethrow.h | 15 + foobar2000/helpers/seekabilizer.cpp | 221 +++ foobar2000/helpers/seekabilizer.h | 38 + foobar2000/helpers/stream_buffer_helper.cpp | 89 + foobar2000/helpers/stream_buffer_helper.h | 29 + foobar2000/helpers/tag_write_callback_impl.h | 79 + foobar2000/helpers/text_file_loader.cpp | 124 ++ foobar2000/helpers/text_file_loader.h | 14 + foobar2000/helpers/text_file_loader_v2.cpp | 25 + foobar2000/helpers/text_file_loader_v2.h | 14 + .../helpers/track_property_callback_impl.cpp | 124 ++ .../helpers/track_property_callback_impl.h | 51 + foobar2000/helpers/ui_element_helpers.cpp | 390 ++++ foobar2000/helpers/ui_element_helpers.h | 377 ++++ foobar2000/helpers/win32_dialog.cpp | 294 +++ foobar2000/helpers/win32_dialog.h | 122 ++ foobar2000/helpers/win32_misc.cpp | 215 +++ foobar2000/helpers/win32_misc.h | 168 ++ .../helpers/window_placement_helper.cpp | 231 +++ foobar2000/helpers/window_placement_helper.h | 51 + foobar2000/helpers/winmm-types.h | 4 + foobar2000/helpers/writer_wav.cpp | 386 ++++ foobar2000/helpers/writer_wav.h | 53 + foobar2000/shared/audio_math.h | 65 + foobar2000/shared/fb2kdebug.h | 131 ++ foobar2000/shared/filedialogs.h | 7 + foobar2000/shared/shared.h | 566 ++++++ foobar2000/shared/shared.lib | Bin 0 -> 38516 bytes foobar2000/shared/win32_misc.h | 38 + libPPUI/AutoComplete.cpp | 77 + libPPUI/AutoComplete.h | 12 + libPPUI/CButtonLite.h | 272 +++ libPPUI/CDialogResizeHelper.cpp | 194 ++ libPPUI/CDialogResizeHelper.h | 67 + libPPUI/CDialogResizeHelperCompat.h | 16 + libPPUI/CEditWithButtons.cpp | 178 ++ libPPUI/CEditWithButtons.h | 213 +++ libPPUI/CEnumString.h | 99 + libPPUI/CFlashWindow.h | 69 + libPPUI/CHeaderCtrlEx.h | 15 + libPPUI/CIconOverlayWindow.h | 47 + libPPUI/CListAccessible.cpp | 744 ++++++++ libPPUI/CListAccessible.h | 292 +++ libPPUI/CListControl-Cell.h | 39 + libPPUI/CListControl-Cells-Compat.h | 13 + libPPUI/CListControl-Cells.cpp | 298 +++ libPPUI/CListControl-Cells.h | 94 + libPPUI/CListControl.cpp | 989 ++++++++++ libPPUI/CListControl.h | 328 ++++ libPPUI/CListControlComplete.h | 22 + libPPUI/CListControlHeaderImpl.cpp | 1078 +++++++++++ libPPUI/CListControlHeaderImpl.h | 224 +++ libPPUI/CListControlOwnerData.h | 206 +++ libPPUI/CListControlSimple.h | 137 ++ libPPUI/CListControlTruncationTooltipImpl.cpp | 209 +++ libPPUI/CListControlTruncationTooltipImpl.h | 51 + libPPUI/CListControlUserOptions.h | 9 + libPPUI/CListControlWithSelection.cpp | 1587 ++++++++++++++++ libPPUI/CListControlWithSelection.h | 265 +++ libPPUI/CListControl_EditImpl.h | 62 + libPPUI/CListViewCtrlEx.h | 34 + libPPUI/CMiddleDragImpl.cpp | 40 + libPPUI/CMiddleDragImpl.h | 280 +++ libPPUI/CPopupTooltipMessage.h | 101 ++ libPPUI/CPowerRequest.cpp | 91 + libPPUI/CPowerRequest.h | 115 ++ libPPUI/CPropVariant.h | 54 + libPPUI/CWindowCreateAndDelete.h | 11 + libPPUI/Controls.cpp | 50 + libPPUI/Controls.h | 101 ++ libPPUI/GDIUtils.h | 261 +++ libPPUI/IDI_SCROLL.ico | Bin 0 -> 766 bytes libPPUI/IDI_SCROLL.txt | 3 + libPPUI/IDataObjectUtils.cpp | 187 ++ libPPUI/IDataObjectUtils.h | 228 +++ libPPUI/InPlaceCombo.cpp | 349 ++++ libPPUI/InPlaceEdit.cpp | 520 ++++++ libPPUI/InPlaceEdit.h | 44 + libPPUI/InPlaceEditTable.cpp | 221 +++ libPPUI/InPlaceEditTable.h | 87 + libPPUI/PaintUtils.cpp | 502 ++++++ libPPUI/PaintUtils.h | 53 + libPPUI/SmartStrStr.cpp | 195 ++ libPPUI/SmartStrStr.h | 42 + libPPUI/TreeMultiSel.h | 397 ++++ libPPUI/TypeFind.cpp | 53 + libPPUI/TypeFind.h | 6 + libPPUI/clipboard.cpp | 51 + libPPUI/clipboard.h | 33 + libPPUI/commandline_parser.cpp | 65 + libPPUI/commandline_parser.h | 18 + libPPUI/gdiplus-helpers-webp.h | 46 + libPPUI/gdiplus_helpers.h | 233 +++ libPPUI/gesture.h | 264 +++ libPPUI/libPPUI-license.txt | 17 + libPPUI/libPPUI-readme.txt | 3 + libPPUI/libPPUI.vcxproj | 260 +++ libPPUI/libPPUI.vcxproj.filters | 260 +++ libPPUI/link-CommonControls6.h | 3 + libPPUI/listview_helper.cpp | 284 +++ libPPUI/listview_helper.h | 49 + libPPUI/pp-COM-macros.h | 13 + libPPUI/ppresources.h | 10 + libPPUI/stdafx.cpp | 1 + libPPUI/stdafx.h | 21 + libPPUI/targetver.h | 6 + libPPUI/win32_op.cpp | 36 + libPPUI/win32_op.h | 36 + libPPUI/win32_utility.cpp | 218 +++ libPPUI/win32_utility.h | 48 + libPPUI/wtl-pp.h | 474 +++++ pfc/alloc.h | 539 ++++++ pfc/array.h | 361 ++++ pfc/audio_math.cpp | 141 ++ pfc/audio_sample.cpp | 73 + pfc/audio_sample.h | 92 + pfc/autoref.h | 38 + pfc/avltree.h | 570 ++++++ pfc/base64.cpp | 118 ++ pfc/base64.h | 18 + pfc/binary_search.h | 83 + pfc/bit_array.cpp | 182 ++ pfc/bit_array.h | 69 + pfc/bit_array_impl.h | 200 +++ pfc/bit_array_impl_part2.h | 47 + pfc/bsearch.cpp | 19 + pfc/bsearch.h | 89 + pfc/bsearch_inline.h | 50 + pfc/byte_order.h | 256 +++ pfc/chain_list_v2.h | 296 +++ pfc/cmd_thread.h | 46 + pfc/com_ptr_t.h | 87 + pfc/cpuid.cpp | 64 + pfc/cpuid.h | 24 + pfc/event.h | 23 + pfc/filehandle.cpp | 33 + pfc/filehandle.h | 31 + pfc/guid.cpp | 196 ++ pfc/guid.h | 41 + pfc/instance_tracker.h | 51 + pfc/int_types.h | 95 + pfc/iterators.h | 140 ++ pfc/list.h | 640 +++++++ pfc/lockless.h | 72 + pfc/map.h | 268 +++ pfc/memalign.h | 137 ++ pfc/nix-objects.cpp | 274 +++ pfc/nix-objects.h | 110 ++ pfc/notifyList.h | 85 + pfc/obj-c.mm | 61 + pfc/order_helper.h | 73 + pfc/other.cpp | 339 ++++ pfc/other.h | 289 +++ pfc/pathUtils.cpp | 297 +++ pfc/pathUtils.h | 42 + pfc/pfc-fb2k-hooks.cpp | 4 + pfc/pfc-fb2k-hooks.h | 9 + pfc/pfc-license.txt | 17 + pfc/pfc-readme.txt | 9 + pfc/pfc.h | 231 +++ pfc/pfc.vcxproj | 521 ++++++ pfc/pfc.vcxproj.filters | 290 +++ pfc/pocket_char_ops.h | 245 +++ pfc/pool.h | 42 + pfc/pp-gettickcount.h | 16 + pfc/pp-winapi.h | 105 ++ pfc/primitives.h | 933 ++++++++++ pfc/primitives_part2.h | 26 + pfc/printf.cpp | 121 ++ pfc/ptr_list.h | 43 + pfc/ptrholder.h | 3 + pfc/rcptr.h | 177 ++ pfc/ref_counter.h | 104 ++ pfc/selftest.cpp | 94 + pfc/sort.cpp | 268 +++ pfc/sort.h | 192 ++ pfc/splitString.h | 3 + pfc/stdafx.cpp | 2 + pfc/stdsort.h | 28 + pfc/string8_impl.h | 119 ++ pfc/stringNew.cpp | 82 + pfc/stringNew.h | 262 +++ pfc/string_base.cpp | 1367 ++++++++++++++ pfc/string_base.h | 1112 ++++++++++++ pfc/string_conv.cpp | 499 +++++ pfc/string_conv.h | 584 ++++++ pfc/string_list.h | 41 + pfc/suppress_fb2k_hooks.h | 19 + pfc/syncd_storage.h | 95 + pfc/synchro.h | 16 + pfc/synchro_nix.cpp | 33 + pfc/synchro_nix.h | 146 ++ pfc/synchro_win.h | 114 ++ pfc/targetver.h | 6 + pfc/threads.cpp | 210 +++ pfc/threads.h | 77 + pfc/timers.cpp | 112 ++ pfc/timers.h | 145 ++ pfc/traits.h | 85 + pfc/utf8.cpp | 107 ++ pfc/wait_queue.h | 190 ++ pfc/weakRef.h | 71 + pfc/wildcard.cpp | 50 + pfc/wildcard.h | 10 + pfc/win-objects.cpp | 431 +++++ pfc/win-objects.h | 341 ++++ sdk-license.txt | 12 + sdk-readme.css | 1291 +++++++++++++ sdk-readme.html | 724 ++++++++ 492 files changed, 80542 insertions(+) create mode 100644 foobar2000/SDK/abort_callback.cpp create mode 100644 foobar2000/SDK/abort_callback.h create mode 100644 foobar2000/SDK/advconfig.cpp create mode 100644 foobar2000/SDK/advconfig.h create mode 100644 foobar2000/SDK/album_art.cpp create mode 100644 foobar2000/SDK/album_art.h create mode 100644 foobar2000/SDK/album_art_helpers.h create mode 100644 foobar2000/SDK/app_close_blocker.cpp create mode 100644 foobar2000/SDK/app_close_blocker.h create mode 100644 foobar2000/SDK/archive.h create mode 100644 foobar2000/SDK/audio_chunk.cpp create mode 100644 foobar2000/SDK/audio_chunk.h create mode 100644 foobar2000/SDK/audio_chunk_channel_config.cpp create mode 100644 foobar2000/SDK/audio_chunk_impl.h create mode 100644 foobar2000/SDK/audio_postprocessor.h create mode 100644 foobar2000/SDK/autoplaylist.h create mode 100644 foobar2000/SDK/cfg_var.cpp create mode 100644 foobar2000/SDK/cfg_var.h create mode 100644 foobar2000/SDK/chapterizer.cpp create mode 100644 foobar2000/SDK/chapterizer.h create mode 100644 foobar2000/SDK/commandline.cpp create mode 100644 foobar2000/SDK/commandline.h create mode 100644 foobar2000/SDK/commonObjects.h create mode 100644 foobar2000/SDK/completion_notify.cpp create mode 100644 foobar2000/SDK/completion_notify.h create mode 100644 foobar2000/SDK/component.h create mode 100644 foobar2000/SDK/component_client.h create mode 100644 foobar2000/SDK/components_menu.h create mode 100644 foobar2000/SDK/componentversion.cpp create mode 100644 foobar2000/SDK/componentversion.h create mode 100644 foobar2000/SDK/config_io_callback.cpp create mode 100644 foobar2000/SDK/config_io_callback.h create mode 100644 foobar2000/SDK/config_object.cpp create mode 100644 foobar2000/SDK/config_object.h create mode 100644 foobar2000/SDK/config_object_impl.h create mode 100644 foobar2000/SDK/console.cpp create mode 100644 foobar2000/SDK/console.h create mode 100644 foobar2000/SDK/contextmenu.h create mode 100644 foobar2000/SDK/contextmenu_manager.h create mode 100644 foobar2000/SDK/core_api.h create mode 100644 foobar2000/SDK/coreversion.h create mode 100644 foobar2000/SDK/decode_postprocessor.h create mode 100644 foobar2000/SDK/dsp.cpp create mode 100644 foobar2000/SDK/dsp.h create mode 100644 foobar2000/SDK/dsp_manager.cpp create mode 100644 foobar2000/SDK/dsp_manager.h create mode 100644 foobar2000/SDK/event_logger.h create mode 100644 foobar2000/SDK/exceptions.h create mode 100644 foobar2000/SDK/file_cached_impl.cpp create mode 100644 foobar2000/SDK/file_format_sanitizer.h create mode 100644 foobar2000/SDK/file_info.cpp create mode 100644 foobar2000/SDK/file_info.h create mode 100644 foobar2000/SDK/file_info_filter.h create mode 100644 foobar2000/SDK/file_info_filter_impl.h create mode 100644 foobar2000/SDK/file_info_impl.cpp create mode 100644 foobar2000/SDK/file_info_impl.h create mode 100644 foobar2000/SDK/file_info_merge.cpp create mode 100644 foobar2000/SDK/file_lock_manager.h create mode 100644 foobar2000/SDK/file_operation_callback.cpp create mode 100644 foobar2000/SDK/file_operation_callback.h create mode 100644 foobar2000/SDK/filesystem.cpp create mode 100644 foobar2000/SDK/filesystem.h create mode 100644 foobar2000/SDK/filesystem_helper.cpp create mode 100644 foobar2000/SDK/filesystem_helper.h create mode 100644 foobar2000/SDK/filesystem_transacted.h create mode 100644 foobar2000/SDK/foobar2000-dsp.h create mode 100644 foobar2000/SDK/foobar2000-pfc.h create mode 100644 foobar2000/SDK/foobar2000-winver.h create mode 100644 foobar2000/SDK/foobar2000.h create mode 100644 foobar2000/SDK/foobar2000_SDK.vcxproj create mode 100644 foobar2000/SDK/foobar2000_SDK.vcxproj.filters create mode 100644 foobar2000/SDK/foosort.cpp create mode 100644 foobar2000/SDK/foosort.h create mode 100644 foobar2000/SDK/genrand.h create mode 100644 foobar2000/SDK/guids.cpp create mode 100644 foobar2000/SDK/hasher_md5.cpp create mode 100644 foobar2000/SDK/hasher_md5.h create mode 100644 foobar2000/SDK/http_client.h create mode 100644 foobar2000/SDK/icon_remap.h create mode 100644 foobar2000/SDK/imageLoaderLite.h create mode 100644 foobar2000/SDK/imageViewer.h create mode 100644 foobar2000/SDK/info_lookup_handler.h create mode 100644 foobar2000/SDK/initquit.h create mode 100644 foobar2000/SDK/input.cpp create mode 100644 foobar2000/SDK/input.h create mode 100644 foobar2000/SDK/input_file_type.cpp create mode 100644 foobar2000/SDK/input_file_type.h create mode 100644 foobar2000/SDK/input_impl.h create mode 100644 foobar2000/SDK/library_manager.h create mode 100644 foobar2000/SDK/link_resolver.cpp create mode 100644 foobar2000/SDK/link_resolver.h create mode 100644 foobar2000/SDK/main_thread_callback.cpp create mode 100644 foobar2000/SDK/main_thread_callback.h create mode 100644 foobar2000/SDK/mainmenu.cpp create mode 100644 foobar2000/SDK/mem_block_container.cpp create mode 100644 foobar2000/SDK/mem_block_container.h create mode 100644 foobar2000/SDK/menu.h create mode 100644 foobar2000/SDK/menu_helpers.cpp create mode 100644 foobar2000/SDK/menu_helpers.h create mode 100644 foobar2000/SDK/menu_item.cpp create mode 100644 foobar2000/SDK/menu_manager.cpp create mode 100644 foobar2000/SDK/message_loop.h create mode 100644 foobar2000/SDK/metadb.cpp create mode 100644 foobar2000/SDK/metadb.h create mode 100644 foobar2000/SDK/metadb_handle.cpp create mode 100644 foobar2000/SDK/metadb_handle.h create mode 100644 foobar2000/SDK/metadb_handle_list.cpp create mode 100644 foobar2000/SDK/modeless_dialog.h create mode 100644 foobar2000/SDK/ole_interaction.h create mode 100644 foobar2000/SDK/output.cpp create mode 100644 foobar2000/SDK/output.h create mode 100644 foobar2000/SDK/packet_decoder.cpp create mode 100644 foobar2000/SDK/packet_decoder.h create mode 100644 foobar2000/SDK/play_callback.h create mode 100644 foobar2000/SDK/playable_location.cpp create mode 100644 foobar2000/SDK/playable_location.h create mode 100644 foobar2000/SDK/playback_control.cpp create mode 100644 foobar2000/SDK/playback_control.h create mode 100644 foobar2000/SDK/playback_stream_capture.h create mode 100644 foobar2000/SDK/playlist.cpp create mode 100644 foobar2000/SDK/playlist.h create mode 100644 foobar2000/SDK/playlist_loader.cpp create mode 100644 foobar2000/SDK/playlist_loader.h create mode 100644 foobar2000/SDK/popup_message.cpp create mode 100644 foobar2000/SDK/popup_message.h create mode 100644 foobar2000/SDK/preferences_page.cpp create mode 100644 foobar2000/SDK/preferences_page.h create mode 100644 foobar2000/SDK/progress_meter.h create mode 100644 foobar2000/SDK/replaygain.cpp create mode 100644 foobar2000/SDK/replaygain.h create mode 100644 foobar2000/SDK/replaygain_info.cpp create mode 100644 foobar2000/SDK/replaygain_scanner.h create mode 100644 foobar2000/SDK/resampler.h create mode 100644 foobar2000/SDK/search_tools.cpp create mode 100644 foobar2000/SDK/search_tools.h create mode 100644 foobar2000/SDK/service.cpp create mode 100644 foobar2000/SDK/service.h create mode 100644 foobar2000/SDK/service_by_guid.h create mode 100644 foobar2000/SDK/service_compat.h create mode 100644 foobar2000/SDK/service_impl.h create mode 100644 foobar2000/SDK/shortcut_actions.h create mode 100644 foobar2000/SDK/stdafx.cpp create mode 100644 foobar2000/SDK/system_time_keeper.h create mode 100644 foobar2000/SDK/tag_processor.cpp create mode 100644 foobar2000/SDK/tag_processor.h create mode 100644 foobar2000/SDK/tag_processor_id3v2.cpp create mode 100644 foobar2000/SDK/threaded_process.cpp create mode 100644 foobar2000/SDK/threaded_process.h create mode 100644 foobar2000/SDK/titleformat.cpp create mode 100644 foobar2000/SDK/titleformat.h create mode 100644 foobar2000/SDK/track_property.cpp create mode 100644 foobar2000/SDK/track_property.h create mode 100644 foobar2000/SDK/tracks.h create mode 100644 foobar2000/SDK/ui.cpp create mode 100644 foobar2000/SDK/ui.h create mode 100644 foobar2000/SDK/ui_edit_context.h create mode 100644 foobar2000/SDK/ui_element.cpp create mode 100644 foobar2000/SDK/ui_element.h create mode 100644 foobar2000/SDK/ui_element_typable_window_manager.h create mode 100644 foobar2000/SDK/unpack.h create mode 100644 foobar2000/SDK/utility.cpp create mode 100644 foobar2000/SDK/vis.h create mode 100644 foobar2000/foo_input_validator/foo_input_validator.dll create mode 100644 foobar2000/foo_input_validator/readme.txt create mode 100644 foobar2000/foo_sample/IO.cpp create mode 100644 foobar2000/foo_sample/PCH.cpp create mode 100644 foobar2000/foo_sample/contextmenu.cpp create mode 100644 foobar2000/foo_sample/decode.cpp create mode 100644 foobar2000/foo_sample/dsp.cpp create mode 100644 foobar2000/foo_sample/foo_sample.rc create mode 100644 foobar2000/foo_sample/foo_sample.sln create mode 100644 foobar2000/foo_sample/foo_sample.vcxproj create mode 100644 foobar2000/foo_sample/foo_sample.vcxproj.filters create mode 100644 foobar2000/foo_sample/initquit.cpp create mode 100644 foobar2000/foo_sample/input_raw.cpp create mode 100644 foobar2000/foo_sample/listcontrol-advanced.cpp create mode 100644 foobar2000/foo_sample/listcontrol-ownerdata.cpp create mode 100644 foobar2000/foo_sample/listcontrol-simple.cpp create mode 100644 foobar2000/foo_sample/main.cpp create mode 100644 foobar2000/foo_sample/mainmenu-dynamic.cpp create mode 100644 foobar2000/foo_sample/mainmenu.cpp create mode 100644 foobar2000/foo_sample/playback_state.cpp create mode 100644 foobar2000/foo_sample/playback_stream_capture.cpp create mode 100644 foobar2000/foo_sample/playback_stream_capture.h create mode 100644 foobar2000/foo_sample/preferences.cpp create mode 100644 foobar2000/foo_sample/rating.cpp create mode 100644 foobar2000/foo_sample/readme.txt create mode 100644 foobar2000/foo_sample/resource.h create mode 100644 foobar2000/foo_sample/stdafx.h create mode 100644 foobar2000/foo_sample/ui_and_threads.cpp create mode 100644 foobar2000/foo_sample/ui_element.cpp create mode 100644 foobar2000/foo_sample/ui_element_dialog.cpp create mode 100644 foobar2000/foobar2000_component_client/component_client.cpp create mode 100644 foobar2000/foobar2000_component_client/foobar2000_component_client.vcxproj create mode 100644 foobar2000/helpers/AutoComplete.cpp create mode 100644 foobar2000/helpers/AutoComplete.h create mode 100644 foobar2000/helpers/BumpableElem.h create mode 100644 foobar2000/helpers/CDialogResizeHelper.h create mode 100644 foobar2000/helpers/COM_utils.h create mode 100644 foobar2000/helpers/CPropVariant.h create mode 100644 foobar2000/helpers/CSingleThreadWrapper.h create mode 100644 foobar2000/helpers/CTableEditHelper-Legacy.cpp create mode 100644 foobar2000/helpers/CTableEditHelper-Legacy.h create mode 100644 foobar2000/helpers/CallForwarder.h create mode 100644 foobar2000/helpers/CmdThread.h create mode 100644 foobar2000/helpers/ProcessUtils.h create mode 100644 foobar2000/helpers/ProfileCache.h create mode 100644 foobar2000/helpers/StdAfx.cpp create mode 100644 foobar2000/helpers/StdAfx.h create mode 100644 foobar2000/helpers/ThreadUtils.cpp create mode 100644 foobar2000/helpers/ThreadUtils.h create mode 100644 foobar2000/helpers/VisUtils.cpp create mode 100644 foobar2000/helpers/VisUtils.h create mode 100644 foobar2000/helpers/VolumeMap.cpp create mode 100644 foobar2000/helpers/VolumeMap.h create mode 100644 foobar2000/helpers/WindowPositionUtils.h create mode 100644 foobar2000/helpers/advconfig_impl.h create mode 100644 foobar2000/helpers/advconfig_runtime.h create mode 100644 foobar2000/helpers/album_art_helpers.h create mode 100644 foobar2000/helpers/atl-misc.h create mode 100644 foobar2000/helpers/bitreader_helper.h create mode 100644 foobar2000/helpers/cfg_guidlist.h create mode 100644 foobar2000/helpers/create_directory_helper.cpp create mode 100644 foobar2000/helpers/create_directory_helper.h create mode 100644 foobar2000/helpers/cue_creator.cpp create mode 100644 foobar2000/helpers/cue_creator.h create mode 100644 foobar2000/helpers/cue_parser.cpp create mode 100644 foobar2000/helpers/cue_parser.h create mode 100644 foobar2000/helpers/cue_parser_embedding.cpp create mode 100644 foobar2000/helpers/cuesheet_index_list.cpp create mode 100644 foobar2000/helpers/cuesheet_index_list.h create mode 100644 foobar2000/helpers/dialog_resize_helper.cpp create mode 100644 foobar2000/helpers/dialog_resize_helper.h create mode 100644 foobar2000/helpers/dropdown_helper.cpp create mode 100644 foobar2000/helpers/dropdown_helper.h create mode 100644 foobar2000/helpers/duration_counter.h create mode 100644 foobar2000/helpers/dynamic_bitrate_helper.cpp create mode 100644 foobar2000/helpers/dynamic_bitrate_helper.h create mode 100644 foobar2000/helpers/fb2k_threads.h create mode 100644 foobar2000/helpers/fb2k_wfx.h create mode 100644 foobar2000/helpers/fileReadAhead.h create mode 100644 foobar2000/helpers/file_cached.h create mode 100644 foobar2000/helpers/file_info_const_impl.cpp create mode 100644 foobar2000/helpers/file_info_const_impl.h create mode 100644 foobar2000/helpers/file_list_helper.cpp create mode 100644 foobar2000/helpers/file_list_helper.h create mode 100644 foobar2000/helpers/file_move_helper.cpp create mode 100644 foobar2000/helpers/file_move_helper.h create mode 100644 foobar2000/helpers/file_readonly.h create mode 100644 foobar2000/helpers/file_win32_wrapper.cpp create mode 100644 foobar2000/helpers/file_win32_wrapper.h create mode 100644 foobar2000/helpers/filetimetools.cpp create mode 100644 foobar2000/helpers/filetimetools.h create mode 100644 foobar2000/helpers/foobar2000+atl.h create mode 100644 foobar2000/helpers/foobar2000_sdk_helpers.vcxproj create mode 100644 foobar2000/helpers/foobar2000_sdk_helpers.vcxproj.filters create mode 100644 foobar2000/helpers/fullFileBuffer.h create mode 100644 foobar2000/helpers/helpers.h create mode 100644 foobar2000/helpers/icon_remapping_wildcard.h create mode 100644 foobar2000/helpers/image_load_save.cpp create mode 100644 foobar2000/helpers/image_load_save.h create mode 100644 foobar2000/helpers/inplace_edit.cpp create mode 100644 foobar2000/helpers/inplace_edit.h create mode 100644 foobar2000/helpers/input_fix_seeking.h create mode 100644 foobar2000/helpers/input_helper_cue.cpp create mode 100644 foobar2000/helpers/input_helper_cue.h create mode 100644 foobar2000/helpers/input_helpers.cpp create mode 100644 foobar2000/helpers/input_helpers.h create mode 100644 foobar2000/helpers/input_logging.h create mode 100644 foobar2000/helpers/input_stream_info_reader.h create mode 100644 foobar2000/helpers/meta_table_builder.h create mode 100644 foobar2000/helpers/metadb_handle_set.h create mode 100644 foobar2000/helpers/metadb_io_hintlist.h create mode 100644 foobar2000/helpers/mp3_utils.cpp create mode 100644 foobar2000/helpers/mp3_utils.h create mode 100644 foobar2000/helpers/packet_decoder_aac_common.cpp create mode 100644 foobar2000/helpers/packet_decoder_aac_common.h create mode 100644 foobar2000/helpers/packet_decoder_mp3_common.cpp create mode 100644 foobar2000/helpers/packet_decoder_mp3_common.h create mode 100644 foobar2000/helpers/playlist_position_reference_tracker.h create mode 100644 foobar2000/helpers/reader_pretend_nonseekable.h create mode 100644 foobar2000/helpers/readers.cpp create mode 100644 foobar2000/helpers/readers.h create mode 100644 foobar2000/helpers/rethrow.h create mode 100644 foobar2000/helpers/seekabilizer.cpp create mode 100644 foobar2000/helpers/seekabilizer.h create mode 100644 foobar2000/helpers/stream_buffer_helper.cpp create mode 100644 foobar2000/helpers/stream_buffer_helper.h create mode 100644 foobar2000/helpers/tag_write_callback_impl.h create mode 100644 foobar2000/helpers/text_file_loader.cpp create mode 100644 foobar2000/helpers/text_file_loader.h create mode 100644 foobar2000/helpers/text_file_loader_v2.cpp create mode 100644 foobar2000/helpers/text_file_loader_v2.h create mode 100644 foobar2000/helpers/track_property_callback_impl.cpp create mode 100644 foobar2000/helpers/track_property_callback_impl.h create mode 100644 foobar2000/helpers/ui_element_helpers.cpp create mode 100644 foobar2000/helpers/ui_element_helpers.h create mode 100644 foobar2000/helpers/win32_dialog.cpp create mode 100644 foobar2000/helpers/win32_dialog.h create mode 100644 foobar2000/helpers/win32_misc.cpp create mode 100644 foobar2000/helpers/win32_misc.h create mode 100644 foobar2000/helpers/window_placement_helper.cpp create mode 100644 foobar2000/helpers/window_placement_helper.h create mode 100644 foobar2000/helpers/winmm-types.h create mode 100644 foobar2000/helpers/writer_wav.cpp create mode 100644 foobar2000/helpers/writer_wav.h create mode 100644 foobar2000/shared/audio_math.h create mode 100644 foobar2000/shared/fb2kdebug.h create mode 100644 foobar2000/shared/filedialogs.h create mode 100644 foobar2000/shared/shared.h create mode 100644 foobar2000/shared/shared.lib create mode 100644 foobar2000/shared/win32_misc.h create mode 100644 libPPUI/AutoComplete.cpp create mode 100644 libPPUI/AutoComplete.h create mode 100644 libPPUI/CButtonLite.h create mode 100644 libPPUI/CDialogResizeHelper.cpp create mode 100644 libPPUI/CDialogResizeHelper.h create mode 100644 libPPUI/CDialogResizeHelperCompat.h create mode 100644 libPPUI/CEditWithButtons.cpp create mode 100644 libPPUI/CEditWithButtons.h create mode 100644 libPPUI/CEnumString.h create mode 100644 libPPUI/CFlashWindow.h create mode 100644 libPPUI/CHeaderCtrlEx.h create mode 100644 libPPUI/CIconOverlayWindow.h create mode 100644 libPPUI/CListAccessible.cpp create mode 100644 libPPUI/CListAccessible.h create mode 100644 libPPUI/CListControl-Cell.h create mode 100644 libPPUI/CListControl-Cells-Compat.h create mode 100644 libPPUI/CListControl-Cells.cpp create mode 100644 libPPUI/CListControl-Cells.h create mode 100644 libPPUI/CListControl.cpp create mode 100644 libPPUI/CListControl.h create mode 100644 libPPUI/CListControlComplete.h create mode 100644 libPPUI/CListControlHeaderImpl.cpp create mode 100644 libPPUI/CListControlHeaderImpl.h create mode 100644 libPPUI/CListControlOwnerData.h create mode 100644 libPPUI/CListControlSimple.h create mode 100644 libPPUI/CListControlTruncationTooltipImpl.cpp create mode 100644 libPPUI/CListControlTruncationTooltipImpl.h create mode 100644 libPPUI/CListControlUserOptions.h create mode 100644 libPPUI/CListControlWithSelection.cpp create mode 100644 libPPUI/CListControlWithSelection.h create mode 100644 libPPUI/CListControl_EditImpl.h create mode 100644 libPPUI/CListViewCtrlEx.h create mode 100644 libPPUI/CMiddleDragImpl.cpp create mode 100644 libPPUI/CMiddleDragImpl.h create mode 100644 libPPUI/CPopupTooltipMessage.h create mode 100644 libPPUI/CPowerRequest.cpp create mode 100644 libPPUI/CPowerRequest.h create mode 100644 libPPUI/CPropVariant.h create mode 100644 libPPUI/CWindowCreateAndDelete.h create mode 100644 libPPUI/Controls.cpp create mode 100644 libPPUI/Controls.h create mode 100644 libPPUI/GDIUtils.h create mode 100644 libPPUI/IDI_SCROLL.ico create mode 100644 libPPUI/IDI_SCROLL.txt create mode 100644 libPPUI/IDataObjectUtils.cpp create mode 100644 libPPUI/IDataObjectUtils.h create mode 100644 libPPUI/InPlaceCombo.cpp create mode 100644 libPPUI/InPlaceEdit.cpp create mode 100644 libPPUI/InPlaceEdit.h create mode 100644 libPPUI/InPlaceEditTable.cpp create mode 100644 libPPUI/InPlaceEditTable.h create mode 100644 libPPUI/PaintUtils.cpp create mode 100644 libPPUI/PaintUtils.h create mode 100644 libPPUI/SmartStrStr.cpp create mode 100644 libPPUI/SmartStrStr.h create mode 100644 libPPUI/TreeMultiSel.h create mode 100644 libPPUI/TypeFind.cpp create mode 100644 libPPUI/TypeFind.h create mode 100644 libPPUI/clipboard.cpp create mode 100644 libPPUI/clipboard.h create mode 100644 libPPUI/commandline_parser.cpp create mode 100644 libPPUI/commandline_parser.h create mode 100644 libPPUI/gdiplus-helpers-webp.h create mode 100644 libPPUI/gdiplus_helpers.h create mode 100644 libPPUI/gesture.h create mode 100644 libPPUI/libPPUI-license.txt create mode 100644 libPPUI/libPPUI-readme.txt create mode 100644 libPPUI/libPPUI.vcxproj create mode 100644 libPPUI/libPPUI.vcxproj.filters create mode 100644 libPPUI/link-CommonControls6.h create mode 100644 libPPUI/listview_helper.cpp create mode 100644 libPPUI/listview_helper.h create mode 100644 libPPUI/pp-COM-macros.h create mode 100644 libPPUI/ppresources.h create mode 100644 libPPUI/stdafx.cpp create mode 100644 libPPUI/stdafx.h create mode 100644 libPPUI/targetver.h create mode 100644 libPPUI/win32_op.cpp create mode 100644 libPPUI/win32_op.h create mode 100644 libPPUI/win32_utility.cpp create mode 100644 libPPUI/win32_utility.h create mode 100644 libPPUI/wtl-pp.h create mode 100644 pfc/alloc.h create mode 100644 pfc/array.h create mode 100644 pfc/audio_math.cpp create mode 100644 pfc/audio_sample.cpp create mode 100644 pfc/audio_sample.h create mode 100644 pfc/autoref.h create mode 100644 pfc/avltree.h create mode 100644 pfc/base64.cpp create mode 100644 pfc/base64.h create mode 100644 pfc/binary_search.h create mode 100644 pfc/bit_array.cpp create mode 100644 pfc/bit_array.h create mode 100644 pfc/bit_array_impl.h create mode 100644 pfc/bit_array_impl_part2.h create mode 100644 pfc/bsearch.cpp create mode 100644 pfc/bsearch.h create mode 100644 pfc/bsearch_inline.h create mode 100644 pfc/byte_order.h create mode 100644 pfc/chain_list_v2.h create mode 100644 pfc/cmd_thread.h create mode 100644 pfc/com_ptr_t.h create mode 100644 pfc/cpuid.cpp create mode 100644 pfc/cpuid.h create mode 100644 pfc/event.h create mode 100644 pfc/filehandle.cpp create mode 100644 pfc/filehandle.h create mode 100644 pfc/guid.cpp create mode 100644 pfc/guid.h create mode 100644 pfc/instance_tracker.h create mode 100644 pfc/int_types.h create mode 100644 pfc/iterators.h create mode 100644 pfc/list.h create mode 100644 pfc/lockless.h create mode 100644 pfc/map.h create mode 100644 pfc/memalign.h create mode 100644 pfc/nix-objects.cpp create mode 100644 pfc/nix-objects.h create mode 100644 pfc/notifyList.h create mode 100644 pfc/obj-c.mm create mode 100644 pfc/order_helper.h create mode 100644 pfc/other.cpp create mode 100644 pfc/other.h create mode 100644 pfc/pathUtils.cpp create mode 100644 pfc/pathUtils.h create mode 100644 pfc/pfc-fb2k-hooks.cpp create mode 100644 pfc/pfc-fb2k-hooks.h create mode 100644 pfc/pfc-license.txt create mode 100644 pfc/pfc-readme.txt create mode 100644 pfc/pfc.h create mode 100644 pfc/pfc.vcxproj create mode 100644 pfc/pfc.vcxproj.filters create mode 100644 pfc/pocket_char_ops.h create mode 100644 pfc/pool.h create mode 100644 pfc/pp-gettickcount.h create mode 100644 pfc/pp-winapi.h create mode 100644 pfc/primitives.h create mode 100644 pfc/primitives_part2.h create mode 100644 pfc/printf.cpp create mode 100644 pfc/ptr_list.h create mode 100644 pfc/ptrholder.h create mode 100644 pfc/rcptr.h create mode 100644 pfc/ref_counter.h create mode 100644 pfc/selftest.cpp create mode 100644 pfc/sort.cpp create mode 100644 pfc/sort.h create mode 100644 pfc/splitString.h create mode 100644 pfc/stdafx.cpp create mode 100644 pfc/stdsort.h create mode 100644 pfc/string8_impl.h create mode 100644 pfc/stringNew.cpp create mode 100644 pfc/stringNew.h create mode 100644 pfc/string_base.cpp create mode 100644 pfc/string_base.h create mode 100644 pfc/string_conv.cpp create mode 100644 pfc/string_conv.h create mode 100644 pfc/string_list.h create mode 100644 pfc/suppress_fb2k_hooks.h create mode 100644 pfc/syncd_storage.h create mode 100644 pfc/synchro.h create mode 100644 pfc/synchro_nix.cpp create mode 100644 pfc/synchro_nix.h create mode 100644 pfc/synchro_win.h create mode 100644 pfc/targetver.h create mode 100644 pfc/threads.cpp create mode 100644 pfc/threads.h create mode 100644 pfc/timers.cpp create mode 100644 pfc/timers.h create mode 100644 pfc/traits.h create mode 100644 pfc/utf8.cpp create mode 100644 pfc/wait_queue.h create mode 100644 pfc/weakRef.h create mode 100644 pfc/wildcard.cpp create mode 100644 pfc/wildcard.h create mode 100644 pfc/win-objects.cpp create mode 100644 pfc/win-objects.h create mode 100644 sdk-license.txt create mode 100644 sdk-readme.css create mode 100644 sdk-readme.html diff --git a/foobar2000/SDK/abort_callback.cpp b/foobar2000/SDK/abort_callback.cpp new file mode 100644 index 0000000..7bb8fa4 --- /dev/null +++ b/foobar2000/SDK/abort_callback.cpp @@ -0,0 +1,42 @@ +#include "foobar2000.h" + +void abort_callback::check() const { + if (is_aborting()) throw exception_aborted(); +} + +void abort_callback::sleep(double p_timeout_seconds) const { + if (!sleep_ex(p_timeout_seconds)) throw exception_aborted(); +} + +bool abort_callback::sleep_ex(double p_timeout_seconds) const { + // return true IF NOT SET (timeout), false if set + return !pfc::event::g_wait_for(get_abort_event(),p_timeout_seconds); +} + +bool abort_callback::waitForEvent( pfc::eventHandle_t evtHandle, double timeOut ) { + int status = pfc::event::g_twoEventWait( this->get_abort_event(), evtHandle, timeOut ); + switch(status) { + case 1: throw exception_aborted(); + case 2: return true; + case 0: return false; + default: uBugCheck(); + } +} + +bool abort_callback::waitForEvent(pfc::event& evt, double timeOut) { + return waitForEvent(evt.get_handle(), timeOut); +} + +void abort_callback::waitForEvent(pfc::eventHandle_t evtHandle) { + bool status = waitForEvent(evtHandle, -1); (void)status; + PFC_ASSERT(status); // should never return false +} + +void abort_callback::waitForEvent(pfc::event& evt) { + bool status = waitForEvent(evt, -1); (void)status; + PFC_ASSERT(status); // should never return false +} + +namespace fb2k { + abort_callback_dummy noAbort; +} diff --git a/foobar2000/SDK/abort_callback.h b/foobar2000/SDK/abort_callback.h new file mode 100644 index 0000000..e0c30cc --- /dev/null +++ b/foobar2000/SDK/abort_callback.h @@ -0,0 +1,119 @@ +#ifndef _foobar2000_sdk_abort_callback_h_ +#define _foobar2000_sdk_abort_callback_h_ + +namespace foobar2000_io { + +PFC_DECLARE_EXCEPTION(exception_aborted,pfc::exception,"User abort"); + +typedef pfc::eventHandle_t abort_callback_event; + +#ifdef check +#undef check +#endif +//! This class is used to signal underlying worker code whether user has decided to abort a potentially time-consuming operation. \n +//! It is commonly required by all filesystem related or decoding-related operations. \n +//! Code that receives an abort_callback object should periodically check it and abort any operations being performed if it is signaled, typically throwing exception_aborted. \n +//! See abort_callback_impl for an implementation. +class NOVTABLE abort_callback +{ +public: + //! Returns whether user has requested the operation to be aborted. + virtual bool is_aborting() const = 0; + + inline bool is_set() const {return is_aborting();} + + //! Retrieves event object that can be used with some OS calls. The even object becomes signaled when abort is triggered. On win32, this is equivalent to win32 event handle (see: CreateEvent). \n + //! You must not close this handle or call any methods that change this handle's state (SetEvent() or ResetEvent()), you can only wait for it. + virtual abort_callback_event get_abort_event() const = 0; + + inline abort_callback_event get_handle() const {return get_abort_event();} + + //! Checks if user has requested the operation to be aborted, and throws exception_aborted if so. + void check() const; + + //! For compatibility with old code. Do not call. + inline void check_e() const {check();} + + + //! Sleeps p_timeout_seconds or less when aborted, throws exception_aborted on abort. + void sleep(double p_timeout_seconds) const; + //! Sleeps p_timeout_seconds or less when aborted, returns true when execution should continue, false when not. + bool sleep_ex(double p_timeout_seconds) const; + + //! Waits for an event. Returns true if event is now signaled, false if the specified period has elapsed and the event did not become signaled. \n + //! Throws exception_aborted if aborted. + bool waitForEvent( pfc::eventHandle_t evtHandle, double timeOut ); + //! Waits for an event. Returns true if event is now signaled, false if the specified period has elapsed and the event did not become signaled. \n + //! Throws exception_aborted if aborted. + bool waitForEvent(pfc::event& evt, double timeOut); + + //! Waits for an event. Returns once the event became signaled; throw exception_aborted if abort occurred first. + void waitForEvent(pfc::eventHandle_t evtHandle); + //! Waits for an event. Returns once the event became signaled; throw exception_aborted if abort occurred first. + void waitForEvent(pfc::event& evt); +protected: + abort_callback() {} + ~abort_callback() {} +}; + + + +//! Implementation of abort_callback interface. +class abort_callback_impl : public abort_callback { +public: + abort_callback_impl() : m_aborting(false) {} + inline void abort() {set_state(true);} + inline void set() {set_state(true);} + inline void reset() {set_state(false);} + + void set_state(bool p_state) {m_aborting = p_state; m_event.set_state(p_state);} + + bool is_aborting() const {return m_aborting;} + + abort_callback_event get_abort_event() const {return m_event.get_handle();} + +private: + abort_callback_impl(const abort_callback_impl &) = delete; + const abort_callback_impl & operator=(const abort_callback_impl&) = delete; + + volatile bool m_aborting; + pfc::event m_event; +}; + +#ifdef _WIN32 +//! Dummy abort_callback that never gets aborted. \n +//! Slightly more efficient than the regular one especially when you need to regularly create temporary instances of it. +class abort_callback_dummy : public abort_callback { +public: + bool is_aborting() const { return false; } + + abort_callback_event get_abort_event() const { return GetInfiniteWaitEvent();} +}; +#else + +// FIX ME come up with a scheme to produce a persistent infinite wait filedescriptor on non Windows +// Could use /dev/null but still need to open it on upon object creation which defeats the purpose +typedef abort_callback_impl abort_callback_dummy; + +#endif + +} +typedef foobar2000_io::abort_callback_event fb2k_event_handle; +typedef foobar2000_io::abort_callback fb2k_event; +typedef foobar2000_io::abort_callback_impl fb2k_event_impl; + +using namespace foobar2000_io; + +#define FB2K_PFCv2_ABORTER_SCOPE( abortObj ) \ + (abortObj).check(); \ + PP::waitableReadRef_t aborterRef = {(abortObj).get_abort_event()}; \ + PP::aborter aborter_pfcv2( aborterRef ); \ + PP::aborterScope l_aborterScope( aborter_pfcv2 ); + + +namespace fb2k { + // A shared abort_callback_dummy instance + extern abort_callback_dummy noAbort; +} + +#endif //_foobar2000_sdk_abort_callback_h_ diff --git a/foobar2000/SDK/advconfig.cpp b/foobar2000/SDK/advconfig.cpp new file mode 100644 index 0000000..b75cdf9 --- /dev/null +++ b/foobar2000/SDK/advconfig.cpp @@ -0,0 +1,47 @@ +#include "foobar2000.h" + +bool advconfig_entry::is_branch() { + advconfig_branch::ptr branch; + return branch &= this; +} + +bool advconfig_entry::g_find(service_ptr_t& out, const GUID & id) { + service_enum_t e; service_ptr_t ptr; while(e.next(ptr)) { if (ptr->get_guid() == id) {out = ptr; return true;} } return false; +} + +t_uint32 advconfig_entry::get_preferences_flags_() { + { + advconfig_entry_string_v2::ptr ex; + if (service_query_t(ex)) return ex->get_preferences_flags(); + } + { + advconfig_entry_checkbox_v2::ptr ex; + if (service_query_t(ex)) return ex->get_preferences_flags(); + } + return 0; +} + +bool advconfig_entry_checkbox::get_default_state_() { + { + advconfig_entry_checkbox_v2::ptr ex; + if (service_query_t(ex)) return ex->get_default_state(); + } + + bool backup = get_state(); + reset(); + bool rv = get_state(); + set_state(backup); + return rv; +} + +void advconfig_entry_string::get_default_state_(pfc::string_base & out) { + { + advconfig_entry_string_v2::ptr ex; + if (service_query_t(ex)) {ex->get_default_state(out); return;} + } + pfc::string8 backup; + get_state(backup); + reset(); + get_state(out); + set_state(backup); +} diff --git a/foobar2000/SDK/advconfig.h b/foobar2000/SDK/advconfig.h new file mode 100644 index 0000000..72ab8fe --- /dev/null +++ b/foobar2000/SDK/advconfig.h @@ -0,0 +1,333 @@ +#pragma once + +//! Entrypoint class for adding items to Advanced Preferences page. \n +//! Implementations must derive from one of subclasses: advconfig_branch, advconfig_entry_checkbox, advconfig_entry_string. \n +//! Implementations are typically registered using static service_factory_single_t, or using provided helper classes in case of standard implementations declared in this header. +class NOVTABLE advconfig_entry : public service_base { +public: + virtual void get_name(pfc::string_base & p_out) = 0; + virtual GUID get_guid() = 0; + virtual GUID get_parent() = 0; + virtual void reset() = 0; + virtual double get_sort_priority() = 0; + + bool is_branch(); + t_uint32 get_preferences_flags_(); + + static bool g_find(service_ptr_t& out, const GUID & id); + + template static bool g_find_t(outptr & out, const GUID & id) { + service_ptr_t temp; + if (!g_find(temp, id)) return false; + return temp->service_query_t(out); + } + + static const GUID guid_root; + static const GUID guid_branch_tagging,guid_branch_decoding,guid_branch_tools,guid_branch_playback,guid_branch_display,guid_branch_debug, guid_branch_tagging_general; + + FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(advconfig_entry); +}; + +//! Creates a new branch in Advanced Preferences. \n +//! Implementation: see advconfig_branch_impl / advconfig_branch_factory. +class NOVTABLE advconfig_branch : public advconfig_entry { +public: + FB2K_MAKE_SERVICE_INTERFACE(advconfig_branch,advconfig_entry); +}; + +//! Creates a checkbox/radiocheckbox entry in Advanced Preferences. \n +//! The difference between checkboxes and radiocheckboxes is different icon (obviously) and that checking a radiocheckbox unchecks all other radiocheckboxes in the same branch. \n +//! Implementation: see advconfig_entry_checkbox_impl / advconfig_checkbox_factory_t. +class NOVTABLE advconfig_entry_checkbox : public advconfig_entry { +public: + virtual bool get_state() = 0; + virtual void set_state(bool p_state) = 0; + virtual bool is_radio() = 0; + + bool get_default_state_(); + + FB2K_MAKE_SERVICE_INTERFACE(advconfig_entry_checkbox,advconfig_entry); +}; + +class NOVTABLE advconfig_entry_checkbox_v2 : public advconfig_entry_checkbox { + FB2K_MAKE_SERVICE_INTERFACE(advconfig_entry_checkbox_v2, advconfig_entry_checkbox) +public: + virtual bool get_default_state() = 0; + virtual t_uint32 get_preferences_flags() {return 0;} //signals whether changing this setting should trigger playback restart or app restart; see: preferences_state::* constants +}; + +//! Creates a string/integer editbox entry in Advanced Preferences.\n +//! Implementation: see advconfig_entry_string_impl / advconfig_string_factory. +class NOVTABLE advconfig_entry_string : public advconfig_entry { +public: + virtual void get_state(pfc::string_base & p_out) = 0; + virtual void set_state(const char * p_string,t_size p_length = ~0) = 0; + virtual t_uint32 get_flags() = 0; + + void get_default_state_(pfc::string_base & out); + + enum { + flag_is_integer = 1 << 0, + flag_is_signed = 1 << 1, + }; + + FB2K_MAKE_SERVICE_INTERFACE(advconfig_entry_string,advconfig_entry); +}; + +class NOVTABLE advconfig_entry_string_v2 : public advconfig_entry_string { + FB2K_MAKE_SERVICE_INTERFACE(advconfig_entry_string_v2, advconfig_entry_string) +public: + virtual void get_default_state(pfc::string_base & out) = 0; + virtual void validate(pfc::string_base & val) {} + virtual t_uint32 get_preferences_flags() {return 0;} //signals whether changing this setting should trigger playback restart or app restart; see: preferences_state::* constants +}; + + +//! Standard implementation of advconfig_branch. \n +//! Usage: no need to use this class directly - use advconfig_branch_factory instead. +class advconfig_branch_impl : public advconfig_branch { +public: + advconfig_branch_impl(const char * p_name,const GUID & p_guid,const GUID & p_parent,double p_priority) : m_name(p_name), m_guid(p_guid), m_parent(p_parent), m_priority(p_priority) {} + void get_name(pfc::string_base & p_out) {p_out = m_name;} + GUID get_guid() {return m_guid;} + GUID get_parent() {return m_parent;} + void reset() {} + double get_sort_priority() {return m_priority;} +private: + pfc::string8 m_name; + GUID m_guid,m_parent; + const double m_priority; +}; + +//! Standard implementation of advconfig_entry_checkbox. \n +//! p_is_radio parameter controls whether we're implementing a checkbox or a radiocheckbox (see advconfig_entry_checkbox description for more details). +template +class advconfig_entry_checkbox_impl : public advconfig_entry_checkbox_v2 { +public: + advconfig_entry_checkbox_impl(const char * p_name,const GUID & p_guid,const GUID & p_parent,double p_priority,bool p_initialstate) + : m_name(p_name), m_initialstate(p_initialstate), m_state(p_guid,p_initialstate), m_parent(p_parent), m_priority(p_priority) {} + + void get_name(pfc::string_base & p_out) {p_out = m_name;} + GUID get_guid() {return m_state.get_guid();} + GUID get_parent() {return m_parent;} + void reset() {m_state = m_initialstate;} + bool get_state() {return m_state;} + void set_state(bool p_state) {m_state = p_state;} + bool is_radio() {return p_is_radio;} + double get_sort_priority() {return m_priority;} + bool get_state_() const {return m_state;} + bool get_default_state() {return m_initialstate;} + bool get_default_state_() const {return m_initialstate;} + t_uint32 get_preferences_flags() {return prefFlags;} +private: + pfc::string8 m_name; + const bool m_initialstate; + cfg_bool m_state; + GUID m_parent; + const double m_priority; +}; + +//! Service factory helper around standard advconfig_branch implementation. Use this class to register your own Advanced Preferences branches. \n +//! Usage: static advconfig_branch_factory mybranch(name, branchID, parentBranchID, priority); +class advconfig_branch_factory : public service_factory_single_t { +public: + advconfig_branch_factory(const char * p_name,const GUID & p_guid,const GUID & p_parent,double p_priority) + : service_factory_single_t(p_name,p_guid,p_parent,p_priority) {} +}; + +//! Service factory helper around standard advconfig_entry_checkbox implementation. Use this class to register your own Advanced Preferences checkbox/radiocheckbox entries. \n +//! Usage: static advconfig_entry_checkbox mybox(name, itemID, parentID, priority, initialstate); +template +class advconfig_checkbox_factory_t : public service_factory_single_t > { +public: + advconfig_checkbox_factory_t(const char * p_name,const GUID & p_guid,const GUID & p_parent,double p_priority,bool p_initialstate) + : service_factory_single_t >(p_name,p_guid,p_parent,p_priority,p_initialstate) {} + + bool get() const {return this->get_static_instance().get_state_();} + void set(bool val) {this->get_static_instance().set_state(val);} + operator bool() const {return get();} + bool operator=(bool val) {set(val); return val;} +}; + +//! Service factory helper around standard advconfig_entry_checkbox implementation, specialized for checkboxes (rather than radiocheckboxes). See advconfig_checkbox_factory_t<> for more details. +typedef advconfig_checkbox_factory_t advconfig_checkbox_factory; +//! Service factory helper around standard advconfig_entry_checkbox implementation, specialized for radiocheckboxes (rather than standard checkboxes). See advconfig_checkbox_factory_t<> for more details. +typedef advconfig_checkbox_factory_t advconfig_radio_factory; + + +//! Standard advconfig_entry_string implementation. Use advconfig_string_factory to register your own string entries in Advanced Preferences instead of using this class directly. +class advconfig_entry_string_impl : public advconfig_entry_string_v2 { +public: + advconfig_entry_string_impl(const char * p_name,const GUID & p_guid,const GUID & p_parent,double p_priority,const char * p_initialstate, t_uint32 p_prefFlags) + : m_name(p_name), m_parent(p_parent), m_priority(p_priority), m_initialstate(p_initialstate), m_state(p_guid,p_initialstate), m_prefFlags(p_prefFlags) {} + void get_name(pfc::string_base & p_out) {p_out = m_name;} + GUID get_guid() {return m_state.get_guid();} + GUID get_parent() {return m_parent;} + void reset() {core_api::ensure_main_thread();m_state = m_initialstate;} + double get_sort_priority() {return m_priority;} + void get_state(pfc::string_base & p_out) {core_api::ensure_main_thread();p_out = m_state;} + void set_state(const char * p_string,t_size p_length = ~0) {core_api::ensure_main_thread();m_state.set_string(p_string,p_length);} + t_uint32 get_flags() {return 0;} + void get_default_state(pfc::string_base & out) {out = m_initialstate;} + t_uint32 get_preferences_flags() {return m_prefFlags;} +private: + const pfc::string8 m_initialstate, m_name; + cfg_string m_state; + const double m_priority; + const GUID m_parent; + const t_uint32 m_prefFlags; +}; + +//! Service factory helper around standard advconfig_entry_string implementation. Use this class to register your own string entries in Advanced Preferences. \n +//! Usage: static advconfig_string_factory mystring(name, itemID, branchID, priority, initialValue); +class advconfig_string_factory : public service_factory_single_t { +public: + advconfig_string_factory(const char * p_name,const GUID & p_guid,const GUID & p_parent,double p_priority,const char * p_initialstate, t_uint32 p_prefFlags = 0) + : service_factory_single_t(p_name,p_guid,p_parent,p_priority,p_initialstate, p_prefFlags) {} + + void get(pfc::string_base & out) {get_static_instance().get_state(out);} + void set(const char * in) {get_static_instance().set_state(in);} +}; + + +//! Special advconfig_entry_string implementation - implements integer entries. Use advconfig_integer_factory to register your own integer entries in Advanced Preferences instead of using this class directly. +template +class advconfig_entry_integer_impl_ : public advconfig_entry_string_v2 { +public: + typedef int_t_ int_t; + advconfig_entry_integer_impl_(const char * p_name,const GUID & p_guid,const GUID & p_parent,double p_priority,int_t p_initialstate,int_t p_min,int_t p_max, t_uint32 p_prefFlags) + : m_name(p_name), m_parent(p_parent), m_priority(p_priority), m_initval(p_initialstate), m_min(p_min), m_max(p_max), m_state(p_guid,p_initialstate), m_prefFlags(p_prefFlags) { + PFC_ASSERT( p_min < p_max ); + } + void get_name(pfc::string_base & p_out) {p_out = m_name;} + GUID get_guid() {return m_state.get_guid();} + GUID get_parent() {return m_parent;} + void reset() {m_state = m_initval;} + double get_sort_priority() {return m_priority;} + void get_state(pfc::string_base & p_out) {format(p_out, m_state.get_value());} + void set_state(const char * p_string,t_size p_length) {set_state_int(myATOI(p_string,p_length));} + t_uint32 get_flags() {return advconfig_entry_string::flag_is_integer | (is_signed() ? flag_is_signed : 0);} + + int_t get_state_int() const {return m_state;} + void set_state_int(int_t val) {m_state = pfc::clip_t(val,m_min,m_max);} + + void get_default_state(pfc::string_base & out) { + format(out, m_initval); + } + void validate(pfc::string_base & val) { + format(val, pfc::clip_t(myATOI(val,~0), m_min, m_max) ); + } + t_uint32 get_preferences_flags() {return m_prefFlags;} +private: + static void format( pfc::string_base & out, int_t v ) { + if (is_signed()) out = pfc::format_int( v ).get_ptr(); + else out = pfc::format_uint( v ).get_ptr(); + } + static int_t myATOI( const char * s, size_t l ) { + if (is_signed()) return pfc::atoi64_ex(s,l); + else return pfc::atoui64_ex(s,l); + } + static bool is_signed() { + return ((int_t)-1) < ((int_t)0); + } + cfg_int_t m_state; + const double m_priority; + const int_t m_initval, m_min, m_max; + const GUID m_parent; + const pfc::string8 m_name; + const t_uint32 m_prefFlags; +}; + +typedef advconfig_entry_integer_impl_ advconfig_entry_integer_impl; + +//! Service factory helper around integer-specialized advconfig_entry_string implementation. Use this class to register your own integer entries in Advanced Preferences. \n +//! Usage: static advconfig_integer_factory myint(name, itemID, parentID, priority, initialValue, minValue, maxValue); +template +class advconfig_integer_factory_ : public service_factory_single_t > { +public: + typedef int_t_ int_t; + advconfig_integer_factory_(const char * p_name,const GUID & p_guid,const GUID & p_parent,double p_priority,t_uint64 p_initialstate,t_uint64 p_min,t_uint64 p_max, t_uint32 p_prefFlags = 0) + : service_factory_single_t >(p_name,p_guid,p_parent,p_priority,p_initialstate,p_min,p_max,p_prefFlags) {} + + int_t get() const {return this->get_static_instance().get_state_int();} + void set(int_t val) {this->get_static_instance().set_state_int(val);} + + operator int_t() const {return get();} + int_t operator=(int_t val) {set(val); return val;} +}; + +typedef advconfig_integer_factory_ advconfig_integer_factory; +typedef advconfig_integer_factory_ advconfig_signed_integer_factory; + +//! Not currently used, reserved for future use. +class NOVTABLE advconfig_entry_enum : public advconfig_entry { +public: + virtual t_size get_value_count() = 0; + virtual void enum_value(pfc::string_base & p_out,t_size p_index) = 0; + virtual t_size get_state() = 0; + virtual void set_state(t_size p_value) = 0; + + FB2K_MAKE_SERVICE_INTERFACE(advconfig_entry_enum,advconfig_entry); +}; + + + + +//! Special version if advconfig_entry_string_impl that allows the value to be retrieved from worker threads. +class advconfig_entry_string_impl_MT : public advconfig_entry_string_v2 { +public: + advconfig_entry_string_impl_MT(const char * p_name,const GUID & p_guid,const GUID & p_parent,double p_priority,const char * p_initialstate, t_uint32 p_prefFlags) + : m_name(p_name), m_parent(p_parent), m_priority(p_priority), m_initialstate(p_initialstate), m_state(p_guid,p_initialstate), m_prefFlags(p_prefFlags) {} + void get_name(pfc::string_base & p_out) {p_out = m_name;} + GUID get_guid() {return m_state.get_guid();} + GUID get_parent() {return m_parent;} + void reset() { + inWriteSync(m_sync); + m_state = m_initialstate; + } + double get_sort_priority() {return m_priority;} + void get_state(pfc::string_base & p_out) { + inReadSync(m_sync); + p_out = m_state; + } + void set_state(const char * p_string,t_size p_length = ~0) { + inWriteSync(m_sync); + m_state.set_string(p_string,p_length); + } + t_uint32 get_flags() {return 0;} + void get_default_state(pfc::string_base & out) {out = m_initialstate;} + t_uint32 get_preferences_flags() {return m_prefFlags;} +private: + const pfc::string8 m_initialstate, m_name; + cfg_string m_state; + pfc::readWriteLock m_sync; + const double m_priority; + const GUID m_parent; + const t_uint32 m_prefFlags; +}; + +//! Special version if advconfig_string_factory that allows the value to be retrieved from worker threads. +class advconfig_string_factory_MT : public service_factory_single_t { +public: + advconfig_string_factory_MT(const char * p_name,const GUID & p_guid,const GUID & p_parent,double p_priority,const char * p_initialstate, t_uint32 p_prefFlags = 0) + : service_factory_single_t(p_name,p_guid,p_parent,p_priority,p_initialstate, p_prefFlags) {} + + void get(pfc::string_base & out) {get_static_instance().get_state(out);} + void set(const char * in) {get_static_instance().set_state(in);} +}; + + + + +/* + Advanced Preferences variable declaration examples + + static advconfig_string_factory mystring("name goes here",myguid,parentguid,0,"asdf"); + to retrieve state: pfc::string8 val; mystring.get(val); + + static advconfig_checkbox_factory mycheckbox("name goes here",myguid,parentguid,0,false); + to retrieve state: mycheckbox.get(); + + static advconfig_integer_factory myint("name goes here",myguid,parentguid,0,initialValue,minimumValue,maximumValue); + to retrieve state: myint.get(); +*/ diff --git a/foobar2000/SDK/album_art.cpp b/foobar2000/SDK/album_art.cpp new file mode 100644 index 0000000..edf0daa --- /dev/null +++ b/foobar2000/SDK/album_art.cpp @@ -0,0 +1,185 @@ +#include "foobar2000.h" + +GUID album_art_extractor::get_guid() { + album_art_extractor_v2::ptr v2; + if ( v2 &= this ) return v2->get_guid(); + return pfc::guid_null; +} + +GUID album_art_editor::get_guid() { + album_art_editor_v2::ptr v2; + if ( v2 &= this ) return v2->get_guid(); + return pfc::guid_null; +} + +bool album_art_extractor_instance::query(const GUID & what, album_art_data::ptr & out, abort_callback & abort) { + try { out = query(what, abort); return true; } catch (exception_album_art_not_found) { return false; } +} + +bool album_art_extractor_instance::have_entry(const GUID & what, abort_callback & abort) { + try { query(what, abort); return true; } catch(exception_album_art_not_found) { return false; } +} + +void album_art_editor_instance::remove_all_() { + album_art_editor_instance_v2::ptr v2; + if ( v2 &= this ) { + v2->remove_all(); + } else { + for( size_t walk = 0; walk < album_art_ids::num_types(); ++ walk ) { + try { + this->remove( album_art_ids::query_type( walk ) ); + } catch(exception_io_data) {} + catch(exception_album_art_not_found) {} + } + } +} + +bool album_art_editor::g_get_interface(service_ptr_t & out,const char * path) { + service_enum_t e; ptr ptr; + auto ext = pfc::string_extension(path); + while(e.next(ptr)) { + if (ptr->is_our_path(path,ext)) { out = ptr; return true; } + } + return false; +} + +bool album_art_editor::g_is_supported_path(const char * path) { + ptr ptr; return g_get_interface(ptr,path); +} + +album_art_editor_instance_ptr album_art_editor::g_open(file_ptr p_filehint,const char * p_path,abort_callback & p_abort) { + +#ifdef FOOBAR2000_DESKTOP + { + input_manager_v2::ptr m; + if (fb2k::std_api_try_get(m)) { + album_art_editor_instance::ptr ret; + ret ^= m->open_v2(album_art_editor_instance::class_guid, p_filehint, p_path, false, nullptr, p_abort); + return ret; + } + } +#endif + + album_art_editor::ptr obj; + if (!g_get_interface(obj, p_path)) throw exception_album_art_unsupported_format(); + return obj->open(p_filehint, p_path, p_abort); +} + + +bool album_art_extractor::g_get_interface(service_ptr_t & out,const char * path) { + service_enum_t e; ptr ptr; + auto ext = pfc::string_extension(path); + while(e.next(ptr)) { + if (ptr->is_our_path(path,ext)) { out = ptr; return true; } + } + return false; +} + +bool album_art_extractor::g_is_supported_path(const char * path) { + ptr ptr; return g_get_interface(ptr,path); +} +album_art_extractor_instance_ptr album_art_extractor::g_open(file_ptr p_filehint,const char * p_path,abort_callback & p_abort) { +#ifdef FOOBAR2000_DESKTOP + { + input_manager_v2::ptr m; + if (fb2k::std_api_try_get(m)) { + album_art_extractor_instance::ptr ret; + ret ^= m->open_v2(album_art_extractor_instance::class_guid, p_filehint, p_path, false, nullptr, p_abort); + return ret; + } + } +#endif + + album_art_extractor::ptr obj; + if (!g_get_interface(obj, p_path)) throw exception_album_art_unsupported_format(); + return obj->open(p_filehint, p_path, p_abort); +} + + +album_art_extractor_instance_ptr album_art_extractor::g_open_allowempty(file_ptr p_filehint,const char * p_path,abort_callback & p_abort) { + try { + return g_open(p_filehint, p_path, p_abort); + } catch(exception_album_art_not_found) { + return new service_impl_t(); + } +} + +namespace { + class now_playing_album_art_notify_lambda : public now_playing_album_art_notify { + public: + void on_album_art(album_art_data::ptr data) { + f(data); + } + std::function f; + }; +} + +now_playing_album_art_notify * now_playing_album_art_notify_manager::add(std::function f ) { + PFC_ASSERT ( f != nullptr ); + auto obj = new now_playing_album_art_notify_lambda; + obj->f = f; + add(obj); + return obj; +} + +namespace { + struct aa_t { + GUID type; const char * name; + }; + static const GUID guids[] = { + album_art_ids::cover_front, + album_art_ids::cover_back, + album_art_ids::artist, + album_art_ids::disc, + album_art_ids::icon, + }; + static const char * const names[] = { + "front cover", + "back cover", + "artist", + "disc", + "icon" + }; + static const char * const names2[] = { + "Front Cover", + "Back Cover", + "Artist", + "Disc", + "Icon" + }; +} + +size_t album_art_ids::num_types() { + PFC_STATIC_ASSERT( PFC_TABSIZE( guids ) == PFC_TABSIZE( names ) ); + PFC_STATIC_ASSERT( PFC_TABSIZE( guids ) == PFC_TABSIZE( names2 ) ); + return PFC_TABSIZE( guids ); +} + +GUID album_art_ids::query_type(size_t idx) { + PFC_ASSERT( idx < PFC_TABSIZE( guids ) ); + return guids[idx]; +} + +const char * album_art_ids::query_name(size_t idx) { + PFC_ASSERT( idx < PFC_TABSIZE( names ) ); + return names[idx]; +} + +const char * album_art_ids::name_of(const GUID & id) { + for( size_t w = 0; w < num_types(); ++w ) { + if ( query_type(w) == id ) return query_name(w); + } + return nullptr; +} + +const char * album_art_ids::query_capitalized_name( size_t idx ) { + PFC_ASSERT( idx < PFC_TABSIZE( names2 ) ); + return names2[idx]; +} + +const char * album_art_ids::capitalized_name_of( const GUID & id) { + for( size_t w = 0; w < num_types(); ++w ) { + if ( query_type(w) == id ) return query_capitalized_name(w); + } + return nullptr; +} diff --git a/foobar2000/SDK/album_art.h b/foobar2000/SDK/album_art.h new file mode 100644 index 0000000..7c0cf90 --- /dev/null +++ b/foobar2000/SDK/album_art.h @@ -0,0 +1,260 @@ +#pragma once + +#include + +//! Common class for handling picture data. \n +//! Type of contained picture data is unknown and to be determined according to memory block contents by code parsing/rendering the picture. Commonly encountered types are: BMP, PNG, JPEG and GIF. \n +//! Implementation: use album_art_data_impl. +class NOVTABLE album_art_data : public service_base { +public: + //! Retrieves a pointer to a memory block containing the picture. + virtual const void * get_ptr() const = 0; + //! Retrieves size of the memory block containing the picture. + virtual t_size get_size() const = 0; + + //! Determine whether two album_art_data objects store the same picture data. + static bool equals(album_art_data const & v1, album_art_data const & v2) { + const t_size s = v1.get_size(); + if (s != v2.get_size()) return false; + return memcmp(v1.get_ptr(), v2.get_ptr(),s) == 0; + } + bool operator==(const album_art_data & other) const {return equals(*this,other);} + bool operator!=(const album_art_data & other) const {return !equals(*this,other);} + + FB2K_MAKE_SERVICE_INTERFACE(album_art_data,service_base); +}; + +typedef service_ptr_t album_art_data_ptr; +namespace fb2k { + typedef album_art_data_ptr memBlockRef; +} + +//! Namespace containing identifiers of album art types. +namespace album_art_ids { + //! Front cover. + static const GUID cover_front = { 0xf1e66f4e, 0xfe09, 0x4b94, { 0x91, 0xa3, 0x67, 0xc2, 0x3e, 0xd1, 0x44, 0x5e } }; + //! Back cover. + static const GUID cover_back = { 0xcb552d19, 0x86d5, 0x434c, { 0xac, 0x77, 0xbb, 0x24, 0xed, 0x56, 0x7e, 0xe4 } }; + //! Picture of a disc or other storage media. + static const GUID disc = { 0x3dba9f36, 0xf928, 0x4fa4, { 0x87, 0x9c, 0xd3, 0x40, 0x47, 0x59, 0x58, 0x7e } }; + //! Album-specific icon (NOT a file type icon). + static const GUID icon = { 0x74cdf5b4, 0x7053, 0x4b3d, { 0x9a, 0x3c, 0x54, 0x69, 0xf5, 0x82, 0x6e, 0xec } }; + //! Artist picture. + static const GUID artist = { 0x9a654042, 0xacd1, 0x43f7, { 0xbf, 0xcf, 0xd3, 0xec, 0xf, 0xfe, 0x40, 0xfa } }; + + size_t num_types(); + GUID query_type( size_t ); + // returns lowercase name + const char * query_name( size_t ); + const char * name_of( const GUID & ); + // returns Capitalized name + const char * query_capitalized_name( size_t ); + const char * capitalized_name_of( const GUID & ); +}; + +PFC_DECLARE_EXCEPTION(exception_album_art_not_found,exception_io_not_found,"Attached picture not found"); +PFC_DECLARE_EXCEPTION(exception_album_art_unsupported_entry,exception_io_data,"Unsupported attached picture entry"); + +PFC_DECLARE_EXCEPTION(exception_album_art_unsupported_format,exception_io_data,"Attached picture operations not supported for this file format"); + +//! Class encapsulating access to album art stored in a media file. Use album_art_extractor class obtain album_art_extractor_instance referring to specified media file. +class NOVTABLE album_art_extractor_instance : public service_base { + FB2K_MAKE_SERVICE_INTERFACE(album_art_extractor_instance,service_base); +public: + //! Throws exception_album_art_not_found when the requested album art entry could not be found in the referenced media file. + virtual album_art_data_ptr query(const GUID & p_what,abort_callback & p_abort) = 0; + + bool have_entry( const GUID & what, abort_callback & abort ); + bool query(const GUID & what, album_art_data::ptr & out, abort_callback & abort); +}; + +//! Class encapsulating access to album art stored in a media file. Use album_art_editor class to obtain album_art_editor_instance referring to specified media file. +class NOVTABLE album_art_editor_instance : public album_art_extractor_instance { + FB2K_MAKE_SERVICE_INTERFACE(album_art_editor_instance,album_art_extractor_instance); +public: + //! Throws exception_album_art_unsupported_entry when the file format we're dealing with does not support specific entry. + virtual void set(const GUID & p_what,album_art_data_ptr p_data,abort_callback & p_abort) = 0; + + //! Removes the requested entry. Fails silently when the entry doesn't exist. + virtual void remove(const GUID & p_what) = 0; + + //! Finalizes file tag update operation. + virtual void commit(abort_callback & p_abort) = 0; + + //! Helper; see album_art_editor_instance_v2::remove_all(); + void remove_all_(); +}; + +class NOVTABLE album_art_editor_instance_v2 : public album_art_editor_instance { + FB2K_MAKE_SERVICE_INTERFACE(album_art_editor_instance_v2, album_art_editor_instance); +public: + //! Tells the editor to remove all entries, including unsupported picture types that do not translate to fb2k ids. + virtual void remove_all() = 0; +}; + +typedef service_ptr_t album_art_extractor_instance_ptr; +typedef service_ptr_t album_art_editor_instance_ptr; + +//! Entrypoint class for accessing album art extraction functionality. Register your own implementation to allow album art extraction from your media file format. \n +//! If you want to extract album art from a media file, it's recommended that you use album_art_manager API instead of calling album_art_extractor directly. +class NOVTABLE album_art_extractor : public service_base { + FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(album_art_extractor); +public: + //! Returns whether the specified file is one of formats supported by our album_art_extractor implementation. + //! @param p_path Path to file being queried. + //! @param p_extension Extension of file being queried (also present in p_path parameter) - provided as a separate parameter for performance reasons. + virtual bool is_our_path(const char * p_path,const char * p_extension) = 0; + + //! Instantiates album_art_extractor_instance providing access to album art stored in a specified media file. \n + //! Throws one of I/O exceptions on failure; exception_album_art_not_found when the file has no album art record at all. + //! @param p_filehint Optional; specifies a file interface to use for accessing the specified file; can be null - in that case, the implementation will open and close the file internally. + virtual album_art_extractor_instance_ptr open(file_ptr p_filehint,const char * p_path,abort_callback & p_abort) = 0; + + static bool g_get_interface(service_ptr_t & out,const char * path); + static bool g_is_supported_path(const char * path); + static album_art_extractor_instance_ptr g_open(file_ptr p_filehint,const char * p_path,abort_callback & p_abort); + static album_art_extractor_instance_ptr g_open_allowempty(file_ptr p_filehint,const char * p_path,abort_callback & p_abort); + + //! Returns GUID of the corresponding input class. Null GUID if none. + GUID get_guid(); +}; + +//! \since 1.5 +class NOVTABLE album_art_extractor_v2 : public album_art_extractor { + FB2K_MAKE_SERVICE_INTERFACE(album_art_extractor_v2 , album_art_extractor); +public: + //! Returns GUID of the corresponding input class. Null GUID if none. + virtual GUID get_guid() = 0; +}; + +//! Entrypoint class for accessing album art editing functionality. Register your own implementation to allow album art editing on your media file format. +class NOVTABLE album_art_editor : public service_base { + FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(album_art_editor); +public: + //! Returns whether the specified file is one of formats supported by our album_art_editor implementation. + //! @param p_path Path to file being queried. + //! @param p_extension Extension of file being queried (also present in p_path parameter) - provided as a separate parameter for performance reasons. + virtual bool is_our_path(const char * p_path,const char * p_extension) = 0; + + //! Instantiates album_art_editor_instance providing access to album art stored in a specified media file. \n + //! @param p_filehint Optional; specifies a file interface to use for accessing the specified file; can be null - in that case, the implementation will open and close the file internally. + virtual album_art_editor_instance_ptr open(file_ptr p_filehint,const char * p_path,abort_callback & p_abort) = 0; + + //! Helper; attempts to retrieve an album_art_editor service pointer that supports the specified file. + //! @returns True on success, false on failure (no registered album_art_editor supports this file type). + static bool g_get_interface(service_ptr_t & out,const char * path); + //! Helper; returns whether one of registered album_art_editor implementations is capable of opening the specified file. + static bool g_is_supported_path(const char * path); + + static album_art_editor_instance_ptr g_open(file_ptr p_filehint,const char * p_path,abort_callback & p_abort); + + //! Returns GUID of the corresponding input class. Null GUID if none. + GUID get_guid(); +}; + +//! \since 1.5 +class NOVTABLE album_art_editor_v2 : public album_art_editor { + FB2K_MAKE_SERVICE_INTERFACE( album_art_editor_v2, album_art_editor ) +public: + //! Returns GUID of the corresponding input class. Null GUID if none. + virtual GUID get_guid() = 0; +}; + +//! \since 0.9.5 +//! Helper API for extracting album art from APEv2 tags. +class NOVTABLE tag_processor_album_art_utils : public service_base { + FB2K_MAKE_SERVICE_COREAPI(tag_processor_album_art_utils) +public: + + //! Throws one of I/O exceptions on failure; exception_album_art_not_found when the file has no album art record at all. + virtual album_art_extractor_instance_ptr open(file_ptr p_file,abort_callback & p_abort) = 0; + + //! \since 1.1.6 + //! Throws exception_not_implemented on earlier than 1.1.6. + virtual album_art_editor_instance_ptr edit(file_ptr p_file,abort_callback & p_abort) = 0; +}; + + +//! Album art path list - see album_art_extractor_instance_v2 +class NOVTABLE album_art_path_list : public service_base { + FB2K_MAKE_SERVICE_INTERFACE(album_art_path_list, service_base) +public: + virtual const char * get_path(t_size index) const = 0; + virtual t_size get_count() const = 0; +}; + +//! album_art_extractor_instance extension; lets the frontend query referenced file paths (eg. when using external album art). +class NOVTABLE album_art_extractor_instance_v2 : public album_art_extractor_instance { + FB2K_MAKE_SERVICE_INTERFACE(album_art_extractor_instance_v2, album_art_extractor_instance) +public: + virtual album_art_path_list::ptr query_paths(const GUID & p_what, abort_callback & p_abort) = 0; +}; + + +//! \since 1.0 +//! Provides methods for interfacing with the foobar2000 core album art loader. \n +//! Use this when you need to load album art for a specific group of tracks. +class NOVTABLE album_art_manager_v2 : public service_base { + FB2K_MAKE_SERVICE_COREAPI(album_art_manager_v2) +public: + //! Instantiates an album art extractor object for the specified group of items. + virtual album_art_extractor_instance_v2::ptr open(metadb_handle_list_cref items, pfc::list_base_const_t const & ids, abort_callback & abort) = 0; + + //! Instantiates an album art extractor object that retrieves stub images. + virtual album_art_extractor_instance_v2::ptr open_stub(abort_callback & abort) = 0; +}; + + +//! \since 1.0 +//! Called when no other album art source (internal, external, other registered fallbacks) returns relevant data for the specified items. \n +//! Can be used to implement online lookup and such. +class NOVTABLE album_art_fallback : public service_base { + FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(album_art_fallback) +public: + virtual album_art_extractor_instance_v2::ptr open(metadb_handle_list_cref items, pfc::list_base_const_t const & ids, abort_callback & abort) = 0; +}; + +//! \since 1.1.7 +class NOVTABLE album_art_manager_config : public service_base { + FB2K_MAKE_SERVICE_INTERFACE(album_art_manager_config, service_base) +public: + virtual bool get_external_pattern(pfc::string_base & out, const GUID & type) = 0; + virtual bool use_embedded_pictures() = 0; + virtual bool use_fallbacks() = 0; +}; + +//! \since 1.1.7 +class NOVTABLE album_art_manager_v3 : public album_art_manager_v2 { + FB2K_MAKE_SERVICE_COREAPI_EXTENSION(album_art_manager_v3, album_art_manager_v2) +public: + //! @param config An optional album_art_manager_config object to override global settings. Pass null to use global settings. + virtual album_art_extractor_instance_v2::ptr open_v3(metadb_handle_list_cref items, pfc::list_base_const_t const & ids, album_art_manager_config::ptr config, abort_callback & abort) = 0; +}; + +//! \since 1.4 +//! A notification about a newly loaded album art being ready to display. \n +//! See: now_playing_album_art_notify_manager. +class NOVTABLE now_playing_album_art_notify { +public: + //! Called when album art has finished loading for the now playing track. + //! @param data The newly loaded album art. Never a null object - the callbacks are simply not called when there is nothing to show. + virtual void on_album_art( album_art_data::ptr data ) = 0; +}; + +//! \since 1.4 +//! Since various components require the album art of the now-playing track, a centralized loader has been provided, so the file isn't hammered independently by different components. \n +//! Use this in conjunction with play_callback notifications to render now-playing track information. +class NOVTABLE now_playing_album_art_notify_manager : public service_base { + FB2K_MAKE_SERVICE_COREAPI(now_playing_album_art_notify_manager) +public: + //! Register a notification to be told when the album art has been loaded. + virtual void add(now_playing_album_art_notify*) = 0; + //! Unregister a previously registered notification. + virtual void remove(now_playing_album_art_notify*) = 0; + //! Retrieves the album art for the currently playing track. + //! @returns The current album art (front cover), or null if there is no art or the art is being loaded and is not yet available. + virtual album_art_data::ptr current() = 0; + + //! Helper; register a lambda notification. Pass the returned obejct to remove() to unregister. + now_playing_album_art_notify* add( std::function ); +}; diff --git a/foobar2000/SDK/album_art_helpers.h b/foobar2000/SDK/album_art_helpers.h new file mode 100644 index 0000000..786c22c --- /dev/null +++ b/foobar2000/SDK/album_art_helpers.h @@ -0,0 +1,157 @@ +#pragma once +//! Implements album_art_data. +class album_art_data_impl : public album_art_data { +public: + const void * get_ptr() const {return m_content.get_ptr();} + t_size get_size() const {return m_content.get_size();} + + void * get_ptr() {return m_content.get_ptr();} + void set_size(t_size p_size) {m_content.set_size(p_size);} + + //! Reads picture data from the specified stream object. + void from_stream(stream_reader * p_stream,t_size p_bytes,abort_callback & p_abort) { + set_size(p_bytes); p_stream->read_object(get_ptr(),p_bytes,p_abort); + } + + //! Creates an album_art_data object from picture data contained in a memory buffer. + static album_art_data_ptr g_create(const void * p_buffer,t_size p_bytes) { + service_ptr_t instance = new service_impl_t(); + instance->set_size(p_bytes); + memcpy(instance->get_ptr(),p_buffer,p_bytes); + return instance; + } + //! Creates an album_art_data object from picture data contained in a stream. + static album_art_data_ptr g_create(stream_reader * p_stream,t_size p_bytes,abort_callback & p_abort) { + service_ptr_t instance = new service_impl_t(); + instance->from_stream(p_stream,p_bytes,p_abort); + return instance; + } + +private: + pfc::array_t m_content; +}; + + +//! Helper - simple implementation of album_art_extractor_instance. +class album_art_extractor_instance_simple : public album_art_extractor_instance { +public: + void set(const GUID & p_what,album_art_data_ptr p_content) {m_content.set(p_what,p_content);} + bool have_item(const GUID & p_what) {return m_content.have_item(p_what);} + album_art_data_ptr query(const GUID & p_what,abort_callback & p_abort) { + album_art_data_ptr temp; + if (!m_content.query(p_what,temp)) throw exception_album_art_not_found(); + return temp; + } + bool is_empty() const {return m_content.get_count() == 0;} + bool remove(const GUID & p_what) { + return m_content.remove(p_what); + } +private: + pfc::map_t m_content; +}; + +//! Helper implementation of album_art_extractor - reads album art from arbitrary file formats that comply with APEv2 tagging specification. +class album_art_extractor_impl_stdtags : public album_art_extractor_v2 { +public: + //! @param exts Semicolon-separated list of file format extensions to support. + album_art_extractor_impl_stdtags(const char * exts, const GUID & guid) : m_guid(guid) { + pfc::splitStringSimple_toList(m_extensions,';',exts); + } + + bool is_our_path(const char * p_path,const char * p_extension) override { + return m_extensions.have_item(p_extension); + } + + album_art_extractor_instance_ptr open(file_ptr p_filehint,const char * p_path,abort_callback & p_abort) override { + PFC_ASSERT( is_our_path(p_path, pfc::string_extension(p_path) ) ); + file_ptr l_file ( p_filehint ); + if (l_file.is_empty()) filesystem::g_open_read(l_file, p_path, p_abort); + return tag_processor_album_art_utils::get()->open( l_file, p_abort ); + } + + GUID get_guid() override { + return m_guid; + } +private: + pfc::avltree_t m_extensions; + const GUID m_guid; +}; + +//! Helper implementation of album_art_editor - edits album art from arbitrary file formats that comply with APEv2 tagging specification. +class album_art_editor_impl_stdtags : public album_art_editor_v2 { +public: + //! @param exts Semicolon-separated list of file format extensions to support. + album_art_editor_impl_stdtags(const char * exts, const GUID & guid) : m_guid(guid) { + pfc::splitStringSimple_toList(m_extensions,';',exts); + } + + bool is_our_path(const char * p_path,const char * p_extension) { + return m_extensions.have_item(p_extension); + } + + album_art_editor_instance_ptr open(file_ptr p_filehint,const char * p_path,abort_callback & p_abort) { + PFC_ASSERT( is_our_path(p_path, pfc::string_extension(p_path) ) ); + file_ptr l_file ( p_filehint ); + if (l_file.is_empty()) filesystem::g_open(l_file, p_path, filesystem::open_mode_write_existing, p_abort); + return tag_processor_album_art_utils::get()->edit( l_file, p_abort ); + } + GUID get_guid() override { + return m_guid; + } +private: + pfc::avltree_t m_extensions; + const GUID m_guid; + +}; + +//! Helper - a more advanced implementation of album_art_extractor_instance. +class album_art_extractor_instance_fileref : public album_art_extractor_instance { +public: + album_art_extractor_instance_fileref(file::ptr f) : m_file(f) {} + + void set(const GUID & p_what,t_filesize p_offset, t_filesize p_size) { + const t_fileref ref = {p_offset, p_size}; + m_data.set(p_what, ref); + m_cache.remove(p_what); + } + + bool have_item(const GUID & p_what) { + return m_data.have_item(p_what); + } + + album_art_data_ptr query(const GUID & p_what,abort_callback & p_abort) { + album_art_data_ptr item; + if (m_cache.query(p_what,item)) return item; + t_fileref ref; + if (!m_data.query(p_what, ref)) throw exception_album_art_not_found(); + m_file->seek(ref.m_offset, p_abort); + item = album_art_data_impl::g_create(m_file.get_ptr(), pfc::downcast_guarded(ref.m_size), p_abort); + m_cache.set(p_what, item); + return item; + } + bool is_empty() const {return m_data.get_count() == 0;} +private: + struct t_fileref { + t_filesize m_offset, m_size; + }; + const file::ptr m_file; + pfc::map_t m_data; + pfc::map_t m_cache; +}; + +//! album_art_path_list implementation helper +class album_art_path_list_impl : public album_art_path_list { +public: + template album_art_path_list_impl(const t_in & in) {pfc::list_to_array(m_data, in);} + const char * get_path(t_size index) const {return m_data[index];} + t_size get_count() const {return m_data.get_size();} +private: + pfc::array_t m_data; +}; + +//! album_art_path_list implementation helper +class album_art_path_list_dummy : public album_art_path_list { +public: + const char * get_path(t_size index) const {uBugCheck();} + t_size get_count() const {return 0;} +}; diff --git a/foobar2000/SDK/app_close_blocker.cpp b/foobar2000/SDK/app_close_blocker.cpp new file mode 100644 index 0000000..c50d76b --- /dev/null +++ b/foobar2000/SDK/app_close_blocker.cpp @@ -0,0 +1,30 @@ +#include "foobar2000.h" + +bool app_close_blocker::g_query() +{ + service_ptr_t ptr; + service_enum_t e; + while(e.next(ptr)) + { + if (!ptr->query()) return false; + } + return true; +} + +service_ptr async_task_manager::g_acquire() { +#if FOOBAR2000_TARGET_VERSION >= 80 + return get()->acquire(); +#else + ptr obj; + if ( tryGet(obj) ) return obj->acquire(); + return nullptr; +#endif +} + +void fb2k::splitTask( std::function f) { + auto taskref = async_task_manager::g_acquire(); + pfc::splitThread( [f,taskref] { + f(); + (void)taskref; // retain until here + } ); +} \ No newline at end of file diff --git a/foobar2000/SDK/app_close_blocker.h b/foobar2000/SDK/app_close_blocker.h new file mode 100644 index 0000000..94b070d --- /dev/null +++ b/foobar2000/SDK/app_close_blocker.h @@ -0,0 +1,88 @@ +#pragma once + +#include + +//! (DEPRECATED) This service is used to signal whether something is currently preventing main window from being closed and app from being shut down. +class NOVTABLE app_close_blocker : public service_base +{ +public: + //! Checks whether this service is currently preventing main window from being closed and app from being shut down. + virtual bool query() = 0; + + //! Static helper function, checks whether any of registered app_close_blocker services is currently preventing main window from being closed and app from being shut down. + static bool g_query(); + + FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(app_close_blocker); +}; + +//! An interface encapsulating a task preventing the foobar2000 application from being closed. Instances of this class need to be registered using app_close_blocking_task_manager methods. \n +//! Implementation: it's recommended that you derive from app_close_blocking_task_impl class instead of deriving from app_close_blocking_task directly, it manages registration/unregistration behind-the-scenes. +class NOVTABLE app_close_blocking_task { +public: + virtual void query_task_name(pfc::string_base & out) = 0; + +protected: + app_close_blocking_task() {} + ~app_close_blocking_task() {} + + PFC_CLASS_NOT_COPYABLE_EX(app_close_blocking_task); +}; + +//! Entrypoint class for registering app_close_blocking_task instances. Introduced in 0.9.5.1. \n +//! Usage: static_api_ptr_t(). May fail if user runs pre-0.9.5.1. It's recommended that you use app_close_blocking_task_impl class instead of calling app_close_blocking_task_manager directly. +class NOVTABLE app_close_blocking_task_manager : public service_base { + FB2K_MAKE_SERVICE_COREAPI(app_close_blocking_task_manager); +public: + virtual void register_task(app_close_blocking_task * task) = 0; + virtual void unregister_task(app_close_blocking_task * task) = 0; +}; + +//! Helper; implements standard functionality required by app_close_blocking_task implementations - registers/unregisters the task on construction/destruction. +class app_close_blocking_task_impl : public app_close_blocking_task { +public: + app_close_blocking_task_impl() { app_close_blocking_task_manager::get()->register_task(this);} + ~app_close_blocking_task_impl() { app_close_blocking_task_manager::get()->unregister_task(this);} + + void query_task_name(pfc::string_base & out) { out = ""; } +}; + +class app_close_blocking_task_impl_dynamic : public app_close_blocking_task { +public: + app_close_blocking_task_impl_dynamic() : m_taskActive() {} + ~app_close_blocking_task_impl_dynamic() { toggle_blocking(false); } + + void query_task_name(pfc::string_base & out) { out = ""; } + +protected: + void toggle_blocking(bool state) { + if (state != m_taskActive) { + auto api = app_close_blocking_task_manager::get(); + if (state) api->register_task(this); + else api->unregister_task(this); + m_taskActive = state; + } + } +private: + bool m_taskActive; +}; + + +//! \since 1.4.5 +//! Provides means for async tasks - running detached from any UI - to reliably finish before the app terminates. \n +//! During a late phase of app shutdown, past initquit ops, when no worker code should be still running - a core-managed instance of abort_callback is signaled, \n +//! then main thread stalls until all objects created with acquire() have been released. \n +//! As this API was introduced out-of-band with 1.4.5, you should use tryGet() to obtain it with FOOBAR2000_TARGET_VERSION < 80, but can safely use ::get() with FOOBAR2000_TARGET_VERSION >= 80. +class async_task_manager : public service_base { + FB2K_MAKE_SERVICE_COREAPI( async_task_manager ); +public: + virtual abort_callback & get_aborter() = 0; + virtual service_ptr acquire() = 0; + + //! acquire() helper; returns nullptr if the API isn't available due to old fb2k + static service_ptr g_acquire(); +}; + +namespace fb2k { + //! pfc::splitThread() + async_task_manager::acquire + void splitTask( std::function ); +} diff --git a/foobar2000/SDK/archive.h b/foobar2000/SDK/archive.h new file mode 100644 index 0000000..d70548a --- /dev/null +++ b/foobar2000/SDK/archive.h @@ -0,0 +1,85 @@ +#pragma once + +namespace foobar2000_io { + class archive; + + class NOVTABLE archive_callback : public abort_callback { + public: + virtual bool on_entry(archive * owner,const char * url,const t_filestats & p_stats,const service_ptr_t & p_reader) = 0; + }; + + //! Interface for archive reader services. When implementing, derive from archive_impl rather than from deriving from archive directly. + class NOVTABLE archive : public filesystem { + FB2K_MAKE_SERVICE_INTERFACE(archive,filesystem); + public: + //! Lists archive contents. \n + //! May be called with any path, not only path accepted by is_our_archive. + virtual void archive_list(const char * p_path,const service_ptr_t & p_reader,archive_callback & p_callback,bool p_want_readers) = 0; + + //! Optional method to weed out unsupported formats prior to calling archive_list. \n + //! Use this to suppress calls to archive_list() to avoid spurious exceptions being thrown. \n + //! Implemented via archive_v2. + bool is_our_archive( const char * path ); + }; + + //! \since 1.5 + //! New 1.5 series API, though allowed to implement/call in earlier versions. \n + //! Suppresses spurious C++ exceptions on all files not recognized as archives by this instance. + class NOVTABLE archive_v2 : public archive { + FB2K_MAKE_SERVICE_INTERFACE(archive_v2, archive) + public: + + //! Optional method to weed out unsupported formats prior to calling archive_list. \n + //! Use this to suppress calls to archive_list() to avoid spurious exceptions being thrown. + virtual bool is_our_archive( const char * path ) = 0; + }; + + //! \since 1.6 + //! New 1.6 series API, though allowed to implement/call in earlier versions. + class NOVTABLE archive_v3 : public archive_v2 { + FB2K_MAKE_SERVICE_INTERFACE(archive_v3, archive_v2) + public: + //! Determine supported archive file types. \n + //! Returns a list of extensions, colon delimited, e.g.: "zip,rar,7z" + virtual void list_extensions(pfc::string_base & out) = 0; + }; + + //! Root class for archive implementations. Derive from this instead of from archive directly. + class NOVTABLE archive_impl : public archive_v3 { + private: + //do not override these + bool get_canonical_path(const char * path,pfc::string_base & out); + bool is_our_path(const char * path); + bool get_display_path(const char * path,pfc::string_base & out); + void remove(const char * path,abort_callback & p_abort); + void move(const char * src,const char * dst,abort_callback & p_abort); + bool is_remote(const char * src); + bool relative_path_create(const char * file_path,const char * playlist_path,pfc::string_base & out); + bool relative_path_parse(const char * relative_path,const char * playlist_path,pfc::string_base & out); + void open(service_ptr_t & p_out,const char * path, t_open_mode mode,abort_callback & p_abort); + void create_directory(const char * path,abort_callback &); + void list_directory(const char * p_path,directory_callback & p_out,abort_callback & p_abort); + void get_stats(const char * p_path,t_filestats & p_stats,bool & p_is_writeable,abort_callback & p_abort); + void list_extensions(pfc::string_base & out) override { out = get_archive_type(); } + protected: + //override these + virtual const char * get_archive_type()=0;//eg. "zip", must be lowercase + virtual t_filestats get_stats_in_archive(const char * p_archive,const char * p_file,abort_callback & p_abort) = 0; + virtual void open_archive(service_ptr_t & p_out,const char * archive,const char * file, abort_callback & p_abort) = 0;//opens for reading + public: + //override these + virtual void archive_list(const char * path,const service_ptr_t & p_reader,archive_callback & p_out,bool p_want_readers)= 0 ; + virtual bool is_our_archive( const char * path ) = 0; + + static bool g_is_unpack_path(const char * path); + static bool g_parse_unpack_path(const char * path,pfc::string_base & archive,pfc::string_base & file); + static bool g_parse_unpack_path_ex(const char * path,pfc::string_base & archive,pfc::string_base & file, pfc::string_base & type); + static void g_make_unpack_path(pfc::string_base & path,const char * archive,const char * file,const char * type); + void make_unpack_path(pfc::string_base & path,const char * archive,const char * file); + + + }; + + template + class archive_factory_t : public service_factory_single_t {}; +} diff --git a/foobar2000/SDK/audio_chunk.cpp b/foobar2000/SDK/audio_chunk.cpp new file mode 100644 index 0000000..8817ca7 --- /dev/null +++ b/foobar2000/SDK/audio_chunk.cpp @@ -0,0 +1,702 @@ +#include "foobar2000.h" + +void audio_chunk::set_data(const audio_sample * src,t_size samples,unsigned nch,unsigned srate,unsigned channel_config) +{ + t_size size = samples * nch; + set_data_size(size); + if (src) + pfc::memcpy_t(get_data(),src,size); + else + pfc::memset_t(get_data(),(audio_sample)0,size); + set_sample_count(samples); + set_channels(nch,channel_config); + set_srate(srate); +} + +inline bool check_exclusive(unsigned val, unsigned mask) +{ + return (val&mask)!=0 && (val&mask)!=mask; +} + +static void _import8u(uint8_t const * in, audio_sample * out, size_t count) { + for(size_t walk = 0; walk < count; ++walk) { + uint32_t i = *(in++); + i -= 0x80; // to signed + *(out++) = (float) (int32_t) i / (float) 0x80; + } +} + +static void _import8s(uint8_t const * in, audio_sample * out, size_t count) { + for(size_t walk = 0; walk < count; ++walk) { + int32_t i = (int8_t) *(in++); + *(out++) = (float) i / (float) 0x80; + } +} + +static audio_sample _import24s(uint32_t i) { + i ^= 0x800000; // to unsigned + i -= 0x800000; // and back to signed / fill MSBs proper + return (float) (int32_t) i / (float) 0x800000; +} + +static void _import24(const void * in_, audio_sample * out, size_t count) { + const uint8_t * in = (const uint8_t*) in_; +#if 1 + while(count > 0 && !pfc::is_ptr_aligned_t<4>(in)) { + uint32_t i = *(in++); + i |= (uint32_t) *(in++) << 8; + i |= (uint32_t) *(in++) << 16; + *(out++) = _import24s(i); + --count; + } + { + for(size_t loop = count >> 2; loop; --loop) { + uint32_t i1 = * (uint32_t*) in; in += 4; + uint32_t i2 = * (uint32_t*) in; in += 4; + uint32_t i3 = * (uint32_t*) in; in += 4; + *out++ = _import24s( i1 & 0xFFFFFF ); + *out++ = _import24s( (i1 >> 24) | ((i2 & 0xFFFF) << 8) ); + *out++ = _import24s( (i2 >> 16) | ((i3 & 0xFF) << 16) ); + *out++ = _import24s( i3 >> 8 ); + } + count &= 3; + } + for( ; count ; --count) { + uint32_t i = *(in++); + i |= (uint32_t) *(in++) << 8; + i |= (uint32_t) *(in++) << 16; + *(out++) = _import24s(i); + } +#else + if (count > 0) { + int32_t i = *(in++); + i |= (int32_t) *(in++) << 8; + i |= (int32_t) (int8_t) *in << 16; + *out++ = (audio_sample) i / (audio_sample) 0x800000; + --count; + + // Now we have in ptr at offset_of_next - 1 and we can read as int32 then discard the LSBs + for(;count;--count) { + int32_t i = *( int32_t*) in; in += 3; + *out++ = (audio_sample) (i >> 8) / (audio_sample) 0x800000; + } + } +#endif +} + +template static void _import16any(const void * in, audio_sample * out, size_t count) { + uint16_t const * inPtr = (uint16_t const*) in; + const audio_sample factor = 1.0f / (audio_sample) 0x8000; + for(size_t walk = 0; walk < count; ++walk) { + uint16_t v = *inPtr++; + if (byteSwap) v = pfc::byteswap_t(v); + if (!isSigned) v ^= 0x8000; // to signed + *out++ = (audio_sample) (int16_t) v * factor; + } +} + +template static void _import32any(const void * in, audio_sample * out, size_t count) { + uint32_t const * inPtr = (uint32_t const*) in; + const audio_sample factor = 1.0f / (audio_sample) 0x80000000ul; + for(size_t walk = 0; walk < count; ++walk) { + uint32_t v = *inPtr++; + if (byteSwap) v = pfc::byteswap_t(v); + if (!isSigned) v ^= 0x80000000u; // to signed + *out++ = (audio_sample) (int32_t) v * factor; + } +} + +template static void _import24any(const void * in, audio_sample * out, size_t count) { + uint8_t const * inPtr = (uint8_t const*) in; + const audio_sample factor = 1.0f / (audio_sample) 0x800000; + for(size_t walk = 0; walk < count; ++walk) { + uint32_t v; + if (byteSwap) v = (uint32_t) inPtr[2] | ( (uint32_t) inPtr[1] << 8 ) | ( (uint32_t) inPtr[0] << 16 ); + else v = (uint32_t) inPtr[0] | ( (uint32_t) inPtr[1] << 8 ) | ( (uint32_t) inPtr[2] << 16 ); + inPtr += 3; + if (isSigned) v ^= 0x800000; // to unsigned + v -= 0x800000; // then subtract to get proper MSBs + *out++ = (audio_sample) (int32_t) v * factor; + } +} + +void audio_chunk::set_data_fixedpoint_ex(const void * source,t_size size,unsigned srate,unsigned nch,unsigned bps,unsigned flags,unsigned p_channel_config) +{ + PFC_ASSERT( check_exclusive(flags,FLAG_SIGNED|FLAG_UNSIGNED) ); + PFC_ASSERT( check_exclusive(flags,FLAG_LITTLE_ENDIAN|FLAG_BIG_ENDIAN) ); + + bool byteSwap = !!(flags & FLAG_BIG_ENDIAN); + if (pfc::byte_order_is_big_endian) byteSwap = !byteSwap; + + t_size count = size / (bps/8); + set_data_size(count); + audio_sample * buffer = get_data(); + bool isSigned = !!(flags & FLAG_SIGNED); + + switch(bps) + { + case 8: + // byte order irrelevant + if (isSigned) _import8s( (const uint8_t*) source , buffer, count); + else _import8u( (const uint8_t*) source , buffer, count); + break; + case 16: + if (byteSwap) { + if (isSigned) { + _import16any( source, buffer, count ); + } else { + _import16any( source, buffer, count ); + } + } else { + if (isSigned) { + //_import16any( source, buffer, count ); + audio_math::convert_from_int16((const int16_t*)source,count,buffer,1.0); + } else { + _import16any( source, buffer, count); + } + } + break; + case 24: + if (byteSwap) { + if (isSigned) { + _import24any( source, buffer, count ); + } else { + _import24any( source, buffer, count ); + } + } else { + if (isSigned) { + //_import24any( source, buffer, count); + _import24( source, buffer, count); + } else { + _import24any( source, buffer, count); + } + } + break; + case 32: + if (byteSwap) { + if (isSigned) { + _import32any( source, buffer, count ); + } else { + _import32any( source, buffer, count ); + } + } else { + if (isSigned) { + audio_math::convert_from_int32((const int32_t*)source,count,buffer,1.0); + } else { + _import32any( source, buffer, count); + } + } + break; + default: + //unknown size, cant convert + pfc::memset_t(buffer,(audio_sample)0,count); + break; + } + set_sample_count(count/nch); + set_srate(srate); + set_channels(nch,p_channel_config); +} + +void audio_chunk::set_data_fixedpoint_ms(const void * ptr, size_t bytes, unsigned sampleRate, unsigned channels, unsigned bps, unsigned channelConfig) { + //set_data_fixedpoint_ex(ptr,bytes,sampleRate,channels,bps,(bps==8 ? FLAG_UNSIGNED : FLAG_SIGNED) | flags_autoendian(), channelConfig); + PFC_ASSERT( bps != 0 ); + size_t count = bytes / (bps/8); + this->set_data_size( count ); + audio_sample * buffer = this->get_data(); + switch(bps) { + case 8: + _import8u((const uint8_t*)ptr, buffer, count); + break; + case 16: + audio_math::convert_from_int16((const int16_t*) ptr, count, buffer, 1.0); + break; + case 24: + _import24( ptr, buffer, count); + break; + case 32: + audio_math::convert_from_int32((const int32_t*) ptr, count, buffer, 1.0); + break; + default: + PFC_ASSERT(!"Unknown bit depth!"); + memset(buffer, 0, sizeof(audio_sample) * count); + break; + } + set_sample_count(count/channels); + set_srate(sampleRate); + set_channels(channels,channelConfig); +} + +void audio_chunk::set_data_fixedpoint_signed(const void * ptr,t_size bytes,unsigned sampleRate,unsigned channels,unsigned bps,unsigned channelConfig) { + PFC_ASSERT( bps != 0 ); + size_t count = bytes / (bps/8); + this->set_data_size( count ); + audio_sample * buffer = this->get_data(); + switch(bps) { + case 8: + _import8s((const uint8_t*)ptr, buffer, count); + break; + case 16: + audio_math::convert_from_int16((const int16_t*) ptr, count, buffer, 1.0); + break; + case 24: + _import24( ptr, buffer, count); + break; + case 32: + audio_math::convert_from_int32((const int32_t*) ptr, count, buffer, 1.0); + break; + default: + PFC_ASSERT(!"Unknown bit depth!"); + memset(buffer, 0, sizeof(audio_sample) * count); + break; + } + set_sample_count(count/channels); + set_srate(sampleRate); + set_channels(channels,channelConfig); +} + +void audio_chunk::set_data_int16(const int16_t * src,t_size samples,unsigned nch,unsigned srate,unsigned channel_config) { + const size_t count = samples * nch; + this->set_data_size( count ); + audio_sample * buffer = this->get_data(); + audio_math::convert_from_int16(src, count, buffer, 1.0); + set_sample_count(samples); + set_srate(srate); + set_channels(nch,channel_config); +} + +template +static void process_float_multi(audio_sample * p_out,const t_float * p_in,const t_size p_count) +{ + for(size_t n=0;n +static void process_float_multi_swap(audio_sample * p_out,const t_float * p_in,const t_size p_count) +{ + for(size_t n=0;n(ptr),count); + else + process_float_multi(out,reinterpret_cast(ptr),count); + } + else if (bps == 64) + { + if (use_swap) + process_float_multi_swap(out,reinterpret_cast(ptr),count); + else + process_float_multi(out,reinterpret_cast(ptr),count); + } else if (bps == 16) { + const uint16_t * in = reinterpret_cast(ptr); + if (use_swap) { + for(size_t walk = 0; walk < count; ++walk) out[walk] = audio_math::decodeFloat16(pfc::byteswap_t(in[walk])); + } else { + for(size_t walk = 0; walk < count; ++walk) out[walk] = audio_math::decodeFloat16(in[walk]); + } + } else if (bps == 24) { + const uint8_t * in = reinterpret_cast(ptr); + if (use_swap) { + for(size_t walk = 0; walk < count; ++walk) out[walk] = audio_math::decodeFloat24ptrbs(&in[walk*3]); + } else { + for(size_t walk = 0; walk < count; ++walk) out[walk] = audio_math::decodeFloat24ptr(&in[walk*3]); + } + } else pfc::throw_exception_with_message< exception_io_data >("invalid bit depth"); + + set_sample_count(count/nch); + set_srate(srate); + set_channels(nch,p_channel_config); +} + +pfc::string8 audio_chunk::formatChunkSpec() const { + pfc::string8 msg; + msg << get_sample_rate() << " Hz, " << get_channels() << ":0x" << pfc::format_hex(get_channel_config(), 2) << " channels, " << get_sample_count() << " samples"; + return msg; +} + +void audio_chunk::debugChunkSpec() const { + FB2K_DebugLog() << "Chunk: " << this->formatChunkSpec(); +} + +#if PFC_DEBUG +void audio_chunk::assert_valid(const char * ctx) const { + if (!is_valid()) { + FB2K_DebugLog() << "audio_chunk::assert_valid failure in " << ctx; + debugChunkSpec(); + uBugCheck(); + } +} +#endif +bool audio_chunk::is_valid() const +{ + unsigned nch = get_channels(); + if (nch == 0 || nch > 32) return false; + if (!g_is_valid_sample_rate(get_srate())) return false; + t_size samples = get_sample_count(); + if (samples==0 || samples >= 0x80000000ul / (sizeof(audio_sample) * nch) ) return false; + t_size size = get_data_size(); + if (samples * nch > size) return false; + if (!get_data()) return false; + return true; +} + +bool audio_chunk::is_spec_valid() const { + return this->get_spec().is_valid(); +} + +void audio_chunk::pad_with_silence_ex(t_size samples,unsigned hint_nch,unsigned hint_srate) { + if (is_empty()) + { + if (hint_srate && hint_nch) { + return set_data(0,samples,hint_nch,hint_srate); + } else throw exception_io_data(); + } + else + { + if (hint_srate && hint_srate != get_srate()) samples = MulDiv_Size(samples,get_srate(),hint_srate); + if (samples > get_sample_count()) + { + t_size old_size = get_sample_count() * get_channels(); + t_size new_size = samples * get_channels(); + set_data_size(new_size); + pfc::memset_t(get_data() + old_size,(audio_sample)0,new_size - old_size); + set_sample_count(samples); + } + } +} + +void audio_chunk::pad_with_silence(t_size samples) { + if (samples > get_sample_count()) + { + t_size old_size = get_sample_count() * get_channels(); + t_size new_size = pfc::multiply_guarded(samples,(size_t)get_channels()); + set_data_size(new_size); + pfc::memset_t(get_data() + old_size,(audio_sample)0,new_size - old_size); + set_sample_count(samples); + } +} + +void audio_chunk::set_silence(t_size samples) { + t_size items = samples * get_channels(); + set_data_size(items); + pfc::memset_null_t(get_data(), items); + set_sample_count(samples); +} + +void audio_chunk::set_silence_seconds( double seconds ) { + set_silence( (size_t) audio_math::time_to_samples( seconds, this->get_sample_rate() ) ); +} + +void audio_chunk::insert_silence_fromstart(t_size samples) { + t_size old_size = get_sample_count() * get_channels(); + t_size delta = samples * get_channels(); + t_size new_size = old_size + delta; + set_data_size(new_size); + audio_sample * ptr = get_data(); + pfc::memmove_t(ptr+delta,ptr,old_size); + pfc::memset_t(ptr,(audio_sample)0,delta); + set_sample_count(get_sample_count() + samples); +} + +bool audio_chunk::process_skip(double & skipDuration) { + t_uint64 skipSamples = audio_math::time_to_samples(skipDuration, get_sample_rate()); + if (skipSamples == 0) {skipDuration = 0; return true;} + const t_size mySamples = get_sample_count(); + if (skipSamples < mySamples) { + skip_first_samples((t_size)skipSamples); + skipDuration = 0; + return true; + } + if (skipSamples == mySamples) { + skipDuration = 0; + return false; + } + skipDuration -= audio_math::samples_to_time(mySamples, get_sample_rate()); + return false; +} + +t_size audio_chunk::skip_first_samples(t_size samples_delta) +{ + t_size samples_old = get_sample_count(); + if (samples_delta >= samples_old) + { + set_sample_count(0); + set_data_size(0); + return samples_old; + } + else + { + t_size samples_new = samples_old - samples_delta; + unsigned nch = get_channels(); + audio_sample * ptr = get_data(); + pfc::memmove_t(ptr,ptr+nch*samples_delta,nch*samples_new); + set_sample_count(samples_new); + set_data_size(nch*samples_new); + return samples_delta; + } +} + +audio_sample audio_chunk::get_peak(audio_sample p_peak) const { + return pfc::max_t(p_peak, get_peak()); +} + +audio_sample audio_chunk::get_peak() const { + return audio_math::calculate_peak(get_data(),get_sample_count() * get_channels()); +} + +void audio_chunk::scale(audio_sample p_value) +{ + audio_sample * ptr = get_data(); + audio_math::scale(ptr,get_sample_count() * get_channels(),ptr,p_value); +} + + +namespace { + +struct sampleToIntDesc { + unsigned bps, bpsValid; + bool useUpperBits; + float scale; +}; +template class sampleToInt { +public: + sampleToInt(sampleToIntDesc const & d) { + clipLo = - ( (int_t) 1 << (d.bpsValid-1)); + clipHi = ( (int_t) 1 << (d.bpsValid-1)) - 1; + scale = (float) ( (int64_t) 1 << (d.bpsValid - 1) ) * d.scale; + if (d.useUpperBits) { + shift = d.bps - d.bpsValid; + } else { + shift = 0; + } + } + inline int_t operator() (audio_sample s) const { + int_t v; + if (sizeof(int_t) > 4) v = (int_t) audio_math::rint64( s * scale ); + else v = (int_t)audio_math::rint32( s * scale ); + return pfc::clip_t( v, clipLo, clipHi) << shift; + } +private: + int_t clipLo, clipHi; + int8_t shift; + float scale; +}; +} +static void render_24bit(const audio_sample * in, t_size inLen, void * out, sampleToIntDesc const & d) { + t_uint8 * outWalk = reinterpret_cast(out); + sampleToInt gen(d); + for(t_size walk = 0; walk < inLen; ++walk) { + int32_t v = gen(in[walk]); + *(outWalk ++) = (t_uint8) (v & 0xFF); + *(outWalk ++) = (t_uint8) ((v >> 8) & 0xFF); + *(outWalk ++) = (t_uint8) ((v >> 16) & 0xFF); + } +} +static void render_8bit(const audio_sample * in, t_size inLen, void * out, sampleToIntDesc const & d) { + sampleToInt gen(d); + t_int8 * outWalk = reinterpret_cast(out); + for(t_size walk = 0; walk < inLen; ++walk) { + *outWalk++ = (t_int8)gen(in[walk]); + } +} +static void render_16bit(const audio_sample * in, t_size inLen, void * out, sampleToIntDesc const & d) { + sampleToInt gen(d); + int16_t * outWalk = reinterpret_cast(out); + for(t_size walk = 0; walk < inLen; ++walk) { + *outWalk++ = (int16_t)gen(in[walk]); + } +} + +template +static void render_32bit_(const audio_sample * in, t_size inLen, void * out, sampleToIntDesc const & d) { + sampleToInt gen(d); // must use int64 for clipping + int32_t * outWalk = reinterpret_cast(out); + for(t_size walk = 0; walk < inLen; ++walk) { + *outWalk++ = (int32_t)gen(in[walk]); + } +} + +bool audio_chunk::g_toFixedPoint(const audio_sample * in, void * out, size_t count, uint32_t bps, uint32_t bpsValid, bool useUpperBits, float scale) { + const sampleToIntDesc d = {bps, bpsValid, useUpperBits, scale}; + if (bps == 0) { + PFC_ASSERT(!"How did we get here?"); + return false; + } else if (bps <= 8) { + render_8bit(in, count, out, d); + } else if (bps <= 16) { + render_16bit(in, count, out, d); + } else if (bps <= 24) { + render_24bit(in, count, out, d); + } else if (bps <= 32) { + if (bpsValid <= 28) { // for speed + render_32bit_(in, count, out, d); + } else { + render_32bit_(in, count, out, d); + } + } else { + PFC_ASSERT(!"How did we get here?"); + return false; + } + + return true; +} + +bool audio_chunk::toFixedPoint(class mem_block_container & out, uint32_t bps, uint32_t bpsValid, bool useUpperBits, float scale) const { + bps = (bps + 7) & ~7; + if (bps < bpsValid) return false; + const size_t count = get_sample_count() * get_channel_count(); + out.set_size( count * (bps/8) ); + return g_toFixedPoint(get_data(), out.get_ptr(), count, bps, bpsValid, useUpperBits, scale); +} + +bool audio_chunk::to_raw_data(mem_block_container & out, t_uint32 bps, bool useUpperBits, float scale) const { + uint32_t bpsValid = bps; + bps = (bps + 7) & ~7; + const size_t count = get_sample_count() * get_channel_count(); + out.set_size( count * (bps/8) ); + void * outPtr = out.get_ptr(); + audio_sample const * inPtr = get_data(); + if (bps == 32) { + float * f = (float*) outPtr; + for(size_t w = 0; w < count; ++w) f[w] = inPtr[w] * scale; + return true; + } else { + return g_toFixedPoint(inPtr, outPtr, count, bps, bpsValid, useUpperBits, scale); + } +} + +audio_chunk::spec_t audio_chunk::makeSpec(uint32_t rate, uint32_t channels) { + return makeSpec( rate, channels, g_guess_channel_config(channels) ); +} + +audio_chunk::spec_t audio_chunk::makeSpec(uint32_t rate, uint32_t channels, uint32_t mask) { + spec_t spec = {}; + spec.sampleRate = rate; spec.chanCount = channels; spec.chanMask = mask; + return spec; +} + +bool audio_chunk::spec_t::equals( const spec_t & v1, const spec_t & v2 ) { + return v1.sampleRate == v2.sampleRate && v1.chanCount == v2.chanCount && v1.chanMask == v2.chanMask; +} + +pfc::string8 audio_chunk::spec_t::toString(const char * delim) const { + pfc::string_formatter temp; + if ( sampleRate > 0 ) temp << sampleRate << "Hz"; + if (chanCount > 0) { + if ( temp.length() > 0 ) temp << delim; + temp << chanCount << "ch"; + } + + if ( chanMask != audio_chunk::channel_config_mono && chanMask != audio_chunk::channel_config_stereo ) { + pfc::string8 strMask; + audio_chunk::g_formatChannelMaskDesc( chanMask, strMask ); + if ( temp.length() > 0) temp << delim; + temp << strMask; + } + return temp; +} + +audio_chunk::spec_t audio_chunk::get_spec() const { + spec_t spec = {}; + spec.sampleRate = this->get_sample_rate(); + spec.chanCount = this->get_channel_count(); + spec.chanMask = this->get_channel_config(); + return spec; +} +void audio_chunk::set_spec(const spec_t & spec) { + set_sample_rate(spec.sampleRate); + set_channels( spec.chanCount, spec.chanMask ); +} + +bool audio_chunk::spec_t::is_valid() const { + if (this->chanCount==0 || this->chanCount>256) return false; + if (!audio_chunk::g_is_valid_sample_rate(this->sampleRate)) return false; + return true; +} + +#ifdef _WIN32 + +WAVEFORMATEX audio_chunk::spec_t::toWFX() const { + const uint32_t sampleWidth = sizeof(audio_sample); + + WAVEFORMATEX wfx = {}; + wfx.wFormatTag = WAVE_FORMAT_IEEE_FLOAT; + wfx.nChannels = chanCount; + wfx.nSamplesPerSec = sampleRate; + wfx.nAvgBytesPerSec = sampleRate * chanCount * sampleWidth; + wfx.nBlockAlign = chanCount * sampleWidth; + wfx.wBitsPerSample = sampleWidth * 8; + return wfx; +} + +WAVEFORMATEXTENSIBLE audio_chunk::spec_t::toWFXEX() const { + const uint32_t sampleWidth = sizeof(audio_sample); + const bool isFloat = true; + + WAVEFORMATEXTENSIBLE wfxe; + wfxe.Format = toWFX(); + wfxe.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE; + wfxe.Format.cbSize = sizeof(wfxe) - sizeof(wfxe.Format); + wfxe.Samples.wValidBitsPerSample = sampleWidth * 8; + wfxe.dwChannelMask = audio_chunk::g_channel_config_to_wfx(this->chanMask); + wfxe.SubFormat = isFloat ? KSDATAFORMAT_SUBTYPE_IEEE_FLOAT : KSDATAFORMAT_SUBTYPE_PCM; + + return wfxe; +} + +WAVEFORMATEX audio_chunk::spec_t::toWFXWithBPS(uint32_t bps) const { + const uint32_t sampleWidth = (bps+7)/8; + + WAVEFORMATEX wfx = {}; + wfx.wFormatTag = WAVE_FORMAT_PCM; + wfx.nChannels = chanCount; + wfx.nSamplesPerSec = sampleRate; + wfx.nAvgBytesPerSec = sampleRate * chanCount * sampleWidth; + wfx.nBlockAlign = chanCount * sampleWidth; + wfx.wBitsPerSample = sampleWidth * 8; + return wfx; +} + +WAVEFORMATEXTENSIBLE audio_chunk::spec_t::toWFXEXWithBPS(uint32_t bps) const { + const uint32_t sampleWidth = (bps + 7) / 8; + const bool isFloat = false; + + WAVEFORMATEXTENSIBLE wfxe; + wfxe.Format = toWFXWithBPS(bps); + wfxe.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE; + wfxe.Format.cbSize = sizeof(wfxe) - sizeof(wfxe.Format); + wfxe.Samples.wValidBitsPerSample = sampleWidth * 8; + wfxe.dwChannelMask = audio_chunk::g_channel_config_to_wfx(this->chanMask); + wfxe.SubFormat = isFloat ? KSDATAFORMAT_SUBTYPE_IEEE_FLOAT : KSDATAFORMAT_SUBTYPE_PCM; + + return wfxe; +} +#endif // _WIN32 + +void audio_chunk::append(const audio_chunk& other) { + if (other.get_spec() != this->get_spec()) { + throw pfc::exception_invalid_params(); + } + + this->grow_data_size(get_used_size() + other.get_used_size()); + audio_sample* p = this->get_data() + get_used_size(); + memcpy(p, other.get_data(), other.get_used_size() * sizeof(audio_sample)); + set_sample_count(get_sample_count() + other.get_sample_count()); +} diff --git a/foobar2000/SDK/audio_chunk.h b/foobar2000/SDK/audio_chunk.h new file mode 100644 index 0000000..22df50c --- /dev/null +++ b/foobar2000/SDK/audio_chunk.h @@ -0,0 +1,383 @@ +#pragma once + +#ifdef _WIN32 +#include +#endif +//! Thrown when audio_chunk sample rate or channel mapping changes in mid-stream and the code receiving audio_chunks can't deal with that scenario. +PFC_DECLARE_EXCEPTION(exception_unexpected_audio_format_change, exception_io_data, "Unexpected audio format change" ); + +//! Interface to container of a chunk of audio data. See audio_chunk_impl for an implementation. +class NOVTABLE audio_chunk { +public: + + enum { + sample_rate_min = 1000, sample_rate_max = 20000000 + }; + static bool g_is_valid_sample_rate(t_uint32 p_val) {return p_val >= sample_rate_min && p_val <= sample_rate_max;} + + //! Channel map flag declarations. Note that order of interleaved channel data in the stream is same as order of these flags. + enum + { + channel_front_left = 1<<0, + channel_front_right = 1<<1, + channel_front_center = 1<<2, + channel_lfe = 1<<3, + channel_back_left = 1<<4, + channel_back_right = 1<<5, + channel_front_center_left = 1<<6, + channel_front_center_right = 1<<7, + channel_back_center = 1<<8, + channel_side_left = 1<<9, + channel_side_right = 1<<10, + channel_top_center = 1<<11, + channel_top_front_left = 1<<12, + channel_top_front_center = 1<<13, + channel_top_front_right = 1<<14, + channel_top_back_left = 1<<15, + channel_top_back_center = 1<<16, + channel_top_back_right = 1<<17, + + channel_config_mono = channel_front_center, + channel_config_stereo = channel_front_left | channel_front_right, + channel_config_4point0 = channel_front_left | channel_front_right | channel_back_left | channel_back_right, + channel_config_5point0 = channel_front_left | channel_front_right | channel_front_center | channel_back_left | channel_back_right, + channel_config_5point1 = channel_front_left | channel_front_right | channel_front_center | channel_lfe | channel_back_left | channel_back_right, + channel_config_5point1_side = channel_front_left | channel_front_right | channel_front_center | channel_lfe | channel_side_left | channel_side_right, + channel_config_7point1 = channel_config_5point1 | channel_side_left | channel_side_right, + + channels_back_left_right = channel_back_left | channel_back_right, + channels_side_left_right = channel_side_left | channel_side_right, + + defined_channel_count = 18, + }; + + //! Helper function; guesses default channel map for the specified channel count. Returns 0 on failure. + static unsigned g_guess_channel_config(unsigned count); + //! Helper function; determines channel map for the specified channel count according to Xiph specs. Throws exception_io_data on failure. + static unsigned g_guess_channel_config_xiph(unsigned count); + + //! Helper function; translates audio_chunk channel map to WAVEFORMATEXTENSIBLE channel map. + static uint32_t g_channel_config_to_wfx(unsigned p_config); + //! Helper function; translates WAVEFORMATEXTENSIBLE channel map to audio_chunk channel map. + static unsigned g_channel_config_from_wfx(uint32_t p_wfx); + + //! Extracts flag describing Nth channel from specified map. Usable to figure what specific channel in a stream means. + static unsigned g_extract_channel_flag(unsigned p_config,unsigned p_index); + //! Counts channels specified by channel map. + static unsigned g_count_channels(unsigned p_config); + //! Calculates index of a channel specified by p_flag in a stream where channel map is described by p_config. + static unsigned g_channel_index_from_flag(unsigned p_config,unsigned p_flag); + + static const char * g_channel_name(unsigned p_flag); + static const char * g_channel_name_byidx(unsigned p_index); + static unsigned g_find_channel_idx(unsigned p_flag); + static void g_formatChannelMaskDesc(unsigned flags, pfc::string_base & out); + static pfc::string8 g_formatChannelMaskDesc(unsigned flags); + + + + //! Retrieves audio data buffer pointer (non-const version). Returned pointer is for temporary use only; it is valid until next set_data_size call, or until the object is destroyed. \n + //! Size of returned buffer is equal to get_data_size() return value (in audio_samples). Amount of actual data may be smaller, depending on sample count and channel count. Conditions where sample count * channel count are greater than data size should not be possible. + virtual audio_sample * get_data() = 0; + //! Retrieves audio data buffer pointer (const version). Returned pointer is for temporary use only; it is valid until next set_data_size call, or until the object is destroyed. \n + //! Size of returned buffer is equal to get_data_size() return value (in audio_samples). Amount of actual data may be smaller, depending on sample count and channel count. Conditions where sample count * channel count are greater than data size should not be possible. + virtual const audio_sample * get_data() const = 0; + //! Retrieves size of allocated buffer space, in audio_samples. + virtual t_size get_data_size() const = 0; + //! Resizes audio data buffer to specified size. Throws std::bad_alloc on failure. + virtual void set_data_size(t_size p_new_size) = 0; + //! Sanity helper, same as set_data_size. + void allocate(size_t size) { set_data_size( size ); } + + //! Retrieves sample rate of contained audio data. + virtual unsigned get_srate() const = 0; + //! Sets sample rate of contained audio data. + virtual void set_srate(unsigned val) = 0; + //! Retrieves channel count of contained audio data. + virtual unsigned get_channels() const = 0; + //! Helper - for consistency - same as get_channels(). + inline unsigned get_channel_count() const {return get_channels();} + //! Retrieves channel map of contained audio data. Conditions where number of channels specified by channel map don't match get_channels() return value should not be possible. + virtual unsigned get_channel_config() const = 0; + //! Sets channel count / channel map. + virtual void set_channels(unsigned p_count,unsigned p_config) = 0; + + //! Retrieves number of valid samples in the buffer. \n + //! Note that a "sample" means a unit of interleaved PCM data representing states of each channel at given point of time, not a single PCM value. \n + //! For an example, duration of contained audio data is equal to sample count / sample rate, while actual size of contained data is equal to sample count * channel count. + virtual t_size get_sample_count() const = 0; + + //! Sets number of valid samples in the buffer. WARNING: sample count * channel count should never be above allocated buffer size. + virtual void set_sample_count(t_size val) = 0; + + //! Helper, same as get_srate(). + inline unsigned get_sample_rate() const {return get_srate();} + //! Helper, same as set_srate(). + inline void set_sample_rate(unsigned val) {set_srate(val);} + + //! Helper; sets channel count to specified value and uses default channel map for this channel count. + void set_channels(unsigned val) {set_channels(val,g_guess_channel_config(val));} + + + //! Helper; resizes audio data buffer when its current size is smaller than requested. + inline void grow_data_size(t_size p_requested) {if (p_requested > get_data_size()) set_data_size(p_requested);} + + + //! Retrieves duration of contained audio data, in seconds. + inline double get_duration() const + { + double rv = 0; + t_size srate = get_srate (), samples = get_sample_count(); + if (srate>0 && samples>0) rv = (double)samples/(double)srate; + return rv; + } + + //! Returns whether the chunk is empty (contains no audio data). + inline bool is_empty() const {return get_channels()==0 || get_srate()==0 || get_sample_count()==0;} + + //! Returns whether the chunk contents are valid (for bug check purposes). + bool is_valid() const; + + void debugChunkSpec() const; + pfc::string8 formatChunkSpec() const; +#if PFC_DEBUG + void assert_valid(const char * ctx) const; +#else + void assert_valid(const char * ctx) const {} +#endif + + + //! Returns whether the chunk contains valid sample rate & channel info (but allows an empty chunk). + bool is_spec_valid() const; + + //! Returns actual amount of audio data contained in the buffer (sample count * channel count). Must not be greater than data size (see get_data_size()). + size_t get_used_size() const {return get_sample_count() * get_channels();} + //! Same as get_used_size(); old confusingly named version. + size_t get_data_length() const {return get_sample_count() * get_channels();} +#ifdef _MSC_VER +#pragma deprecated( get_data_length ) +#endif + + //! Resets all audio_chunk data. + inline void reset() { + set_sample_count(0); + set_srate(0); + set_channels(0,0); + set_data_size(0); + } + + //! Helper, sets chunk data to contents of specified buffer, with specified number of channels / sample rate / channel map. + void set_data(const audio_sample * src,t_size samples,unsigned nch,unsigned srate,unsigned channel_config); + + //! Helper, sets chunk data to contents of specified buffer, with specified number of channels / sample rate, using default channel map for specified channel count. + inline void set_data(const audio_sample * src,t_size samples,unsigned nch,unsigned srate) {set_data(src,samples,nch,srate,g_guess_channel_config(nch));} + + void set_data_int16(const int16_t * src,t_size samples,unsigned nch,unsigned srate,unsigned channel_config); + + //! Helper, sets chunk data to contents of specified buffer, using default win32/wav conventions for signed/unsigned switch. + inline void set_data_fixedpoint(const void * ptr,t_size bytes,unsigned srate,unsigned nch,unsigned bps,unsigned channel_config) { + this->set_data_fixedpoint_ms(ptr, bytes, srate, nch, bps, channel_config); + } + + void set_data_fixedpoint_signed(const void * ptr,t_size bytes,unsigned srate,unsigned nch,unsigned bps,unsigned channel_config); + + enum + { + FLAG_LITTLE_ENDIAN = 1, + FLAG_BIG_ENDIAN = 2, + FLAG_SIGNED = 4, + FLAG_UNSIGNED = 8, + }; + + inline static unsigned flags_autoendian() { + return pfc::byte_order_is_big_endian ? FLAG_BIG_ENDIAN : FLAG_LITTLE_ENDIAN; + } + + void set_data_fixedpoint_ex(const void * ptr,t_size bytes,unsigned p_sample_rate,unsigned p_channels,unsigned p_bits_per_sample,unsigned p_flags,unsigned p_channel_config);//p_flags - see FLAG_* above + + void set_data_fixedpoint_ms(const void * ptr, size_t bytes, unsigned sampleRate, unsigned channels, unsigned bps, unsigned channelConfig); + + void set_data_floatingpoint_ex(const void * ptr,t_size bytes,unsigned p_sample_rate,unsigned p_channels,unsigned p_bits_per_sample,unsigned p_flags,unsigned p_channel_config);//signed/unsigned flags dont apply + + inline void set_data_32(const float * src,t_size samples,unsigned nch,unsigned srate) {return set_data(src,samples,nch,srate);} + + //! Appends silent samples at the end of the chunk. \n + //! The chunk may be empty prior to this call, its sample rate & channel count will be set to the specified values then. \n + //! The chunk may have different sample rate than requested; silent sample count will be recalculated to the used sample rate retaining actual duration. + //! @param samples Number of silent samples to append. + //! @param hint_nch If no channel count is set on this chunk, it will be set to this value. + //! @param hint_srate The sample rate of silent samples being inserted. If no sampler ate is set on this chunk, it will be set to this value.\n + //! Otherwise if chunk's sample rate doesn't match hint_srate, sample count will be recalculated to chunk's actual sample rate. + void pad_with_silence_ex(t_size samples,unsigned hint_nch,unsigned hint_srate); + //! Appends silent samples at the end of the chunk. \n + //! The chunk must have valid sample rate & channel count prior to this call. + //! @param Number of silent samples to append.s + void pad_with_silence(t_size samples); + //! Inserts silence at the beginning of the audio chunk. + //! @param Number of silent samples to insert. + void insert_silence_fromstart(t_size samples); + //! Helper; removes N first samples from the chunk. \n + //! If the chunk contains fewer samples than requested, it becomes empty. + //! @returns Number of samples actually removed. + t_size skip_first_samples(t_size samples); + //! Produces a chunk of silence, with the specified duration. \n + //! Any existing audio sdata will be discarded. \n + //! Expects sample rate and channel count to be set first. \n + //! Also allocates memory for the requested amount of data see: set_data_size(). + //! @param samples Desired number of samples. + void set_silence(t_size samples); + //! Produces a chunk of silence, with the specified duration. \n + //! Any existing audio sdata will be discarded. \n + //! Expects sample rate and channel count to be set first. \n + //! Also allocates memory for the requested amount of data see: set_data_size(). + //! @param samples Desired duration in seconds. + void set_silence_seconds( double seconds ); + + //! Helper; skips first samples of the chunk updating a remaining to-skip counter. + //! @param skipDuration Reference to the duration of audio remining to be skipped, in seconds. Updated by each call. + //! @returns False if the chunk became empty, true otherwise. + bool process_skip(double & skipDuration); + + //! Simple function to get original PCM stream back. Assumes host's endianness, integers are signed - including the 8bit mode; 32bit mode assumed to be float. + //! @returns false when the conversion could not be performed because of unsupported bit depth etc. + bool to_raw_data(class mem_block_container & out, t_uint32 bps, bool useUpperBits = true, float scale = 1.0) const; + + //! Convert audio_chunk contents to fixed-point PCM format. + //! @param useUpperBits relevant if bps != bpsValid, signals whether upper or lower bits of each sample should be used. + bool toFixedPoint(class mem_block_container & out, uint32_t bps, uint32_t bpsValid, bool useUpperBits = true, float scale = 1.0) const; + + //! Convert a buffer of audio_samples to fixed-point PCM format. + //! @param useUpperBits relevant if bps != bpsValid, signals whether upper or lower bits of each sample should be used. + static bool g_toFixedPoint(const audio_sample * in, void * out, size_t count, uint32_t bps, uint32_t bpsValid, bool useUpperBits = true, float scale = 1.0); + + + //! Helper, calculates peak value of data in the chunk. The optional parameter specifies initial peak value, to simplify calling code. + audio_sample get_peak(audio_sample p_peak) const; + audio_sample get_peak() const; + + //! Helper function; scales entire chunk content by specified value. + void scale(audio_sample p_value); + + //! Helper; copies content of another audio chunk to this chunk. + void copy(const audio_chunk & p_source) { + set_data(p_source.get_data(),p_source.get_sample_count(),p_source.get_channels(),p_source.get_srate(),p_source.get_channel_config()); + } + + const audio_chunk & operator=(const audio_chunk & p_source) { + copy(p_source); + return *this; + } + + struct spec_t { + uint32_t sampleRate; + uint32_t chanCount, chanMask; + + static bool equals( const spec_t & v1, const spec_t & v2 ); + bool operator==(const spec_t & other) const { return equals(*this, other);} + bool operator!=(const spec_t & other) const { return !equals(*this, other);} + bool is_valid() const; + void clear() { sampleRate = 0; chanCount = 0; chanMask = 0; } + +#ifdef _WIN32 + //! Creates WAVE_FORMAT_IEEE_FLOAT WAVEFORMATEX structure + WAVEFORMATEX toWFX() const; + //! Creates WAVE_FORMAT_IEEE_FLOAT WAVEFORMATEXTENSIBLE structure + WAVEFORMATEXTENSIBLE toWFXEX() const; + //! Creates WAVE_FORMAT_PCM WAVEFORMATEX structure + WAVEFORMATEX toWFXWithBPS(uint32_t bps) const; + //! Creates WAVE_FORMAT_PCM WAVEFORMATEXTENSIBLE structure + WAVEFORMATEXTENSIBLE toWFXEXWithBPS(uint32_t bps) const; +#endif + + pfc::string8 toString( const char * delim = " " ) const; + }; + static spec_t makeSpec(uint32_t rate, uint32_t channels); + static spec_t makeSpec(uint32_t rate, uint32_t channels, uint32_t chanMask); + static spec_t emptySpec() { return makeSpec(0, 0, 0); } + + spec_t get_spec() const; + void set_spec(const spec_t &); + + void append(const audio_chunk& other); +protected: + audio_chunk() {} + ~audio_chunk() {} +}; + +//! Implementation of audio_chunk. Takes pfc allocator template as template parameter. +template > +class audio_chunk_impl_t : public audio_chunk { + typedef audio_chunk_impl_t t_self; + container_t m_data; + unsigned m_srate = 0, m_nch = 0, m_setup = 0; + t_size m_samples = 0; + +public: + audio_chunk_impl_t() {} + audio_chunk_impl_t(const audio_sample * src,unsigned samples,unsigned nch,unsigned srate) {set_data(src,samples,nch,srate);} + audio_chunk_impl_t(const audio_chunk & p_source) {copy(p_source);} + + + virtual audio_sample * get_data() {return m_data.get_ptr();} + virtual const audio_sample * get_data() const {return m_data.get_ptr();} + virtual t_size get_data_size() const {return m_data.get_size();} + virtual void set_data_size(t_size new_size) {m_data.set_size(new_size);} + + virtual unsigned get_srate() const {return m_srate;} + virtual void set_srate(unsigned val) {m_srate=val;} + virtual unsigned get_channels() const {return m_nch;} + virtual unsigned get_channel_config() const {return m_setup;} + virtual void set_channels(unsigned val,unsigned setup) {m_nch = val;m_setup = setup;} + void set_channels(unsigned val) {set_channels(val,g_guess_channel_config(val));} + + virtual t_size get_sample_count() const {return m_samples;} + virtual void set_sample_count(t_size val) {m_samples = val;} + + const t_self & operator=(const audio_chunk & p_source) {copy(p_source);return *this;} +}; + +typedef audio_chunk_impl_t<> audio_chunk_impl; +typedef audio_chunk_impl_t > audio_chunk_fast_impl; + +//! Implements const methods of audio_chunk only, referring to an external buffer. For temporary use only (does not maintain own storage), e.g.: somefunc( audio_chunk_temp_impl(mybuffer,....) ); +class audio_chunk_memref_impl : public audio_chunk { +public: + audio_chunk_memref_impl(const audio_sample * p_data,t_size p_samples,t_uint32 p_sample_rate,t_uint32 p_channels,t_uint32 p_channel_config) : + m_samples(p_samples), m_sample_rate(p_sample_rate), m_channels(p_channels), m_channel_config(p_channel_config), m_data(p_data) + { +#if PFC_DEBUG + assert_valid(__FUNCTION__); +#endif + } + + audio_sample * get_data() {throw pfc::exception_not_implemented();} + const audio_sample * get_data() const {return m_data;} + t_size get_data_size() const {return m_samples * m_channels;} + void set_data_size(t_size) {throw pfc::exception_not_implemented();} + + unsigned get_srate() const {return m_sample_rate;} + void set_srate(unsigned) {throw pfc::exception_not_implemented();} + unsigned get_channels() const {return m_channels;} + unsigned get_channel_config() const {return m_channel_config;} + void set_channels(unsigned,unsigned) {throw pfc::exception_not_implemented();} + + t_size get_sample_count() const {return m_samples;} + + void set_sample_count(t_size) {throw pfc::exception_not_implemented();} + +private: + t_size m_samples; + t_uint32 m_sample_rate,m_channels,m_channel_config; + const audio_sample * m_data; +}; + + +// Compatibility typedefs. +typedef audio_chunk_fast_impl audio_chunk_impl_temporary; +typedef audio_chunk_impl audio_chunk_i; +typedef audio_chunk_memref_impl audio_chunk_temp_impl; + +class audio_chunk_partial_ref : public audio_chunk_temp_impl { +public: + audio_chunk_partial_ref(const audio_chunk & chunk, t_size base, t_size count) : audio_chunk_temp_impl(chunk.get_data() + base * chunk.get_channels(), count, chunk.get_sample_rate(), chunk.get_channels(), chunk.get_channel_config()) {} +}; diff --git a/foobar2000/SDK/audio_chunk_channel_config.cpp b/foobar2000/SDK/audio_chunk_channel_config.cpp new file mode 100644 index 0000000..d4a700d --- /dev/null +++ b/foobar2000/SDK/audio_chunk_channel_config.cpp @@ -0,0 +1,210 @@ +#include "foobar2000.h" + +#ifdef _WIN32 +#include +#include + +#if 0 +#define SPEAKER_FRONT_LEFT 0x1 +#define SPEAKER_FRONT_RIGHT 0x2 +#define SPEAKER_FRONT_CENTER 0x4 +#define SPEAKER_LOW_FREQUENCY 0x8 +#define SPEAKER_BACK_LEFT 0x10 +#define SPEAKER_BACK_RIGHT 0x20 +#define SPEAKER_FRONT_LEFT_OF_CENTER 0x40 +#define SPEAKER_FRONT_RIGHT_OF_CENTER 0x80 +#define SPEAKER_BACK_CENTER 0x100 +#define SPEAKER_SIDE_LEFT 0x200 +#define SPEAKER_SIDE_RIGHT 0x400 +#define SPEAKER_TOP_CENTER 0x800 +#define SPEAKER_TOP_FRONT_LEFT 0x1000 +#define SPEAKER_TOP_FRONT_CENTER 0x2000 +#define SPEAKER_TOP_FRONT_RIGHT 0x4000 +#define SPEAKER_TOP_BACK_LEFT 0x8000 +#define SPEAKER_TOP_BACK_CENTER 0x10000 +#define SPEAKER_TOP_BACK_RIGHT 0x20000 + +static struct {DWORD m_wfx; unsigned m_native; } const g_translation_table[] = +{ + {SPEAKER_FRONT_LEFT, audio_chunk::channel_front_left}, + {SPEAKER_FRONT_RIGHT, audio_chunk::channel_front_right}, + {SPEAKER_FRONT_CENTER, audio_chunk::channel_front_center}, + {SPEAKER_LOW_FREQUENCY, audio_chunk::channel_lfe}, + {SPEAKER_BACK_LEFT, audio_chunk::channel_back_left}, + {SPEAKER_BACK_RIGHT, audio_chunk::channel_back_right}, + {SPEAKER_FRONT_LEFT_OF_CENTER, audio_chunk::channel_front_center_left}, + {SPEAKER_FRONT_RIGHT_OF_CENTER, audio_chunk::channel_front_center_right}, + {SPEAKER_BACK_CENTER, audio_chunk::channel_back_center}, + {SPEAKER_SIDE_LEFT, audio_chunk::channel_side_left}, + {SPEAKER_SIDE_RIGHT, audio_chunk::channel_side_right}, + {SPEAKER_TOP_CENTER, audio_chunk::channel_top_center}, + {SPEAKER_TOP_FRONT_LEFT, audio_chunk::channel_top_front_left}, + {SPEAKER_TOP_FRONT_CENTER, audio_chunk::channel_top_front_center}, + {SPEAKER_TOP_FRONT_RIGHT, audio_chunk::channel_top_front_right}, + {SPEAKER_TOP_BACK_LEFT, audio_chunk::channel_top_back_left}, + {SPEAKER_TOP_BACK_CENTER, audio_chunk::channel_top_back_center}, + {SPEAKER_TOP_BACK_RIGHT, audio_chunk::channel_top_back_right}, +}; + +#endif +#endif + +// foobar2000 channel flags are 1:1 identical to Windows WFX ones. +uint32_t audio_chunk::g_channel_config_to_wfx(unsigned p_config) +{ + return p_config; +#if 0 + DWORD ret = 0; + unsigned n; + for(n=0;n 32) throw exception_io_data(); + unsigned ret = 0; + if (count < PFC_TABSIZE(g_audio_channel_config_table)) ret = g_audio_channel_config_table[count]; + if (ret == 0) { + ret = (1 << count) - 1; + } + PFC_ASSERT(g_count_channels(ret) == count); + return ret; +} + +unsigned audio_chunk::g_guess_channel_config_xiph(unsigned count) { + return g_guess_channel_config(count); +} + +unsigned audio_chunk::g_channel_index_from_flag(unsigned p_config,unsigned p_flag) { + unsigned index = 0; + for(unsigned walk = 0; walk < 32; walk++) { + unsigned query = 1 << walk; + if (p_flag & query) return index; + if (p_config & query) index++; + } + return ~0; +} + +unsigned audio_chunk::g_extract_channel_flag(unsigned p_config,unsigned p_index) +{ + unsigned toskip = p_index; + unsigned flag = 1; + while(flag) + { + if (p_config & flag) + { + if (toskip == 0) break; + toskip--; + } + flag <<= 1; + } + return flag; +} + +unsigned audio_chunk::g_count_channels(unsigned p_config) +{ + return pfc::countBits32(p_config); +} + +static const char * const chanNames[] = { + "FL", //channel_front_left = 1<<0, + "FR", //channel_front_right = 1<<1, + "FC", //channel_front_center = 1<<2, + "LFE", //channel_lfe = 1<<3, + "BL", //channel_back_left = 1<<4, + "BR", //channel_back_right = 1<<5, + "FCL", //channel_front_center_left = 1<<6, + "FCR", //channel_front_center_right = 1<<7, + "BC", //channel_back_center = 1<<8, + "SL", //channel_side_left = 1<<9, + "SR", //channel_side_right = 1<<10, + "TC", //channel_top_center = 1<<11, + "TFL", //channel_top_front_left = 1<<12, + "TFC", //channel_top_front_center = 1<<13, + "TFR", //channel_top_front_right = 1<<14, + "TBL", //channel_top_back_left = 1<<15, + "TBC", //channel_top_back_center = 1<<16, + "TBR", //channel_top_back_right = 1<<17, +}; + +unsigned audio_chunk::g_find_channel_idx(unsigned p_flag) { + unsigned rv = 0; + if ((p_flag & 0xFFFF) == 0) { + rv += 16; p_flag >>= 16; + } + if ((p_flag & 0xFF) == 0) { + rv += 8; p_flag >>= 8; + } + if ((p_flag & 0xF) == 0) { + rv += 4; p_flag >>= 4; + } + if ((p_flag & 0x3) == 0) { + rv += 2; p_flag >>= 2; + } + if ((p_flag & 0x1) == 0) { + rv += 1; p_flag >>= 1; + } + PFC_ASSERT( p_flag & 1 ); + return rv; +} + +const char * audio_chunk::g_channel_name(unsigned p_flag) { + return g_channel_name_byidx(g_find_channel_idx(p_flag)); +} + +const char * audio_chunk::g_channel_name_byidx(unsigned p_index) { + if (p_index < PFC_TABSIZE(chanNames)) return chanNames[p_index]; + else return "?"; +} + +pfc::string8 audio_chunk::g_formatChannelMaskDesc(unsigned flags) { + pfc::string8 temp; g_formatChannelMaskDesc(flags, temp); return temp; +} +void audio_chunk::g_formatChannelMaskDesc(unsigned flags, pfc::string_base & out) { + out.reset(); + unsigned idx = 0; + while(flags) { + if (flags & 1) { + if (!out.is_empty()) out << " "; + out << g_channel_name_byidx(idx); + } + flags >>= 1; + ++idx; + } +} \ No newline at end of file diff --git a/foobar2000/SDK/audio_chunk_impl.h b/foobar2000/SDK/audio_chunk_impl.h new file mode 100644 index 0000000..49f92bd --- /dev/null +++ b/foobar2000/SDK/audio_chunk_impl.h @@ -0,0 +1,3 @@ +#pragma once + +// header added for fb2k mobile compatibility \ No newline at end of file diff --git a/foobar2000/SDK/audio_postprocessor.h b/foobar2000/SDK/audio_postprocessor.h new file mode 100644 index 0000000..6526b64 --- /dev/null +++ b/foobar2000/SDK/audio_postprocessor.h @@ -0,0 +1,27 @@ +#pragma once + +//! This class handles conversion of audio data (audio_chunk) to various linear PCM types, with optional dithering. + +class NOVTABLE audio_postprocessor : public service_base +{ +public: + //! Processes one chunk of audio data. + //! @param p_chunk Chunk of audio data to process. + //! @param p_output Receives output linear signed PCM data. + //! @param p_out_bps Desired bit depth of output. + //! @param p_out_bps_physical Desired physical word width of output. Must be either 8, 16, 24 or 32, greater or equal to p_out_bps. This is typically set to same value as p_out_bps. + //! @param p_dither Indicates whether dithering should be used. Note that dithering is CPU-heavy. + //! @param p_prescale Value to scale all audio samples by when converting. Set to 1.0 to do nothing. + + virtual void run(const audio_chunk & p_chunk, + mem_block_container & p_output, + t_uint32 p_out_bps, + t_uint32 p_out_bps_physical, + bool p_dither, + audio_sample p_prescale + ) = 0; + + + + FB2K_MAKE_SERVICE_COREAPI(audio_postprocessor); +}; diff --git a/foobar2000/SDK/autoplaylist.h b/foobar2000/SDK/autoplaylist.h new file mode 100644 index 0000000..e95c30c --- /dev/null +++ b/foobar2000/SDK/autoplaylist.h @@ -0,0 +1,104 @@ +/* + Autoplaylist APIs + These APIs were introduced in foobar2000 0.9.5, to reduce amount of code required to create your own autoplaylists. Creation of autoplaylists is was also possible before through playlist lock APIs. + In most cases, you'll want to turn regular playlists into autoplaylists using the following code: + autoplaylist_manager::get()->add_client_simple(querypattern, sortpattern, playlistindex, forceSort ? autoplaylist_flag_sort : 0); + If you require more advanced functionality, such as using your own code to filter which part of user's Media Library should be placed in specific autoplaylist, you must implement autoplaylist_client (to let autoplaylist manager invoke your handlers when needed) / autoplaylist_client_factory (to re-instantiate your autoplaylist_client after a foobar2000 restart cycle). +*/ + +enum { + //! When set, core will keep the autoplaylist sorted and prevent user from reordering it. + autoplaylist_flag_sort = 1 << 0, +}; +//! Main class controlling autoplaylist behaviors. Implemented by autoplaylist client in scenarios where simple query/sort strings are not enough (core provides a standard implementation for simple queries). +class NOVTABLE autoplaylist_client : public service_base { + FB2K_MAKE_SERVICE_INTERFACE(autoplaylist_client,service_base) +public: + virtual GUID get_guid() = 0; + //! Provides a boolean mask of which items from the specified list should appear in this autoplaylist. + virtual void filter(metadb_handle_list_cref data, bool * out) = 0; + //! Return true when you have filled p_orderbuffer with a permutation to apply to p_items, false when you don't support sorting (core's own sort scheme will be applied). + virtual bool sort(metadb_handle_list_cref p_items,t_size * p_orderbuffer) = 0; + //! Retrieves your configuration data to be used later when re-instantiating your autoplaylist_client after a restart. + virtual void get_configuration(stream_writer * p_stream,abort_callback & p_abort) = 0; + + virtual void show_ui(t_size p_source_playlist) = 0; + + //! Helper. + template void get_configuration(t_array & p_out) { + PFC_STATIC_ASSERT( sizeof(p_out[0]) == 1 ); + typedef pfc::array_t t_temp; t_temp temp; + { + stream_writer_buffer_append_ref_t writer(temp); + get_configuration(&writer,fb2k::noAbort); + } + p_out = temp; + } +}; + +typedef service_ptr_t autoplaylist_client_ptr; + +//! \since 0.9.5.3 +class NOVTABLE autoplaylist_client_v2 : public autoplaylist_client { + FB2K_MAKE_SERVICE_INTERFACE(autoplaylist_client_v2, autoplaylist_client); +public: + //! Sets a completion_notify object that the autoplaylist_client implementation should call when its filtering behaviors have changed so the whole playlist needs to be rebuilt. \n + //! completion_notify::on_completion() status parameter meaning: \n + //! 0.9.5.3 : ignored. \n + //! 0.9.5.4 and newer: set to 1 to indicate that your configuration has changed as well (for an example as a result of user edits) to get a get_configuration() call as well as cause the playlist to be rebuilt; set to zero otherwise - when the configuration hasn't changed but the playlist needs to be rebuilt as a result of some other event. + virtual void set_full_refresh_notify(completion_notify::ptr notify) = 0; + + //! Returns whether the show_ui() method is available / does anything useful with our implementation (not everyone implements show_ui). + virtual bool show_ui_available() = 0; + + //! Returns a human-readable autoplaylist implementer's label to display in playlist's context menu / description / etc. + virtual void get_display_name(pfc::string_base & out) = 0; +}; + +//! Class needed to re-instantiate autoplaylist_client after a restart. Not directly needed to set up an autoplaylist_client, but without it, your autoplaylist will be lost after a restart. +class NOVTABLE autoplaylist_client_factory : public service_base { + FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(autoplaylist_client_factory) +public: + //! Must return same GUID as your autoplaylist_client::get_guid() + virtual GUID get_guid() = 0; + //! Instantiates your autoplaylist_client with specified configuration. + virtual autoplaylist_client_ptr instantiate(stream_reader * p_stream,t_size p_sizehint,abort_callback & p_abort) = 0; +}; + +PFC_DECLARE_EXCEPTION(exception_autoplaylist,pfc::exception,"Autoplaylist error") + +PFC_DECLARE_EXCEPTION(exception_autoplaylist_already_owned,exception_autoplaylist,"This playlist is already an autoplaylist") +PFC_DECLARE_EXCEPTION(exception_autoplaylist_not_owned,exception_autoplaylist,"This playlist is not an autoplaylist") +PFC_DECLARE_EXCEPTION(exception_autoplaylist_lock_failure,exception_autoplaylist,"Playlist could not be locked") + + +//! Primary class for managing autoplaylists. Implemented by core, do not reimplement; instantiate using autoplaylist_manager::get(). +class NOVTABLE autoplaylist_manager : public service_base { + FB2K_MAKE_SERVICE_COREAPI(autoplaylist_manager) +public: + //! Throws exception_autoplaylist or one of its subclasses on failure. + //! @param p_flags See autoplaylist_flag_* constants. + virtual void add_client(autoplaylist_client_ptr p_client,t_size p_playlist,t_uint32 p_flags) = 0; + virtual bool is_client_present(t_size p_playlist) = 0; + //! Throws exception_autoplaylist or one of its subclasses on failure (eg. not an autoplaylist). + virtual autoplaylist_client_ptr query_client(t_size p_playlist) = 0; + virtual void remove_client(t_size p_playlist) = 0; + //! Helper; sets up an autoplaylist using standard autoplaylist_client implementation based on simple query/sort strings. When using this, you don't need to maintain own autoplaylist_client/autoplaylist_client_factory implementations, and autoplaylists that you create will not be lost when your DLL is removed, as opposed to using add_client() directly. + //! Throws exception_autoplaylist or one of its subclasses on failure. + //! @param p_flags See autoplaylist_flag_* constants. + virtual void add_client_simple(const char * p_query,const char * p_sort,t_size p_playlist,t_uint32 p_flags) = 0; +}; + +//! \since 0.9.5.4 +//! Extended version of autoplaylist_manager, available from 0.9.5.4 up, with methods allowing modification of autoplaylist flags. +class NOVTABLE autoplaylist_manager_v2 : public autoplaylist_manager { + FB2K_MAKE_SERVICE_COREAPI_EXTENSION(autoplaylist_manager_v2, autoplaylist_manager) +public: + virtual t_uint32 get_client_flags(t_size playlist) = 0; + virtual void set_client_flags(t_size playlist, t_uint32 newFlags) = 0; + + //! For use with autoplaylist client configuration dialogs. It's recommended not to call this from anything else. + virtual t_uint32 get_client_flags(autoplaylist_client::ptr client) = 0; + //! For use with autoplaylist client configuration dialogs. It's recommended not to call this from anything else. + virtual void set_client_flags(autoplaylist_client::ptr client, t_uint32 newFlags) = 0; +}; diff --git a/foobar2000/SDK/cfg_var.cpp b/foobar2000/SDK/cfg_var.cpp new file mode 100644 index 0000000..6887813 --- /dev/null +++ b/foobar2000/SDK/cfg_var.cpp @@ -0,0 +1,60 @@ +#include "foobar2000.h" + +cfg_var_reader * cfg_var_reader::g_list = NULL; +cfg_var_writer * cfg_var_writer::g_list = NULL; + +void cfg_var_reader::config_read_file(stream_reader * p_stream,abort_callback & p_abort) +{ + pfc::map_t vars; + for(cfg_var_reader * walk = g_list; walk != NULL; walk = walk->m_next) { + vars.set(walk->m_guid,walk); + } + for(;;) { + + GUID guid; + t_uint32 size; + + if (p_stream->read(&guid,sizeof(guid),p_abort) != sizeof(guid)) break; + guid = pfc::byteswap_if_be_t(guid); + p_stream->read_lendian_t(size,p_abort); + + cfg_var_reader * var; + if (vars.query(guid,var)) { + stream_reader_limited_ref wrapper(p_stream,size); + try { + var->set_data_raw(&wrapper,size,p_abort); + } catch(exception_io_data) {} + wrapper.flush_remaining(p_abort); + } else { + p_stream->skip_object(size,p_abort); + } + } +} + +void cfg_var_writer::config_write_file(stream_writer * p_stream,abort_callback & p_abort) { + cfg_var_writer * ptr; + pfc::array_t temp; + for(ptr = g_list; ptr; ptr = ptr->m_next) { + temp.set_size(0); + { + stream_writer_buffer_append_ref_t > stream(temp); + ptr->get_data_raw(&stream,p_abort); + } + p_stream->write_lendian_t(ptr->m_guid,p_abort); + p_stream->write_lendian_t(pfc::downcast_guarded(temp.get_size()),p_abort); + if (temp.get_size() > 0) { + p_stream->write_object(temp.get_ptr(),temp.get_size(),p_abort); + } + } +} + + +void cfg_string::get_data_raw(stream_writer * p_stream,abort_callback & p_abort) { + p_stream->write_object(get_ptr(),length(),p_abort); +} + +void cfg_string::set_data_raw(stream_reader * p_stream,t_size p_sizehint,abort_callback & p_abort) { + pfc::string8_fastalloc temp; + p_stream->read_string_raw(temp,p_abort); + set_string(temp); +} diff --git a/foobar2000/SDK/cfg_var.h b/foobar2000/SDK/cfg_var.h new file mode 100644 index 0000000..7c75b3f --- /dev/null +++ b/foobar2000/SDK/cfg_var.h @@ -0,0 +1,292 @@ +#ifndef _FOOBAR2000_SDK_CFG_VAR_H_ +#define _FOOBAR2000_SDK_CFG_VAR_H_ + +#define CFG_VAR_ASSERT_SAFEINIT PFC_ASSERT(!core_api::are_services_available());/*imperfect check for nonstatic instantiation*/ + + +//! Reader part of cfg_var object. In most cases, you should use cfg_var instead of using cfg_var_reader directly. +class NOVTABLE cfg_var_reader { +public: + //! @param p_guid GUID of the variable, used to identify variable implementations owning specific configuration file entries when reading the configuration file back. You must generate a new GUID every time you declare a new cfg_var. + cfg_var_reader(const GUID & guid) : m_guid(guid) { CFG_VAR_ASSERT_SAFEINIT; m_next = g_list; g_list = this; } + ~cfg_var_reader() { CFG_VAR_ASSERT_SAFEINIT; } + + //! Sets state of the variable. Called only from main thread, when reading configuration file. + //! @param p_stream Stream containing new state of the variable. + //! @param p_sizehint Number of bytes contained in the stream; reading past p_sizehint bytes will fail (EOF). + virtual void set_data_raw(stream_reader * p_stream,t_size p_sizehint,abort_callback & p_abort) = 0; + + //! For internal use only, do not call. + static void config_read_file(stream_reader * p_stream,abort_callback & p_abort); + + const GUID m_guid; +private: + static cfg_var_reader * g_list; + cfg_var_reader * m_next; + + PFC_CLASS_NOT_COPYABLE_EX(cfg_var_reader) +}; + +//! Writer part of cfg_var object. In most cases, you should use cfg_var instead of using cfg_var_writer directly. +class NOVTABLE cfg_var_writer { +public: + //! @param p_guid GUID of the variable, used to identify variable implementations owning specific configuration file entries when reading the configuration file back. You must generate a new GUID every time you declare a new cfg_var. + cfg_var_writer(const GUID & guid) : m_guid(guid) { CFG_VAR_ASSERT_SAFEINIT; m_next = g_list; g_list = this;} + ~cfg_var_writer() { CFG_VAR_ASSERT_SAFEINIT; } + + //! Retrieves state of the variable. Called only from main thread, when writing configuration file. + //! @param p_stream Stream receiving state of the variable. + virtual void get_data_raw(stream_writer * p_stream,abort_callback & p_abort) = 0; + + //! For internal use only, do not call. + static void config_write_file(stream_writer * p_stream,abort_callback & p_abort); + + const GUID m_guid; +private: + static cfg_var_writer * g_list; + cfg_var_writer * m_next; + + PFC_CLASS_NOT_COPYABLE_EX(cfg_var_writer) +}; + +//! Base class for configuration variable classes; provides self-registration mechaisms and methods to set/retrieve configuration data; those methods are automatically called for all registered instances by backend when configuration file is being read or written.\n +//! Note that cfg_var class and its derivatives may be only instantiated statically (as static objects or members of other static objects), NEVER dynamically (operator new, local variables, members of objects instantiated as such). +class NOVTABLE cfg_var : public cfg_var_reader, public cfg_var_writer { +protected: + //! @param p_guid GUID of the variable, used to identify variable implementations owning specific configuration file entries when reading the configuration file back. You must generate a new GUID every time you declare a new cfg_var. + cfg_var(const GUID & p_guid) : cfg_var_reader(p_guid), cfg_var_writer(p_guid) {} +public: + GUID get_guid() const {return cfg_var_reader::m_guid;} +}; + +//! Generic integer config variable class. Template parameter can be used to specify integer type to use.\n +//! Note that cfg_var class and its derivatives may be only instantiated statically (as static objects or members of other static objects), NEVER dynamically (operator new, local variables, members of objects instantiated as such). +template +class cfg_int_t : public cfg_var { +private: + t_inttype m_val; +protected: + void get_data_raw(stream_writer * p_stream,abort_callback & p_abort) {p_stream->write_lendian_t(m_val,p_abort);} + void set_data_raw(stream_reader * p_stream,t_size,abort_callback & p_abort) { + t_inttype temp; + p_stream->read_lendian_t(temp,p_abort);//alter member data only on success, this will throw an exception when something isn't right + m_val = temp; + } + +public: + //! @param p_guid GUID of the variable, used to identify variable implementations owning specific configuration file entries when reading the configuration file back. You must generate a new GUID every time you declare a new cfg_var. + //! @param p_default Default value of the variable. + explicit inline cfg_int_t(const GUID & p_guid,t_inttype p_default) : cfg_var(p_guid), m_val(p_default) {} + + inline const cfg_int_t & operator=(const cfg_int_t & p_val) {m_val=p_val.m_val;return *this;} + inline t_inttype operator=(t_inttype p_val) {m_val=p_val;return m_val;} + + inline operator t_inttype() const {return m_val;} + + inline t_inttype get_value() const {return m_val;} +}; + +typedef cfg_int_t cfg_int; +typedef cfg_int_t cfg_uint; +//! Since relevant byteswapping functions also understand GUIDs, this can be used to declare a cfg_guid. +typedef cfg_int_t cfg_guid; +typedef cfg_int_t cfg_bool; +typedef cfg_int_t cfg_float; +typedef cfg_int_t cfg_double; + +//! String config variable. Stored in the stream with int32 header containing size in bytes, followed by non-null-terminated UTF-8 data.\n +//! Note that cfg_var class and its derivatives may be only instantiated statically (as static objects or members of other static objects), NEVER dynamically (operator new, local variables, members of objects instantiated as such). +class cfg_string : public cfg_var, public pfc::string8 { +protected: + void get_data_raw(stream_writer * p_stream,abort_callback & p_abort); + void set_data_raw(stream_reader * p_stream,t_size p_sizehint,abort_callback & p_abort); + +public: + //! @param p_guid GUID of the variable, used to identify variable implementations owning specific configuration file entries when reading the configuration file back. You must generate a new GUID every time you declare a new cfg_var. + //! @param p_defaultval Default/initial value of the variable. + explicit inline cfg_string(const GUID & p_guid,const char * p_defaultval) : cfg_var(p_guid), pfc::string8(p_defaultval) {} + + inline const cfg_string& operator=(const cfg_string & p_val) {set_string(p_val);return *this;} + inline const cfg_string& operator=(const char* p_val) {set_string(p_val);return *this;} + + inline operator const char * () const {return get_ptr();} +}; + + +class cfg_string_mt : public cfg_var { +protected: + void get_data_raw(stream_writer * p_stream,abort_callback & p_abort) { + pfc::string8 temp; + get(temp); + p_stream->write_object(temp.get_ptr(), temp.length(),p_abort); + } + void set_data_raw(stream_reader * p_stream,t_size,abort_callback & p_abort) { + pfc::string8_fastalloc temp; + p_stream->read_string_raw(temp,p_abort); + set(temp); + } +public: + cfg_string_mt(const GUID & id, const char * defVal) : cfg_var(id), m_val(defVal) {} + void get(pfc::string_base & out) const { + inReadSync( m_sync ); + out = m_val; + } + void set(const char * val, t_size valLen = ~0) { + inWriteSync( m_sync ); + m_val.set_string(val, valLen); + } +private: + mutable pfc::readWriteLock m_sync; + pfc::string8 m_val; +}; + +//! Struct config variable template. Warning: not endian safe, should be used only for nonportable code.\n +//! Note that cfg_var class and its derivatives may be only instantiated statically (as static objects or members of other static objects), NEVER dynamically (operator new, local variables, members of objects instantiated as such). +template +class cfg_struct_t : public cfg_var { +private: + t_struct m_val; +protected: + + void get_data_raw(stream_writer * p_stream,abort_callback & p_abort) {p_stream->write_object(&m_val,sizeof(m_val),p_abort);} + void set_data_raw(stream_reader * p_stream,t_size p_sizehint,abort_callback & p_abort) { + t_struct temp; + p_stream->read_object(&temp,sizeof(temp),p_abort); + m_val = temp; + } +public: + //! @param p_guid GUID of the variable, used to identify variable implementations owning specific configuration file entries when reading the configuration file back. You must generate a new GUID every time you declare a new cfg_var. + inline cfg_struct_t(const GUID & p_guid,const t_struct & p_val) : cfg_var(p_guid), m_val(p_val) {} + //! @param p_guid GUID of the variable, used to identify variable implementations owning specific configuration file entries when reading the configuration file back. You must generate a new GUID every time you declare a new cfg_var. + inline cfg_struct_t(const GUID & p_guid,int filler) : cfg_var(p_guid) {memset(&m_val,filler,sizeof(t_struct));} + + inline const cfg_struct_t & operator=(const cfg_struct_t & p_val) {m_val = p_val.get_value();return *this;} + inline const cfg_struct_t & operator=(const t_struct & p_val) {m_val = p_val;return *this;} + + inline const t_struct& get_value() const {return m_val;} + inline t_struct& get_value() {return m_val;} + inline operator t_struct() const {return m_val;} +}; + + +template +class cfg_objList : public cfg_var, public pfc::list_t { +public: + typedef cfg_objList t_self; + cfg_objList(const GUID& guid) : cfg_var(guid) {} + template cfg_objList(const GUID& guid, const TSource (& source)[Count]) : cfg_var(guid) { + reset(source); + } + template void reset(const TSource (& source)[Count]) { + this->set_size(Count); for(t_size walk = 0; walk < Count; ++walk) (*this)[walk] = source[walk]; + } + void get_data_raw(stream_writer * p_stream,abort_callback & p_abort) { + stream_writer_formatter<> out(*p_stream,p_abort); + out << pfc::downcast_guarded(this->get_size()); + for(t_size walk = 0; walk < this->get_size(); ++walk) out << (*this)[walk]; + } + void set_data_raw(stream_reader * p_stream,t_size p_sizehint,abort_callback & p_abort) { + try { + stream_reader_formatter<> in(*p_stream,p_abort); + t_uint32 count; in >> count; + this->set_count(count); + for(t_uint32 walk = 0; walk < count; ++walk) in >> (*this)[walk]; + } catch(...) { + this->remove_all(); + throw; + } + } + template t_self & operator=(t_in const & source) {this->remove_all(); this->add_items(source); return *this;} + template t_self & operator+=(t_in const & p_source) {this->add_item(p_source); return *this;} + template t_self & operator|=(t_in const & p_source) {this->add_items(p_source); return *this;} +}; +template +class cfg_objListEx : public cfg_var, public TList { +public: + typedef cfg_objListEx t_self; + cfg_objListEx(const GUID & guid) : cfg_var(guid) {} + void get_data_raw(stream_writer * p_stream, abort_callback & p_abort) { + stream_writer_formatter<> out(*p_stream,p_abort); + out << pfc::downcast_guarded(this->get_count()); + for(typename TList::const_iterator walk = this->first(); walk.is_valid(); ++walk) out << *walk; + } + void set_data_raw(stream_reader * p_stream,t_size p_sizehint,abort_callback & p_abort) { + this->remove_all(); + stream_reader_formatter<> in(*p_stream,p_abort); + t_uint32 count; in >> count; + for(t_uint32 walk = 0; walk < count; ++walk) { + typename TList::t_item item; in >> item; this->add_item(item); + } + } + template t_self & operator=(t_in const & source) {this->remove_all(); this->add_items(source); return *this;} + template t_self & operator+=(t_in const & p_source) {this->add_item(p_source); return *this;} + template t_self & operator|=(t_in const & p_source) {this->add_items(p_source); return *this;} +}; + +template +class cfg_obj : public cfg_var, public TObj { +public: + cfg_obj(const GUID& guid) : cfg_var(guid), TObj() {} + template cfg_obj(const GUID& guid,const TInitData& initData) : cfg_var(guid), TObj(initData) {} + + TObj & val() {return *this;} + TObj const & val() const {return *this;} + + void get_data_raw(stream_writer * p_stream,abort_callback & p_abort) { + stream_writer_formatter<> out(*p_stream,p_abort); + const TObj * ptr = this; + out << *ptr; + } + void set_data_raw(stream_reader * p_stream,t_size p_sizehint,abort_callback & p_abort) { + stream_reader_formatter<> in(*p_stream,p_abort); + TObj * ptr = this; + in >> *ptr; + } +}; + +template class cfg_objListImporter : private cfg_var_reader { +public: + typedef cfg_objList TMasterVar; + cfg_objListImporter(TMasterVar & var, const GUID & guid) : m_var(var), cfg_var_reader(guid) {} + + private: + void set_data_raw(stream_reader * p_stream,t_size p_sizehint,abort_callback & p_abort) { + TImport temp; + try { + stream_reader_formatter<> in(*p_stream,p_abort); + t_uint32 count; in >> count; + m_var.set_count(count); + for(t_uint32 walk = 0; walk < count; ++walk) { + in >> temp; + m_var[walk] = temp; + } + } catch(...) { + m_var.remove_all(); + throw; + } + } + TMasterVar & m_var; +}; +template class cfg_objMap : private cfg_var, public TMap { +public: + cfg_objMap(const GUID & id) : cfg_var(id) {} +private: + void get_data_raw(stream_writer * p_stream,abort_callback & p_abort) { + stream_writer_formatter<> out(*p_stream, p_abort); + out << pfc::downcast_guarded(this->get_count()); + for(typename TMap::const_iterator walk = this->first(); walk.is_valid(); ++walk) { + out << walk->m_key << walk->m_value; + } + } + void set_data_raw(stream_reader * p_stream,t_size p_sizehint,abort_callback & p_abort) { + this->remove_all(); + stream_reader_formatter<> in(*p_stream, p_abort); + t_uint32 count; in >> count; + for(t_uint32 walk = 0; walk < count; ++walk) { + typename TMap::t_key key; in >> key; PFC_ASSERT( !this->have_item(key) ); + try { in >> this->find_or_add( key ); } catch(...) { this->remove(key); throw; } + } + } +}; + +#endif diff --git a/foobar2000/SDK/chapterizer.cpp b/foobar2000/SDK/chapterizer.cpp new file mode 100644 index 0000000..fcb2f4e --- /dev/null +++ b/foobar2000/SDK/chapterizer.cpp @@ -0,0 +1,43 @@ +#include "foobar2000.h" +#include "chapterizer.h" + +void chapter_list::copy(const chapter_list & p_source) +{ + t_size n, count = p_source.get_chapter_count(); + set_chapter_count(count); + for(n=0;n & p_out,const char * p_path) +{ + service_ptr_t ptr; + service_enum_t e; + while(e.next(ptr)) { + if (ptr->is_our_path(p_path)) { + p_out = ptr; + return true; + } + } + return false; +} + +bool chapterizer::g_is_pregap_capable(const char * p_path) { + service_ptr_t ptr; + service_enum_t e; + while(e.next(ptr)) { + if (ptr->supports_pregaps() && ptr->is_our_path(p_path)) { + return true; + } + } + return false; +} + +#endif + diff --git a/foobar2000/SDK/chapterizer.h b/foobar2000/SDK/chapterizer.h new file mode 100644 index 0000000..b447ef6 --- /dev/null +++ b/foobar2000/SDK/chapterizer.h @@ -0,0 +1,91 @@ +#pragma once + +// Not everything is on #ifdef FOOBAR2000_HAVE_CHAPTERIZER +// Some things use chapter_list internally even if chapterizer is disabled + +//! Interface for object storing list of chapters. +class NOVTABLE chapter_list { +public: + //! Returns number of chapters. + virtual t_size get_chapter_count() const = 0; + //! Queries description of specified chapter. + //! @param p_chapter Index of chapter to query, greater or equal zero and less than get_chapter_count() value. If p_chapter value is out of valid range, results are undefined (e.g. crash). + //! @returns reference to file_info object describing specified chapter (length part of file_info indicates distance between beginning of this chapter and next chapter mark). Returned reference value for temporary use only, becomes invalid after any non-const operation on the chapter_list object. + virtual const file_info & get_info(t_size p_chapter) const = 0; + + //! Sets number of chapters. + virtual void set_chapter_count(t_size p_count) = 0; + //! Modifies description of specified chapter. + //! @param p_chapter_index Index of chapter to modify, greater or equal zero and less than get_chapter_count() value. If p_chapter value is out of valid range, results are undefined (e.g. crash). + //! @param p_info New chapter description. Note that length part of file_info is used to calculate chapter marks. + virtual void set_info(t_size p_chapter,const file_info & p_info) = 0; + + virtual double get_pregap() const = 0; + virtual void set_pregap(double val) = 0; + + //! Copies contents of specified chapter_list object to this object. + void copy(const chapter_list & p_source); + + inline const chapter_list & operator=(const chapter_list & p_source) {copy(p_source); return *this;} + +protected: + chapter_list() {} + ~chapter_list() {} +}; + +//! Implements chapter_list. +template +class chapter_list_impl_t : public chapter_list { +public: + chapter_list_impl_t() : m_pregap() {} + typedef chapter_list_impl_t t_self; + chapter_list_impl_t(const chapter_list & p_source) : m_pregap() {copy(p_source);} + + const t_self & operator=(const chapter_list & p_source) {copy(p_source); return *this;} + + t_size get_chapter_count() const {return m_infos.get_size();} + const file_info & get_info(t_size p_chapter) const {return m_infos[p_chapter];} + + void set_chapter_count(t_size p_count) {m_infos.set_size(p_count);} + void set_info(t_size p_chapter,const file_info & p_info) {m_infos[p_chapter] = p_info;} + file_info_ & get_info_(t_size p_chapter) {return m_infos[p_chapter];} + + double get_pregap() const {return m_pregap;} + void set_pregap(double val) {PFC_ASSERT(val >= 0); m_pregap = val;} +private: + pfc::array_t m_infos; + double m_pregap; +}; + +typedef chapter_list_impl_t<> chapter_list_impl; + +#ifdef FOOBAR2000_HAVE_CHAPTERIZER + +//! This service implements chapter list editing operations for various file formats, e.g. for MP4 chapters or CD images with embedded cuesheets. Used by converter "encode single file with chapters" feature. +class NOVTABLE chapterizer : public service_base { + FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(chapterizer); +public: + //! Tests whether specified path is supported by this implementation. + //! @param p_ext Extension of the file being processed. + virtual bool is_our_path(const char * p_path) = 0; + + //! Writes new chapter list to specified file. + //! @param p_path Path of file to modify. + //! @param p_list New chapter list to write. + //! @param p_abort abort_callback object signaling user aborting the operation. + virtual void set_chapters(const char * p_path,chapter_list const & p_list,abort_callback & p_abort) = 0; + //! Retrieves chapter list from specified file. + //! @param p_path Path of file to examine. + //! @param p_list Object receiving chapter list. + //! @param p_abort abort_callback object signaling user aborting the operation. + virtual void get_chapters(const char * p_path,chapter_list & p_list,abort_callback & p_abort) = 0; + + virtual bool supports_pregaps() = 0; + + //! Static helper, tries to find chapterizer interface that supports specified file. + static bool g_find(service_ptr_t & p_out,const char * p_path); + + static bool g_is_pregap_capable(const char * p_path); +}; + +#endif diff --git a/foobar2000/SDK/commandline.cpp b/foobar2000/SDK/commandline.cpp new file mode 100644 index 0000000..6f409d0 --- /dev/null +++ b/foobar2000/SDK/commandline.cpp @@ -0,0 +1,12 @@ +#include "foobar2000.h" + +void commandline_handler_metadb_handle::on_file(const char * url) { + metadb_handle_list handles; + try { + metadb_io::get()->path_to_handles_simple(url, handles); + } catch(std::exception const & e) { + console::complain("Path evaluation failure", e); + return; + } + for(t_size walk = 0; walk < handles.get_size(); ++walk) on_file(handles[walk]); +} diff --git a/foobar2000/SDK/commandline.h b/foobar2000/SDK/commandline.h new file mode 100644 index 0000000..067ccaa --- /dev/null +++ b/foobar2000/SDK/commandline.h @@ -0,0 +1,41 @@ +class NOVTABLE commandline_handler : public service_base +{ +public: + enum result + { + RESULT_NOT_OURS,//not our command + RESULT_PROCESSED,//command processed + RESULT_PROCESSED_EXPECT_FILES,//command processed, we want to takeover file urls after this command + }; + virtual result on_token(const char * token)=0; + virtual void on_file(const char * url) {};//optional + virtual void on_files_done() {};//optional + virtual bool want_directories() {return false;} + + FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(commandline_handler); +}; + +class commandline_handler_metadb_handle : public commandline_handler//helper +{ +protected: + virtual void on_file(const char * url); + virtual bool want_directories() {return true;} +public: + virtual result on_token(const char * token)=0; + virtual void on_files_done() {}; + + virtual void on_file(const metadb_handle_ptr & ptr)=0; +}; + +/* + +how commandline_handler is used: + + scenario #1: + creation => on_token() => deletion + scenario #2: + creation => on_token() returning RESULT_PROCESSED_EXPECT_FILES => on_file(), on_file().... => on_files_done() => deletion +*/ + +template +class commandline_handler_factory_t : public service_factory_t {}; diff --git a/foobar2000/SDK/commonObjects.h b/foobar2000/SDK/commonObjects.h new file mode 100644 index 0000000..263f79b --- /dev/null +++ b/foobar2000/SDK/commonObjects.h @@ -0,0 +1,10 @@ +#pragma once + +#include + +namespace fb2k { + typedef service_ptr objRef; + + objRef callOnRelease( std::function< void () > ); + objRef callOnReleaseInMainThread( std::function< void () > ); +} diff --git a/foobar2000/SDK/completion_notify.cpp b/foobar2000/SDK/completion_notify.cpp new file mode 100644 index 0000000..f582c18 --- /dev/null +++ b/foobar2000/SDK/completion_notify.cpp @@ -0,0 +1,87 @@ +#include "foobar2000.h" + +namespace { + class main_thread_callback_myimpl : public main_thread_callback { + public: + void callback_run() { + m_notify->on_completion(m_code); + } + + main_thread_callback_myimpl(completion_notify_ptr p_notify,unsigned p_code) : m_notify(p_notify), m_code(p_code) {} + private: + completion_notify_ptr m_notify; + unsigned m_code; + }; +} + +void completion_notify::g_signal_completion_async(completion_notify_ptr p_notify,unsigned p_code) { + if (p_notify.is_valid()) { + main_thread_callback_manager::get()->add_callback(new service_impl_t(p_notify,p_code)); + } +} + +void completion_notify::on_completion_async(unsigned p_code) { + main_thread_callback_manager::get()->add_callback(new service_impl_t(this,p_code)); +} + + +completion_notify::ptr completion_notify_receiver::create_or_get_task(unsigned p_id) { + completion_notify_orphanable_ptr ptr; + if (!m_tasks.query(p_id,ptr)) { + ptr = completion_notify_create(this,p_id); + m_tasks.set(p_id,ptr); + } + return ptr; +} + +completion_notify_ptr completion_notify_receiver::create_task(unsigned p_id) { + completion_notify_orphanable_ptr ptr; + if (m_tasks.query(p_id,ptr)) ptr->orphan(); + ptr = completion_notify_create(this,p_id); + m_tasks.set(p_id,ptr); + return ptr; +} + +bool completion_notify_receiver::have_task(unsigned p_id) const { + return m_tasks.have_item(p_id); +} + +void completion_notify_receiver::orphan_task(unsigned p_id) { + completion_notify_orphanable_ptr ptr; + if (m_tasks.query(p_id,ptr)) { + ptr->orphan(); + m_tasks.remove(p_id); + } +} + +completion_notify_receiver::~completion_notify_receiver() { + orphan_all_tasks(); +} + +void completion_notify_receiver::orphan_all_tasks() { + m_tasks.enumerate(orphanfunc); + m_tasks.remove_all(); +} + +namespace { + using namespace fb2k; + + class completion_notify_func : public completion_notify { + public: + void on_completion(unsigned p_code) { + m_func(p_code); + } + + completionNotifyFunc_t m_func; + }; +} + +namespace fb2k { + + completion_notify::ptr makeCompletionNotify( completionNotifyFunc_t func ) { + service_ptr_t n = new service_impl_t< completion_notify_func >; + n->m_func = func; + return n; + } + +} diff --git a/foobar2000/SDK/completion_notify.h b/foobar2000/SDK/completion_notify.h new file mode 100644 index 0000000..9bdc9a0 --- /dev/null +++ b/foobar2000/SDK/completion_notify.h @@ -0,0 +1,85 @@ +#pragma once + +#include + +//! Generic service for receiving notifications about async operation completion. Used by various other services. +class completion_notify : public service_base { +public: + //! Called when an async operation has been completed. Note that on_completion is always called from main thread. You can use on_completion_async() helper if you need to signal completion while your context is in another thread.\n + //! IMPLEMENTATION WARNING: If process being completed creates a window taking caller's window as parent, you must not destroy the parent window inside on_completion(). If you need to do so, use PostMessage() or main_thread_callback to delay the deletion. + //! @param p_code Context-specific status code. Possible values depend on the operation being performed. + virtual void on_completion(unsigned p_code) = 0; + + //! Helper. Queues a notification, using main_thread_callback. + void on_completion_async(unsigned p_code); + + //! Helper. Checks for null ptr and calls on_completion_async when the ptr is not null. + static void g_signal_completion_async(service_ptr_t p_notify,unsigned p_code); + + FB2K_MAKE_SERVICE_INTERFACE(completion_notify,service_base); +}; + +//! Implementation helper. +class completion_notify_dummy : public completion_notify { +public: + void on_completion(unsigned) {} +}; + +//! Implementation helper. +class completion_notify_orphanable : public completion_notify { +public: + virtual void orphan() = 0; +}; + +//! Helper implementation. +//! IMPLEMENTATION WARNING: If process being completed creates a window taking caller's window as parent, you must not destroy the parent window inside on_task_completion(). If you need to do so, use PostMessage() or main_thread_callback to delay the deletion. +template +class completion_notify_impl : public completion_notify_orphanable { +public: + void on_completion(unsigned p_code) { + if (m_receiver != NULL) { + m_receiver->on_task_completion(m_taskid,p_code); + } + } + void setup(t_receiver * p_receiver, unsigned p_task_id) {m_receiver = p_receiver; m_taskid = p_task_id;} + void orphan() {m_receiver = NULL; m_taskid = 0;} +private: + t_receiver * m_receiver; + unsigned m_taskid; +}; + +template +service_nnptr_t completion_notify_create(t_receiver * p_receiver,unsigned p_taskid) { + service_nnptr_t > instance = new service_impl_t >(); + instance->setup(p_receiver,p_taskid); + return instance; +} + +typedef service_ptr_t completion_notify_ptr; +typedef service_ptr_t completion_notify_orphanable_ptr; +typedef service_nnptr_t completion_notify_nnptr; +typedef service_nnptr_t completion_notify_orphanable_nnptr; + +//! Helper base class for classes that manage nonblocking tasks and get notified back thru completion_notify interface. +class completion_notify_receiver { +public: + completion_notify::ptr create_or_get_task(unsigned p_id); + completion_notify_ptr create_task(unsigned p_id); + bool have_task(unsigned p_id) const; + void orphan_task(unsigned p_id); + ~completion_notify_receiver(); + void orphan_all_tasks(); + + virtual void on_task_completion(unsigned p_id,unsigned p_status) {(void)p_id;(void)p_status;} +private: + static void orphanfunc(unsigned,completion_notify_orphanable_nnptr p_item) {p_item->orphan();} + pfc::map_t m_tasks; +}; + +namespace fb2k { + + typedef std::function completionNotifyFunc_t; + + //! Modern completion_notify helper + completion_notify::ptr makeCompletionNotify( completionNotifyFunc_t ); +} diff --git a/foobar2000/SDK/component.h b/foobar2000/SDK/component.h new file mode 100644 index 0000000..fd84b7e --- /dev/null +++ b/foobar2000/SDK/component.h @@ -0,0 +1,56 @@ +#include "foobar2000.h" + +// This is a helper class that gets reliably static-instantiated in all components so any special code that must be executed on startup can be shoved into its constructor. +class foobar2000_component_globals { +public: + foobar2000_component_globals() { +#if defined(_MSC_VER) && !defined(_DEBUG) && !defined(_DLL) + // only with MSVC, non release build, static runtime + ::OverrideCrtAbort(); +#endif + } +}; + +class NOVTABLE foobar2000_client +{ +public: + typedef service_factory_base* pservice_factory_base; + + enum { + FOOBAR2000_CLIENT_VERSION_COMPATIBLE = 72, + FOOBAR2000_CLIENT_VERSION = FOOBAR2000_TARGET_VERSION, + }; + virtual t_uint32 get_version() = 0; + virtual pservice_factory_base get_service_list() = 0; + + virtual void get_config(stream_writer * p_stream,abort_callback & p_abort) = 0; + virtual void set_config(stream_reader * p_stream,abort_callback & p_abort) = 0; + virtual void set_library_path(const char * path,const char * name) = 0; + virtual void services_init(bool val) = 0; + virtual bool is_debug() = 0; +protected: + foobar2000_client() {} + ~foobar2000_client() {} +}; + +class NOVTABLE foobar2000_api { +public: + virtual service_class_ref service_enum_find_class(const GUID & p_guid) = 0; + virtual bool service_enum_create(service_ptr_t & p_out,service_class_ref p_class,t_size p_index) = 0; + virtual t_size service_enum_get_count(service_class_ref p_class) = 0; + virtual HWND get_main_window()=0; + virtual bool assert_main_thread()=0; + virtual bool is_main_thread()=0; + virtual bool is_shutting_down()=0; + virtual const char * get_profile_path()=0; + virtual bool is_initializing() = 0; + + //New in 0.9.6 + virtual bool is_portable_mode_enabled() = 0; + virtual bool is_quiet_mode_enabled() = 0; +protected: + foobar2000_api() {} + ~foobar2000_api() {} +}; + +extern foobar2000_api * g_foobar2000_api; diff --git a/foobar2000/SDK/component_client.h b/foobar2000/SDK/component_client.h new file mode 100644 index 0000000..a0cfb6b --- /dev/null +++ b/foobar2000/SDK/component_client.h @@ -0,0 +1,6 @@ +#ifndef _COMPONENT_CLIENT_H_ +#define _COMPONENT_CLIENT_H_ + + + +#endif //_COMPONENT_CLIENT_H_ \ No newline at end of file diff --git a/foobar2000/SDK/components_menu.h b/foobar2000/SDK/components_menu.h new file mode 100644 index 0000000..5c10027 --- /dev/null +++ b/foobar2000/SDK/components_menu.h @@ -0,0 +1,7 @@ +#ifndef _COMPONENTS_MENU_H_ +#define _COMPONENTS_MENU_H_ + +#error deprecated, see menu_item.h + + +#endif \ No newline at end of file diff --git a/foobar2000/SDK/componentversion.cpp b/foobar2000/SDK/componentversion.cpp new file mode 100644 index 0000000..a5f6536 --- /dev/null +++ b/foobar2000/SDK/componentversion.cpp @@ -0,0 +1,36 @@ +#include "foobar2000.h" + +bool component_installation_validator::test_my_name(const char * fn) { + const char * path = core_api::get_my_full_path(); + path += pfc::scan_filename(path); + bool retVal = ( strcmp(path, fn) == 0 ); + PFC_ASSERT( retVal ); + return retVal; +} +bool component_installation_validator::have_other_file(const char * fn) { + for(int retry = 0;;) { + pfc::string_formatter path = core_api::get_my_full_path(); + path.truncate(path.scan_filename()); + path << fn; + try { + try { + bool v = filesystem::g_exists(path, fb2k::noAbort); + PFC_ASSERT( v ); + return v; + } catch(std::exception const & e) { + FB2K_console_formatter() << "Component integrity check error: " << e << " (on: " << fn << ")"; + throw; + } + } catch(exception_io_denied) { + + } catch(exception_io_sharing_violation) { + + } catch(exception_io_file_corrupted) { // happens + return false; + } catch(...) { + uBugCheck(); + } + if (++retry == 10) uBugCheck(); + Sleep(100); + } +} diff --git a/foobar2000/SDK/componentversion.h b/foobar2000/SDK/componentversion.h new file mode 100644 index 0000000..f8cbb9f --- /dev/null +++ b/foobar2000/SDK/componentversion.h @@ -0,0 +1,93 @@ +//! Entrypoint interface for declaring component's version information. Instead of implementing this directly, use DECLARE_COMPONENT_VERSION(). +class NOVTABLE componentversion : public service_base { +public: + virtual void get_file_name(pfc::string_base & out)=0; + virtual void get_component_name(pfc::string_base & out)=0; + virtual void get_component_version(pfc::string_base & out)=0; + virtual void get_about_message(pfc::string_base & out)=0;//about message uses "\n" for line separators + + FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(componentversion); +}; + +//! Implementation helper. You typically want to use DECLARE_COMPONENT_VERSION() instead. +class componentversion_impl_simple : public componentversion { + const char * name,*version,*about; +public: + //do not derive/override + virtual void get_file_name(pfc::string_base & out) {out.set_string(core_api::get_my_file_name());} + virtual void get_component_name(pfc::string_base & out) {out.set_string(name?name:"");} + virtual void get_component_version(pfc::string_base & out) {out.set_string(version?version:"");} + virtual void get_about_message(pfc::string_base & out) {out.set_string(about?about:"");} + explicit componentversion_impl_simple(const char * p_name,const char * p_version,const char * p_about) : name(p_name), version(p_version), about(p_about ? p_about : "") {} +}; + +//! Implementation helper. You typically want to use DECLARE_COMPONENT_VERSION() instead. +class componentversion_impl_copy : public componentversion { + pfc::string8 name,version,about; +public: + //do not derive/override + virtual void get_file_name(pfc::string_base & out) {out.set_string(core_api::get_my_file_name());} + virtual void get_component_name(pfc::string_base & out) {out.set_string(name);} + virtual void get_component_version(pfc::string_base & out) {out.set_string(version);} + virtual void get_about_message(pfc::string_base & out) {out.set_string(about);} + explicit componentversion_impl_copy(const char * p_name,const char * p_version,const char * p_about) : name(p_name), version(p_version), about(p_about ? p_about : "") {} +}; + +typedef service_factory_single_transparent_t __componentversion_impl_simple_factory; +typedef service_factory_single_transparent_t __componentversion_impl_copy_factory; + +class componentversion_impl_simple_factory : public __componentversion_impl_simple_factory { +public: + componentversion_impl_simple_factory(const char * p_name,const char * p_version,const char * p_about) : __componentversion_impl_simple_factory(p_name,p_version,p_about) {} +}; + +class componentversion_impl_copy_factory : public __componentversion_impl_copy_factory { +public: + componentversion_impl_copy_factory(const char * p_name,const char * p_version,const char * p_about) : __componentversion_impl_copy_factory(p_name,p_version,p_about) {} +}; + +//! Use this to declare your component's version information. Parameters must ba plain const char * string constants. \n +//! You should have only one DECLARE_COMPONENT_VERSION() per component DLL. Having more than one will confuse component updater's version matching. \n +//! Please keep your version numbers formatted as: N[.N[.N....]][ alpha|beta|RC[ N[.N...]] \n +//! Sample version numbers, in ascending order: 0.9 < 0.10 < 1.0 alpha 1 < 1.0 alpha 2 < 1.0 beta 1 < 1.0 RC < 1.0 RC1 < 1.0 < 1.1 < 1.10 \n +//! For a working sample of how foobar2000 sorts version numbers, see http://www.foobar2000.org/versionator.php \n +//! Example: DECLARE_COMPONENT_VERSION("blah","1.3.3.7","") +#define DECLARE_COMPONENT_VERSION(NAME,VERSION,ABOUT) \ + namespace {class componentversion_myimpl : public componentversion { public: componentversion_myimpl() {PFC_ASSERT( ABOUT );} \ + void get_file_name(pfc::string_base & out) {out = core_api::get_my_file_name();} \ + void get_component_name(pfc::string_base & out) {out = NAME;} \ + void get_component_version(pfc::string_base & out) {out = VERSION;} \ + void get_about_message(pfc::string_base & out) {out = ABOUT;} \ + }; static service_factory_single_t g_componentversion_myimpl_factory; } + // static componentversion_impl_simple_factory g_componentversion_service(NAME,VERSION,ABOUT); + +//! Same as DECLARE_COMPONENT_VERSION(), but parameters can be dynamically generated strings rather than compile-time constants. +#define DECLARE_COMPONENT_VERSION_COPY(NAME,VERSION,ABOUT) \ + static componentversion_impl_copy_factory g_componentversion_service(NAME,VERSION,ABOUT); + + +//! \since 1.0 +//! Allows components to cleanly abort app startup in case the installation appears to have become corrupted. +class component_installation_validator : public service_base { +public: + virtual bool is_installed_correctly() = 0; + + static bool test_my_name(const char * fn); + static bool have_other_file(const char * fn); + + FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(component_installation_validator) +}; + +//! Simple implementation of component_installation_validator that makes sure that our component DLL has not been renamed around by idiot users. +class component_installation_validator_filename : public component_installation_validator { +public: + component_installation_validator_filename(const char * dllName) : m_dllName(dllName) {} + bool is_installed_correctly() { + return test_my_name(m_dllName); + } +private: + const char * const m_dllName; +}; + +#define VALIDATE_COMPONENT_FILENAME(FN) \ + static service_factory_single_t g_component_installation_validator_filename(FN); diff --git a/foobar2000/SDK/config_io_callback.cpp b/foobar2000/SDK/config_io_callback.cpp new file mode 100644 index 0000000..cd315bc --- /dev/null +++ b/foobar2000/SDK/config_io_callback.cpp @@ -0,0 +1,14 @@ +#include "foobar2000.h" + +static filesystem::ptr defaultFS() { + return filesystem::get( core_api::get_profile_path() ); +} + +void config_io_callback_v3::on_quicksave() { + this->on_quicksave_v3(defaultFS()); +} +void config_io_callback_v3::on_write(bool bReset) { + auto fs = defaultFS(); + if (bReset) this->on_reset_v3(fs); + else this->on_write_v3(fs); +} diff --git a/foobar2000/SDK/config_io_callback.h b/foobar2000/SDK/config_io_callback.h new file mode 100644 index 0000000..09e2fcc --- /dev/null +++ b/foobar2000/SDK/config_io_callback.h @@ -0,0 +1,43 @@ +#pragma once + +//! Implementing this interface lets you maintain your own configuration files rather than depending on the cfg_var system. \n +//! Note that you must not make assumptions about what happens first: config_io_callback::on_read(), initialization of cfg_var values or config_io_callback::on_read() in other components. Order of these things is undefined and will change with each run. \n +//! Use service_factory_single_t to register your implementations. Do not call other people's implementations, core is responsible for doing that when appropriate. +class NOVTABLE config_io_callback : public service_base { +public: + //! Called on startup. You can read your configuration file from here. \n + //! Hint: use core_api::get_profile_path() to retrieve the path of the folder where foobar2000 configuration files are stored. + virtual void on_read() = 0; + //! Called typically on shutdown but you should expect a call at any point after on_read(). You can write your configuration file from here. + //! Hint: use core_api::get_profile_path() to retrieve the path of the folder where foobar2000 configuration files are stored. + //! @param reset If set to true, our configuration is being reset, so you should wipe your files rather than rewrite them with current configuration. + virtual void on_write(bool reset) = 0; + + FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(config_io_callback); +}; + +//! \since 1.0 +class NOVTABLE config_io_callback_v2 : public config_io_callback { + FB2K_MAKE_SERVICE_INTERFACE(config_io_callback_v2, config_io_callback) +public: + //! Implement optionally. Called to quickly flush recent configuration changes. If your instance of config_io_callback needs to perform timeconsuming tasks when saving, you should skip implementing this method entirely. + virtual void on_quicksave() = 0; +}; + +//! \since 1.4 +//! New methods take a filesystem object that should be used for the update, so the whole config update can be performed as one transacted filesystem operation. \n +//! The core performs necessary checks to ensure that the volume where our profile resides is supports transacted operations. \n +//! However there are odd cases of people junctioning the profile folder and such. We cannot guarantee that your code won't run into such cases. \n +//! If you get a exception_io_transactions_unsupported, let the caller deal with it - your call will be retried with a regular filesystem instead of a transacted one. +class NOVTABLE config_io_callback_v3 : public config_io_callback_v2 { + FB2K_MAKE_SERVICE_INTERFACE(config_io_callback_v3, config_io_callback_v2); +public: + void on_quicksave(); + void on_write(bool bReset); + virtual void on_reset_v3( filesystem::ptr fs ) = 0; + virtual void on_write_v3( filesystem::ptr fs ) = 0; + virtual void on_quicksave_v3( filesystem::ptr fs ) = 0; +}; + +// For internal use in fb2k core +#define FB2K_PROFILE_CONFIG_READS_WRITES 0 \ No newline at end of file diff --git a/foobar2000/SDK/config_object.cpp b/foobar2000/SDK/config_object.cpp new file mode 100644 index 0000000..b6f619d --- /dev/null +++ b/foobar2000/SDK/config_object.cpp @@ -0,0 +1,210 @@ +#include "foobar2000.h" + +void config_object_notify_manager::g_on_changed(const service_ptr_t & p_object) +{ + if (core_api::assert_main_thread()) + { + service_enum_t e; + service_ptr_t ptr; + while(e.next(ptr)) + ptr->on_changed(p_object); + } +} + +bool config_object::g_find(service_ptr_t & p_out,const GUID & p_guid) +{ + service_ptr_t ptr; + service_enum_t e; + while(e.next(ptr)) + { + if (ptr->get_guid() == p_guid) + { + p_out = ptr; + return true; + } + } + return false; +} + +void config_object::g_get_data_string(const GUID & p_guid,pfc::string_base & p_out) +{ + service_ptr_t ptr; + if (!g_find(ptr,p_guid)) throw exception_service_not_found(); + ptr->get_data_string(p_out); +} + +void config_object::g_set_data_string(const GUID & p_guid,const char * p_data,t_size p_length) +{ + service_ptr_t ptr; + if (!g_find(ptr,p_guid)) throw exception_service_not_found(); + ptr->set_data_string(p_data,p_length); +} + +void config_object::get_data_int32(t_int32 & p_out) +{ + t_int32 temp; + get_data_struct_t(temp); + byte_order::order_le_to_native_t(temp); + p_out = temp; +} + +void config_object::set_data_int32(t_int32 p_val) +{ + t_int32 temp = p_val; + byte_order::order_native_to_le_t(temp); + set_data_struct_t(temp); +} + +bool config_object::get_data_bool_simple(bool p_default) { + try { + bool ret = p_default; + get_data_bool(ret); + return ret; + } catch(...) {return p_default;} +} + +t_int32 config_object::get_data_int32_simple(t_int32 p_default) { + try { + t_int32 ret = p_default; + get_data_int32(ret); + return ret; + } catch(...) {return p_default;} +} + +void config_object::g_get_data_int32(const GUID & p_guid,t_int32 & p_out) { + service_ptr_t ptr; + if (!g_find(ptr,p_guid)) throw exception_service_not_found(); + ptr->get_data_int32(p_out); +} + +void config_object::g_set_data_int32(const GUID & p_guid,t_int32 p_val) { + service_ptr_t ptr; + if (!g_find(ptr,p_guid)) throw exception_service_not_found(); + ptr->set_data_int32(p_val); +} + +bool config_object::g_get_data_bool_simple(const GUID & p_guid,bool p_default) +{ + service_ptr_t ptr; + if (!g_find(ptr,p_guid)) throw exception_service_not_found(); + return ptr->get_data_bool_simple(p_default); +} + +t_int32 config_object::g_get_data_int32_simple(const GUID & p_guid,t_int32 p_default) +{ + service_ptr_t ptr; + if (!g_find(ptr,p_guid)) throw exception_service_not_found(); + return ptr->get_data_int32_simple(p_default); +} + +void config_object::get_data_bool(bool & p_out) {get_data_struct_t(p_out);} +void config_object::set_data_bool(bool p_val) {set_data_struct_t(p_val);} + +void config_object::g_get_data_bool(const GUID & p_guid,bool & p_out) {g_get_data_struct_t(p_guid,p_out);} +void config_object::g_set_data_bool(const GUID & p_guid,bool p_val) {g_set_data_struct_t(p_guid,p_val);} + +namespace { + class stream_writer_string : public stream_writer { + public: + void write(const void * p_buffer,t_size p_bytes,abort_callback & p_abort) { + m_out.add_string((const char*)p_buffer,p_bytes); + } + stream_writer_string(pfc::string_base & p_out) : m_out(p_out) {m_out.reset();} + private: + pfc::string_base & m_out; + }; + + class stream_writer_fixedbuffer : public stream_writer { + public: + void write(const void * p_buffer,t_size p_bytes,abort_callback & p_abort) { + if (p_bytes > 0) { + if (p_bytes > m_bytes - m_bytes_read) throw pfc::exception_overflow(); + memcpy((t_uint8*)m_out,p_buffer,p_bytes); + m_bytes_read += p_bytes; + } + } + stream_writer_fixedbuffer(void * p_out,t_size p_bytes,t_size & p_bytes_read) : m_out(p_out), m_bytes(p_bytes), m_bytes_read(p_bytes_read) {m_bytes_read = 0;} + private: + void * m_out; + t_size m_bytes; + t_size & m_bytes_read; + }; + + + + class stream_writer_get_length : public stream_writer { + public: + void write(const void * p_buffer,t_size p_bytes,abort_callback & p_abort) { + m_length += p_bytes; + } + stream_writer_get_length(t_size & p_length) : m_length(p_length) {m_length = 0;} + private: + t_size & m_length; + }; +}; + +t_size config_object::get_data_raw(void * p_out,t_size p_bytes) { + t_size ret = 0; + stream_writer_fixedbuffer stream(p_out,p_bytes,ret); + get_data(&stream,fb2k::noAbort); + return ret; +} + +t_size config_object::get_data_raw_length() { + t_size ret = 0; + stream_writer_get_length stream(ret); + get_data(&stream,fb2k::noAbort); + return ret; +} + +void config_object::set_data_raw(const void * p_data,t_size p_bytes, bool p_notify) { + stream_reader_memblock_ref stream(p_data,p_bytes); + set_data(&stream,fb2k::noAbort,p_notify); +} + +void config_object::set_data_string(const char * p_data,t_size p_length) { + set_data_raw(p_data,pfc::strlen_max(p_data,p_length)); +} + +void config_object::get_data_string(pfc::string_base & p_out) { + stream_writer_string stream(p_out); + get_data(&stream,fb2k::noAbort); +} + + +//config_object_impl stuff + + +void config_object_impl::get_data(stream_writer * p_stream,abort_callback & p_abort) const { + inReadSync(m_sync); + p_stream->write_object(m_data.get_ptr(),m_data.get_size(),p_abort); +} + +void config_object_impl::set_data(stream_reader * p_stream,abort_callback & p_abort,bool p_notify) { + core_api::ensure_main_thread(); + + { + inWriteSync(m_sync); + m_data.set_size(0); + enum {delta = 1024}; + t_uint8 buffer[delta]; + for(;;) + { + t_size delta_done = p_stream->read(buffer,delta,p_abort); + + if (delta_done > 0) + { + m_data.append_fromptr(buffer,delta_done); + } + + if (delta_done != delta) break; + } + } + + if (p_notify) config_object_notify_manager::g_on_changed(this); +} + +config_object_impl::config_object_impl(const GUID & p_guid,const void * p_data,t_size p_bytes) : cfg_var(p_guid) +{ + m_data.set_data_fromptr((const t_uint8*)p_data,p_bytes); +} diff --git a/foobar2000/SDK/config_object.h b/foobar2000/SDK/config_object.h new file mode 100644 index 0000000..f1733c9 --- /dev/null +++ b/foobar2000/SDK/config_object.h @@ -0,0 +1,85 @@ +#ifndef _CONFIG_OBJECT_H_ +#define _CONFIG_OBJECT_H_ + +class config_object; + +class NOVTABLE config_object_notify_manager : public service_base +{ +public: + virtual void on_changed(const service_ptr_t & p_object) = 0; + static void g_on_changed(const service_ptr_t & p_object); + + + FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(config_object_notify_manager); +}; + +class NOVTABLE config_object : public service_base +{ +public: + //interface + virtual GUID get_guid() const = 0; + virtual void get_data(stream_writer * p_stream,abort_callback & p_abort) const = 0; + virtual void set_data(stream_reader * p_stream,abort_callback & p_abort,bool p_sendnotify = true) = 0; + + //helpers + static bool g_find(service_ptr_t & p_out,const GUID & p_guid); + + void set_data_raw(const void * p_data,t_size p_bytes,bool p_sendnotify = true); + t_size get_data_raw(void * p_out,t_size p_bytes); + t_size get_data_raw_length(); + + template void get_data_struct_t(T& p_out); + template void set_data_struct_t(const T& p_in); + template static void g_get_data_struct_t(const GUID & p_guid,T & p_out); + template static void g_set_data_struct_t(const GUID & p_guid,const T & p_in); + + void set_data_string(const char * p_data,t_size p_length); + void get_data_string(pfc::string_base & p_out); + + void get_data_bool(bool & p_out); + void set_data_bool(bool p_val); + void get_data_int32(t_int32 & p_out); + void set_data_int32(t_int32 p_val); + bool get_data_bool_simple(bool p_default); + t_int32 get_data_int32_simple(t_int32 p_default); + + static void g_get_data_string(const GUID & p_guid,pfc::string_base & p_out); + static void g_set_data_string(const GUID & p_guid,const char * p_data,t_size p_length = ~0); + + static void g_get_data_bool(const GUID & p_guid,bool & p_out); + static void g_set_data_bool(const GUID & p_guid,bool p_val); + static void g_get_data_int32(const GUID & p_guid,t_int32 & p_out); + static void g_set_data_int32(const GUID & p_guid,t_int32 p_val); + static bool g_get_data_bool_simple(const GUID & p_guid,bool p_default); + static t_int32 g_get_data_int32_simple(const GUID & p_guid,t_int32 p_default); + + + FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(config_object); +}; + +class standard_config_objects +{ +public: + static const GUID bool_remember_window_positions, bool_ui_always_on_top,bool_playlist_stop_after_current; + static const GUID bool_playback_follows_cursor, bool_cursor_follows_playback; + static const GUID bool_show_keyboard_shortcuts_in_menus; + static const GUID string_gui_last_directory_media,string_gui_last_directory_playlists; + static const GUID int32_dynamic_bitrate_display_rate; + + + inline static bool query_show_keyboard_shortcuts_in_menus() {return config_object::g_get_data_bool_simple(standard_config_objects::bool_show_keyboard_shortcuts_in_menus,true);} + inline static bool query_remember_window_positions() {return config_object::g_get_data_bool_simple(standard_config_objects::bool_remember_window_positions,true);} + +}; + +class config_object_notify : public service_base +{ +public: + virtual t_size get_watched_object_count() = 0; + virtual GUID get_watched_object(t_size p_index) = 0; + virtual void on_watched_object_changed(const service_ptr_t & p_object) = 0; + + FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(config_object_notify); +}; + +#endif // _CONFIG_OBJECT_H_ diff --git a/foobar2000/SDK/config_object_impl.h b/foobar2000/SDK/config_object_impl.h new file mode 100644 index 0000000..e901a3a --- /dev/null +++ b/foobar2000/SDK/config_object_impl.h @@ -0,0 +1,173 @@ +#ifndef _CONFIG_OBJECT_IMPL_H_ +#define _CONFIG_OBJECT_IMPL_H_ + +//template function bodies from config_object class + +template +void config_object::get_data_struct_t(T& p_out) { + if (get_data_raw(&p_out,sizeof(T)) != sizeof(T)) throw exception_io_data_truncation(); +} + +template +void config_object::set_data_struct_t(const T& p_in) { + return set_data_raw(&p_in,sizeof(T)); +} + +template +void config_object::g_get_data_struct_t(const GUID & p_guid,T & p_out) { + service_ptr_t ptr; + if (!g_find(ptr,p_guid)) throw exception_service_not_found(); + return ptr->get_data_struct_t(p_out); +} + +template +void config_object::g_set_data_struct_t(const GUID & p_guid,const T & p_in) { + service_ptr_t ptr; + if (!g_find(ptr,p_guid)) throw exception_service_not_found(); + return ptr->set_data_struct_t(p_in); +} + + +class config_object_impl : public config_object, private cfg_var +{ +public: + GUID get_guid() const {return cfg_var::get_guid();} + void get_data(stream_writer * p_stream,abort_callback & p_abort) const; + void set_data(stream_reader * p_stream,abort_callback & p_abort,bool p_notify); + + config_object_impl(const GUID & p_guid,const void * p_data,t_size p_bytes); +private: + + //cfg_var methods + void get_data_raw(stream_writer * p_stream,abort_callback & p_abort) {get_data(p_stream,p_abort);} + void set_data_raw(stream_reader * p_stream,t_size p_sizehint,abort_callback & p_abort) {set_data(p_stream,p_abort,false);} + + mutable pfc::readWriteLock m_sync; + pfc::array_t m_data; +}; + +typedef service_factory_single_transparent_t config_object_factory; + +template +class config_object_fixed_const_impl_t : public config_object { +public: + config_object_fixed_const_impl_t(const GUID & p_guid, const void * p_data) : m_guid(p_guid) {memcpy(m_data,p_data,p_size);} + GUID get_guid() const {return m_guid;} + + void get_data(stream_writer * p_stream, abort_callback & p_abort) const { p_stream->write_object(m_data,p_size,p_abort); } + void set_data(stream_reader * p_stream, abort_callback & p_abort, bool p_notify) { PFC_ASSERT(!"Should not get here."); } + +private: + t_uint8 m_data[p_size]; + const GUID m_guid; +}; + +template +class config_object_fixed_impl_t : public config_object, private cfg_var { +public: + GUID get_guid() const {return cfg_var::get_guid();} + + void get_data(stream_writer * p_stream,abort_callback & p_abort) const { + inReadSync(m_sync); + p_stream->write_object(m_data,p_size,p_abort); + } + + void set_data(stream_reader * p_stream,abort_callback & p_abort,bool p_notify) { + core_api::ensure_main_thread(); + + { + t_uint8 temp[p_size]; + p_stream->read_object(temp,p_size,p_abort); + inWriteSync(m_sync); + memcpy(m_data,temp,p_size); + } + + if (p_notify) config_object_notify_manager::g_on_changed(this); + } + + config_object_fixed_impl_t (const GUID & p_guid,const void * p_data) + : cfg_var(p_guid) + { + memcpy(m_data,p_data,p_size); + } + +private: + //cfg_var methods + void get_data_raw(stream_writer * p_stream,abort_callback & p_abort) {get_data(p_stream,p_abort);} + void set_data_raw(stream_reader * p_stream,t_size p_sizehint,abort_callback & p_abort) {set_data(p_stream,p_abort,false);} + + mutable pfc::readWriteLock m_sync; + t_uint8 m_data[p_size]; + +}; + +template class _config_object_fixed_impl_switch; +template class _config_object_fixed_impl_switch { public: typedef config_object_fixed_impl_t type; }; +template class _config_object_fixed_impl_switch { public: typedef config_object_fixed_const_impl_t type; }; + +template +class config_object_fixed_factory_t : public service_factory_single_transparent_t< typename _config_object_fixed_impl_switch::type > +{ +public: + config_object_fixed_factory_t(const GUID & p_guid,const void * p_initval) + : + service_factory_single_transparent_t< typename _config_object_fixed_impl_switch::type > + (p_guid,p_initval) + {} +}; + + +class config_object_string_factory : public config_object_factory +{ +public: + config_object_string_factory(const GUID & p_guid,const char * p_string,t_size p_string_length = ~0) + : config_object_factory(p_guid,p_string,pfc::strlen_max(p_string,~0)) {} + +}; + +template +class config_object_bool_factory_t : public config_object_fixed_factory_t<1,isConst> { +public: + config_object_bool_factory_t(const GUID & p_guid,bool p_initval) + : config_object_fixed_factory_t<1,isConst>(p_guid,&p_initval) {} +}; +typedef config_object_bool_factory_t<> config_object_bool_factory; + +template +class config_object_int_factory_t : public config_object_fixed_factory_t +{ +private: + struct t_initval + { + T m_initval; + t_initval(T p_initval) : m_initval(p_initval) {byte_order::order_native_to_le_t(m_initval);} + T * get_ptr() {return &m_initval;} + }; +public: + config_object_int_factory_t(const GUID & p_guid,T p_initval) + : config_object_fixed_factory_t(p_guid,t_initval(p_initval).get_ptr() ) + {} +}; + +typedef config_object_int_factory_t config_object_int32_factory; + + + +class config_object_notify_impl_simple : public config_object_notify +{ +public: + t_size get_watched_object_count() {return 1;} + GUID get_watched_object(t_size p_index) {return m_guid;} + void on_watched_object_changed(const service_ptr_t & p_object) {m_func(p_object);} + + typedef void (*t_func)(const service_ptr_t &); + + config_object_notify_impl_simple(const GUID & p_guid,t_func p_func) : m_guid(p_guid), m_func(p_func) {} +private: + GUID m_guid; + t_func m_func; +}; + +typedef service_factory_single_transparent_t config_object_notify_simple_factory; + +#endif //_CONFIG_OBJECT_IMPL_H_ diff --git a/foobar2000/SDK/console.cpp b/foobar2000/SDK/console.cpp new file mode 100644 index 0000000..935d0c1 --- /dev/null +++ b/foobar2000/SDK/console.cpp @@ -0,0 +1,82 @@ +#include "foobar2000.h" + + +void console::info(const char * p_message) {print(p_message);} +void console::error(const char * p_message) {complain("Error", p_message);} +void console::warning(const char * p_message) {complain("Warning", p_message);} + +void console::info_location(const playable_location & src) {print_location(src);} +void console::info_location(const metadb_handle_ptr & src) {print_location(src);} + +void console::print_location(const metadb_handle_ptr & src) +{ + print_location(src->get_location()); +} + +void console::print_location(const playable_location & src) +{ + FB2K_console_formatter() << src; +} + +void console::complain(const char * what, const char * msg) { + FB2K_console_formatter() << what << ": " << msg; +} +void console::complain(const char * what, std::exception const & e) { + complain(what, e.what()); +} + +void console::print(const char* p_message) +{ + if (core_api::are_services_available()) { + service_ptr_t ptr; + service_enum_t e; + while(e.next(ptr)) ptr->print(p_message,~0); + } +} + +void console::printf(const char* p_format,...) +{ + va_list list; + va_start(list,p_format); + printfv(p_format,list); + va_end(list); +} + +void console::printfv(const char* p_format,va_list p_arglist) +{ + pfc::string8_fastalloc temp; + uPrintfV(temp,p_format,p_arglist); + print(temp); +} + + + +namespace { + + class event_logger_recorder_impl : public event_logger_recorder { + public: + void playback( event_logger::ptr playTo ) { + for(auto i = m_entries.first(); i.is_valid(); ++i ) { + playTo->log_entry( i->line.get_ptr(), i->severity ); + } + } + + void log_entry( const char * line, unsigned severity ) { + auto rec = m_entries.insert_last(); + rec->line = line; + rec->severity = severity; + } + private: + + struct entry_t { + pfc::string_simple line; + unsigned severity; + }; + pfc::chain_list_v2_t< entry_t > m_entries; + }; + +} + +event_logger_recorder::ptr event_logger_recorder::create() { + return new service_impl_t(); +} diff --git a/foobar2000/SDK/console.h b/foobar2000/SDK/console.h new file mode 100644 index 0000000..33fe675 --- /dev/null +++ b/foobar2000/SDK/console.h @@ -0,0 +1,53 @@ +//! Namespace with functions for sending text to console. All functions are fully multi-thread safe, though they must not be called during dll initialization or deinitialization (e.g. static object constructors or destructors) when service system is not available. +namespace console +{ + void info(const char * p_message); + void error(const char * p_message); + void warning(const char * p_message); + void info_location(const playable_location & src); + void info_location(const metadb_handle_ptr & src); + void print_location(const playable_location & src); + void print_location(const metadb_handle_ptr & src); + + void print(const char*); + void printf(const char*,...); + void printfv(const char*,va_list p_arglist); + + class lineWriter { + public: + const lineWriter & operator<<( const char * msg ) const {print(msg);return *this;} + }; + + //! Usage: console::formatter() << "blah " << somenumber << " asdf" << somestring; + class formatter : public pfc::string_formatter { + public: + ~formatter() {if (!is_empty()) console::print(get_ptr());} + }; +#define FB2K_console_formatter() ::console::formatter()._formatter() +#define FB2K_console_formatter1() ::console::lineWriter() +#define FB2K_console_print(X) ::console::print(X) + + void complain(const char * what, const char * msg); + void complain(const char * what, std::exception const & e); + + class timer_scope { + public: + timer_scope(const char * name) : m_name(name) {m_timer.start();} + ~timer_scope() { + try { + FB2K_console_formatter() << m_name << ": " << pfc::format_time_ex(m_timer.query(), 6); + } catch(...) {} + } + private: + pfc::hires_timer m_timer; + const char * const m_name; + }; +}; + +//! Interface receiving console output. Do not call directly; use console namespace functions instead. +class NOVTABLE console_receiver : public service_base { +public: + virtual void print(const char * p_message,t_size p_message_length) = 0; + + FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(console_receiver); +}; diff --git a/foobar2000/SDK/contextmenu.h b/foobar2000/SDK/contextmenu.h new file mode 100644 index 0000000..e593b35 --- /dev/null +++ b/foobar2000/SDK/contextmenu.h @@ -0,0 +1,341 @@ +#pragma once + +//! Reserved for future use. +typedef void * t_glyph; + + +class NOVTABLE contextmenu_item_node { +public: + enum t_flags { + FLAG_CHECKED = 1, + FLAG_DISABLED = 2, + FLAG_GRAYED = 4, + FLAG_DISABLED_GRAYED = FLAG_DISABLED|FLAG_GRAYED, + FLAG_RADIOCHECKED = 8, //new in 0.9.5.2 - overrides FLAG_CHECKED, set together with FLAG_CHECKED for backwards compatibility. + }; + + enum t_type { + type_group, + type_command, + type_separator, + + //for compatibility + TYPE_POPUP = type_group,TYPE_COMMAND = type_command,TYPE_SEPARATOR = type_separator, + }; + + virtual bool get_display_data(pfc::string_base & p_out,unsigned & p_displayflags,metadb_handle_list_cref p_data,const GUID & p_caller) = 0; + virtual t_type get_type() = 0; + virtual void execute(metadb_handle_list_cref p_data,const GUID & p_caller) = 0; + virtual t_glyph get_glyph(metadb_handle_list_cref p_data,const GUID & p_caller) {return 0;}//RESERVED + virtual t_size get_children_count() = 0; + virtual contextmenu_item_node * get_child(t_size p_index) = 0; + virtual bool get_description(pfc::string_base & p_out) = 0; + virtual GUID get_guid() = 0; + virtual bool is_mappable_shortcut() = 0; + +protected: + contextmenu_item_node() {} + ~contextmenu_item_node() {} +}; + +class NOVTABLE contextmenu_item_node_root : public contextmenu_item_node +{ +public: + virtual ~contextmenu_item_node_root() {} +}; + +class NOVTABLE contextmenu_item_node_leaf : public contextmenu_item_node +{ +public: + t_type get_type() {return TYPE_COMMAND;} + t_size get_children_count() {return 0;} + contextmenu_item_node * get_child(t_size) {return NULL;} +}; + +class NOVTABLE contextmenu_item_node_root_leaf : public contextmenu_item_node_root +{ +public: + t_type get_type() {return TYPE_COMMAND;} + t_size get_children_count() {return 0;} + contextmenu_item_node * get_child(t_size) {return NULL;} +}; + +class NOVTABLE contextmenu_item_node_popup : public contextmenu_item_node +{ +public: + t_type get_type() {return TYPE_POPUP;} + void execute(metadb_handle_list_cref data,const GUID & caller) {} + bool get_description(pfc::string_base & p_out) {return false;} +}; + +class NOVTABLE contextmenu_item_node_root_popup : public contextmenu_item_node_root +{ +public: + t_type get_type() {return TYPE_POPUP;} + void execute(metadb_handle_list_cref data,const GUID & caller) {} + bool get_description(pfc::string_base & p_out) {return false;} +}; + +class contextmenu_item_node_separator : public contextmenu_item_node +{ +public: + t_type get_type() {return TYPE_SEPARATOR;} + void execute(metadb_handle_list_cref data,const GUID & caller) {} + bool get_description(pfc::string_base & p_out) {return false;} + t_size get_children_count() {return 0;} + bool get_display_data(pfc::string_base & p_out,unsigned & p_displayflags,metadb_handle_list_cref p_data,const GUID & p_caller) + { + p_displayflags = 0; + p_out = "---"; + return true; + } + contextmenu_item_node * get_child(t_size) {return NULL;} + GUID get_guid() {return pfc::guid_null;} + bool is_mappable_shortcut() {return false;} +}; + +/*! +Service class for declaring context menu commands.\n +See contextmenu_item_simple for implementation helper without dynamic menu generation features.\n +All methods are valid from main app thread only. +*/ +class NOVTABLE contextmenu_item : public service_base { +public: + enum t_enabled_state { + FORCE_OFF, + DEFAULT_OFF, + DEFAULT_ON, + }; + + //! Retrieves number of menu items provided by this contextmenu_item implementation. + virtual unsigned get_num_items() = 0; + //! Instantiates a context menu item (including sub-node tree for items that contain dynamically-generated sub-items). + virtual contextmenu_item_node_root * instantiate_item(unsigned p_index,metadb_handle_list_cref p_data,const GUID & p_caller) = 0; + //! Retrieves GUID of the context menu item. + virtual GUID get_item_guid(unsigned p_index) = 0; + //! Retrieves human-readable name of the context menu item. + virtual void get_item_name(unsigned p_index,pfc::string_base & p_out) = 0; + //! Obsolete since v1.0, don't use or override in new components. + virtual void get_item_default_path(unsigned p_index,pfc::string_base & p_out) {p_out = "";} + //! Retrieves item's description to show in the status bar. Set p_out to the string to be displayed and return true if you provide a description, return false otherwise. + virtual bool get_item_description(unsigned p_index,pfc::string_base & p_out) = 0; + //! Controls default state of context menu preferences for this item: \n + //! Return DEFAULT_ON to show this item in the context menu by default - useful for most cases. \n + //! Return DEFAULT_OFF to hide this item in the context menu by default - useful for rarely used utility commands. \n + //! Return FORCE_OFF to hide this item by default and prevent the user from making it visible (very rarely used). \n + //! foobar2000 v1.6 and newer: FORCE_OFF items are meant for being shown only in the keyboard shortcut list, not anywhere else. \n + //! Values returned by this method should be constant for this context menu item and not change later. Do not use this to conditionally hide the item - return false from get_display_data() instead. + virtual t_enabled_state get_enabled_state(unsigned p_index) = 0; + //! Executes the menu item command without going thru the instantiate_item path. For items with dynamically-generated sub-items, p_node is identifies of the sub-item command to execute. + virtual void item_execute_simple(unsigned p_index,const GUID & p_node,metadb_handle_list_cref p_data,const GUID & p_caller) = 0; + + bool item_get_display_data_root(pfc::string_base & p_out,unsigned & displayflags,unsigned p_index,metadb_handle_list_cref p_data,const GUID & p_caller); + bool item_get_display_data(pfc::string_base & p_out,unsigned & displayflags,unsigned p_index,const GUID & p_node,metadb_handle_list_cref p_data,const GUID & p_caller); + + GUID get_parent_fallback(); + GUID get_parent_(); + + //! Deprecated - use caller_active_playlist_selection instead. + static const GUID caller_playlist; + + static const GUID caller_active_playlist_selection, caller_active_playlist, caller_playlist_manager, caller_now_playing, caller_keyboard_shortcut_list, caller_media_library_viewer; + static const GUID caller_undefined; + + FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(contextmenu_item); +}; + +//! \since 1.0 +class NOVTABLE contextmenu_item_v2 : public contextmenu_item { + FB2K_MAKE_SERVICE_INTERFACE(contextmenu_item_v2, contextmenu_item) +public: + virtual double get_sort_priority() {return 0;} + virtual GUID get_parent() {return get_parent_fallback();} +}; + +//! contextmenu_item implementation helper for implementing non-dynamically-generated context menu items; derive from this instead of from contextmenu_item directly if your menu items are static. +class NOVTABLE contextmenu_item_simple : public contextmenu_item_v2 { +private: +public: + //! Same as contextmenu_item_node::t_flags. + enum t_flags + { + FLAG_CHECKED = 1, + FLAG_DISABLED = 2, + FLAG_GRAYED = 4, + FLAG_DISABLED_GRAYED = FLAG_DISABLED|FLAG_GRAYED, + FLAG_RADIOCHECKED = 8, //new in 0.9.5.2 - overrides FLAG_CHECKED, set together with FLAG_CHECKED for backwards compatibility. + }; + + + // Functions to be overridden by implementers (some are not mandatory). + virtual t_enabled_state get_enabled_state(unsigned p_index) {return contextmenu_item::DEFAULT_ON;} + virtual unsigned get_num_items() = 0; + virtual void get_item_name(unsigned p_index,pfc::string_base & p_out) = 0; + virtual void context_command(unsigned p_index,metadb_handle_list_cref p_data,const GUID& p_caller) = 0; + virtual bool context_get_display(unsigned p_index,metadb_handle_list_cref p_data,pfc::string_base & p_out,unsigned & p_displayflags,const GUID & p_caller) { + PFC_ASSERT(p_index>=0 && p_indexget_display_data(m_index,p_data,p_out,p_displayflags,p_caller);} + void execute(metadb_handle_list_cref p_data,const GUID & p_caller) {m_owner->context_command(m_index,p_data,p_caller);} + bool get_description(pfc::string_base & p_out) {return m_owner->get_item_description(m_index,p_out);} + GUID get_guid() {return pfc::guid_null;} + bool is_mappable_shortcut() {return m_owner->item_is_mappable_shortcut(m_index);} + private: + service_ptr_t m_owner; + unsigned m_index; + }; + + contextmenu_item_node_root * instantiate_item(unsigned p_index,metadb_handle_list_cref p_data,const GUID & p_caller) + { + return new contextmenu_item_node_impl(this,p_index); + } + + + void item_execute_simple(unsigned p_index,const GUID & p_node,metadb_handle_list_cref p_data,const GUID & p_caller) + { + if (p_node == pfc::guid_null) + context_command(p_index,p_data,p_caller); + } + + virtual bool item_is_mappable_shortcut(unsigned p_index) + { + return true; + } + + + virtual bool get_display_data(unsigned n,metadb_handle_list_cref data,pfc::string_base & p_out,unsigned & displayflags,const GUID & caller) + { + bool rv = false; + assert(n>=0 && n0) + { + rv = context_get_display(n,data,p_out,displayflags,caller); + } + return rv; + } + +}; + + +//! Helper. +template +class contextmenu_item_factory_t : public service_factory_single_t {}; + + +//! Helper. +#define DECLARE_CONTEXT_MENU_ITEM(P_CLASSNAME,P_NAME,P_DEFAULTPATH,P_FUNC,P_GUID,P_DESCRIPTION) \ + namespace { \ + class P_CLASSNAME : public contextmenu_item_simple { \ + public: \ + unsigned get_num_items() {return 1;} \ + void get_item_name(unsigned p_index,pfc::string_base & p_out) {p_out = P_NAME;} \ + void get_item_default_path(unsigned p_index,pfc::string_base & p_out) {p_out = P_DEFAULTPATH;} \ + void context_command(unsigned p_index,metadb_handle_list_cref p_data,const GUID& p_caller) {P_FUNC(p_data);} \ + GUID get_item_guid(unsigned p_index) {return P_GUID;} \ + bool get_item_description(unsigned p_index,pfc::string_base & p_out) {if (P_DESCRIPTION[0] == 0) return false;p_out = P_DESCRIPTION; return true;} \ + }; \ + static contextmenu_item_factory_t g_##P_CLASSNAME##_factory; \ + } + + + + +//! New in 0.9.5.1. Static methods safe to use in prior versions as it will use slow fallback mode when the service isn't present. \n +//! Functionality provided by menu_item_resolver methods isn't much different from just walking all registered contextmenu_item / mainmenu_commands implementations to find the command we want, but it uses a hint map to locate the service we're looking for without walking all of them which may be significantly faster in certain scenarios. +class menu_item_resolver : public service_base { + FB2K_MAKE_SERVICE_COREAPI(menu_item_resolver) +public: + virtual bool resolve_context_command(const GUID & id, service_ptr_t & out, t_uint32 & out_index) = 0; + virtual bool resolve_main_command(const GUID & id, service_ptr_t & out, t_uint32 & out_index) = 0; + + static bool g_resolve_context_command(const GUID & id, service_ptr_t & out, t_uint32 & out_index); + static bool g_resolve_main_command(const GUID & id, service_ptr_t & out, t_uint32 & out_index); + + +}; + +//! \since 1.0 +class NOVTABLE contextmenu_group : public service_base { + FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(contextmenu_group); +public: + virtual GUID get_guid() = 0; + virtual GUID get_parent() = 0; + virtual double get_sort_priority() = 0; +}; + +//! \since 1.0 +class NOVTABLE contextmenu_group_popup : public contextmenu_group { + FB2K_MAKE_SERVICE_INTERFACE(contextmenu_group_popup, contextmenu_group) +public: + virtual void get_display_string(pfc::string_base & out) = 0; + void get_name(pfc::string_base & out) {get_display_string(out);} +}; + +class contextmenu_groups { +public: + static const GUID root, utilities, tagging, tagging_pictures, replaygain, fileoperations, playbackstatistics, properties, convert, legacy; +}; + +class contextmenu_group_impl : public contextmenu_group { +public: + contextmenu_group_impl(const GUID & guid, const GUID & parent, double sortPriority = 0) : m_guid(guid), m_parent(parent), m_sortPriority(sortPriority) {} + GUID get_guid() {return m_guid;} + GUID get_parent() {return m_parent;} + double get_sort_priority() {return m_sortPriority;} +private: + const GUID m_guid, m_parent; + const double m_sortPriority; +}; + +class contextmenu_group_popup_impl : public contextmenu_group_popup { +public: + contextmenu_group_popup_impl(const GUID & guid, const GUID & parent, const char * name, double sortPriority = 0) : m_guid(guid), m_parent(parent), m_sortPriority(sortPriority), m_name(name) {} + GUID get_guid() {return m_guid;} + GUID get_parent() {return m_parent;} + double get_sort_priority() {return m_sortPriority;} + void get_display_string(pfc::string_base & out) {out = m_name;} +private: + const GUID m_guid, m_parent; + const double m_sortPriority; + const char * const m_name; +}; + + + +namespace contextmenu_priorities { + enum { + root_queue = -100, + root_main = -50, + root_tagging, + root_fileoperations, + root_convert, + root_utilities, + root_replaygain, + root_playbackstatistics, + root_legacy = 99, + root_properties = 100, + tagging_pictures = 100, + }; +}; + + + +class contextmenu_group_factory : public service_factory_single_t { +public: + contextmenu_group_factory(const GUID & guid, const GUID & parent, double sortPriority = 0) : service_factory_single_t(guid, parent, sortPriority) {} +}; + +class contextmenu_group_popup_factory : public service_factory_single_t { +public: + contextmenu_group_popup_factory(const GUID & guid, const GUID & parent, const char * name, double sortPriority = 0) : service_factory_single_t(guid, parent, name, sortPriority) {} +}; diff --git a/foobar2000/SDK/contextmenu_manager.h b/foobar2000/SDK/contextmenu_manager.h new file mode 100644 index 0000000..e9ca9ce --- /dev/null +++ b/foobar2000/SDK/contextmenu_manager.h @@ -0,0 +1,130 @@ +class NOVTABLE keyboard_shortcut_manager : public service_base +{ +public: + static bool g_get(service_ptr_t & p_out) {return service_enum_create_t(p_out,0);} + + enum shortcut_type + { + TYPE_MAIN, + TYPE_CONTEXT, + TYPE_CONTEXT_PLAYLIST, + TYPE_CONTEXT_NOW_PLAYING, + }; + + + virtual bool process_keydown(shortcut_type type,const pfc::list_base_const_t & data,unsigned keycode)=0; + virtual bool process_keydown_ex(shortcut_type type,const pfc::list_base_const_t & data,unsigned keycode,const GUID & caller)=0; + bool on_keydown(shortcut_type type,WPARAM wp); + bool on_keydown_context(const pfc::list_base_const_t & data,WPARAM wp,const GUID & caller); + + bool on_keydown_auto(WPARAM wp); + bool on_keydown_auto_playlist(WPARAM wp); + bool on_keydown_auto_context(const pfc::list_base_const_t & data,WPARAM wp,const GUID & caller); + + bool on_keydown_restricted_auto(WPARAM wp); + bool on_keydown_restricted_auto_playlist(WPARAM wp); + bool on_keydown_restricted_auto_context(const pfc::list_base_const_t & data,WPARAM wp,const GUID & caller); + + virtual bool get_key_description_for_action(const GUID & p_command,const GUID & p_subcommand, pfc::string_base & out, shortcut_type type, bool is_global)=0; + + static bool is_text_key(t_uint32 vkCode); + static bool is_typing_key(t_uint32 vkCode); + static bool is_typing_key_combo(t_uint32 vkCode, t_uint32 modifiers); + static bool is_typing_modifier(t_uint32 flags); + static bool is_typing_message(HWND editbox, const MSG * msg); + static bool is_typing_message(const MSG * msg); + + FB2K_MAKE_SERVICE_COREAPI(keyboard_shortcut_manager); +}; + + +//! New in 0.9.5. +class keyboard_shortcut_manager_v2 : public keyboard_shortcut_manager { +public: + //! Deprecates old keyboard_shortcut_manager methods. If the action requires selected items, they're obtained from ui_selection_manager API automatically. + virtual bool process_keydown_simple(t_uint32 keycode) = 0; + + //! Helper for use with message filters. + bool pretranslate_message(const MSG * msg, HWND thisPopupWnd); + + FB2K_MAKE_SERVICE_COREAPI_EXTENSION(keyboard_shortcut_manager_v2,keyboard_shortcut_manager); +}; + +class NOVTABLE contextmenu_node { +public: + virtual contextmenu_item_node::t_type get_type()=0; + virtual const char * get_name()=0; + virtual t_size get_num_children()=0;//TYPE_POPUP only + virtual contextmenu_node * get_child(t_size n)=0;//TYPE_POPUP only + virtual unsigned get_display_flags()=0;//TYPE_COMMAND/TYPE_POPUP only, see contextmenu_item::FLAG_* + virtual unsigned get_id()=0;//TYPE_COMMAND only, returns zero-based index (helpful for win32 menu command ids) + virtual void execute()=0;//TYPE_COMMAND only + virtual bool get_description(pfc::string_base & out)=0;//TYPE_COMMAND only + virtual bool get_full_name(pfc::string_base & out)=0;//TYPE_COMMAND only + virtual void * get_glyph()=0;//RESERVED, do not use +protected: + contextmenu_node() {} + ~contextmenu_node() {} +}; + + + +class NOVTABLE contextmenu_manager : public service_base +{ +public: + enum + { + flag_show_shortcuts = 1 << 0, + flag_show_shortcuts_global = 1 << 1, + //! \since 1.0 + //! To control which commands are shown, you should specify either flag_view_reduced or flag_view_full. If neither is specified, the implementation will decide automatically based on shift key being pressed, for backwards compatibility. + flag_view_reduced = 1 << 2, + //! \since 1.0 + //! To control which commands are shown, you should specify either flag_view_reduced or flag_view_full. If neither is specified, the implementation will decide automatically based on shift key being pressed, for backwards compatibility. + flag_view_full = 1 << 3, + + //for compatibility + FLAG_SHOW_SHORTCUTS = 1, + FLAG_SHOW_SHORTCUTS_GLOBAL = 2, + }; + + virtual void init_context(metadb_handle_list_cref data,unsigned flags) = 0; + virtual void init_context_playlist(unsigned flags) = 0; + virtual contextmenu_node * get_root() = 0;//releasing contextmenu_manager service releaases nodes; root may be null in case of error or something + virtual contextmenu_node * find_by_id(unsigned id)=0; + virtual void set_shortcut_preference(const keyboard_shortcut_manager::shortcut_type * data,unsigned count)=0; + + + + static void g_create(service_ptr_t & p_out) {standard_api_create_t(p_out);} + +#ifdef WIN32 + static void win32_build_menu(HMENU menu,contextmenu_node * parent,int base_id,int max_id);//menu item identifiers are base_id<=N in fb2k profile folder. + inline pfc::string8 pathInProfile(const char * fileName) { pfc::string8 p( core_api::get_profile_path() ); p.add_filename( fileName ); return p; } + + //! Returns whether foobar2000 has been installed in "portable" mode. + bool is_portable_mode_enabled(); + + //! Returns whether foobar2000 is currently running in quiet mode. \n + //! Quiet mode bypasses all GUI features, disables Media Library and does not save any changes to app configuration. \n + //! Your component should not display any forms of user interface when running in quiet mode, as well as avoid saving configuration on its own (no need to worry if you only rely on cfg_vars or config_io_callback, they will simply be ignored on shutdown). + bool is_quiet_mode_enabled(); +}; + +#endif diff --git a/foobar2000/SDK/coreversion.h b/foobar2000/SDK/coreversion.h new file mode 100644 index 0000000..41521f2 --- /dev/null +++ b/foobar2000/SDK/coreversion.h @@ -0,0 +1,39 @@ +#pragma once + +class NOVTABLE core_version_info : public service_base { + FB2K_MAKE_SERVICE_COREAPI(core_version_info); +public: + virtual const char * get_version_string() = 0; + static const char * g_get_version_string() {return core_version_info::get()->get_version_string();} + +}; + +struct t_core_version_data { + t_uint32 m_major, m_minor1, m_minor2, m_minor3; +}; + +//! New (0.9.4.2) +class NOVTABLE core_version_info_v2 : public core_version_info { + FB2K_MAKE_SERVICE_COREAPI_EXTENSION(core_version_info_v2, core_version_info); +public: + virtual const char * get_name() = 0;//"foobar2000" + virtual const char * get_version_as_text() = 0;//"N.N.N.N" + virtual t_core_version_data get_version() = 0; + + //! Determine whether running foobar2000 version is newer or equal to the specified version, eg. test_version(0,9,5,0) for 0.9.5. + bool test_version(t_uint32 major, t_uint32 minor1, t_uint32 minor2, t_uint32 minor3) { + const t_core_version_data v = get_version(); + if (v.m_major < major) return false; + else if (v.m_major > major) return true; + // major version matches + else if (v.m_minor1 < minor1) return false; + else if (v.m_minor1 > minor1) return true; + // minor1 version matches + else if (v.m_minor2 < minor2) return false; + else if (v.m_minor2 > minor2) return true; + // minor2 version matches + else if (v.m_minor3 < minor3) return false; + else return true; + } + +}; diff --git a/foobar2000/SDK/decode_postprocessor.h b/foobar2000/SDK/decode_postprocessor.h new file mode 100644 index 0000000..8dbd94e --- /dev/null +++ b/foobar2000/SDK/decode_postprocessor.h @@ -0,0 +1,170 @@ +#pragma once + +#ifdef FOOBAR2000_HAVE_DSP +//! \since 1.1 +//! This service is essentially a special workaround to easily decode DTS/HDCD content stored in files pretending to contain plain PCM data. \n +//! Callers: Instead of calling this directly, you probably want to use input_postprocessed template. \n +//! Implementers: This service is called only by specific decoders, not by all of them! Implementing your own to provide additional functionality is not recommended! +class decode_postprocessor_instance : public service_base { + FB2K_MAKE_SERVICE_INTERFACE(decode_postprocessor_instance, service_base); +public: + enum { + //! End of stream. Flush any buffered data during this call. + flag_eof = 1 << 0, + //! Stream has already been altered by another instance. + flag_altered = 1 << 1, + }; + //! @returns True if the chunk list has been altered by the call, false if not - to tell possible other running instances whether the stream has already been altered or not. + virtual bool run(dsp_chunk_list & p_chunk_list,t_uint32 p_flags,abort_callback & p_abort) = 0; + virtual bool get_dynamic_info(file_info & p_out) = 0; + virtual void flush() = 0; + virtual double get_buffer_ahead() = 0; +}; + +//! \since 1.1 +//! Entrypoint class for instantiating decode_postprocessor_instance. See decode_postprocessor_instance documentation for more information. \n +//! Instead of calling this directly, you probably want to use input_postprocessed template. +class decode_postprocessor_entry : public service_base { + FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(decode_postprocessor_entry) +public: + virtual bool instantiate(const file_info & info, decode_postprocessor_instance::ptr & out) = 0; +}; + + +//! Helper class for managing decode_postprocessor_instance objects. See also: input_postprocessed. +class decode_postprocessor { +public: + typedef decode_postprocessor_instance::ptr item; + void initialize(const file_info & info) { + m_items.remove_all(); + service_enum_t e; + decode_postprocessor_entry::ptr ptr; + while(e.next(ptr)) { + item i; + if (ptr->instantiate(info, i)) m_items += i; + } + } + void run(dsp_chunk_list & p_chunk_list,bool p_eof,abort_callback & p_abort) { + t_uint32 flags = p_eof ? decode_postprocessor_instance::flag_eof : 0; + for(t_size walk = 0; walk < m_items.get_size(); ++walk) { + if (m_items[walk]->run(p_chunk_list, flags, p_abort)) flags |= decode_postprocessor_instance::flag_altered; + } + } + void flush() { + for(t_size walk = 0; walk < m_items.get_size(); ++walk) { + m_items[walk]->flush(); + } + } + static bool should_bother() { + return service_factory_base::is_service_present(decode_postprocessor_entry::class_guid); + } + bool is_active() const { + return m_items.get_size() > 0; + } + bool get_dynamic_info(file_info & p_out) { + bool rv = false; + for(t_size walk = 0; walk < m_items.get_size(); ++walk) { + if (m_items[walk]->get_dynamic_info(p_out)) rv = true; + } + return rv; + } + void close() { + m_items.remove_all(); + } + double get_buffer_ahead() { + double acc = 0; + for(t_size walk = 0; walk < m_items.get_size(); ++walk) { + pfc::max_acc(acc, m_items[walk]->get_buffer_ahead()); + } + return acc; + } +private: + pfc::list_t m_items; +}; + +//! Generic template to add decode_postprocessor support to your input class. Works with both single-track and multi-track inputs. +template class input_postprocessed : public baseclass { +public: + void decode_initialize(t_uint32 p_subsong,unsigned p_flags,abort_callback & p_abort) { + m_chunks.remove_all(); + m_haveEOF = false; + m_toSkip = 0; + m_postproc.close(); + if ((p_flags & input_flag_no_postproc) == 0 && m_postproc.should_bother()) { + file_info_impl info; + this->get_info(p_subsong, info, p_abort); + m_postproc.initialize(info); + } + baseclass::decode_initialize(p_subsong, p_flags, p_abort); + } + void decode_initialize(unsigned p_flags,abort_callback & p_abort) { + m_chunks.remove_all(); + m_haveEOF = false; + m_toSkip = 0; + m_postproc.close(); + if ((p_flags & input_flag_no_postproc) == 0 && m_postproc.should_bother()) { + file_info_impl info; + this->get_info(info, p_abort); + m_postproc.initialize(info); + } + baseclass::decode_initialize(p_flags, p_abort); + } + bool decode_run(audio_chunk & p_chunk,abort_callback & p_abort) { + if (m_postproc.is_active()) { + for(;;) { + p_abort.check(); + if (m_chunks.get_count() > 0) { + audio_chunk * c = m_chunks.get_item(0); + if (m_toSkip > 0) { + if (!c->process_skip(m_toSkip)) { + m_chunks.remove_by_idx(0); + continue; + } + } + p_chunk = *c; + m_chunks.remove_by_idx(0); + return true; + } + if (m_haveEOF) return false; + if (!baseclass::decode_run(*m_chunks.insert_item(0), p_abort)) { + m_haveEOF = true; + m_chunks.remove_by_idx(0); + } + m_postproc.run(m_chunks, m_haveEOF, p_abort); + } + } else { + return baseclass::decode_run(p_chunk, p_abort); + } + } + bool decode_run_raw(audio_chunk & p_chunk, mem_block_container & p_raw, abort_callback & p_abort) { + if (m_postproc.is_active()) { + throw pfc::exception_not_implemented(); + } else { + return baseclass::decode_run_raw(p_chunk, p_raw, p_abort); + } + } + bool decode_get_dynamic_info(file_info & p_out, double & p_timestamp_delta) { + bool rv = baseclass::decode_get_dynamic_info(p_out, p_timestamp_delta); + if (m_postproc.get_dynamic_info(p_out)) rv = true; + return rv; + } + void decode_seek(double p_seconds,abort_callback & p_abort) { + m_chunks.remove_all(); + m_haveEOF = false; + m_postproc.flush(); + double target = pfc::max_t(0, p_seconds - m_postproc.get_buffer_ahead()); + m_toSkip = p_seconds - target; + baseclass::decode_seek(target, p_abort); + } +private: + dsp_chunk_list_impl m_chunks; + bool m_haveEOF; + double m_toSkip; + decode_postprocessor m_postproc; +}; + +#else // FOOBAR2000_HAVE_DSP + +template class input_postprocessed : public baseclass {}; + +#endif // FOOBAR2000_HAVE_DSP diff --git a/foobar2000/SDK/dsp.cpp b/foobar2000/SDK/dsp.cpp new file mode 100644 index 0000000..b1604c8 --- /dev/null +++ b/foobar2000/SDK/dsp.cpp @@ -0,0 +1,556 @@ +#include "foobar2000.h" + +#ifdef FOOBAR2000_HAVE_DSP + +#include + +audio_chunk * dsp_chunk_list::add_item(t_size hint_size) { return insert_item(get_count(), hint_size); } + +void dsp_chunk_list::remove_all() { remove_mask(pfc::bit_array_true()); } + +double dsp_chunk_list::get_duration() { + double rv = 0; + t_size n, m = get_count(); + for (n = 0; nget_duration(); + return rv; +} + +void dsp_chunk_list::add_chunk(const audio_chunk * chunk) { + audio_chunk * dst = insert_item(get_count(), chunk->get_used_size()); + if (dst) dst->copy(*chunk); +} + +t_size dsp_chunk_list_impl::get_count() const {return m_data.get_count();} + +audio_chunk * dsp_chunk_list_impl::get_item(t_size n) const {return nmax) idx = max; + pfc::rcptr_t ret; + if (m_recycled.get_count()>0) + { + t_size best; + if (hint_size>0) + { + best = 0; + t_size best_found = m_recycled[0]->get_data_size(), n, total = m_recycled.get_count(); + for(n=1;nget_data_size(); + int delta_old = abs((int)best_found - (int)hint_size), delta_new = abs((int)size - (int)hint_size); + if (delta_new < delta_old) + { + best_found = size; + best = n; + } + } + } + else best = m_recycled.get_count()-1; + + ret = m_recycled.remove_by_idx(best); + ret->set_sample_count(0); + ret->set_channels(0); + ret->set_srate(0); + } + else ret = pfc::rcnew_t(); + if (idx==max) m_data.add_item(ret); + else m_data.insert_item(ret,idx); + return &*ret; +} + +void dsp_chunk_list::remove_bad_chunks() +{ + bool blah = false; + t_size idx; + for(idx=0;idxis_valid()) + { +#if PFC_DEBUG + FB2K_console_formatter() << "Removing bad chunk: " << chunk->formatChunkSpec(); +#endif + chunk->reset(); + remove_by_idx(idx); + blah = true; + } + else idx++; + } + if (blah) console::info("one or more bad chunks removed from dsp chunk list"); +} + +bool dsp_entry_hidden::g_dsp_exists(const GUID & p_guid) { + dsp_entry_hidden::ptr p; + return g_get_interface(p, p_guid); +} + +bool dsp_entry_hidden::g_get_interface( dsp_entry_hidden::ptr & out, const GUID & guid ) { + service_enum_t e; service_ptr_t p; + while( e.next(p) ) { + if (p->get_guid() == guid) { + out = p; return true; + } + } + return false; +} + +bool dsp_entry_hidden::g_instantiate( dsp::ptr & out, const dsp_preset & preset ) { + dsp_entry_hidden::ptr i; + if (!g_get_interface(i, preset.get_owner())) return false; + return i->instantiate(out, preset); +} + +bool dsp_entry::g_instantiate(service_ptr_t & p_out,const dsp_preset & p_preset) +{ + service_ptr_t ptr; + if (!g_get_interface(ptr,p_preset.get_owner())) return false; + return ptr->instantiate(p_out,p_preset); +} + +bool dsp_entry::g_instantiate_default(service_ptr_t & p_out,const GUID & p_guid) +{ + service_ptr_t ptr; + if (!g_get_interface(ptr,p_guid)) return false; + dsp_preset_impl preset; + if (!ptr->get_default_preset(preset)) return false; + return ptr->instantiate(p_out,preset); +} + +bool dsp_entry::g_name_from_guid(pfc::string_base & p_out,const GUID & p_guid) +{ + service_ptr_t ptr; + if (!g_get_interface(ptr,p_guid)) return false; + ptr->get_name(p_out); + return true; +} + +bool dsp_entry::g_dsp_exists(const GUID & p_guid) +{ + service_ptr_t blah; + return g_get_interface(blah,p_guid); +} + +bool dsp_entry::g_get_default_preset(dsp_preset & p_out,const GUID & p_guid) +{ + service_ptr_t ptr; + if (!g_get_interface(ptr,p_guid)) return false; + return ptr->get_default_preset(p_out); +} + +void dsp_chain_config::contents_to_stream(stream_writer * p_stream,abort_callback & p_abort) const { + uint32_t n, count = pfc::downcast_guarded( get_count() ); + p_stream->write_lendian_t(count,p_abort); + for(n=0;nread_lendian_t(count,p_abort); + + dsp_preset_impl temp; + + for(n=0;n & p_out) +{ + p_out.remove_all(); + t_size n, m = get_count(); + for(n=0;n temp; + auto const & preset = this->get_item(n); + if (dsp_entry::g_instantiate(temp,preset) || dsp_entry_hidden::g_instantiate(temp, preset)) + p_out.add_item(temp); + } +} + +void dsp_chain_config_impl::reorder(const size_t * order, size_t count) { + PFC_ASSERT( count == m_data.get_count() ); + m_data.reorder( order ); +} + +t_size dsp_chain_config_impl::get_count() const +{ + return m_data.get_count(); +} + +const dsp_preset & dsp_chain_config_impl::get_item(t_size p_index) const +{ + return *m_data[p_index]; +} + +void dsp_chain_config_impl::replace_item(const dsp_preset & p_data,t_size p_index) +{ + *m_data[p_index] = p_data; +} + +void dsp_chain_config_impl::insert_item(const dsp_preset & p_data,t_size p_index) +{ + m_data.insert_item(new dsp_preset_impl(p_data),p_index); +} + +void dsp_chain_config_impl::remove_mask(const bit_array & p_mask) +{ + m_data.delete_mask(p_mask); +} + +dsp_chain_config_impl::~dsp_chain_config_impl() +{ + m_data.delete_all(); +} + +pfc::string8 dsp_preset::get_owner_name() const { + pfc::string8 ret; + dsp_entry::ptr obj; + if (dsp_entry::g_get_interface(obj, this->get_owner())) { + obj->get_name(ret); + } + return ret; +} + +pfc::string8 dsp_preset::get_owner_name_debug() const { + pfc::string8 ret; + dsp_entry::ptr obj; + if (dsp_entry::g_get_interface(obj, this->get_owner())) { + obj->get_name(ret); + } else { + ret = "[unknown]"; + } + return ret; +} + +pfc::string8 dsp_preset::debug() const { + pfc::string8 ret; + ret << this->get_owner_name_debug() << " :: " << pfc::print_guid(this->get_owner()) << " :: " << pfc::format_hexdump(this->get_data(), this->get_data_size()); + return ret; +} + +pfc::string8 dsp_chain_config::debug() const { + const size_t count = get_count(); + pfc::string8 ret; + ret << "dsp_chain_config: " << count << " items"; + for (size_t walk = 0; walk < count; ++walk) { + ret << "\n" << get_item(walk).debug(); + } + return ret; +} + +void dsp_preset::contents_to_stream(stream_writer * p_stream,abort_callback & p_abort) const { + t_uint32 size = pfc::downcast_guarded(get_data_size()); + p_stream->write_lendian_t(get_owner(),p_abort); + p_stream->write_lendian_t(size,p_abort); + if (size > 0) { + p_stream->write_object(get_data(),size,p_abort); + } +} + +void dsp_preset::contents_from_stream(stream_reader * p_stream,abort_callback & p_abort) { + t_uint32 size; + GUID guid; + p_stream->read_lendian_t(guid,p_abort); + set_owner(guid); + p_stream->read_lendian_t(size,p_abort); + if (size > 1024*1024*32) throw exception_io_data(); + set_data_from_stream(p_stream,size,p_abort); +} + +void dsp_preset::g_contents_from_stream_skip(stream_reader * p_stream,abort_callback & p_abort) { + t_uint32 size; + GUID guid; + p_stream->read_lendian_t(guid,p_abort); + p_stream->read_lendian_t(size,p_abort); + if (size > 1024*1024*32) throw exception_io_data(); + p_stream->skip_object(size,p_abort); +} + +void dsp_preset_impl::set_data_from_stream(stream_reader * p_stream,t_size p_bytes,abort_callback & p_abort) { + m_data.set_size(p_bytes); + if (p_bytes > 0) p_stream->read_object(m_data.get_ptr(),p_bytes,p_abort); +} + +void dsp_chain_config::copy(const dsp_chain_config & p_source) { + remove_all(); + t_size n, m = p_source.get_count(); + for(n=0;n entry; + if (!g_get_interface(entry,p_guid)) return false; + return entry->have_config_popup(); +} + +bool dsp_entry::g_have_config_popup(const dsp_preset & p_preset) +{ + return g_have_config_popup(p_preset.get_owner()); +} + +bool dsp_entry::g_show_config_popup(dsp_preset & p_preset,HWND p_parent) +{ + service_ptr_t entry; + if (!g_get_interface(entry,p_preset.get_owner())) return false; + return entry->show_config_popup(p_preset,p_parent); +} + +void dsp_entry::g_show_config_popup_v2(const dsp_preset & p_preset,HWND p_parent,dsp_preset_edit_callback & p_callback) { + service_ptr_t entry; + if (g_get_interface(entry,p_preset.get_owner())) { + service_ptr_t entry_v2; + if (entry->service_query_t(entry_v2)) { + entry_v2->show_config_popup_v2(p_preset,p_parent,p_callback); + } else { + dsp_preset_impl temp(p_preset); + if (entry->show_config_popup(temp,p_parent)) p_callback.on_preset_changed(temp); + } + } +} + +bool dsp_entry::g_get_interface(service_ptr_t & p_out,const GUID & p_guid) +{ + service_ptr_t ptr; service_enum_t e; + while(e.next(ptr)) { + if (ptr->get_guid() == p_guid) { + p_out = ptr; + return true; + } + } + return false; +} + +bool resampler_entry::g_get_interface(service_ptr_t & p_out,unsigned p_srate_from,unsigned p_srate_to) +{ +#if FOOBAR2000_TARGET_VERSION >= 79 + auto r = resampler_manager::get()->get_resampler( p_srate_from, p_srate_to ); + bool v = r.is_valid(); + if ( v ) p_out = std::move(r); + return v; +#else + { + resampler_manager::ptr api; + if ( resampler_manager::tryGet(api) ) { + auto r = api->get_resampler( p_srate_from, p_srate_to ); + bool v = r.is_valid(); + if (v) p_out = std::move(r); + return v; + } + } + + resampler_entry::ptr ptr_resampler; + service_enum_t e; + float found_priority = 0; + resampler_entry::ptr found; + while(e.next(ptr_resampler)) + { + if (p_srate_from == 0 || ptr_resampler->is_conversion_supported(p_srate_from,p_srate_to)) + { + float priority = ptr_resampler->get_priority(); + if (found.is_empty() || priority > found_priority) + { + found = ptr_resampler; + found_priority = priority; + } + } + } + if (found.is_empty()) return false; + p_out = found; + return true; +#endif +} + +bool resampler_entry::g_create_preset(dsp_preset & p_out,unsigned p_srate_from,unsigned p_srate_to,float p_qualityscale) +{ + service_ptr_t entry; + if (!g_get_interface(entry,p_srate_from,p_srate_to)) return false; + return entry->create_preset(p_out,p_srate_to,p_qualityscale); +} + +bool resampler_entry::g_create(service_ptr_t & p_out,unsigned p_srate_from,unsigned p_srate_to,float p_qualityscale) +{ + service_ptr_t entry; + if (!g_get_interface(entry,p_srate_from,p_srate_to)) return false; + dsp_preset_impl preset; + if (!entry->create_preset(preset,p_srate_to,p_qualityscale)) return false; + return entry->instantiate(p_out,preset); +} + + +bool dsp_chain_config::equals(dsp_chain_config const & v1, dsp_chain_config const & v2) { + const t_size count = v1.get_count(); + if (count != v2.get_count()) return false; + for(t_size walk = 0; walk < count; ++walk) { + if (v1.get_item(walk) != v2.get_item(walk)) return false; + } + return true; +} +bool dsp_chain_config::equals_debug(dsp_chain_config const& v1, dsp_chain_config const& v2) { + FB2K_DebugLog() << "Comparing DSP chains"; + const t_size count = v1.get_count(); + if (count != v2.get_count()) { + FB2K_DebugLog() << "Count mismatch, " << count << " vs " << v2.get_count(); + return false; + } + for (t_size walk = 0; walk < count; ++walk) { + if (v1.get_item(walk) != v2.get_item(walk)) { + FB2K_DebugLog() << "Item " << (walk+1) << " mismatch"; + FB2K_DebugLog() << "Item 1: " << v1.get_item(walk).debug(); + FB2K_DebugLog() << "Item 2: " << v2.get_item(walk).debug(); + return false; + } + } + FB2K_DebugLog() << "DSP chains are identical"; + return true; +} +void dsp_chain_config::get_name_list(pfc::string_base & p_out) const +{ + const t_size count = get_count(); + bool added = false; + for(unsigned n=0;n ptr; + if (dsp_entry::g_get_interface(ptr,get_item(n).get_owner())) + { + if (added) p_out += ", "; + added = true; + + pfc::string8 temp; + ptr->get_name(temp); + p_out += temp; + } + } +} + +void dsp::run_abortable(dsp_chunk_list * p_chunk_list,const metadb_handle_ptr & p_cur_file,int p_flags,abort_callback & p_abort) { + service_ptr_t this_v2; + if (this->service_query_t(this_v2)) this_v2->run_v2(p_chunk_list,p_cur_file,p_flags,p_abort); + else run(p_chunk_list,p_cur_file,p_flags); +} + +namespace { + class dsp_preset_edit_callback_impl : public dsp_preset_edit_callback { + public: + dsp_preset_edit_callback_impl(dsp_preset & p_data) : m_data(p_data) {} + void on_preset_changed(const dsp_preset & p_data) {m_data = p_data;} + private: + dsp_preset & m_data; + }; +}; + +bool dsp_entry_v2::show_config_popup(dsp_preset & p_data,HWND p_parent) { + PFC_ASSERT(p_data.get_owner() == get_guid()); + dsp_preset_impl temp(p_data); + + { + dsp_preset_edit_callback_impl cb(temp); + show_config_popup_v2(p_data,p_parent,cb); + } + PFC_ASSERT(temp.get_owner() == get_guid()); + if (temp == p_data) return false; + p_data = temp; + return true; +} + +void resampler_manager::make_chain_(dsp_chain_config& outChain, unsigned rateFrom, unsigned rateTo, float qualityScale) { + resampler_manager_v2::ptr v2; + if (v2 &= this) { + v2->make_chain(outChain, rateFrom, rateTo, qualityScale); + } else { + outChain.remove_all(); + auto obj = this->get_resampler(rateFrom, rateTo); + if (obj.is_valid()) { + dsp_preset_impl p; + if (obj->create_preset(p, rateTo, qualityScale)) { + outChain.add_item(p); + } + } + } +} + +#endif // FOOBAR2000_HAVE_DSP diff --git a/foobar2000/SDK/dsp.h b/foobar2000/SDK/dsp.h new file mode 100644 index 0000000..67b29e3 --- /dev/null +++ b/foobar2000/SDK/dsp.h @@ -0,0 +1,558 @@ +#pragma once + +#ifdef FOOBAR2000_HAVE_DSP + +//! Interface to a DSP chunk list. A DSP chunk list object is passed to the DSP chain each time, since DSPs are allowed to remove processed chunks or insert new ones. +class NOVTABLE dsp_chunk_list { +public: + virtual t_size get_count() const = 0; + virtual audio_chunk * get_item(t_size n) const = 0; + virtual void remove_by_idx(t_size idx) = 0; + virtual void remove_mask(const bit_array & mask) = 0; + virtual audio_chunk * insert_item(t_size idx,t_size hint_size=0) = 0; + + audio_chunk * add_item(t_size hint_size=0); + + void remove_all(); + + double get_duration(); + + void add_chunk(const audio_chunk * chunk); + + void remove_bad_chunks(); +protected: + dsp_chunk_list() {} + ~dsp_chunk_list() {} +}; + +class dsp_chunk_list_impl : public dsp_chunk_list//implementation +{ + pfc::list_t > m_data, m_recycled; +public: + t_size get_count() const; + audio_chunk * get_item(t_size n) const; + void remove_by_idx(t_size idx); + void remove_mask(const bit_array & mask); + audio_chunk * insert_item(t_size idx,t_size hint_size=0); +}; + +//! Instance of a DSP.\n +//! Implementation: Derive from dsp_impl_base instead of deriving from dsp directly.\n +//! Instantiation: Use dsp_entry static helper methods to instantiate DSPs, or dsp_chain_config / dsp_manager to deal with entire DSP chains. +class NOVTABLE dsp : public service_base { +public: + enum { + //! Flush whatever you need to when tracks change. + END_OF_TRACK = 1, + //! Flush everything. + FLUSH = 2 + }; + + //! @param p_chunk_list List of chunks to process. The implementation may alter the list in any way, inserting chunks of different sample rate / channel configuration etc. + //! @param p_cur_file Optional, location of currently decoded file. May be null. + //! @param p_flags Flags. Can be null, or a combination of END_OF_TRACK and FLUSH constants. + virtual void run(dsp_chunk_list * p_chunk_list,const metadb_handle_ptr & p_cur_file,int p_flags)=0; + + //! Flushes the DSP (reinitializes / drops any buffered data). Called after seeking, etc. + virtual void flush() = 0; + + //! Retrieves amount of data buffered by the DSP, for syncing visualisation. + //! @returns Amount of buffered audio data, in seconds. + virtual double get_latency() = 0; + //! Returns true if DSP needs to know exact track change point (eg. for crossfading, removing silence).\n + //! Signaling this will force-flush any DSPs placed before this DSP so when it gets END_OF_TRACK, relevant chunks contain last samples of the track.\n + //! Signaling this will often break regular gapless playback so don't use it unless you have reasons to. + virtual bool need_track_change_mark() = 0; + + void run_abortable(dsp_chunk_list * p_chunk_list,const metadb_handle_ptr & p_cur_file,int p_flags,abort_callback & p_abort); + + FB2K_MAKE_SERVICE_INTERFACE(dsp,service_base); +}; + +//! Backwards-compatible extension to dsp interface, allows abortable operation. Introduced in 0.9.2. +class NOVTABLE dsp_v2 : public dsp { +public: + //! Abortable version of dsp::run(). See dsp::run() for descriptions of parameters. + virtual void run_v2(dsp_chunk_list * p_chunk_list,const metadb_handle_ptr & p_cur_file,int p_flags,abort_callback & p_abort) = 0; +private: + void run(dsp_chunk_list * p_chunk_list,const metadb_handle_ptr & p_cur_file,int p_flags) { + run_v2(p_chunk_list,p_cur_file,p_flags,fb2k::noAbort); + } + + FB2K_MAKE_SERVICE_INTERFACE(dsp_v2,dsp); +}; + +//! Helper class for implementing dsps. You should derive from dsp_impl_base instead of from dsp directly.\n +//! The dsp_impl_base_t template allows you to use a custom interface class as a base class for your implementation, in case you provide extended functionality.\n +//! Use dsp_factory_t<> template to register your dsp implementation. +//! The implementation - as required by dsp_factory_t<> template - must also provide following methods:\n +//! A constructor taking const dsp_preset&, initializing the DSP with specified preset data.\n +//! static void g_get_name(pfc::string_base &); - retrieving human-readable name of the DSP to display.\n +//! static bool g_get_default_preset(dsp_preset &); - retrieving default preset for this DSP. Return value is reserved for future use and should always be true.\n +//! static GUID g_get_guid(); - retrieving GUID of your DSP implementation, to be used to identify it when storing DSP chain configuration.\n +//! static bool g_have_config_popup(); - retrieving whether your DSP implementation supplies a popup dialog for configuring it.\n +//! static void g_show_config_popup(const dsp_preset & p_data,HWND p_parent, dsp_preset_edit_callback & p_callback); - displaying your DSP's settings dialog; called only when g_have_config_popup() returns true; call p_callback.on_preset_changed() whenever user has made adjustments to the preset data.\n +template +class dsp_impl_base_t : public t_baseclass { +private: + typedef dsp_impl_base_t t_self; + dsp_chunk_list * m_list; + t_size m_chunk_ptr; + metadb_handle* m_cur_file; + void run_v2(dsp_chunk_list * p_list,const metadb_handle_ptr & p_cur_file,int p_flags,abort_callback & p_abort) override; +protected: + //! Call only from on_chunk / on_endoftrack (on_endoftrack will give info on track being finished).\n + //! May return false when there's no known track and the metadb_handle ptr will be empty/null. + bool get_cur_file(metadb_handle_ptr & p_out) {p_out = m_cur_file; return p_out.is_valid();} + + dsp_impl_base_t() : m_list(NULL), m_cur_file(NULL), m_chunk_ptr(0) {} + + //! Inserts a new chunk of audio data. \n + //! You can call this only from on_chunk(), on_endofplayback() and on_endoftrack(). You're NOT allowed to call this from flush() which should just drop any queued data. + //! @param hint_size Optional, amount of buffer space that you require (in audio_samples). This is just a hint for memory allocation logic and will not cause the framework to allocate the chunk for you. + //! @returns A pointer to the newly allocated chunk. Pass the audio data you want to insert to this chunk object. The chunk is owned by the framework, you can't delete it etc. + audio_chunk * insert_chunk(t_size p_hint_size = 0) { + PFC_ASSERT(m_list != NULL); + return m_list->insert_item(m_chunk_ptr++,p_hint_size); + } + audio_chunk * insert_chunk( const audio_chunk & sourceCopy ) { + audio_chunk * c = insert_chunk( sourceCopy.get_used_size() ); + c->copy( sourceCopy ); + return c; + } + + + //! To be overridden by a DSP implementation.\n + //! Called on track change. You can use insert_chunk() to dump any data you have to flush. \n + //! Note that you must implement need_track_change_mark() to return true if you need this method called. + virtual void on_endoftrack(abort_callback & p_abort) = 0; + //! To be overridden by a DSP implementation.\n + //! Called at the end of played stream, typically at the end of last played track, to allow the DSP to return all data it has buffered-ahead.\n + //! Use insert_chunk() to return any data you have buffered.\n + //! Note that this call does not imply that the DSP will be destroyed next. \n + //! This is also called on track changes if some DSP placed after your DSP requests track change marks. + virtual void on_endofplayback(abort_callback & p_abort) = 0; + //! To be overridden by a DSP implementation.\n + //! Processes a chunk of audio data.\n + //! You can call insert_chunk() from inside on_chunk() to insert any audio data before currently processed chunk.\n + //! @param p_chunk Current chunk being processed. You can alter it in any way you like. + //! @returns True to keep p_chunk (with alterations made inside on_chunk()) in the stream, false to remove it. + virtual bool on_chunk(audio_chunk * p_chunk,abort_callback & p_abort) = 0; + +public: + //! To be overridden by a DSP implementation.\n + //! Flushes the DSP (drops any buffered data). The implementation should reset the DSP to the same state it was in before receiving any audio data. \n + //! Called after seeking, etc. + virtual void flush() = 0; + //! To be overridden by a DSP implementation.\n + //! Retrieves amount of data buffered by the DSP, for syncing visualisation. + //! @returns Amount of buffered audio data, in seconds. + virtual double get_latency() = 0; + //! To be overridden by a DSP implementation.\n + //! Returns true if DSP needs to know exact track change point (eg. for crossfading, removing silence).\n + //! Signaling this will force-flush any DSPs placed before this DSP so when it gets on_endoftrack(), relevant chunks contain last samples of the track.\n + //! Signaling this may interfere with gapless playback in certain scenarios (forces flush of DSPs placed before you) so don't use it unless you have reasons to. + virtual bool need_track_change_mark() = 0; +private: + dsp_impl_base_t(const t_self&) = delete; + const t_self & operator=(const t_self &) = delete; +}; + +template +void dsp_impl_base_t::run_v2(dsp_chunk_list * p_list,const metadb_handle_ptr & p_cur_file,int p_flags,abort_callback & p_abort) { + pfc::vartoggle_t l_list_toggle(m_list,p_list); + pfc::vartoggle_t l_cur_file_toggle(m_cur_file,p_cur_file.get_ptr()); + + for(m_chunk_ptr = 0;m_chunk_ptrget_count();m_chunk_ptr++) { + audio_chunk * c = m_list->get_item(m_chunk_ptr); + if (c->is_empty() || !on_chunk(c,p_abort)) + m_list->remove_by_idx(m_chunk_ptr--); + } + + if (p_flags & dsp::FLUSH) { + on_endofplayback(p_abort); + } else if (p_flags & dsp::END_OF_TRACK) { + if (need_track_change_mark()) on_endoftrack(p_abort); + } +} + + +typedef dsp_impl_base_t dsp_impl_base; + +class NOVTABLE dsp_preset { +public: + virtual GUID get_owner() const = 0; + virtual void set_owner(const GUID & p_owner) = 0; + virtual const void * get_data() const = 0; + virtual t_size get_data_size() const = 0; + virtual void set_data(const void * p_data,t_size p_data_size) = 0; + virtual void set_data_from_stream(stream_reader * p_stream,t_size p_bytes,abort_callback & p_abort) = 0; + + const dsp_preset & operator=(const dsp_preset & p_source) {copy(p_source); return *this;} + + void copy(const dsp_preset & p_source) {set_owner(p_source.get_owner());set_data(p_source.get_data(),p_source.get_data_size());} + + void contents_to_stream(stream_writer * p_stream,abort_callback & p_abort) const; + void contents_from_stream(stream_reader * p_stream,abort_callback & p_abort); + static void g_contents_from_stream_skip(stream_reader * p_stream,abort_callback & p_abort); + + bool operator==(const dsp_preset & p_other) const { + if (get_owner() != p_other.get_owner()) return false; + if (get_data_size() != p_other.get_data_size()) return false; + if (memcmp(get_data(),p_other.get_data(),get_data_size()) != 0) return false; + return true; + } + bool operator!=(const dsp_preset & p_other) const { + return !(*this == p_other); + } + + pfc::string8 get_owner_name() const; + pfc::string8 get_owner_name_debug() const; + pfc::string8 debug() const; +protected: + dsp_preset() {} + ~dsp_preset() {} +}; + +class dsp_preset_writer : public stream_writer { +public: + void write(const void * p_buffer,t_size p_bytes,abort_callback & p_abort) { + p_abort.check(); + m_data.append_fromptr((const t_uint8 *) p_buffer,p_bytes); + } + void flush(dsp_preset & p_preset) { + p_preset.set_data(m_data.get_ptr(),m_data.get_size()); + m_data.set_size(0); + } +private: + pfc::array_t m_data; +}; + +class dsp_preset_reader : public stream_reader { +public: + dsp_preset_reader() : m_walk(0) {} + dsp_preset_reader(const dsp_preset_reader & p_source) : m_walk(0) {*this = p_source;} + void init(const dsp_preset & p_preset) { + m_data.set_data_fromptr( (const t_uint8*) p_preset.get_data(), p_preset.get_data_size() ); + m_walk = 0; + } + t_size read(void * p_buffer,t_size p_bytes,abort_callback & p_abort) { + p_abort.check(); + t_size todo = pfc::min_t(p_bytes,m_data.get_size()-m_walk); + memcpy(p_buffer,m_data.get_ptr()+m_walk,todo); + m_walk += todo; + return todo; + } + bool is_finished() {return m_walk == m_data.get_size();} +private: + t_size m_walk; + pfc::array_t m_data; +}; + +class dsp_preset_impl : public dsp_preset +{ +public: + dsp_preset_impl() : m_owner() {} + dsp_preset_impl(const dsp_preset_impl & p_source) {copy(p_source);} + dsp_preset_impl(const dsp_preset & p_source) {copy(p_source);} + void clear() {m_owner = pfc::guid_null; m_data.set_size(0);} + bool is_valid() const { return m_owner != pfc::guid_null; } + + const dsp_preset_impl& operator=(const dsp_preset_impl & p_source) {copy(p_source); return *this;} + const dsp_preset_impl& operator=(const dsp_preset & p_source) {copy(p_source); return *this;} + + GUID get_owner() const {return m_owner;} + void set_owner(const GUID & p_owner) {m_owner = p_owner;} + const void * get_data() const {return m_data.get_ptr();} + t_size get_data_size() const {return m_data.get_size();} + void set_data(const void * p_data,t_size p_data_size) {m_data.set_data_fromptr((const t_uint8*)p_data,p_data_size);} + void set_data_from_stream(stream_reader * p_stream,t_size p_bytes,abort_callback & p_abort); +private: + GUID m_owner; + pfc::array_t m_data; +}; + +class NOVTABLE dsp_preset_edit_callback { +public: + virtual void on_preset_changed(const dsp_preset &) = 0; +private: + dsp_preset_edit_callback(const dsp_preset_edit_callback&) = delete; + const dsp_preset_edit_callback & operator=(const dsp_preset_edit_callback &) = delete; +protected: + dsp_preset_edit_callback() {} + ~dsp_preset_edit_callback() {} +}; + +class NOVTABLE dsp_entry : public service_base { +public: + virtual void get_name(pfc::string_base & p_out) = 0; + virtual bool get_default_preset(dsp_preset & p_out) = 0; + virtual bool instantiate(service_ptr_t & p_out,const dsp_preset & p_preset) = 0; + virtual GUID get_guid() = 0; + virtual bool have_config_popup() = 0; + virtual bool show_config_popup(dsp_preset & p_data,HWND p_parent) = 0; + + //! Obsolete method, hidden DSPs now use a different entry class. + bool is_user_accessible() { return true; } + + static bool g_get_interface(service_ptr_t & p_out,const GUID & p_guid); + static bool g_instantiate(service_ptr_t & p_out,const dsp_preset & p_preset); + static bool g_instantiate_default(service_ptr_t & p_out,const GUID & p_guid); + static bool g_name_from_guid(pfc::string_base & p_out,const GUID & p_guid); + static bool g_dsp_exists(const GUID & p_guid); + static bool g_get_default_preset(dsp_preset & p_out,const GUID & p_guid); + static bool g_have_config_popup(const GUID & p_guid); + static bool g_have_config_popup(const dsp_preset & p_preset); + static bool g_show_config_popup(dsp_preset & p_preset,HWND p_parent); + + static void g_show_config_popup_v2(const dsp_preset & p_preset,HWND p_parent,dsp_preset_edit_callback & p_callback); + + FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(dsp_entry); +}; + +class NOVTABLE dsp_entry_v2 : public dsp_entry { +public: + virtual void show_config_popup_v2(const dsp_preset & p_data,HWND p_parent,dsp_preset_edit_callback & p_callback) = 0; + +private: + bool show_config_popup(dsp_preset & p_data,HWND p_parent); + + FB2K_MAKE_SERVICE_INTERFACE(dsp_entry_v2,dsp_entry); +}; + +class NOVTABLE dsp_entry_hidden : public service_base { + FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(dsp_entry_hidden); +public: + //! Obsolete method, hidden DSPs now use a different entry class from ordinary ones. + bool is_user_accessible() {return false; } + + static bool g_get_interface( dsp_entry_hidden::ptr & out, const GUID & guid ); + static bool g_instantiate( dsp::ptr & out, const dsp_preset & preset ); + static bool g_dsp_exists(const GUID & p_guid); + + virtual bool instantiate(service_ptr_t & p_out,const dsp_preset & p_preset) = 0; + virtual GUID get_guid() = 0; +}; + +template +class dsp_entry_impl_nopreset_t : public t_entry { +public: + void get_name(pfc::string_base & p_out) {T::g_get_name(p_out);} + bool get_default_preset(dsp_preset & p_out) + { + p_out.set_owner(T::g_get_guid()); + p_out.set_data(0,0); + return true; + } + bool instantiate(service_ptr_t & p_out,const dsp_preset & p_preset) + { + if (p_preset.get_owner() == T::g_get_guid() && p_preset.get_data_size() == 0) + { + p_out = new service_impl_t(); + return p_out.is_valid(); + } + else return false; + } + GUID get_guid() {return T::g_get_guid();} + + bool have_config_popup() {return false;} + bool show_config_popup(dsp_preset & p_data,HWND p_parent) {return false;} +}; + +template +class dsp_entry_impl_t : public t_entry { +public: + void get_name(pfc::string_base & p_out) {T::g_get_name(p_out);} + bool get_default_preset(dsp_preset & p_out) {return T::g_get_default_preset(p_out);} + bool instantiate(service_ptr_t & p_out,const dsp_preset & p_preset) { + if (p_preset.get_owner() == T::g_get_guid()) { + p_out = new service_impl_t(p_preset); + return true; + } + else return false; + } + GUID get_guid() {return T::g_get_guid();} + + bool have_config_popup() {return T::g_have_config_popup();} + bool show_config_popup(dsp_preset & p_data,HWND p_parent) {return T::g_show_config_popup(p_data,p_parent);} + //void show_config_popup_v2(const dsp_preset & p_data,HWND p_parent,dsp_preset_edit_callback & p_callback) {T::g_show_config_popup(p_data,p_parent,p_callback);} +}; + +template +class dsp_entry_v2_impl_t : public t_entry { +public: + void get_name(pfc::string_base & p_out) {T::g_get_name(p_out);} + bool get_default_preset(dsp_preset & p_out) {return T::g_get_default_preset(p_out);} + bool instantiate(service_ptr_t & p_out,const dsp_preset & p_preset) { + if (p_preset.get_owner() == T::g_get_guid()) { + p_out = new service_impl_t(p_preset); + return true; + } + else return false; + } + GUID get_guid() {return T::g_get_guid();} + + bool have_config_popup() {return T::g_have_config_popup();} + //bool show_config_popup(dsp_preset & p_data,HWND p_parent) {return T::g_show_config_popup(p_data,p_parent);} + void show_config_popup_v2(const dsp_preset & p_data,HWND p_parent,dsp_preset_edit_callback & p_callback) {T::g_show_config_popup(p_data,p_parent,p_callback);} +}; + +template +class dsp_entry_hidden_t : public dsp_entry_hidden { +public: + bool instantiate(service_ptr_t & p_out,const dsp_preset & p_preset) { + if (p_preset.get_owner() == T::g_get_guid()) { + p_out = new service_impl_t(p_preset); + return true; + } else return false; + } + GUID get_guid() {return T::g_get_guid();} +#if 0 + void get_name( pfc::string_base& out ) {out = ""; } + bool get_default_preset(dsp_preset & p_out) { return false; } + bool have_config_popup() { return false; } + bool show_config_popup(dsp_preset & p_data,HWND p_parent) { uBugCheck(); } + void show_config_popup_v2(const dsp_preset & p_data,HWND p_parent,dsp_preset_edit_callback & p_callback) { uBugCheck(); } + + bool is_user_accessible() { return false; } +#endif +}; + +template +class dsp_factory_nopreset_t : public service_factory_single_t > {}; + +template +class dsp_factory_t : public service_factory_single_t > {}; + +template +class dsp_factory_hidden_t : public service_factory_single_t< dsp_entry_hidden_t > {}; + +class NOVTABLE dsp_chain_config +{ +public: + virtual t_size get_count() const = 0; + virtual const dsp_preset & get_item(t_size p_index) const = 0; + virtual void replace_item(const dsp_preset & p_data,t_size p_index) = 0; + virtual void insert_item(const dsp_preset & p_data,t_size p_index) = 0; + virtual void remove_mask(const bit_array & p_mask) = 0; + + void remove_item(t_size p_index); + void remove_all(); + void add_item(const dsp_preset & p_data); + void copy(const dsp_chain_config & p_source); + + const dsp_chain_config & operator=(const dsp_chain_config & p_source) {copy(p_source); return *this;} + + void contents_to_stream(stream_writer * p_stream,abort_callback & p_abort) const; + void contents_from_stream(stream_reader * p_stream,abort_callback & p_abort); + + void instantiate(service_list_t & p_out); + + void get_name_list(pfc::string_base & p_out) const; + + static bool equals(dsp_chain_config const & v1, dsp_chain_config const & v2); + static bool equals_debug(dsp_chain_config const& v1, dsp_chain_config const& v2); + + pfc::string8 debug() const; + + bool operator==(const dsp_chain_config & other) const {return equals(*this, other);} + bool operator!=(const dsp_chain_config & other) const {return !equals(*this, other);} +}; + +FB2K_STREAM_READER_OVERLOAD(dsp_chain_config) { + value.contents_from_stream(&stream.m_stream, stream.m_abort); return stream; +} + +FB2K_STREAM_WRITER_OVERLOAD(dsp_chain_config) { + value.contents_to_stream(&stream.m_stream, stream.m_abort); return stream; +} + +class dsp_chain_config_impl : public dsp_chain_config +{ +public: + dsp_chain_config_impl() {} + dsp_chain_config_impl(const dsp_chain_config & p_source) {copy(p_source);} + dsp_chain_config_impl(const dsp_chain_config_impl & p_source) {copy(p_source);} + t_size get_count() const; + const dsp_preset & get_item(t_size p_index) const; + void replace_item(const dsp_preset & p_data,t_size p_index); + void insert_item(const dsp_preset & p_data,t_size p_index); + void remove_mask(const bit_array & p_mask); + + const dsp_chain_config_impl & operator=(const dsp_chain_config & p_source) {copy(p_source); return *this;} + const dsp_chain_config_impl & operator=(const dsp_chain_config_impl & p_source) {copy(p_source); return *this;} + + ~dsp_chain_config_impl(); + + void reorder( const size_t * order, size_t count ); +private: + pfc::ptr_list_t m_data; +}; + +class cfg_dsp_chain_config : public cfg_var { +protected: + void get_data_raw(stream_writer * p_stream,abort_callback & p_abort); + void set_data_raw(stream_reader * p_stream,t_size p_sizehint,abort_callback & p_abort); +public: + void reset(); + inline cfg_dsp_chain_config(const GUID & p_guid) : cfg_var(p_guid) {} + t_size get_count() const {return m_data.get_count();} + const dsp_preset & get_item(t_size p_index) const {return m_data.get_item(p_index);} + void get_data(dsp_chain_config & p_data) const; + void set_data(const dsp_chain_config & p_data); + dsp_chain_config_impl & _data() {return m_data; } +private: + dsp_chain_config_impl m_data; +}; + +class cfg_dsp_chain_config_mt : private cfg_var { +public: + cfg_dsp_chain_config_mt( const GUID & id ) : cfg_var(id) {} + void reset(); + void get_data(dsp_chain_config & p_data); + void set_data(const dsp_chain_config & p_data); +protected: + void get_data_raw(stream_writer * p_stream, abort_callback & p_abort); + void set_data_raw(stream_reader * p_stream, t_size p_sizehint, abort_callback & p_abort); +private: + pfc::readWriteLock m_sync; + dsp_chain_config_impl m_data; +}; + + + + +//! Helper. +class dsp_preset_parser : public stream_reader_formatter<> { +public: + dsp_preset_parser(const dsp_preset & in) : m_data(in), _m_stream(in.get_data(),in.get_data_size()), stream_reader_formatter(_m_stream,fb2k::noAbort) {} + + void reset() {_m_stream.reset();} + t_size get_remaining() const {return _m_stream.get_remaining();} + + void assume_empty() const { + if (get_remaining() != 0) throw exception_io_data(); + } + + GUID get_owner() const {return m_data.get_owner();} +private: + const dsp_preset & m_data; + stream_reader_memblock_ref _m_stream; +}; + +//! Helper. +class dsp_preset_builder : public stream_writer_formatter<> { +public: + dsp_preset_builder() : stream_writer_formatter(_m_stream,fb2k::noAbort) {} + void finish(const GUID & id, dsp_preset & out) { + out.set_owner(id); + out.set_data(_m_stream.m_buffer.get_ptr(), _m_stream.m_buffer.get_size()); + } + void reset() { + _m_stream.m_buffer.set_size(0); + } +private: + stream_writer_buffer_simple _m_stream; +}; + +#endif diff --git a/foobar2000/SDK/dsp_manager.cpp b/foobar2000/SDK/dsp_manager.cpp new file mode 100644 index 0000000..68b9267 --- /dev/null +++ b/foobar2000/SDK/dsp_manager.cpp @@ -0,0 +1,209 @@ +#include "foobar2000.h" + +#ifdef FOOBAR2000_HAVE_DSP + +void dsp_manager::close() { + m_chain.remove_all(); + m_config_changed = true; +} + +void dsp_manager::set_config( const dsp_chain_config & p_data ) +{ + //dsp_chain_config::g_instantiate(m_dsp_list,p_data); + m_config.copy(p_data); + m_config_changed = true; +} + +bool dsp_manager::need_track_change_mark() const { + for ( auto i = this->m_chain.first(); i.is_valid(); ++ i ) { + if ( i->m_dsp->need_track_change_mark() ) return true; + } + return false; +} + +void dsp_manager::dsp_run(t_dsp_chain::const_iterator p_iter,dsp_chunk_list * p_list,const metadb_handle_ptr & cur_file,unsigned flags,double & latency,abort_callback & p_abort) +{ + p_list->remove_bad_chunks(); + + TRACK_CODE("dsp::run",p_iter->m_dsp->run_abortable(p_list,cur_file,flags,p_abort)); + TRACK_CODE("dsp::get_latency",latency += p_iter->m_dsp->get_latency()); +} + +double dsp_manager::run(dsp_chunk_list * p_list,const metadb_handle_ptr & p_cur_file,unsigned p_flags,abort_callback & p_abort) { + TRACK_CALL_TEXT("dsp_manager::run"); + + try { +#if defined(_MSC_VER) && defined(_M_IX86) + fpu_control_default l_fpu_control; +#endif + double latency=0; + bool done = false; + + t_dsp_chain::const_iterator flush_mark; + if ((p_flags & dsp::END_OF_TRACK) && ! (p_flags & dsp::FLUSH)) { + for(t_dsp_chain::const_iterator iter = m_chain.first(); iter.is_valid(); ++iter) { + if (iter->m_dsp->need_track_change_mark()) flush_mark = iter; + } + } + + if (m_config_changed) + { + t_dsp_chain newchain; + bool recycle_available = true; + + for(t_size n=0;n temp; + + const dsp_preset & preset = m_config.get_item(n); + const GUID owner = preset.get_owner(); + if (dsp_entry::g_dsp_exists(owner) || dsp_entry_hidden::g_dsp_exists(owner)) { + t_dsp_chain::iterator iter = newchain.insert_last(); + iter->m_preset = m_config.get_item(n); + iter->m_recycle_flag = false; + } + } + + + // Recycle existing DSPs in a special case when user has apparently only altered settings of one of DSPs. + if (newchain.get_count() == m_chain.get_count()) { + t_size data_mismatch_count = 0; + t_size owner_mismatch_count = 0; + t_dsp_chain::iterator iter_src, iter_dst; + iter_src = m_chain.first(); iter_dst = newchain.first(); + while(iter_src.is_valid() && iter_dst.is_valid()) { + if (iter_src->m_preset.get_owner() != iter_dst->m_preset.get_owner()) { + owner_mismatch_count++; + } else if (iter_src->m_preset != iter_dst->m_preset) { + data_mismatch_count++; + } + ++iter_src; ++iter_dst; + } + recycle_available = (owner_mismatch_count == 0 && data_mismatch_count <= 1); + } else { + recycle_available = false; + } + + if (recycle_available) { + t_dsp_chain::iterator iter_src, iter_dst; + iter_src = m_chain.first(); iter_dst = newchain.first(); + while(iter_src.is_valid() && iter_dst.is_valid()) { + if (iter_src->m_preset == iter_dst->m_preset) { + iter_src->m_recycle_flag = true; + iter_dst->m_dsp = iter_src->m_dsp; + } + ++iter_src; ++iter_dst; + } + } + + for(t_dsp_chain::iterator iter = newchain.first(); iter.is_valid(); ++iter) { + if (iter->m_dsp.is_empty()) { + if (!dsp_entry::g_instantiate(iter->m_dsp,iter->m_preset) && !dsp_entry_hidden::g_instantiate(iter->m_dsp, iter->m_preset)) uBugCheck(); + } + } + + if (m_chain.get_count()>0) { + bool flushflag = flush_mark.is_valid(); + for(t_dsp_chain::const_iterator iter = m_chain.first(); iter.is_valid(); ++iter) { + unsigned flags2 = p_flags; + if (iter == flush_mark) flushflag = false; + if (flushflag || !iter->m_recycle_flag) flags2|=dsp::FLUSH; + dsp_run(iter,p_list,p_cur_file,flags2,latency,p_abort); + } + done = true; + } + + m_chain = newchain; + m_config_changed = false; + } + + if (!done) + { + bool flushflag = flush_mark.is_valid(); + for(t_dsp_chain::const_iterator iter = m_chain.first(); iter.is_valid(); ++iter) { + unsigned flags2 = p_flags; + if (iter == flush_mark) flushflag = false; + if (flushflag) flags2|=dsp::FLUSH; + dsp_run(iter,p_list,p_cur_file,flags2,latency,p_abort); + } + done = true; + } + + p_list->remove_bad_chunks(); + + return latency; + } catch(...) { + p_list->remove_all(); + throw; + } +} + +void dsp_manager::flush() +{ + for(t_dsp_chain::const_iterator iter = m_chain.first(); iter.is_valid(); ++iter) { + TRACK_CODE("dsp::flush",iter->m_dsp->flush()); + } +} + + +bool dsp_manager::is_active() const {return m_config.get_count()>0;} + +void dsp_config_manager::core_enable_dsp(const dsp_preset & preset, default_insert_t insertWhere ) { + dsp_chain_config_impl cfg; + get_core_settings(cfg); + + bool found = false; + bool changed = false; + t_size n,m = cfg.get_count(); + for(n=0;n m_dsp; + dsp_preset_impl m_preset; + bool m_recycle_flag; + }; + typedef pfc::chain_list_v2_t t_dsp_chain; + + t_dsp_chain m_chain; + dsp_chain_config_impl m_config; + bool m_config_changed = false; + + void dsp_run(t_dsp_chain::const_iterator p_iter,dsp_chunk_list * list,const metadb_handle_ptr & cur_file,unsigned flags,double & latency,abort_callback&); + + dsp_manager(const dsp_manager &) = delete; + const dsp_manager & operator=(const dsp_manager&) = delete; +}; + +//! Core API for accessing core playback DSP settings as well as spawning DSP configuration dialogs. \n +//! Use dsp_config_manager::get() to obtain an instance. +class dsp_config_manager : public service_base { + FB2K_MAKE_SERVICE_COREAPI(dsp_config_manager); +public: + //! Retrieves current core playback DSP settings. + virtual void get_core_settings(dsp_chain_config & p_out) = 0; + //! Changes current core playback DSP settings. + virtual void set_core_settings(const dsp_chain_config & p_data) = 0; + + //! Runs a modal DSP settings dialog. + //! @param p_data DSP chain configuration to edit - contains initial configuration to put in the dialog when called, receives the new configuration on successful edit. + //! @returns True when user approved DSP configuration changes (pressed the "OK" button), false when the user cancelled them ("Cancel" button). + virtual bool configure_popup(dsp_chain_config & p_data,HWND p_parent,const char * p_title) = 0; + + //! Spawns an embedded DSP settings dialog. + //! @param p_initdata Initial DSP chain configuration to put in the dialog. + //! @param p_parent Parent window to contain the embedded dialog. + //! @param p_id Control ID of the embedded dialog. The parent window will receive a WM_COMMAND with BN_CLICKED and this identifier when user changes settings in the embedded dialog. + //! @param p_from_modal Must be set to true when the parent window is a modal dialog, false otherwise. + virtual HWND configure_embedded(const dsp_chain_config & p_initdata,HWND p_parent,unsigned p_id,bool p_from_modal) = 0; + //! Retrieves current settings from an embedded DSP settings dialog. See also: configure_embedded(). + virtual void configure_embedded_retrieve(HWND wnd,dsp_chain_config & p_data) = 0; + //! Changes current settings in an embedded DSP settings dialog. See also: configure_embedded(). + virtual void configure_embedded_change(HWND wnd,const dsp_chain_config & p_data) = 0; + + + enum default_insert_t { + default_insert_last, + default_insert_first, + }; + //! Helper - enables a DSP in core playback settings. + void core_enable_dsp(const dsp_preset & preset, default_insert_t insertWhere = default_insert_first ); + //! Helper - disables a DSP in core playback settings. + void core_disable_dsp(const GUID & id); + //! Helper - if a DSP with the specified identifier is present in playback settings, retrieves its configuration and returns true, otherwise returns false. + bool core_query_dsp(const GUID & id, dsp_preset & out); +}; + +//! \since 1.4 +//! Allows manipulation of DSP presets saved by user. \n +//! Note that there's no multi thread safety implemented, all methods are valid from main thread only. +class dsp_config_manager_v2 : public dsp_config_manager { + FB2K_MAKE_SERVICE_COREAPI_EXTENSION(dsp_config_manager_v2, dsp_config_manager) +public: + virtual size_t get_preset_count() = 0; + virtual void get_preset_name( size_t index, pfc::string_base & out ) = 0; + virtual void get_preset_data( size_t index, dsp_chain_config & out ) = 0; + virtual void select_preset( size_t which ) = 0; + virtual size_t get_selected_preset() = 0; +}; + +//! Callback class for getting notified about core playback DSP settings getting altered. \n +//! Register your implementations with static service_factory_single_t g_myclass_factory; +class NOVTABLE dsp_config_callback : public service_base { + FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(dsp_config_callback); +public: + //! Called when core playback DSP settings change. \n + //! Note: you must not try to alter core playback DSP settings inside this callback, or call anything else that possibly alters core playback DSP settings. + virtual void on_core_settings_change(const dsp_chain_config & p_newdata) = 0; +}; + +#endif // FOOBAR2000_HAVE_DSP \ No newline at end of file diff --git a/foobar2000/SDK/event_logger.h b/foobar2000/SDK/event_logger.h new file mode 100644 index 0000000..6436c9b --- /dev/null +++ b/foobar2000/SDK/event_logger.h @@ -0,0 +1,50 @@ +#pragma once + +#if defined(FOOBAR2000_DESKTOP) || PFC_DEBUG +// RATIONALE +// Mobile target doesn't really care about event logging, logger interface exists there only for source compat +// We can use macros to suppress all PFC_string_formatter bloat for targets that do not care about any of this +#define FB2K_HAVE_EVENT_LOGGER +#endif + +class NOVTABLE event_logger : public service_base { + FB2K_MAKE_SERVICE_INTERFACE(event_logger, service_base); +public: + enum { + severity_status, + severity_warning, + severity_error + }; + void log_status(const char * line) {log_entry(line, severity_status);} + void log_warning(const char * line) {log_entry(line, severity_warning);} + void log_error(const char * line) {log_entry(line, severity_error);} + + virtual void log_entry(const char * line, unsigned severity) = 0; +}; + +class event_logger_fallback : public event_logger { +public: + void log_entry(const char * line, unsigned) {console::print(line);} +}; + +class NOVTABLE event_logger_recorder : public event_logger { + FB2K_MAKE_SERVICE_INTERFACE( event_logger_recorder , event_logger ); +public: + virtual void playback( event_logger::ptr playTo ) = 0; + + static event_logger_recorder::ptr create(); +}; + +#ifdef FB2K_HAVE_EVENT_LOGGER + +#define FB2K_LOG_STATUS(X,Y) (X)->log_status(Y) +#define FB2K_LOG_WARNING(X,Y) (X)->log_warning(Y) +#define FB2K_LOG_ERROR(X,Y) (X)->log_error(Y) + +#else + +#define FB2K_LOG_STATUS(X,Y) ((void)0) +#define FB2K_LOG_WARNING(X,Y) ((void)0) +#define FB2K_LOG_ERROR(X,Y) ((void)0) + +#endif diff --git a/foobar2000/SDK/exceptions.h b/foobar2000/SDK/exceptions.h new file mode 100644 index 0000000..bf1e23e --- /dev/null +++ b/foobar2000/SDK/exceptions.h @@ -0,0 +1,13 @@ + +//! Base class for exceptions that should show a human readable message when caught in app entrypoint function rather than crash and send a crash report. +PFC_DECLARE_EXCEPTION(exception_messagebox,pfc::exception,"Internal Error"); + +//! Base class for exceptions that should result in a quiet app shutdown. +PFC_DECLARE_EXCEPTION(exception_shutdownrequest,pfc::exception,"Shutdown Request"); + + +PFC_DECLARE_EXCEPTION(exception_installdamaged, exception_messagebox, "Internal error - one or more of the installed components have been damaged."); +PFC_DECLARE_EXCEPTION(exception_osfailure, exception_messagebox, "Internal error - broken Windows installation?"); +PFC_DECLARE_EXCEPTION(exception_out_of_resources, exception_messagebox, "Not enough system resources available."); + +PFC_DECLARE_EXCEPTION(exception_configdamaged, exception_messagebox, "Internal error - configuration files are unreadable."); diff --git a/foobar2000/SDK/file_cached_impl.cpp b/foobar2000/SDK/file_cached_impl.cpp new file mode 100644 index 0000000..55f0b97 --- /dev/null +++ b/foobar2000/SDK/file_cached_impl.cpp @@ -0,0 +1,387 @@ +#include "foobar2000.h" +namespace { + +#define FILE_CACHED_DEBUG_LOG 0 + +class file_cached_impl_v2 : public service_multi_inherit< file_cached, file_lowLevelIO > { +public: + enum {minBlockSize = 4096}; + enum {maxSkipSize = 128*1024}; + file_cached_impl_v2(size_t maxBlockSize) : m_maxBlockSize(maxBlockSize) { + //m_buffer.set_size(blocksize); + } + size_t get_cache_block_size() {return m_maxBlockSize;} + void suggest_grow_cache(size_t suggestSize) { + if (m_maxBlockSize < suggestSize) m_maxBlockSize = suggestSize; + } + + void initialize(service_ptr_t p_base,abort_callback & p_abort) { + m_base = p_base; + m_can_seek = m_base->can_seek(); + _reinit(p_abort); + } + size_t lowLevelIO(const GUID & guid, size_t arg1, void * arg2, size_t arg2size, abort_callback & abort) override { + abort.check(); + file_lowLevelIO::ptr ll; + if ( ll &= m_base ) { + flush_buffer(); + return ll->lowLevelIO(guid, arg1, arg2, arg2size, abort ); + } + return 0; + } +private: + void _reinit(abort_callback & p_abort) { + m_position = 0; + + if (m_can_seek) { + m_position_base = m_base->get_position(p_abort); + } else { + m_position_base = 0; + } + + m_size = m_base->get_size(p_abort); + + flush_buffer(); + } +public: + + t_filesize skip(t_filesize p_bytes,abort_callback & p_abort) { + if (p_bytes > maxSkipSize) { + const t_filesize size = get_size(p_abort); + if (size != filesize_invalid) { + const t_filesize position = get_position(p_abort); + const t_filesize toskip = pfc::min_t( p_bytes, size - position ); + seek(position + toskip,p_abort); + return toskip; + } + } + return skip_( p_bytes, p_abort ); + } + t_filesize skip_(t_filesize p_bytes,abort_callback & p_abort) { +#if FILE_CACHED_DEBUG_LOG + FB2K_DebugLog() << "Skipping bytes: " << p_bytes; +#endif + t_filesize todo = p_bytes; + for(;;) { + size_t inBuffer = this->bufferRemaining(); + size_t delta = (size_t) pfc::min_t(inBuffer, todo); + m_bufferReadPtr += delta; + m_position += delta; + todo -= delta; + if (todo == 0) break; + p_abort.check(); + this->m_bufferState = 0; // null it early to leave in a consistent state if base read fails + this->m_bufferReadPtr = 0; + baseSeek(m_position,p_abort); + m_readSize = pfc::min_t(m_readSize << 1, this->m_maxBlockSize); + if (m_readSize < minBlockSize) m_readSize = minBlockSize; +#if FILE_CACHED_DEBUG_LOG + FB2K_DebugLog() << "Growing read size: " << m_readSize; +#endif + m_buffer.grow_size(m_readSize); + m_bufferState = m_base->read(m_buffer.get_ptr(), m_readSize, p_abort); + if (m_bufferState == 0) break; + m_position_base += m_bufferState; + } + + return p_bytes - todo; + } + + t_size read(void * p_buffer,t_size p_bytes,abort_callback & p_abort) { +#if FILE_CACHED_DEBUG_LOG + FB2K_DebugLog() << "Reading bytes: " << p_bytes; +#endif + t_uint8 * outptr = (t_uint8*)p_buffer; + size_t todo = p_bytes; + for(;;) { + size_t inBuffer = this->bufferRemaining(); + size_t delta = pfc::min_t(inBuffer, todo); + memcpy(outptr, this->m_buffer.get_ptr() + m_bufferReadPtr, delta); + m_bufferReadPtr += delta; + m_position += delta; + todo -= delta; + if (todo == 0) break; + p_abort.check(); + outptr += delta; + this->m_bufferState = 0; // null it early to leave in a consistent state if base read fails + this->m_bufferReadPtr = 0; + baseSeek(m_position,p_abort); + m_readSize = pfc::min_t(m_readSize << 1, this->m_maxBlockSize); + if (m_readSize < minBlockSize) m_readSize = minBlockSize; +#if FILE_CACHED_DEBUG_LOG + FB2K_DebugLog() << "Growing read size: " << m_readSize; +#endif + m_buffer.grow_size(m_readSize); + m_bufferState = m_base->read(m_buffer.get_ptr(), m_readSize, p_abort); + if (m_bufferState == 0) break; + m_position_base += m_bufferState; + } + + return p_bytes - todo; + } + + void write(const void * p_buffer,t_size p_bytes,abort_callback & p_abort) { +#if FILE_CACHED_DEBUG_LOG + FB2K_DebugLog() << "Writing bytes: " << p_bytes; +#endif + p_abort.check(); + baseSeek(m_position,p_abort); + m_base->write(p_buffer,p_bytes,p_abort); + m_position_base = m_position = m_position + p_bytes; + if (m_size < m_position) m_size = m_position; + flush_buffer(); + } + + t_filesize get_size(abort_callback & p_abort) { + p_abort.check(); + return m_size; + } + t_filesize get_position(abort_callback & p_abort) { + p_abort.check(); + return m_position; + } + void set_eof(abort_callback & p_abort) { + p_abort.check(); + baseSeek(m_position,p_abort); + m_base->set_eof(p_abort); + flush_buffer(); + } + void seek(t_filesize p_position,abort_callback & p_abort) { +#if FILE_CACHED_DEBUG_LOG + FB2K_DebugLog() << "Seeking: " << p_position; +#endif + p_abort.check(); + if (!m_can_seek) throw exception_io_object_not_seekable(); + if (p_position > m_size) throw exception_io_seek_out_of_range(); + int64_t delta = p_position - m_position; + + // special case + if (delta >= 0 && delta <= this->minBlockSize) { +#if FILE_CACHED_DEBUG_LOG + FB2K_DebugLog() << "Skip-seeking: " << p_position; +#endif + t_filesize skipped = this->skip_( delta, p_abort ); + PFC_ASSERT( skipped == delta ); (void) skipped; + return; + } + + m_position = p_position; + // within currently buffered data? + if ((delta >= 0 && (uint64_t) delta <= bufferRemaining()) || (delta < 0 && (uint64_t)(-delta) <= m_bufferReadPtr)) { +#if FILE_CACHED_DEBUG_LOG + FB2K_DebugLog() << "Quick-seeking: " << p_position; +#endif + m_bufferReadPtr += (ptrdiff_t)delta; + } else { +#if FILE_CACHED_DEBUG_LOG + FB2K_DebugLog() << "Slow-seeking: " << p_position; +#endif + this->flush_buffer(); + } + } + void reopen(abort_callback & p_abort) { + if (this->m_can_seek) { + seek(0,p_abort); + } else { + this->m_base->reopen( p_abort ); + this->_reinit( p_abort ); + } + } + bool can_seek() {return m_can_seek;} + bool get_content_type(pfc::string_base & out) {return m_base->get_content_type(out);} + void on_idle(abort_callback & p_abort) {p_abort.check();m_base->on_idle(p_abort);} + t_filetimestamp get_timestamp(abort_callback & p_abort) {p_abort.check(); return m_base->get_timestamp(p_abort);} + bool is_remote() {return m_base->is_remote();} + void resize(t_filesize p_size,abort_callback & p_abort) { + flush_buffer(); + m_base->resize(p_size,p_abort); + m_size = p_size; + if (m_position > m_size) m_position = m_size; + if (m_position_base > m_size) m_position_base = m_size; + } +private: + size_t bufferRemaining() const {return m_bufferState - m_bufferReadPtr;} + void baseSeek(t_filesize p_target,abort_callback & p_abort) { + if (p_target != m_position_base) { + m_base->seek(p_target,p_abort); + m_position_base = p_target; + } + } + + void flush_buffer() { + m_bufferState = m_bufferReadPtr = 0; + m_readSize = 0; + } + + service_ptr_t m_base; + t_filesize m_position,m_position_base,m_size; + bool m_can_seek; + size_t m_bufferState, m_bufferReadPtr; + pfc::array_t m_buffer; + size_t m_maxBlockSize; + size_t m_readSize; +}; + +class file_cached_impl : public service_multi_inherit< file_cached, file_lowLevelIO > { +public: + file_cached_impl(t_size blocksize) { + m_buffer.set_size(blocksize); + } + size_t get_cache_block_size() {return m_buffer.get_size();} + void suggest_grow_cache(size_t suggestSize) {} + void initialize(service_ptr_t p_base,abort_callback & p_abort) { + m_base = p_base; + m_can_seek = m_base->can_seek(); + _reinit(p_abort); + } +private: + void _reinit(abort_callback & p_abort) { + m_position = 0; + + if (m_can_seek) { + m_position_base = m_base->get_position(p_abort); + } else { + m_position_base = 0; + } + + m_size = m_base->get_size(p_abort); + + flush_buffer(); + } +public: + size_t lowLevelIO(const GUID & guid, size_t arg1, void * arg2, size_t arg2size, abort_callback & abort) override { + abort.check(); + file_lowLevelIO::ptr ll; + if ( ll &= m_base ) { + flush_buffer(); + return ll->lowLevelIO(guid, arg1, arg2, arg2size, abort); + } + return 0; + } + t_size read(void * p_buffer,t_size p_bytes,abort_callback & p_abort) { + t_uint8 * outptr = (t_uint8*)p_buffer; + t_size done = 0; + while(done < p_bytes && m_position < m_size) { + p_abort.check(); + + if (m_position >= m_buffer_position && m_position < m_buffer_position + m_buffer_status) { + t_size delta = pfc::min_t((t_size)(m_buffer_position + m_buffer_status - m_position),p_bytes - done); + t_size bufptr = (t_size)(m_position - m_buffer_position); + memcpy(outptr+done,m_buffer.get_ptr()+bufptr,delta); + done += delta; + m_position += delta; + if (m_buffer_status != m_buffer.get_size() && done < p_bytes) break;//EOF before m_size is hit + } else { + m_buffer_position = m_position - m_position % m_buffer.get_size(); + baseSeek(m_buffer_position,p_abort); + + m_buffer_status = m_base->read(m_buffer.get_ptr(),m_buffer.get_size(),p_abort); + m_position_base += m_buffer_status; + + if (m_buffer_status <= (t_size)(m_position - m_buffer_position)) break; + } + } + + return done; + } + + void write(const void * p_buffer,t_size p_bytes,abort_callback & p_abort) { + p_abort.check(); + baseSeek(m_position,p_abort); + m_base->write(p_buffer,p_bytes,p_abort); + m_position_base = m_position = m_position + p_bytes; + if (m_size < m_position) m_size = m_position; + flush_buffer(); + } + + t_filesize get_size(abort_callback & p_abort) { + p_abort.check(); + return m_size; + } + t_filesize get_position(abort_callback & p_abort) { + p_abort.check(); + return m_position; + } + void set_eof(abort_callback & p_abort) { + p_abort.check(); + baseSeek(m_position,p_abort); + m_base->set_eof(p_abort); + flush_buffer(); + } + void seek(t_filesize p_position,abort_callback & p_abort) { + p_abort.check(); + if (!m_can_seek) throw exception_io_object_not_seekable(); + if (p_position > m_size) throw exception_io_seek_out_of_range(); + m_position = p_position; + } + void reopen(abort_callback & p_abort) { + if (this->m_can_seek) { + seek(0,p_abort); + } else { + this->m_base->reopen( p_abort ); + this->_reinit( p_abort ); + } + } + bool can_seek() {return m_can_seek;} + bool get_content_type(pfc::string_base & out) {return m_base->get_content_type(out);} + void on_idle(abort_callback & p_abort) {p_abort.check();m_base->on_idle(p_abort);} + t_filetimestamp get_timestamp(abort_callback & p_abort) {p_abort.check(); return m_base->get_timestamp(p_abort);} + bool is_remote() {return m_base->is_remote();} + void resize(t_filesize p_size,abort_callback & p_abort) { + flush_buffer(); + m_base->resize(p_size,p_abort); + m_size = p_size; + if (m_position > m_size) m_position = m_size; + if (m_position_base > m_size) m_position_base = m_size; + } +private: + void baseSeek(t_filesize p_target,abort_callback & p_abort) { + if (p_target != m_position_base) { + m_base->seek(p_target,p_abort); + m_position_base = p_target; + } + } + + void flush_buffer() { + m_buffer_status = 0; + m_buffer_position = 0; + } + + service_ptr_t m_base; + t_filesize m_position,m_position_base,m_size; + bool m_can_seek; + t_filesize m_buffer_position; + t_size m_buffer_status; + pfc::array_t m_buffer; +}; + +} + +file::ptr file_cached::g_create(service_ptr_t p_base,abort_callback & p_abort, t_size blockSize) { + + if (p_base->is_in_memory()) { + return p_base; // do not want + } + + { // do not duplicate cache layers, check if the file we're being handed isn't already cached + file_cached::ptr c; + if (p_base->service_query_t(c)) { + c->suggest_grow_cache(blockSize); + return p_base; + } + } + + service_ptr_t temp = new service_impl_t(blockSize); + temp->initialize(p_base,p_abort); + return temp; +} + +void file_cached::g_create(service_ptr_t & p_out,service_ptr_t p_base,abort_callback & p_abort, t_size blockSize) { + p_out = g_create(p_base, p_abort, blockSize); +} + +void file_cached::g_decodeInitCache(file::ptr & theFile, abort_callback & abort, size_t blockSize) { + if (theFile->is_remote() || !theFile->can_seek()) return; + + g_create(theFile, theFile, abort, blockSize); +} diff --git a/foobar2000/SDK/file_format_sanitizer.h b/foobar2000/SDK/file_format_sanitizer.h new file mode 100644 index 0000000..1bbb2e6 --- /dev/null +++ b/foobar2000/SDK/file_format_sanitizer.h @@ -0,0 +1,27 @@ +#pragma once + +#ifdef FOOBAR2000_HAVE_FILE_FORMAT_SANITIZER +//! Utility service to perform file format specific cleanup routines, optimize tags layout, remove padding, etc. +class NOVTABLE file_format_sanitizer : public service_base { + FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT( file_format_sanitizer ); +public: + //! Returns whether the file path appears to be of a supported format. \n + //! Used for display purposes (menu command will be disabled when no selected file can be cleaned up). + virtual bool is_supported_format( const char * path, const char * ext ) = 0; + //! Performs file format specific cleanup of the file: \n + //! Strips excessive padding, optimizes file layout for network streaming (MP4). \n + //! @param path File path to clean up. The file must be writeable. \n + //! @param bMinimizeSize Set to true to throw away all padding. If set to false, some padding will be left to allow future tag updates without full file rewrite. + //! @returns True if the file has been successfully processed, false if we do not resupport this file format. + virtual bool sanitize_file( const char * path, bool bMinimizeSize, abort_callback & aborter ) = 0; +}; + +//! Utility service to perform sanitization of generic ID3v2 tags. Called by format-specific implementations of file_format_sanitizer. +class NOVTABLE file_format_sanitizer_stdtags : public service_base { + FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT( file_format_sanitizer_stdtags ); +public: + //! Similar to file_format_sanitizer method of the same name. Performs sanitization of generic ID3v2 tags. + virtual bool sanitize_file( const char * path, bool bMinimizeSize, abort_callback & aborter ) = 0; +}; + +#endif // FOOBAR2000_HAVE_FILE_FORMAT_SANITIZER \ No newline at end of file diff --git a/foobar2000/SDK/file_info.cpp b/foobar2000/SDK/file_info.cpp new file mode 100644 index 0000000..4f43f52 --- /dev/null +++ b/foobar2000/SDK/file_info.cpp @@ -0,0 +1,769 @@ +#include "foobar2000.h" + +#ifndef _MSC_VER +#define strcat_s strcat +#define _atoi64 atoll +#endif + +const float replaygain_info::peak_invalid = -1; +const float replaygain_info::gain_invalid = -1000; + +t_size file_info::meta_find_ex(const char * p_name,t_size p_name_length) const +{ + t_size n, m = meta_get_count(); + for(n=0;n= max) return 0; + return meta_enum_value(index,p_index); +} + +const char * file_info::info_get_ex(const char * p_name,t_size p_name_length) const +{ + t_size index = info_find_ex(p_name,p_name_length); + if (index == pfc_infinite) return 0; + return info_enum_value(index); +} + +t_int64 file_info::info_get_int(const char * name) const +{ + PFC_ASSERT(pfc::is_valid_utf8(name)); + const char * val = info_get(name); + if (val==0) return 0; + return _atoi64(val); +} + +t_int64 file_info::info_get_length_samples() const +{ + t_int64 ret = 0; + double len = get_length(); + t_int64 srate = info_get_int("samplerate"); + + if (srate>0 && len>0) + { + ret = audio_math::time_to_samples(len,(unsigned)srate); + } + return ret; +} + +double file_info::info_get_float(const char * name) const +{ + const char * ptr = info_get(name); + if (ptr) return pfc::string_to_float(ptr); + else return 0; +} + +void file_info::info_set_int(const char * name,t_int64 value) +{ + PFC_ASSERT(pfc::is_valid_utf8(name)); + info_set(name,pfc::format_int(value)); +} + +void file_info::info_set_float(const char * name,double value,unsigned precision,bool force_sign,const char * unit) +{ + PFC_ASSERT(pfc::is_valid_utf8(name)); + PFC_ASSERT(unit==0 || strlen(unit) <= 64); + char temp[128]; + pfc::float_to_string(temp,64,value,precision,force_sign); + temp[63] = 0; + if (unit) + { + strcat_s(temp," "); + strcat_s(temp,unit); + } + info_set(name,temp); +} + + +void file_info::info_set_replaygain_album_gain(float value) +{ + replaygain_info temp = get_replaygain(); + temp.m_album_gain = value; + set_replaygain(temp); +} + +void file_info::info_set_replaygain_album_peak(float value) +{ + replaygain_info temp = get_replaygain(); + temp.m_album_peak = value; + set_replaygain(temp); +} + +void file_info::info_set_replaygain_track_gain(float value) +{ + replaygain_info temp = get_replaygain(); + temp.m_track_gain = value; + set_replaygain(temp); +} + +void file_info::info_set_replaygain_track_peak(float value) +{ + replaygain_info temp = get_replaygain(); + temp.m_track_peak = value; + set_replaygain(temp); +} + + +static bool is_valid_bps(t_int64 val) +{ + return val>0 && val<=256; +} + +unsigned file_info::info_get_decoded_bps() const +{ + t_int64 val = info_get_int("decoded_bitspersample"); + if (is_valid_bps(val)) return (unsigned)val; + val = info_get_int("bitspersample"); + if (is_valid_bps(val)) return (unsigned)val; + return 0; + +} + +void file_info::reset() +{ + info_remove_all(); + meta_remove_all(); + set_length(0); + reset_replaygain(); +} + +void file_info::reset_replaygain() +{ + replaygain_info temp; + temp.reset(); + set_replaygain(temp); +} + +void file_info::copy_meta_single_rename_ex(const file_info & p_source,t_size p_index,const char * p_new_name,t_size p_new_name_length) +{ + t_size n, m = p_source.meta_enum_value_count(p_index); + t_size new_index = pfc_infinite; + for(n=0;n 0); + for(val=0;val 0) out += separator; + out += meta_enum_value(index,val); + } +} + +bool file_info::meta_format(const char * p_name,pfc::string_base & p_out, const char * separator) const { + p_out.reset(); + t_size index = meta_find(p_name); + if (index == pfc_infinite) return false; + meta_format_entry(index, p_out, separator); + return true; +} + +void file_info::info_calculate_bitrate(t_filesize p_filesize,double p_length) +{ + unsigned b = audio_math::bitrate_kbps( p_filesize, p_length ); + if ( b > 0 ) info_set_bitrate(b); +} + +bool file_info::is_encoding_overkill() const { + auto bs = info_get_int("bitspersample"); + auto extra = info_get("bitspersample_extra"); + if ( bs <= 24 ) return false; // fixedpoint up to 24bit, OK + if ( bs > 32 ) return true; // fixed or float beyond 32bit, overkill + + if ( extra != nullptr ) { + if (strcmp(extra, "fixed-point") == 0) return true; // int32, overkill + } + + return false; +} + +bool file_info::is_encoding_lossy() const { + const char * encoding = info_get("encoding"); + if (encoding != NULL) { + if (pfc::stricmp_ascii(encoding,"lossy") == 0 /*|| pfc::stricmp_ascii(encoding,"hybrid") == 0*/) return true; + } else { + //the old way + //disabled: don't whine if we're not sure what we're dealing with - might be a file with info not-yet-loaded in oddball cases or a mod file + //if (info_get("bitspersample") == NULL) return true; + } + return false; +} + +bool file_info::g_is_meta_equal(const file_info & p_item1,const file_info & p_item2) { + const t_size count = p_item1.meta_get_count(); + if (count != p_item2.meta_get_count()) { + //uDebugLog() << "meta count mismatch"; + return false; + } + pfc::map_t item2_meta_map; + for(t_size n=0; n item2_meta_map; + for(t_size n=0; n= 32 && p_char < 127 && p_char != '=' && p_char != '%' && p_char != '<' && p_char != '>'; +} + +bool file_info::g_is_valid_field_name(const char * p_name,t_size p_length) { + t_size walk; + for(walk = 0; walk < p_length && p_name[walk] != 0; walk++) { + if (!is_valid_field_name_char(p_name[walk])) return false; + } + return walk > 0; +} + +void file_info::to_formatter(pfc::string_formatter& out) const { + out << "File info dump:\n"; + if (get_length() > 0) out<< "Duration: " << pfc::format_time_ex(get_length(), 6) << "\n"; + pfc::string_formatter temp; + for(t_size metaWalk = 0; metaWalk < meta_get_count(); ++metaWalk) { + meta_format_entry(metaWalk, temp); + out << "Meta: " << meta_enum_name(metaWalk) << " = " << temp << "\n"; + } + for(t_size infoWalk = 0; infoWalk < info_get_count(); ++infoWalk) { + out << "Info: " << info_enum_name(infoWalk) << " = " << info_enum_value(infoWalk) << "\n"; + } + auto rg = this->get_replaygain(); + replaygain_info::t_text_buffer rgbuf; + if (rg.format_track_gain(rgbuf)) out << "RG track gain: " << rgbuf << "\n"; + if (rg.format_track_peak(rgbuf)) out << "RG track peak: " << rgbuf << "\n"; + if (rg.format_album_gain(rgbuf)) out << "RG album gain: " << rgbuf << "\n"; + if (rg.format_album_peak(rgbuf)) out << "RG album peak: " << rgbuf << "\n"; +} + +void file_info::to_console() const { + FB2K_console_formatter1() << "File info dump:"; + if (get_length() > 0) FB2K_console_formatter() << "Duration: " << pfc::format_time_ex(get_length(), 6); + pfc::string_formatter temp; + for(t_size metaWalk = 0; metaWalk < meta_get_count(); ++metaWalk) { + const char * name = meta_enum_name( metaWalk ); + const auto valCount = meta_enum_value_count( metaWalk ); + for ( size_t valWalk = 0; valWalk < valCount; ++valWalk ) { + FB2K_console_formatter() << "Meta: " << name << " = " << meta_enum_value( metaWalk, valWalk ); + } + + /* + meta_format_entry(metaWalk, temp); + FB2K_console_formatter() << "Meta: " << meta_enum_name(metaWalk) << " = " << temp; + */ + } + for(t_size infoWalk = 0; infoWalk < info_get_count(); ++infoWalk) { + FB2K_console_formatter() << "Info: " << info_enum_name(infoWalk) << " = " << info_enum_value(infoWalk); + } +} + +void file_info::info_set_wfx_chanMask(uint32_t val) { + switch(val) { + case 0: + case 4: + case 3: + break; + default: + info_set ("WAVEFORMATEXTENSIBLE_CHANNEL_MASK", PFC_string_formatter() << "0x" << pfc::format_hex(val) ); + break; + } +} + +uint32_t file_info::info_get_wfx_chanMask() const { + const char * str = this->info_get("WAVEFORMATEXTENSIBLE_CHANNEL_MASK"); + if (str == NULL) return 0; + if (pfc::strcmp_partial( str, "0x") != 0) return 0; + try { + return pfc::atohex( str + 2, strlen(str+2) ); + } catch(...) { return 0;} +} + +bool file_info::field_is_person(const char * fieldName) { + return field_name_equals(fieldName, "artist") || + field_name_equals(fieldName, "album artist") || + field_name_equals(fieldName, "composer") || + field_name_equals(fieldName, "performer") || + field_name_equals(fieldName, "conductor") || + field_name_equals(fieldName, "orchestra") || + field_name_equals(fieldName, "ensemble") || + field_name_equals(fieldName, "engineer"); +} + +bool file_info::field_is_title(const char * fieldName) { + return field_name_equals(fieldName, "title") || field_name_equals(fieldName, "album"); +} + + +void file_info::to_stream( stream_writer * stream, abort_callback & abort ) const { + stream_writer_formatter<> out(* stream, abort ); + + out << this->get_length(); + + { + const auto rg = this->get_replaygain(); + out << rg.m_track_gain << rg.m_album_gain << rg.m_track_peak << rg.m_album_peak; + } + + + { + const uint32_t metaCount = pfc::downcast_guarded( this->meta_get_count() ); + for(uint32_t metaWalk = 0; metaWalk < metaCount; ++metaWalk) { + const char * name = this->meta_enum_name( metaWalk ); + if (*name) { + out.write_string_nullterm( this->meta_enum_name( metaWalk ) ); + const size_t valCount = this->meta_enum_value_count( metaWalk ); + for(size_t valWalk = 0; valWalk < valCount; ++valWalk) { + const char * value = this->meta_enum_value( metaWalk, valWalk ); + if (*value) { + out.write_string_nullterm( value ); + } + } + out.write_int(0); + } + } + out.write_int(0); + } + + { + const uint32_t infoCount = pfc::downcast_guarded( this->info_get_count() ); + for(uint32_t infoWalk = 0; infoWalk < infoCount; ++infoWalk) { + const char * name = this->info_enum_name( infoWalk ); + const char * value = this->info_enum_value( infoWalk ); + if (*name && *value) { + out.write_string_nullterm(name); out.write_string_nullterm(value); + } + } + out.write_int(0); + } +} + +void file_info::from_stream( stream_reader * stream, abort_callback & abort ) { + stream_reader_formatter<> in( *stream, abort ); + pfc::string_formatter tempName, tempValue; + { + double len; in >> len; this->set_length( len ); + } + { + replaygain_info rg; + in >> rg.m_track_gain >> rg.m_album_gain >> rg.m_track_peak >> rg.m_album_peak; + } + + { + this->meta_remove_all(); + for(;;) { + in.read_string_nullterm( tempName ); + if (tempName.length() == 0) break; + size_t metaIndex = pfc_infinite; + for(;;) { + in.read_string_nullterm( tempValue ); + if (tempValue.length() == 0) break; + if (metaIndex == pfc_infinite) metaIndex = this->meta_add( tempName, tempValue ); + else this->meta_add_value( metaIndex, tempValue ); + } + } + } + { + this->info_remove_all(); + for(;;) { + in.read_string_nullterm( tempName ); + if (tempName.length() == 0) break; + in.read_string_nullterm( tempValue ); + this->info_set( tempName, tempValue ); + } + } +} + +static const char * _readString( const uint8_t * & ptr, size_t & remaining ) { + const char * rv = (const char*)ptr; + for(;;) { + if (remaining == 0) throw exception_io_data(); + uint8_t byte = *ptr++; --remaining; + if (byte == 0) break; + } + return rv; +} + +template void _readInt( int_t & out, const uint8_t * &ptr, size_t & remaining) { + if (remaining < sizeof(out)) throw exception_io_data(); + pfc::decode_little_endian( out, ptr ); ptr += sizeof(out); remaining -= sizeof(out); +} + +template static void _readFloat(float_t & out, const uint8_t * &ptr, size_t & remaining) { + union { + typename pfc::sized_int_t::t_unsigned i; + float_t f; + } u; + _readInt(u.i, ptr, remaining); + out = u.f; +} + +void file_info::from_mem( const void * memPtr, size_t memSize ) { + size_t remaining = memSize; + const uint8_t * walk = (const uint8_t*) memPtr; + + { + double len; _readFloat(len, walk, remaining); + this->set_length( len ); + } + + { + replaygain_info rg; + _readFloat(rg.m_track_gain, walk, remaining ); + _readFloat(rg.m_album_gain, walk, remaining ); + _readFloat(rg.m_track_peak, walk, remaining ); + _readFloat(rg.m_album_peak, walk, remaining ); + this->set_replaygain( rg ); + } + + { + this->meta_remove_all(); + for(;;) { + const char * metaName = _readString( walk, remaining ); + if (*metaName == 0) break; + size_t metaIndex = pfc_infinite; + for(;;) { + const char * metaValue = _readString( walk, remaining ); + if (*metaValue == 0) break; + if (metaIndex == pfc_infinite) metaIndex = this->meta_add( metaName, metaValue ); + else this->meta_add_value( metaIndex, metaName ); + } + } + } + { + this->info_remove_all(); + for(;;) { + const char * infoName = _readString( walk, remaining ); + if (*infoName == 0) break; + const char * infoValue = _readString( walk, remaining ); + this->info_set( infoName, infoValue ); + } + } +} + +audio_chunk::spec_t file_info::audio_chunk_spec() const +{ + audio_chunk::spec_t rv = {}; + rv.sampleRate = (uint32_t)this->info_get_int("samplerate"); + rv.chanCount = (uint32_t)this->info_get_int("channels"); + rv.chanMask = (uint32_t)this->info_get_wfx_chanMask(); + if (audio_chunk::g_count_channels( rv.chanMask ) != rv.chanCount ) { + rv.chanMask = audio_chunk::g_guess_channel_config( rv.chanCount ); + } + return rv; +} diff --git a/foobar2000/SDK/file_info.h b/foobar2000/SDK/file_info.h new file mode 100644 index 0000000..41a4b96 --- /dev/null +++ b/foobar2000/SDK/file_info.h @@ -0,0 +1,287 @@ +//! Structure containing ReplayGain scan results from some playable object, also providing various helper methods to manipulate those results. +struct replaygain_info +{ + float m_album_gain,m_track_gain; + float m_album_peak,m_track_peak; + + enum {text_buffer_size = 16 }; + typedef char t_text_buffer[text_buffer_size]; + + static const float peak_invalid, gain_invalid; + + static bool g_format_gain(float p_value,char p_buffer[text_buffer_size]); + static bool g_format_peak(float p_value,char p_buffer[text_buffer_size]); + static bool g_format_peak_db(float p_value, char p_buffer[text_buffer_size]); + + inline bool format_album_gain(char p_buffer[text_buffer_size]) const {return g_format_gain(m_album_gain,p_buffer);} + inline bool format_track_gain(char p_buffer[text_buffer_size]) const {return g_format_gain(m_track_gain,p_buffer);} + inline bool format_album_peak(char p_buffer[text_buffer_size]) const {return g_format_peak(m_album_peak,p_buffer);} + inline bool format_track_peak(char p_buffer[text_buffer_size]) const {return g_format_peak(m_track_peak,p_buffer);} + + static float g_parse_gain_text(const char * p_text, t_size p_text_len = SIZE_MAX); + void set_album_gain_text(const char * p_text,t_size p_text_len = SIZE_MAX); + void set_track_gain_text(const char * p_text,t_size p_text_len = SIZE_MAX); + void set_album_peak_text(const char * p_text,t_size p_text_len = SIZE_MAX); + void set_track_peak_text(const char * p_text,t_size p_text_len = SIZE_MAX); + + static bool g_is_meta_replaygain(const char * p_name,t_size p_name_len = SIZE_MAX); + bool set_from_meta_ex(const char * p_name,t_size p_name_len,const char * p_value,t_size p_value_len); + inline bool set_from_meta(const char * p_name,const char * p_value) {return set_from_meta_ex(p_name,SIZE_MAX,p_value,SIZE_MAX);} + + inline bool is_album_gain_present() const {return m_album_gain != gain_invalid;} + inline bool is_track_gain_present() const {return m_track_gain != gain_invalid;} + inline bool is_album_peak_present() const {return m_album_peak != peak_invalid;} + inline bool is_track_peak_present() const {return m_track_peak != peak_invalid;} + + inline void remove_album_gain() {m_album_gain = gain_invalid;} + inline void remove_track_gain() {m_track_gain = gain_invalid;} + inline void remove_album_peak() {m_album_peak = peak_invalid;} + inline void remove_track_peak() {m_track_peak = peak_invalid;} + + float anyGain(bool bPreferAlbum = false) const; + + t_size get_value_count(); + + static replaygain_info g_merge(replaygain_info r1,replaygain_info r2); + + static bool g_equalLoose( const replaygain_info & item1, const replaygain_info & item2); + static bool g_equal(const replaygain_info & item1,const replaygain_info & item2); + + void reset(); +}; + +class format_rg_gain { +public: + format_rg_gain(float val) {replaygain_info::g_format_gain(val, m_buffer);} + + operator const char * () const {return m_buffer;} + const char * c_str() const { return m_buffer; } +private: + replaygain_info::t_text_buffer m_buffer; +}; + +class format_rg_peak { +public: + format_rg_peak(float val) {replaygain_info::g_format_peak(val, m_buffer);} + + operator const char * () const {return m_buffer;} + const char * c_str() const { return m_buffer; } +private: + replaygain_info::t_text_buffer m_buffer; +}; + +inline bool operator==(const replaygain_info & item1,const replaygain_info & item2) {return replaygain_info::g_equal(item1,item2);} +inline bool operator!=(const replaygain_info & item1,const replaygain_info & item2) {return !replaygain_info::g_equal(item1,item2);} + +static const replaygain_info replaygain_info_invalid = {replaygain_info::gain_invalid,replaygain_info::gain_invalid,replaygain_info::peak_invalid,replaygain_info::peak_invalid}; + + +//! Main interface class for information about some playable object. +class NOVTABLE file_info { +public: + //! Retrieves audio duration, in seconds. \n + //! Note that the reported duration should not be assumed to be the exact length of the track -\n + //! with many popular audio formats, exact duration is impossible to determine without performing a full decode pass;\n + //! with other formats, the decoded data may be shorter than reported due to truncation other damage. + virtual double get_length() const = 0; + //! Sets audio duration, in seconds. \n + //! Note that the reported duration should not be assumed to be the exact length of the track -\n + //! with many popular audio formats, exact duration is impossible to determine without performing a full decode pass;\n + //! with other formats, the decoded data may be shorter than reported due to truncation other damage. + virtual void set_length(double p_length) = 0; + + //! Sets ReplayGain information. + virtual void set_replaygain(const replaygain_info & p_info) = 0; + //! Retrieves ReplayGain information. + virtual replaygain_info get_replaygain() const = 0; + + //! Retrieves count of metadata entries. + virtual t_size meta_get_count() const = 0; + //! Retrieves the name of metadata entry of specified index. Return value is a null-terminated UTF-8 encoded string. + virtual const char* meta_enum_name(t_size p_index) const = 0; + //! Retrieves count of values in metadata entry of specified index. The value is always equal to or greater than 1. + virtual t_size meta_enum_value_count(t_size p_index) const = 0; + //! Retrieves specified value from specified metadata entry. Return value is a null-terminated UTF-8 encoded string. + virtual const char* meta_enum_value(t_size p_index,t_size p_value_number) const = 0; + //! Finds index of metadata entry of specified name. Returns infinite when not found. + virtual t_size meta_find_ex(const char * p_name,t_size p_name_length) const; + //! Creates a new metadata entry of specified name with specified value. If an entry of same name already exists, it is erased. Return value is the index of newly created metadata entry. + virtual t_size meta_set_ex(const char * p_name,t_size p_name_length,const char * p_value,t_size p_value_length) = 0; + //! Inserts a new value into specified metadata entry. + virtual void meta_insert_value_ex(t_size p_index,t_size p_value_index,const char * p_value,t_size p_value_length) = 0; + //! Removes metadata entries according to specified bit mask. + virtual void meta_remove_mask(const bit_array & p_mask) = 0; + //! Reorders metadata entries according to specified permutation. + virtual void meta_reorder(const t_size * p_order) = 0; + //! Removes values according to specified bit mask from specified metadata entry. If all values are removed, entire metadata entry is removed as well. + virtual void meta_remove_values(t_size p_index,const bit_array & p_mask) = 0; + //! Alters specified value in specified metadata entry. + virtual void meta_modify_value_ex(t_size p_index,t_size p_value_index,const char * p_value,t_size p_value_length) = 0; + + //! Retrieves number of technical info entries. + virtual t_size info_get_count() const = 0; + //! Retrieves the name of specified technical info entry. Return value is a null-terminated UTF-8 encoded string. + virtual const char* info_enum_name(t_size p_index) const = 0; + //! Retrieves the value of specified technical info entry. Return value is a null-terminated UTF-8 encoded string. + virtual const char* info_enum_value(t_size p_index) const = 0; + //! Creates a new technical info entry with specified name and specified value. If an entry of the same name already exists, it is erased. Return value is the index of newly created entry. + virtual t_size info_set_ex(const char * p_name,t_size p_name_length,const char * p_value,t_size p_value_length) = 0; + //! Removes technical info entries indicated by specified bit mask. + virtual void info_remove_mask(const bit_array & p_mask) = 0; + //! Finds technical info entry of specified name. Returns index of found entry on success, infinite on failure. + virtual t_size info_find_ex(const char * p_name,t_size p_name_length) const; + + //! Copies entire file_info contents from specified file_info object. + virtual void copy(const file_info & p_source);//virtualized for performance reasons, can be faster in two-pass + //! Copies metadata from specified file_info object. + virtual void copy_meta(const file_info & p_source);//virtualized for performance reasons, can be faster in two-pass + //! Copies technical info from specified file_info object. + virtual void copy_info(const file_info & p_source);//virtualized for performance reasons, can be faster in two-pass + + bool meta_exists_ex(const char * p_name,t_size p_name_length) const; + void meta_remove_field_ex(const char * p_name,t_size p_name_length); + void meta_remove_index(t_size p_index); + void meta_remove_all(); + void meta_remove_value(t_size p_index,t_size p_value); + const char * meta_get_ex(const char * p_name,t_size p_name_length,t_size p_index) const; + t_size meta_get_count_by_name_ex(const char * p_name,t_size p_name_length) const; + void meta_add_value_ex(t_size p_index,const char * p_value,t_size p_value_length); + t_size meta_add_ex(const char * p_name,t_size p_name_length,const char * p_value,t_size p_value_length); + t_size meta_calc_total_value_count() const; + bool meta_format(const char * p_name,pfc::string_base & p_out, const char * separator = ", ") const; + void meta_format_entry(t_size index, pfc::string_base & p_out, const char * separator = ", ") const;//same as meta_format but takes index instead of meta name. + + + bool info_exists_ex(const char * p_name,t_size p_name_length) const; + void info_remove_index(t_size p_index); + void info_remove_all(); + bool info_remove_ex(const char * p_name,t_size p_name_length); + const char * info_get_ex(const char * p_name,t_size p_name_length) const; + + inline t_size meta_find(const char * p_name) const {return meta_find_ex(p_name,SIZE_MAX);} + inline bool meta_exists(const char * p_name) const {return meta_exists_ex(p_name,SIZE_MAX);} + inline void meta_remove_field(const char * p_name) {meta_remove_field_ex(p_name,SIZE_MAX);} + inline t_size meta_set(const char * p_name,const char * p_value) {return meta_set_ex(p_name,SIZE_MAX,p_value,SIZE_MAX);} + inline void meta_insert_value(t_size p_index,t_size p_value_index,const char * p_value) {meta_insert_value_ex(p_index,p_value_index,p_value,SIZE_MAX);} + inline void meta_add_value(t_size p_index,const char * p_value) {meta_add_value_ex(p_index,p_value,SIZE_MAX);} + inline const char* meta_get(const char * p_name,t_size p_index) const {return meta_get_ex(p_name,SIZE_MAX,p_index);} + inline t_size meta_get_count_by_name(const char * p_name) const {return meta_get_count_by_name_ex(p_name,SIZE_MAX);} + inline t_size meta_add(const char * p_name,const char * p_value) {return meta_add_ex(p_name,SIZE_MAX,p_value,SIZE_MAX);} + inline void meta_modify_value(t_size p_index,t_size p_value_index,const char * p_value) {meta_modify_value_ex(p_index,p_value_index,p_value,SIZE_MAX);} + + + + inline t_size info_set(const char * p_name,const char * p_value) {return info_set_ex(p_name,SIZE_MAX,p_value,SIZE_MAX);} + inline t_size info_find(const char * p_name) const {return info_find_ex(p_name,SIZE_MAX);} + inline bool info_exists(const char * p_name) const {return info_exists_ex(p_name,SIZE_MAX);} + inline bool info_remove(const char * p_name) {return info_remove_ex(p_name,SIZE_MAX);} + inline const char * info_get(const char * p_name) const {return info_get_ex(p_name,SIZE_MAX);} + + bool info_set_replaygain_ex(const char * p_name,t_size p_name_len,const char * p_value,t_size p_value_len); + inline bool info_set_replaygain(const char * p_name,const char * p_value) {return info_set_replaygain_ex(p_name,SIZE_MAX,p_value,SIZE_MAX);} + void info_set_replaygain_auto_ex(const char * p_name,t_size p_name_len,const char * p_value,t_size p_value_len); + inline void info_set_replaygain_auto(const char * p_name,const char * p_value) {info_set_replaygain_auto_ex(p_name,SIZE_MAX,p_value,SIZE_MAX);} + + + + void copy_meta_single(const file_info & p_source,t_size p_index); + void copy_info_single(const file_info & p_source,t_size p_index); + void copy_meta_single_by_name_ex(const file_info & p_source,const char * p_name,t_size p_name_length); + void copy_info_single_by_name_ex(const file_info & p_source,const char * p_name,t_size p_name_length); + inline void copy_meta_single_by_name(const file_info & p_source,const char * p_name) {copy_meta_single_by_name_ex(p_source,p_name,SIZE_MAX);} + inline void copy_info_single_by_name(const file_info & p_source,const char * p_name) {copy_info_single_by_name_ex(p_source,p_name,SIZE_MAX);} + void reset(); + void reset_replaygain(); + void copy_meta_single_rename_ex(const file_info & p_source,t_size p_index,const char * p_new_name,t_size p_new_name_length); + inline void copy_meta_single_rename(const file_info & p_source,t_size p_index,const char * p_new_name) {copy_meta_single_rename_ex(p_source,p_index,p_new_name,SIZE_MAX);} + void overwrite_info(const file_info & p_source); + void overwrite_meta(const file_info & p_source); + + t_int64 info_get_int(const char * name) const; + t_int64 info_get_length_samples() const; + double info_get_float(const char * name) const; + void info_set_int(const char * name,t_int64 value); + void info_set_float(const char * name,double value,unsigned precision,bool force_sign = false,const char * unit = 0); + void info_set_replaygain_track_gain(float value); + void info_set_replaygain_album_gain(float value); + void info_set_replaygain_track_peak(float value); + void info_set_replaygain_album_peak(float value); + + inline t_int64 info_get_bitrate_vbr() const {return info_get_int("bitrate_dynamic");} + inline void info_set_bitrate_vbr(t_int64 val) {info_set_int("bitrate_dynamic",val);} + inline t_int64 info_get_bitrate() const {return info_get_int("bitrate");} + inline void info_set_bitrate(t_int64 val) {info_set_int("bitrate",val);} + + void info_set_wfx_chanMask(uint32_t val); + uint32_t info_get_wfx_chanMask() const; + + bool is_encoding_lossy() const; + bool is_encoding_overkill() const; + + + void info_calculate_bitrate(t_filesize p_filesize,double p_length); + + unsigned info_get_decoded_bps() const;//what bps the stream originally was (before converting to audio_sample), 0 if unknown + +private: + void merge(const pfc::list_base_const_t & p_sources); +public: + + void _set_tag(const file_info & tag); + void _add_tag(const file_info & otherTag); + + void merge_fallback(const file_info & fallback); + + bool are_meta_fields_identical(t_size p_index1,t_size p_index2) const; + + inline const file_info & operator=(const file_info & p_source) {copy(p_source);return *this;} + + static bool g_is_meta_equal(const file_info & p_item1,const file_info & p_item2); + static bool g_is_meta_equal_debug(const file_info & p_item1,const file_info & p_item2); + static bool g_is_info_equal(const file_info & p_item1,const file_info & p_item2); + + //! Unsafe - does not check whether the field already exists and will result in duplicates if it does - call only when appropriate checks have been applied externally. + t_size __meta_add_unsafe_ex(const char * p_name,t_size p_name_length,const char * p_value,t_size p_value_length) {return meta_set_nocheck_ex(p_name,p_name_length,p_value,p_value_length);} + //! Unsafe - does not check whether the field already exists and will result in duplicates if it does - call only when appropriate checks have been applied externally. + t_size __meta_add_unsafe(const char * p_name,const char * p_value) {return meta_set_nocheck_ex(p_name,SIZE_MAX,p_value,SIZE_MAX);} + + //! Unsafe - does not check whether the field already exists and will result in duplicates if it does - call only when appropriate checks have been applied externally. + t_size __info_add_unsafe_ex(const char * p_name,t_size p_name_length,const char * p_value,t_size p_value_length) {return info_set_nocheck_ex(p_name,p_name_length,p_value,p_value_length);} + //! Unsafe - does not check whether the field already exists and will result in duplicates if it does - call only when appropriate checks have been applied externally. + t_size __info_add_unsafe(const char * p_name,const char * p_value) {return info_set_nocheck_ex(p_name,SIZE_MAX,p_value,SIZE_MAX);} + + void _copy_meta_single_nocheck(const file_info & p_source,t_size p_index) {copy_meta_single_nocheck(p_source, p_index);} + + static bool g_is_valid_field_name(const char * p_name,t_size p_length = SIZE_MAX); + //typedef pfc::comparator_stricmp_ascii field_name_comparator; + typedef pfc::string::comparatorCaseInsensitiveASCII field_name_comparator; + + static bool field_name_equals(const char * n1, const char * n2) {return field_name_comparator::compare(n1, n2) == 0;} + + void to_console() const; + void to_formatter(pfc::string_formatter&) const; + static bool field_is_person(const char * fieldName); + static bool field_is_title(const char * fieldName); + + void to_stream( stream_writer * stream, abort_callback & abort ) const; + void from_stream( stream_reader * stream, abort_callback & abort ); + void from_mem( const void * memPtr, size_t memSize); + + //! Returns ESTIMATED audio chunk spec from what has been put in the file_info. \n + //! Provided for convenience. Do not rely on it for processing decoded data. + audio_chunk::spec_t audio_chunk_spec() const; +protected: + file_info() {} + ~file_info() {} + void copy_meta_single_nocheck(const file_info & p_source,t_size p_index); + void copy_info_single_nocheck(const file_info & p_source,t_size p_index); + void copy_meta_single_by_name_nocheck_ex(const file_info & p_source,const char * p_name,t_size p_name_length); + void copy_info_single_by_name_nocheck_ex(const file_info & p_source,const char * p_name,t_size p_name_length); + inline void copy_meta_single_by_name_nocheck(const file_info & p_source,const char * p_name) {copy_meta_single_by_name_nocheck_ex(p_source,p_name,SIZE_MAX);} + inline void copy_info_single_by_name_nocheck(const file_info & p_source,const char * p_name) {copy_info_single_by_name_nocheck_ex(p_source,p_name,SIZE_MAX);} + + virtual t_size meta_set_nocheck_ex(const char * p_name,t_size p_name_length,const char * p_value,t_size p_value_length) = 0; + virtual t_size info_set_nocheck_ex(const char * p_name,t_size p_name_length,const char * p_value,t_size p_value_length) = 0; + inline t_size meta_set_nocheck(const char * p_name,const char * p_value) {return meta_set_nocheck_ex(p_name,SIZE_MAX,p_value,SIZE_MAX);} + inline t_size info_set_nocheck(const char * p_name,const char * p_value) {return info_set_nocheck_ex(p_name,SIZE_MAX,p_value,SIZE_MAX);} +}; diff --git a/foobar2000/SDK/file_info_filter.h b/foobar2000/SDK/file_info_filter.h new file mode 100644 index 0000000..92bef39 --- /dev/null +++ b/foobar2000/SDK/file_info_filter.h @@ -0,0 +1,46 @@ +#pragma once +#include "tracks.h" + +//! Implementing this class gives you direct control over which part of file_info gets altered during a tag update uperation. To be used with metadb_io_v2::update_info_async(). +class NOVTABLE file_info_filter : public service_base { + FB2K_MAKE_SERVICE_INTERFACE(file_info_filter, service_base); +public: + //! Alters specified file_info entry; called as a part of tag update process. Specified file_info has been read from a file, and will be written back.\n + //! WARNING: This will be typically called from another thread than main app thread (precisely, from thread created by tag updater). You should copy all relevant data to members of your file_info_filter instance in constructor and reference only member data in apply_filter() implementation. + //! @returns True when you have altered file_info and changes need to be written back to the file; false if no changes have been made. + virtual bool apply_filter(trackRef p_location, t_filestats p_stats, file_info & p_info) = 0; + + typedef std::function< bool (trackRef, t_filestats, file_info & ) > func_t; + static file_info_filter::ptr create( func_t f ); +}; + +//! Extended file_info_filter allowing the caller to do their own manipulation of the file before and after the metadata update takes place. \n +//! Respected by foobar2000 v1.5 and up; if metadb_io_v4 is supported, then file_info_filter_v2 is understood. +class NOVTABLE file_info_filter_v2 : public file_info_filter { + FB2K_MAKE_SERVICE_INTERFACE(file_info_filter_v2, file_info_filter); +public: + + enum filterStatus_t { + filterNoUpdate = 0, + filterProceed, + filterAlreadyUpdated + }; + //! Called after just before rewriting metadata. The file is not yet opened for writing, but a file_lock has already been granted (so don't call it on your own). \n + //! You can use this method to perform album art updates (via album_art_editor API) alongside metadata updates. \n + //! Return value can be used to stop fb2k from proceeding with metadata update on this file. \n + //! If your own operations on this file fail, just pass the exceptions to the caller and they will be reported just as other tag update errors. + //! @param fileIfAlreadyOpened Reference to an already opened file object, if already opened by the caller. May be null. + virtual filterStatus_t before_tag_update(const char * location, file::ptr fileIfAlreadyOpened, abort_callback & aborter) = 0; + + //! Called after metadata has been updated. \n + //! If you wish to alter the file on your own, use before_tag_update() for this instead. \n + //! If your own operations on this file fail, just pass the exceptions to the caller and they will be reported just as other tag update errors. \n + //! The passed reader object can be used to read the properties of the updated file back. In most cases it will be the writer that was used to update the tags. Do not call tag writing methods on it from this function. + virtual void after_tag_update(const char * location, service_ptr_t reader, abort_callback & aborter) = 0; + + virtual void after_all_tag_updates(abort_callback & aborter) = 0; + + //! Allows you to do your own error logging. + //! @returns True if the error has been noted by your code and does not need to be shown to the user. + virtual bool filter_error(const char * location, const char * msg) = 0; +}; diff --git a/foobar2000/SDK/file_info_filter_impl.h b/foobar2000/SDK/file_info_filter_impl.h new file mode 100644 index 0000000..6af30f6 --- /dev/null +++ b/foobar2000/SDK/file_info_filter_impl.h @@ -0,0 +1,33 @@ +#pragma once + +//! Generic implementation of file_info_filter_impl. +class file_info_filter_impl : public file_info_filter { +public: + file_info_filter_impl(const pfc::list_base_const_t & p_list, const pfc::list_base_const_t & p_new_info) { + FB2K_DYNAMIC_ASSERT(p_list.get_count() == p_new_info.get_count()); + pfc::array_t order; + order.set_size(p_list.get_count()); + order_helper::g_fill(order.get_ptr(), order.get_size()); + p_list.sort_get_permutation_t(pfc::compare_t, order.get_ptr()); + m_handles.set_count(order.get_size()); + m_infos.set_size(order.get_size()); + for (t_size n = 0; n < order.get_size(); n++) { + m_handles[n] = p_list[order[n]]; + m_infos[n] = *p_new_info[order[n]]; + } + } + + bool apply_filter(metadb_handle_ptr p_location, t_filestats p_stats, file_info & p_info) { + t_size index; + if (m_handles.bsearch_t(pfc::compare_t, p_location, index)) { + p_info = m_infos[index]; + return true; + } + else { + return false; + } + } +private: + metadb_handle_list m_handles; + pfc::array_t m_infos; +}; diff --git a/foobar2000/SDK/file_info_impl.cpp b/foobar2000/SDK/file_info_impl.cpp new file mode 100644 index 0000000..b0fcf7d --- /dev/null +++ b/foobar2000/SDK/file_info_impl.cpp @@ -0,0 +1,243 @@ +#include "foobar2000.h" + + +t_size file_info_impl::meta_get_count() const +{ + return m_meta.get_count(); +} + +const char* file_info_impl::meta_enum_name(t_size p_index) const +{ + return m_meta.get_name(p_index); +} + +t_size file_info_impl::meta_enum_value_count(t_size p_index) const +{ + return m_meta.get_value_count(p_index); +} + +const char* file_info_impl::meta_enum_value(t_size p_index,t_size p_value_number) const +{ + return m_meta.get_value(p_index,p_value_number); +} + +t_size file_info_impl::meta_set_ex(const char * p_name,t_size p_name_length,const char * p_value,t_size p_value_length) +{ + meta_remove_field_ex(p_name,p_name_length); + return meta_set_nocheck_ex(p_name,p_name_length,p_value,p_value_length); +} + +t_size file_info_impl::meta_set_nocheck_ex(const char * p_name,t_size p_name_length,const char * p_value,t_size p_value_length) +{ + return m_meta.add_entry(p_name,p_name_length,p_value,p_value_length); +} + +void file_info_impl::meta_insert_value_ex(t_size p_index,t_size p_value_index,const char * p_value,t_size p_value_length) +{ + m_meta.insert_value(p_index,p_value_index,p_value,p_value_length); +} + +void file_info_impl::meta_remove_mask(const bit_array & p_mask) +{ + m_meta.remove_mask(p_mask); +} + +void file_info_impl::meta_reorder(const t_size * p_order) +{ + m_meta.reorder(p_order); +} + +void file_info_impl::meta_remove_values(t_size p_index,const bit_array & p_mask) +{ + m_meta.remove_values(p_index,p_mask); + if (m_meta.get_value_count(p_index) == 0) + m_meta.remove_mask(pfc::bit_array_one(p_index)); +} + +t_size file_info_impl::info_get_count() const +{ + return m_info.get_count(); +} + +const char* file_info_impl::info_enum_name(t_size p_index) const +{ + return m_info.get_name(p_index); +} + +const char* file_info_impl::info_enum_value(t_size p_index) const +{ + return m_info.get_value(p_index); +} + +t_size file_info_impl::info_set_ex(const char * p_name,t_size p_name_length,const char * p_value,t_size p_value_length) +{ + info_remove_ex(p_name,p_name_length); + return info_set_nocheck_ex(p_name,p_name_length,p_value,p_value_length); +} + +t_size file_info_impl::info_set_nocheck_ex(const char * p_name,t_size p_name_length,const char * p_value,t_size p_value_length) +{ + return m_info.add_item(p_name,p_name_length,p_value,p_value_length); +} + +void file_info_impl::info_remove_mask(const bit_array & p_mask) +{ + m_info.remove_mask(p_mask); +} + + +file_info_impl::file_info_impl(const file_info & p_source) : m_length(0) +{ + copy(p_source); +} + +file_info_impl::file_info_impl(const file_info_impl & p_source) : m_length(0) +{ + copy(p_source); +} + +const file_info_impl & file_info_impl::operator=(const file_info_impl & p_source) +{ + copy(p_source); + return *this; +} + +file_info_impl::file_info_impl() : m_length(0) +{ + m_replaygain.reset(); +} + +double file_info_impl::get_length() const +{ + return m_length; +} + +void file_info_impl::set_length(double p_length) +{ + m_length = p_length; +} + +void file_info_impl::meta_modify_value_ex(t_size p_index,t_size p_value_index,const char * p_value,t_size p_value_length) +{ + m_meta.modify_value(p_index,p_value_index,p_value,p_value_length); +} + +replaygain_info file_info_impl::get_replaygain() const +{ + return m_replaygain; +} + +void file_info_impl::set_replaygain(const replaygain_info & p_info) +{ + m_replaygain = p_info; +} + + + + +file_info_impl::~file_info_impl() +{ +} + +t_size file_info_impl_utils::info_storage::add_item(const char * p_name,t_size p_name_length,const char * p_value,t_size p_value_length) { + t_size index = m_info.get_size(); + m_info.set_size(index + 1); + m_info[index].init(p_name,p_name_length,p_value,p_value_length); + return index; +} + +void file_info_impl_utils::info_storage::remove_mask(const bit_array & p_mask) { + pfc::remove_mask_t(m_info,p_mask); +} + + + +t_size file_info_impl_utils::meta_storage::add_entry(const char * p_name,t_size p_name_length,const char * p_value,t_size p_value_length) +{ + meta_entry temp(p_name,p_name_length,p_value,p_value_length); + return pfc::append_swap_t(m_data,temp); +} + +void file_info_impl_utils::meta_storage::insert_value(t_size p_index,t_size p_value_index,const char * p_value,t_size p_value_length) +{ + m_data[p_index].insert_value(p_value_index,p_value,p_value_length); +} + +void file_info_impl_utils::meta_storage::modify_value(t_size p_index,t_size p_value_index,const char * p_value,t_size p_value_length) +{ + m_data[p_index].modify_value(p_value_index,p_value,p_value_length); +} + +void file_info_impl_utils::meta_storage::remove_values(t_size p_index,const bit_array & p_mask) +{ + m_data[p_index].remove_values(p_mask); +} + +void file_info_impl_utils::meta_storage::remove_mask(const bit_array & p_mask) +{ + pfc::remove_mask_t(m_data,p_mask); +} + + +file_info_impl_utils::meta_entry::meta_entry(const char * p_name,t_size p_name_len,const char * p_value,t_size p_value_len) +{ + m_name.set_string(p_name,p_name_len); + m_values.set_size(1); + m_values[0].set_string(p_value,p_value_len); +} + + +void file_info_impl_utils::meta_entry::remove_values(const bit_array & p_mask) +{ + pfc::remove_mask_t(m_values,p_mask); +} + +void file_info_impl_utils::meta_entry::insert_value(t_size p_value_index,const char * p_value,t_size p_value_length) +{ + pfc::string_simple temp; + temp.set_string(p_value,p_value_length); + pfc::insert_t(m_values,temp,p_value_index); +} + +void file_info_impl_utils::meta_entry::modify_value(t_size p_value_index,const char * p_value,t_size p_value_length) +{ + m_values[p_value_index].set_string(p_value,p_value_length); +} + +void file_info_impl_utils::meta_storage::reorder(const t_size * p_order) +{ + pfc::reorder_t(m_data,p_order,m_data.get_size()); +} + +void file_info_impl::copy_meta(const file_info & p_source) +{ + m_meta.copy_from(p_source); +} + +void file_info_impl::copy_info(const file_info & p_source) +{ + m_info.copy_from(p_source); +} + +void file_info_impl_utils::meta_storage::copy_from(const file_info & p_info) +{ + t_size meta_index,meta_count = p_info.meta_get_count(); + m_data.set_size(meta_count); + for(meta_index=0;meta_index info_entry_array; + +} + +namespace pfc { + template<> class traits_t : public traits_t {}; +}; + + +namespace file_info_impl_utils { + class info_storage + { + public: + t_size add_item(const char * p_name,t_size p_name_length,const char * p_value,t_size p_value_length); + void remove_mask(const bit_array & p_mask); + inline t_size get_count() const {return m_info.get_count();} + inline const char * get_name(t_size p_index) const {return m_info[p_index].get_name();} + inline const char * get_value(t_size p_index) const {return m_info[p_index].get_value();} + void copy_from(const file_info & p_info); + private: + info_entry_array m_info; + }; +} + + +namespace file_info_impl_utils { + typedef pfc::array_hybrid_t meta_value_array; + struct meta_entry { + meta_entry() {} + meta_entry(const char * p_name,t_size p_name_len,const char * p_value,t_size p_value_len); + + void remove_values(const bit_array & p_mask); + void insert_value(t_size p_value_index,const char * p_value,t_size p_value_length); + void modify_value(t_size p_value_index,const char * p_value,t_size p_value_length); + + inline const char * get_name() const {return m_name;} + inline const char * get_value(t_size p_index) const {return m_values[p_index];} + inline t_size get_value_count() const {return m_values.get_size();} + + + pfc::string_simple m_name; + meta_value_array m_values; + }; + typedef pfc::array_hybrid_t meta_entry_array; +} +namespace pfc { + template<> class traits_t : public pfc::traits_combined {}; +} + + +namespace file_info_impl_utils { + class meta_storage + { + public: + t_size add_entry(const char * p_name,t_size p_name_length,const char * p_value,t_size p_value_length); + void insert_value(t_size p_index,t_size p_value_index,const char * p_value,t_size p_value_length); + void modify_value(t_size p_index,t_size p_value_index,const char * p_value,t_size p_value_length); + void remove_values(t_size p_index,const bit_array & p_mask); + void remove_mask(const bit_array & p_mask); + void copy_from(const file_info & p_info); + + inline void reorder(const t_size * p_order); + + inline t_size get_count() const {return m_data.get_size();} + + inline const char * get_name(t_size p_index) const {PFC_ASSERT(p_index < m_data.get_size()); return m_data[p_index].get_name();} + inline const char * get_value(t_size p_index,t_size p_value_index) const {PFC_ASSERT(p_index < m_data.get_size()); return m_data[p_index].get_value(p_value_index);} + inline t_size get_value_count(t_size p_index) const {PFC_ASSERT(p_index < m_data.get_size()); return m_data[p_index].get_value_count();} + + private: + meta_entry_array m_data; + }; +} + +//! Implements file_info. +class file_info_impl : public file_info +{ +public: + file_info_impl(const file_info_impl & p_source); + file_info_impl(const file_info & p_source); + file_info_impl(); + ~file_info_impl(); + + double get_length() const; + void set_length(double p_length); + + void copy_meta(const file_info & p_source);//virtualized for performance reasons, can be faster in two-pass + void copy_info(const file_info & p_source);//virtualized for performance reasons, can be faster in two-pass + + t_size meta_get_count() const; + const char* meta_enum_name(t_size p_index) const; + t_size meta_enum_value_count(t_size p_index) const; + const char* meta_enum_value(t_size p_index,t_size p_value_number) const; + t_size meta_set_ex(const char * p_name,t_size p_name_length,const char * p_value,t_size p_value_length); + void meta_insert_value_ex(t_size p_index,t_size p_value_index,const char * p_value,t_size p_value_length); + void meta_remove_mask(const bit_array & p_mask); + void meta_reorder(const t_size * p_order); + void meta_remove_values(t_size p_index,const bit_array & p_mask); + void meta_modify_value_ex(t_size p_index,t_size p_value_index,const char * p_value,t_size p_value_length); + + t_size info_get_count() const; + const char* info_enum_name(t_size p_index) const; + const char* info_enum_value(t_size p_index) const; + t_size info_set_ex(const char * p_name,t_size p_name_length,const char * p_value,t_size p_value_length); + void info_remove_mask(const bit_array & p_mask); + + const file_info_impl & operator=(const file_info_impl & p_source); + + replaygain_info get_replaygain() const; + void set_replaygain(const replaygain_info & p_info); + +protected: + t_size meta_set_nocheck_ex(const char * p_name,t_size p_name_length,const char * p_value,t_size p_value_length); + t_size info_set_nocheck_ex(const char * p_name,t_size p_name_length,const char * p_value,t_size p_value_length); +private: + + + file_info_impl_utils::meta_storage m_meta; + file_info_impl_utils::info_storage m_info; + + + double m_length; + + replaygain_info m_replaygain; +}; diff --git a/foobar2000/SDK/file_info_merge.cpp b/foobar2000/SDK/file_info_merge.cpp new file mode 100644 index 0000000..e82ebea --- /dev/null +++ b/foobar2000/SDK/file_info_merge.cpp @@ -0,0 +1,190 @@ +#include "foobar2000.h" + +static t_size merge_tags_calc_rating_by_index(const file_info & p_info,t_size p_index) { + t_size n,m = p_info.meta_enum_value_count(p_index); + t_size ret = 0; + for(n=0;ninfo_get(field); + if (val) to->info_set(field,val); +} +#endif + +namespace { + struct meta_merge_entry { + meta_merge_entry() : m_rating(0) {} + t_size m_rating; + pfc::array_t m_data; + }; + + class meta_merge_map_enumerator { + public: + meta_merge_map_enumerator(file_info & p_out) : m_out(p_out) { + m_out.meta_remove_all(); + } + void operator() (const char * p_name, const meta_merge_entry & p_entry) { + if (p_entry.m_data.get_size() > 0) { + t_size index = m_out.__meta_add_unsafe(p_name,p_entry.m_data[0]); + for(t_size walk = 1; walk < p_entry.m_data.get_size(); ++walk) { + m_out.meta_add_value(index,p_entry.m_data[walk]); + } + } + } + private: + file_info & m_out; + }; +} + +static void merge_meta(file_info & p_out,const pfc::list_base_const_t & p_in) { + pfc::map_t map; + for(t_size in_walk = 0; in_walk < p_in.get_count(); in_walk++) { + const file_info & in = * p_in[in_walk]; + for(t_size meta_walk = 0, meta_count = in.meta_get_count(); meta_walk < meta_count; meta_walk++ ) { + meta_merge_entry & entry = map.find_or_add(in.meta_enum_name(meta_walk)); + t_size rating = merge_tags_calc_rating_by_index(in,meta_walk); + if (rating > entry.m_rating) { + entry.m_rating = rating; + const t_size value_count = in.meta_enum_value_count(meta_walk); + entry.m_data.set_size(value_count); + for(t_size value_walk = 0; value_walk < value_count; value_walk++ ) { + entry.m_data[value_walk] = in.meta_enum_value(meta_walk,value_walk); + } + } + } + } + + meta_merge_map_enumerator en(p_out); + map.enumerate(en); +} + +void file_info::merge(const pfc::list_base_const_t & p_in) +{ + t_size in_count = p_in.get_count(); + if (in_count == 0) + { + meta_remove_all(); + return; + } + else if (in_count == 1) + { + const file_info * info = p_in[0]; + + copy_meta(*info); + + set_replaygain(replaygain_info::g_merge(get_replaygain(),info->get_replaygain())); + + overwrite_info(*info); + + //copy_info_single_by_name(*info,"tagtype"); + + return; + } + + merge_meta(*this,p_in); + + { + pfc::string8_fastalloc tagtype; + replaygain_info rg = get_replaygain(); + t_size in_ptr; + for(in_ptr = 0; in_ptr < in_count; in_ptr++ ) + { + const file_info * info = p_in[in_ptr]; + rg = replaygain_info::g_merge(rg, info->get_replaygain()); + t_size field_ptr, field_max = info->info_get_count(); + for(field_ptr = 0; field_ptr < field_max; field_ptr++ ) + { + const char * field_name = info->info_enum_name(field_ptr), * field_value = info->info_enum_value(field_ptr); + if (*field_value) + { + if (!pfc::stricmp_ascii(field_name,"tagtype")) + { + if (!tagtype.is_empty()) tagtype += "|"; + tagtype += field_value; + } + } + } + } + if (!tagtype.is_empty()) info_set("tagtype",tagtype); + set_replaygain(rg); + } +} + +void file_info::overwrite_info(const file_info & p_source) { + t_size count = p_source.info_get_count(); + for(t_size n=0;ncopy_meta(tag); + this->set_replaygain( replaygain_info::g_merge( this->get_replaygain(), tag.get_replaygain() ) ); + + const size_t iCount = tag.info_get_count(); + for( size_t iWalk = 0; iWalk < iCount; ++iWalk ) { + auto n = tag.info_enum_name(iWalk); + if ( pfc::stringEqualsI_ascii( n, _tagtype ) || isSC(n) ) { + this->info_set(n, tag.info_enum_value( iWalk ) ); + } + } +} + +void file_info::_add_tag(const file_info & otherTag) { + this->set_replaygain( replaygain_info::g_merge( this->get_replaygain(), otherTag.get_replaygain() ) ); + + const char * tt1 = this->info_get(_tagtype); + const char * tt2 = otherTag.info_get(_tagtype); + if (tt2) { + if (tt1) { + this->info_set(_tagtype, PFC_string_formatter() << tt1 << "|" << tt2); + } else { + this->info_set(_tagtype, tt2); + } + } + + { + const size_t iCount = otherTag.info_get_count(); + for( size_t w = 0; w < iCount; ++ w ) { + auto n = otherTag.info_enum_name(w); + if (isSC(n) && !this->info_get(n)) { + this->info_set( n, otherTag.info_enum_value(w) ); + } + } + } +} diff --git a/foobar2000/SDK/file_lock_manager.h b/foobar2000/SDK/file_lock_manager.h new file mode 100644 index 0000000..3bda3f2 --- /dev/null +++ b/foobar2000/SDK/file_lock_manager.h @@ -0,0 +1,77 @@ +#pragma once + +/* +File lock management API +Historical note: while this API was first published in a 2018 release of the SDK, it had been around for at least a decade and is supported in all fb2k versions from 0.9 up. +The semantics are similar to those of blocking POSIX flock(). +Since read locks are expected to be held for a long period of time - for an example, when playing an audio track, a holder of such lock can query whether someone else is waiting for this lock to be released. +Various audio file operations performed by fb2k core use file locks to synchronize access to the files - in particular, to allow graceful updates of tags on the currently playing track. + +Usage examples: +If you want to write tags to an audio file, using low level methods such as input or album_art_editor APIs- + Obtain a write lock before accessing your file and release it when done. +If you want to keep an audio file open for an extended period of time and allow others to write to it- + Obtain a read lock, check is_release_requested() on it periodically; when it returns true, close the file, release the lock, obtain a new lock (which will block you until the peding write operation has completed), reopen the file and resume decoding where you left. + +Final note: +Majority of the fb2k components will never need this API. +If you carry out tag updates via metadb_io, the locking is already dealt with by fb2k core. +*/ + +//! An instance of a file lock object. Use file_lock_manager to instantiate. +class NOVTABLE file_lock : public service_base { +public: + //! Returns whether we're blocking other attempts to access this file. A read lock does not block other read locks, but a write lock requires exclusive access.\n + //! Typically, time consuming read operations check this periodically and close the file / release the lock / reacquire the lock / reopen the file when signaled. + virtual bool is_release_requested() = 0; + + FB2K_MAKE_SERVICE_INTERFACE(file_lock, service_base); +}; + +typedef service_ptr_t file_lock_ptr; + +//! \since 1.5 +//! Modern version of file locking. \n +//! A read lock can be interrupted by a write lock request, from the thread that requested writing. \n +class NOVTABLE file_lock_interrupt : public service_base { + FB2K_MAKE_SERVICE_INTERFACE(file_lock_interrupt, service_base); +public: + //! Please note that interrupt() is called outside any sync scopes and may be called after lock reference has been released. \n + //! It is implementer's responsibility to safeguard against such. \n + //! The interrupt() function must *never* fail, unless aborted by calling context - which means that whoever asked for write access is aborting whatever they're doing. \n + //! This function may block for as long as it takes to release the owned resources, but must be able to abort cleanly if doing so. \n + //! If the function was aborted, it may be called again on the same object. \n + //! If the function succeeded, it will not be called again on the same object; the object will be released immediately after. + virtual void interrupt( abort_callback & aborter ) = 0; + + static file_lock_interrupt::ptr create( std::function< void (abort_callback&)> ); +}; + +//! Entry point class for obtaining file_lock objects. +class NOVTABLE file_lock_manager : public service_base { +public: + enum t_mode { + mode_read = 0, + mode_write + }; + //! Acquires a read or write lock for this file path. \n + //! If asked for read access, waits until nobody else holds a write lock for this path (but others may read at the same time). + //! If asked for write access, access until nobody else holds a read or write lock for this path. \n + //! The semantics are similar to those of blocking POSIX flock(). + virtual file_lock_ptr acquire(const char * p_path, t_mode p_mode, abort_callback & p_abort) = 0; + + //! Helper, calls acquire() with mode_read. + file_lock_ptr acquire_read(const char * p_path, abort_callback & p_abort) { return acquire(p_path, mode_read, p_abort); } + //! Helper, calls acquire() with mode_write. + file_lock_ptr acquire_write(const char * p_path, abort_callback & p_abort) { return acquire(p_path, mode_write, p_abort); } + + + FB2K_MAKE_SERVICE_COREAPI(file_lock_manager); +}; + +// \since 1.5 +class NOVTABLE file_lock_manager_v2 : public file_lock_manager { + FB2K_MAKE_SERVICE_COREAPI_EXTENSION( file_lock_manager_v2, file_lock_manager ); +public: + virtual fb2k::objRef acquire_read_v2(const char * p_path, file_lock_interrupt::ptr interruptHandler, abort_callback & p_abort) = 0; +}; diff --git a/foobar2000/SDK/file_operation_callback.cpp b/foobar2000/SDK/file_operation_callback.cpp new file mode 100644 index 0000000..2dc3302 --- /dev/null +++ b/foobar2000/SDK/file_operation_callback.cpp @@ -0,0 +1,131 @@ +#include "foobar2000.h" + + +static void g_on_files_deleted_sorted(const pfc::list_base_const_t & p_items) +{ + //library_manager::get()->on_files_deleted_sorted(p_items); + playlist_manager::get()->on_files_deleted_sorted(p_items); + + FB2K_FOR_EACH_SERVICE(file_operation_callback, on_files_deleted_sorted(p_items)); +} + +static void g_on_files_moved_sorted(const pfc::list_base_const_t & p_from,const pfc::list_base_const_t & p_to) +{ + { + auto api = playlist_manager::get(); + api->on_files_moved_sorted(p_from,p_to); + api->on_files_deleted_sorted(p_from); + } + FB2K_FOR_EACH_SERVICE(file_operation_callback, on_files_moved_sorted(p_from,p_to)); +} + +static void g_on_files_copied_sorted(const pfc::list_base_const_t & p_from,const pfc::list_base_const_t & p_to) +{ + FB2K_FOR_EACH_SERVICE(file_operation_callback, on_files_copied_sorted(p_from,p_to)); +} + +void file_operation_callback::g_on_files_deleted(const pfc::list_base_const_t & p_items) +{ + core_api::ensure_main_thread(); + t_size count = p_items.get_count(); + if (count > 0) + { + if (count == 1) g_on_files_deleted_sorted(p_items); + else + { + pfc::array_t order; order.set_size(count); + order_helper::g_fill(order); + p_items.sort_get_permutation_t(metadb::path_compare,order.get_ptr()); + g_on_files_deleted_sorted(pfc::list_permutation_t(p_items,order.get_ptr(),count)); + } + } +} + +void file_operation_callback::g_on_files_moved(const pfc::list_base_const_t & p_from,const pfc::list_base_const_t & p_to) +{ + core_api::ensure_main_thread(); + pfc::dynamic_assert(p_from.get_count() == p_to.get_count()); + t_size count = p_from.get_count(); + if (count > 0) + { + if (count == 1) g_on_files_moved_sorted(p_from,p_to); + else + { + pfc::array_t order; order.set_size(count); + order_helper::g_fill(order); + p_from.sort_get_permutation_t(metadb::path_compare,order.get_ptr()); + g_on_files_moved_sorted(pfc::list_permutation_t(p_from,order.get_ptr(),count),pfc::list_permutation_t(p_to,order.get_ptr(),count)); + } + } +} + +void file_operation_callback::g_on_files_copied(const pfc::list_base_const_t & p_from,const pfc::list_base_const_t & p_to) +{ + if (core_api::assert_main_thread()) + { + assert(p_from.get_count() == p_to.get_count()); + t_size count = p_from.get_count(); + if (count > 0) + { + if (count == 1) g_on_files_copied_sorted(p_from,p_to); + else + { + pfc::array_t order; order.set_size(count); + order_helper::g_fill(order); + p_from.sort_get_permutation_t(metadb::path_compare,order.get_ptr()); + g_on_files_copied_sorted(pfc::list_permutation_t(p_from,order.get_ptr(),count),pfc::list_permutation_t(p_to,order.get_ptr(),count)); + } + } + } +} +bool file_operation_callback::g_search_sorted_list(const pfc::list_base_const_t & p_list,const char * p_string,t_size & p_index) { + return pfc::binarySearch::run(p_list,0,p_list.get_count(),p_string,p_index); +} + +bool file_operation_callback::g_update_list_on_moved_ex(metadb_handle_list_ref p_list,t_pathlist p_from,t_pathlist p_to, metadb_handle_list_ref itemsAdded, metadb_handle_list_ref itemsRemoved) { + auto api = metadb::get(); + bool changed = false; + itemsAdded.remove_all(); itemsRemoved.remove_all(); + for(t_size walk = 0; walk < p_list.get_count(); ++walk) { + metadb_handle_ptr item = p_list[walk]; + t_size index; + if (g_search_sorted_list(p_from,item->get_path(),index)) { + metadb_handle_ptr newItem; + api->handle_create_replace_path_canonical(newItem,item,p_to[index]); + p_list.replace_item(walk,newItem); + changed = true; + itemsAdded.add_item(newItem); itemsRemoved.add_item(item); + } + } + return changed; +} +bool file_operation_callback::g_update_list_on_moved(metadb_handle_list_ref p_list,const pfc::list_base_const_t & p_from,const pfc::list_base_const_t & p_to) { + auto api = metadb::get(); + bool changed = false; + for(t_size walk = 0; walk < p_list.get_count(); ++walk) { + metadb_handle_ptr item = p_list[walk]; + t_size index; + if (g_search_sorted_list(p_from,item->get_path(),index)) { + metadb_handle_ptr newItem; + api->handle_create_replace_path_canonical(newItem,item,p_to[index]); + p_list.replace_item(walk,newItem); + changed = true; + } + } + return changed; +} + + +bool file_operation_callback::g_mark_dead_entries(metadb_handle_list_cref items, bit_array_var & mask, t_pathlist deadPaths) { + bool found = false; + const t_size total = items.get_count(); + for(t_size walk = 0; walk < total; ++walk) { + t_size index; + if (g_search_sorted_list(deadPaths,items[walk]->get_path(),index)) { + mask.set(walk,true); found = true; + } else { + mask.set(walk,false); + } + } + return found; +} diff --git a/foobar2000/SDK/file_operation_callback.h b/foobar2000/SDK/file_operation_callback.h new file mode 100644 index 0000000..d5742a3 --- /dev/null +++ b/foobar2000/SDK/file_operation_callback.h @@ -0,0 +1,62 @@ +#pragma once +//! Interface to notify component system about files being deleted or moved. Operates in app's main thread only. + +class NOVTABLE file_operation_callback : public service_base { +public: + typedef const pfc::list_base_const_t & t_pathlist; + //! p_items is a metadb::path_compare sorted list of files that have been deleted. + virtual void on_files_deleted_sorted(t_pathlist p_items) = 0; + //! p_from is a metadb::path_compare sorted list of files that have been moved, p_to is a list of corresponding target locations. + virtual void on_files_moved_sorted(t_pathlist p_from,t_pathlist p_to) = 0; + //! p_from is a metadb::path_compare sorted list of files that have been copied, p_to is a list of corresponding target locations. + virtual void on_files_copied_sorted(t_pathlist p_from,t_pathlist p_to) = 0; + + static void g_on_files_deleted(const pfc::list_base_const_t & p_items); + static void g_on_files_moved(const pfc::list_base_const_t & p_from,const pfc::list_base_const_t & p_to); + static void g_on_files_copied(const pfc::list_base_const_t & p_from,const pfc::list_base_const_t & p_to); + + static bool g_search_sorted_list(const pfc::list_base_const_t & p_list,const char * p_string,t_size & p_index); + static bool g_update_list_on_moved(metadb_handle_list_ref p_list,t_pathlist p_from,t_pathlist p_to); + + static bool g_update_list_on_moved_ex(metadb_handle_list_ref p_list,t_pathlist p_from,t_pathlist p_to, metadb_handle_list_ref itemsAdded, metadb_handle_list_ref itemsRemoved); + + static bool g_mark_dead_entries(metadb_handle_list_cref items, bit_array_var & mask, t_pathlist deadPaths); + + + FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(file_operation_callback); +}; + + + +//! New in 0.9.5. +class NOVTABLE file_operation_callback_dynamic { +public: + //! p_items is a metadb::path_compare sorted list of files that have been deleted. + virtual void on_files_deleted_sorted(const pfc::list_base_const_t & p_items) = 0; + //! p_from is a metadb::path_compare sorted list of files that have been moved, p_to is a list of corresponding target locations. + virtual void on_files_moved_sorted(const pfc::list_base_const_t & p_from,const pfc::list_base_const_t & p_to) = 0; + //! p_from is a metadb::path_compare sorted list of files that have been copied, p_to is a list of corresponding target locations. + virtual void on_files_copied_sorted(const pfc::list_base_const_t & p_from,const pfc::list_base_const_t & p_to) = 0; +}; + +//! New in 0.9.5. +class NOVTABLE file_operation_callback_dynamic_manager : public service_base { +public: + virtual void register_callback(file_operation_callback_dynamic * p_callback) = 0; + virtual void unregister_callback(file_operation_callback_dynamic * p_callback) = 0; + + FB2K_MAKE_SERVICE_COREAPI(file_operation_callback_dynamic_manager); +}; + +//! New in 0.9.5. +class file_operation_callback_dynamic_impl_base : public file_operation_callback_dynamic { +public: + file_operation_callback_dynamic_impl_base() {file_operation_callback_dynamic_manager::get()->register_callback(this);} + ~file_operation_callback_dynamic_impl_base() {file_operation_callback_dynamic_manager::get()->unregister_callback(this);} + + void on_files_deleted_sorted(const pfc::list_base_const_t & p_items) {} + void on_files_moved_sorted(const pfc::list_base_const_t & p_from,const pfc::list_base_const_t & p_to) {} + void on_files_copied_sorted(const pfc::list_base_const_t & p_from,const pfc::list_base_const_t & p_to) {} + + PFC_CLASS_NOT_COPYABLE_EX(file_operation_callback_dynamic_impl_base); +}; diff --git a/foobar2000/SDK/filesystem.cpp b/foobar2000/SDK/filesystem.cpp new file mode 100644 index 0000000..8497648 --- /dev/null +++ b/foobar2000/SDK/filesystem.cpp @@ -0,0 +1,1599 @@ +#include "foobar2000.h" + +// For reasons unknown, MS linker will not throw these global constants out if the code using them is not referenced +// To verify, flip the #if and search your DLL for "unpack://" +// Using #defines instead fixes it +#if 1 +#define unpack_prefix "unpack://" +#define unpack_prefix_len 9 +#else +static const char unpack_prefix[] = "unpack://"; +static const unsigned unpack_prefix_len = 9; +#endif + +void unpacker::g_open(service_ptr_t & p_out,const service_ptr_t & p,abort_callback & p_abort) +{ + service_enum_t e; + service_ptr_t ptr; + if (e.first(ptr)) do { + p->reopen(p_abort); + try { + ptr->open(p_out,p,p_abort); + return; + } catch(exception_io_data const &) {} + } while(e.next(ptr)); + throw exception_io_data(); +} + +void file::seek_probe(t_filesize p_position, abort_callback & p_abort) { + try { seek(p_position, p_abort); } catch(exception_io_seek_out_of_range) {throw exception_io_data();} +} + +void file::seek_ex(t_sfilesize p_position, file::t_seek_mode p_mode, abort_callback &p_abort) { + switch(p_mode) { + case seek_from_beginning: + seek(p_position,p_abort); + break; + case seek_from_current: + seek(p_position + get_position(p_abort),p_abort); + break; + case seek_from_eof: + seek(p_position + get_size_ex(p_abort),p_abort); + break; + default: + throw exception_io_data(); + } +} + +static void makeBuffer(pfc::array_t & buffer, size_t size) { + for(;;) {// Tolerant malloc - allocate a smaller buffer if we're unable to acquire the requested size. + try { + buffer.set_size_discard( size ); + return; + } catch(std::bad_alloc) { + if (size < 256) throw; + size >>= 1; + } + } +} + +t_filesize file::g_transfer(stream_reader * p_src,stream_writer * p_dst,t_filesize p_bytes,abort_callback & p_abort) { + pfc::array_t temp; + makeBuffer(temp, (t_size)pfc::min_t(1024*1024*8,p_bytes)); + void* ptr = temp.get_ptr(); + t_filesize done = 0; + while(done(temp.get_size(),p_bytes-done); + delta = p_src->read(ptr,delta,p_abort); + if (delta<=0) break; + p_dst->write(ptr,delta,p_abort); + done += delta; + } + return done; +} + +void file::g_transfer_object(stream_reader * p_src,stream_writer * p_dst,t_filesize p_bytes,abort_callback & p_abort) { + if (g_transfer(p_src,p_dst,p_bytes,p_abort) != p_bytes) + throw exception_io_data_truncation(); +} + + +void filesystem::g_get_canonical_path(const char * path,pfc::string_base & out) +{ + TRACK_CALL_TEXT("filesystem::g_get_canonical_path"); + + service_enum_t e; + service_ptr_t ptr; + if (e.first(ptr)) do { + if (ptr->get_canonical_path(path,out)) return; + } while(e.next(ptr)); + //no one wants to process this, let's copy over + out = path; +} + +void filesystem::g_get_display_path(const char * path,pfc::string_base & out) +{ + TRACK_CALL_TEXT("filesystem::g_get_display_path"); + service_ptr_t ptr; + if (!g_get_interface(ptr,path)) + { + //no one wants to process this, let's copy over + out = path; + } + else + { + if (!ptr->get_display_path(path,out)) + out = path; + } +} + +bool filesystem::g_get_native_path( const char * path, pfc::string_base & out) { + // Is proper file:// path? + if (foobar2000_io::extract_native_path( path, out ) ) return true; + + // Set anyway + out = path; + + // Maybe just a file:// less local path? Check for other protocol markers + // If no :// present, return true anyway + return strstr( path, "://" ) == NULL; +} + +filesystem::ptr filesystem::g_get_interface(const char * path) { + filesystem::ptr rv; + if (!g_get_interface(rv, path)) throw exception_io_no_handler_for_path(); + return rv; + +} +bool filesystem::g_get_interface(service_ptr_t & p_out,const char * path) +{ + PFC_ASSERT( path != nullptr ); + PFC_ASSERT( path[0] != 0 ); + + service_enum_t e; + service_ptr_t ptr; + if (e.first(ptr)) do { + if (ptr->is_our_path(path)) + { + p_out = ptr; + return true; + } + } while(e.next(ptr)); + return false; +} + + +void filesystem::g_open(service_ptr_t & p_out,const char * path,t_open_mode mode,abort_callback & p_abort) +{ + TRACK_CALL_TEXT("filesystem::g_open"); + g_get_interface(path)->open(p_out,path,mode,p_abort); +} + + +void filesystem::g_open_timeout(service_ptr_t & p_out,const char * p_path,t_open_mode p_mode,double p_timeout,abort_callback & p_abort) { + FB2K_RETRY_ON_SHARING_VIOLATION( g_open(p_out, p_path, p_mode, p_abort), p_abort, p_timeout); +} + +bool filesystem::g_exists(const char * p_path,abort_callback & p_abort) +{ + t_filestats stats; + bool dummy; + try { + g_get_stats(p_path,stats,dummy,p_abort); + } catch(exception_io_not_found) {return false;} + return true; +} + +bool filesystem::g_exists_writeable(const char * p_path,abort_callback & p_abort) +{ + t_filestats stats; + bool writeable; + try { + g_get_stats(p_path,stats,writeable,p_abort); + } catch(exception_io_not_found) {return false;} + return writeable; +} + +void filesystem::g_remove(const char * p_path,abort_callback & p_abort) { + g_get_interface(p_path)->remove(p_path,p_abort); +} + +void filesystem::g_remove_timeout(const char * p_path,double p_timeout,abort_callback & p_abort) { + FB2K_RETRY_FILE_MOVE( g_remove(p_path, p_abort), p_abort, p_timeout ); +} + +void filesystem::g_move_timeout(const char * p_src,const char * p_dst,double p_timeout,abort_callback & p_abort) { + FB2K_RETRY_FILE_MOVE( g_move(p_src, p_dst, p_abort), p_abort, p_timeout ); +} + +void filesystem::g_copy_timeout(const char * p_src,const char * p_dst,double p_timeout,abort_callback & p_abort) { + FB2K_RETRY_FILE_MOVE( g_copy(p_src, p_dst, p_abort), p_abort, p_timeout ); +} + +void filesystem::g_create_directory(const char * p_path,abort_callback & p_abort) +{ + g_get_interface(p_path)->create_directory(p_path,p_abort); +} + +void filesystem::g_move(const char * src,const char * dst,abort_callback & p_abort) { + service_enum_t e; + service_ptr_t ptr; + if (e.first(ptr)) do { + if (ptr->is_our_path(src) && ptr->is_our_path(dst)) { + ptr->move(src,dst,p_abort); + return; + } + } while(e.next(ptr)); + throw exception_io_no_handler_for_path(); +} + +void filesystem::g_link(const char * p_src,const char * p_dst,abort_callback & p_abort) { + if (!foobar2000_io::_extract_native_path_ptr(p_src) || !foobar2000_io::_extract_native_path_ptr(p_dst)) throw exception_io_no_handler_for_path(); + WIN32_IO_OP( CreateHardLink( pfc::stringcvt::string_os_from_utf8( pfc::winPrefixPath( p_dst ) ), pfc::stringcvt::string_os_from_utf8( pfc::winPrefixPath( p_src ) ), NULL) ); +} + +void filesystem::g_link_timeout(const char * p_src,const char * p_dst,double p_timeout,abort_callback & p_abort) { + FB2K_RETRY_FILE_MOVE( g_link(p_src, p_dst, p_abort), p_abort, p_timeout ); +} + + +void filesystem::g_list_directory(const char * p_path,directory_callback & p_out,abort_callback & p_abort) +{ + TRACK_CALL_TEXT("filesystem::g_list_directory"); + g_get_interface(p_path)->list_directory(p_path,p_out,p_abort); +} + + +static void path_pack_string(pfc::string_base & out,const char * src) +{ + out.add_char('|'); + out << (unsigned) strlen(src); + out.add_char('|'); + out << src; + out.add_char('|'); +} + +static int path_unpack_string(pfc::string_base & out,const char * src) +{ + int ptr=0; + if (src[ptr++]!='|') return -1; + int len = atoi(src+ptr); + if (len<=0) return -1; + while(src[ptr]!=0 && src[ptr]!='|') ptr++; + if (src[ptr]!='|') return -1; + ptr++; + int start = ptr; + while(ptr-start & p_out,const char * p_path,abort_callback & p_abort) { + service_ptr_t fs = g_get_interface(p_path); + if (fs->is_remote(p_path)) throw exception_io_object_is_remote(); + fs->open(p_out,p_path,open_mode_read,p_abort); +} + +bool filesystem::g_is_remote(const char * p_path) { + return g_get_interface(p_path)->is_remote(p_path); +} + +bool filesystem::g_is_recognized_and_remote(const char * p_path) { + service_ptr_t fs; + if (g_get_interface(fs,p_path)) return fs->is_remote(p_path); + else return false; +} + +bool filesystem::g_is_remote_or_unrecognized(const char * p_path) { + service_ptr_t fs; + if (g_get_interface(fs,p_path)) return fs->is_remote(p_path); + else return true; +} + +bool filesystem::g_relative_path_create(const char * file_path,const char * playlist_path,pfc::string_base & out) +{ + + bool rv = false; + service_ptr_t fs; + + if (g_get_interface(fs,file_path)) + rv = fs->relative_path_create(file_path,playlist_path,out); + + return rv; +} + +bool filesystem::g_relative_path_parse(const char * relative_path,const char * playlist_path,pfc::string_base & out) +{ + service_enum_t e; + service_ptr_t ptr; + if (e.first(ptr)) do { + if (ptr->relative_path_parse(relative_path,playlist_path,out)) return true; + } while(e.next(ptr)); + return false; +} + +bool archive::is_our_archive( const char * path ) { + archive_v2::ptr v2; + if ( v2 &= this ) return v2->is_our_archive( path ); + return true; // accept all files +} + +bool archive_impl::get_canonical_path(const char * path,pfc::string_base & out) +{ + if (is_our_path(path)) + { + pfc::string8 archive,file,archive_canonical; + if (g_parse_unpack_path(path,archive,file)) + { + g_get_canonical_path(archive,archive_canonical); + make_unpack_path(out,archive_canonical,file); + + return true; + } + else return false; + } + else return false; +} + +bool archive_impl::is_our_path(const char * path) +{ + if (!g_is_unpack_path(path)) return false; + const char * type = get_archive_type(); + path += 9; + while(*type) + { + if (*type!=*path) return false; + type++; + path++; + } + if (*path!='|') return false; + return true; +} + +bool archive_impl::get_display_path(const char * path,pfc::string_base & out) +{ + pfc::string8 archive,file; + if (g_parse_unpack_path(path,archive,file)) + { + g_get_display_path(archive,out); + out.add_string("|"); + out.add_string(file); + return true; + } + else return false; +} + +void archive_impl::open(service_ptr_t & p_out,const char * path,t_open_mode mode, abort_callback & p_abort) +{ + if (mode != open_mode_read) throw exception_io_denied(); + pfc::string8 archive,file; + if (!g_parse_unpack_path(path,archive,file)) throw exception_io_not_found(); + open_archive(p_out,archive,file,p_abort); +} + + +void archive_impl::remove(const char * path,abort_callback & p_abort) { + throw exception_io_denied(); +} + +void archive_impl::move(const char * src,const char * dst,abort_callback & p_abort) { + throw exception_io_denied(); +} + +bool archive_impl::is_remote(const char * src) { + pfc::string8 archive,file; + if (g_parse_unpack_path(src,archive,file)) return g_is_remote(archive); + else throw exception_io_not_found(); +} + +bool archive_impl::relative_path_create(const char * file_path,const char * playlist_path,pfc::string_base & out) { + pfc::string8 archive,file; + if (g_parse_unpack_path(file_path,archive,file)) + { + pfc::string8 archive_rel; + if (g_relative_path_create(archive,playlist_path,archive_rel)) + { + pfc::string8 out_path; + make_unpack_path(out_path,archive_rel,file); + out.set_string(out_path); + return true; + } + } + return false; +} + +bool archive_impl::relative_path_parse(const char * relative_path,const char * playlist_path,pfc::string_base & out) +{ + if (!is_our_path(relative_path)) return false; + pfc::string8 archive_rel,file; + if (g_parse_unpack_path(relative_path,archive_rel,file)) + { + pfc::string8 archive; + if (g_relative_path_parse(archive_rel,playlist_path,archive)) + { + pfc::string8 out_path; + make_unpack_path(out_path,archive,file); + out.set_string(out_path); + return true; + } + } + return false; +} + +bool archive_impl::g_parse_unpack_path_ex(const char * path,pfc::string_base & archive,pfc::string_base & file, pfc::string_base & type) { + PFC_ASSERT( g_is_unpack_path(path) ); + const char * base = path + unpack_prefix_len; // strstr(path, "//"); + const char * split = strchr(path,'|'); + if (base == NULL || split == NULL || base > split) return false; + // base += 2; + type.set_string( base, split - base ); + int delta = path_unpack_string(archive,split); + if (delta<0) return false; + split += delta; + file = split; + return true; +} +bool archive_impl::g_parse_unpack_path(const char * path,pfc::string_base & archive,pfc::string_base & file) { + PFC_ASSERT( g_is_unpack_path(path) ); + path = strchr(path,'|'); + if (!path) return false; + int delta = path_unpack_string(archive,path); + if (delta<0) return false; + path += delta; + file = path; + return true; +} + +bool archive_impl::g_is_unpack_path(const char * path) { + return strncmp(path,unpack_prefix,unpack_prefix_len) == 0; +} + +void archive_impl::g_make_unpack_path(pfc::string_base & path,const char * archive,const char * file,const char * name) +{ + path = unpack_prefix; + path += name; + path_pack_string(path,archive); + path += file; +} + +void archive_impl::make_unpack_path(pfc::string_base & path,const char * archive,const char * file) {g_make_unpack_path(path,archive,file,get_archive_type());} + + +FILE * filesystem::streamio_open(const char * path,const char * flags) +{ + FILE * ret = 0; + pfc::string8 temp; + g_get_canonical_path(path,temp); + if (!strncmp(temp,"file://",7)) + { + ret = _wfopen(pfc::stringcvt::string_wide_from_utf8(path+7),pfc::stringcvt::string_wide_from_utf8(flags)); + } + return ret; +} + + +namespace { + + class directory_callback_isempty : public directory_callback + { + bool m_isempty; + public: + directory_callback_isempty() : m_isempty(true) {} + bool on_entry(filesystem * owner,abort_callback & p_abort,const char * url,bool is_subdirectory,const t_filestats & p_stats) + { + m_isempty = false; + return false; + } + bool isempty() {return m_isempty;} + }; + + class directory_callback_dummy : public directory_callback + { + public: + bool on_entry(filesystem * owner,abort_callback & p_abort,const char * url,bool is_subdirectory,const t_filestats & p_stats) {return false;} + }; + +} + +bool filesystem::g_is_empty_directory(const char * path,abort_callback & p_abort) +{ + directory_callback_isempty callback; + try { + g_list_directory(path,callback,p_abort); + } catch(exception_io const &) {return false;} + return callback.isempty(); +} + +bool filesystem::g_is_valid_directory(const char * path,abort_callback & p_abort) { + if ( path == NULL || path[0] == 0 ) return false; + + return get(path)->directory_exists( path, p_abort ); +} + +bool directory_callback_impl::on_entry(filesystem * owner,abort_callback & p_abort,const char * url,bool is_subdirectory,const t_filestats & p_stats) { + p_abort.check_e(); + if (is_subdirectory) { + if (m_recur) { + try { + owner->list_directory(url,*this,p_abort); + } catch(exception_io const &) {} + } + } else { + m_data.add_item(pfc::rcnew_t(url,p_stats)); + } + return true; +} + +namespace { + class directory_callback_impl_copy : public directory_callback + { + public: + directory_callback_impl_copy(const char * p_target, filesystem::ptr fs) : m_fs(fs) + { + m_target = p_target; + m_target.fix_dir_separator('\\'); + } + + bool on_entry(filesystem * owner,abort_callback & p_abort,const char * url,bool is_subdirectory,const t_filestats & p_stats) { + const char * fn = url + pfc::scan_filename(url); + t_size truncat = m_target.length(); + m_target += fn; + if (is_subdirectory) { + try { + m_fs->create_directory(m_target,p_abort); + } catch(exception_io_already_exists) {} + m_target += "\\"; + owner->list_directory(url,*this,p_abort); + } else { + _copy(url, m_target, owner, p_abort); + } + m_target.truncate(truncat); + return true; + } + void _copy(const char * src, const char * dst, filesystem * srcFS, abort_callback & p_abort) { + service_ptr_t r_src,r_dst; + t_filesize size; + + srcFS->open(r_src,src,filesystem::open_mode_read,p_abort); + size = r_src->get_size_ex(p_abort); + m_fs->open(r_dst,dst,filesystem::open_mode_write_new,p_abort); + + if (size > 0) { + try { + file::g_transfer_object(r_src,r_dst,size,p_abort); + } catch(...) { + r_dst.release(); + try {m_fs->remove(dst,fb2k::noAbort);} catch(...) {} + throw; + } + } + } + private: + pfc::string8_fastalloc m_target; + filesystem::ptr m_fs; + }; +} + +file::ptr filesystem::openEx(const char * path, filesystem::t_open_mode mode, abort_callback & abort, double timeout) { + file::ptr f; + retryOnSharingViolation([&] { + this->open(f, path, mode, abort); + }, timeout, abort); + return f; +} + +file::ptr filesystem::openRead(const char * path, abort_callback & abort, double timeout) { + return this->openEx(path, open_mode_read, abort, timeout); +} + +file::ptr filesystem::openWriteExisting(const char * path, abort_callback & abort, double timeout) { + return this->openEx(path, open_mode_write_existing, abort, timeout); +} + +file::ptr filesystem::openWriteNew(const char * path, abort_callback & abort, double timeout) { + return this->openEx( path, open_mode_write_new, abort, timeout ); +} + +void filesystem::copy_directory(const char * src, const char * dst, abort_callback & p_abort) { + try { + this->create_directory( dst, p_abort ); + } catch(exception_io_already_exists) {} + directory_callback_impl_copy cb(dst, this); + list_directory(src, cb, p_abort); +} + +void filesystem::g_copy_directory(const char * src,const char * dst,abort_callback & p_abort) { + filesystem::ptr dstFS = filesystem::g_get_interface(dst); + try { + dstFS->create_directory( dst, p_abort ); + } catch(exception_io_already_exists) {} + directory_callback_impl_copy cb(dst, dstFS); + g_list_directory(src,cb,p_abort); +} + +void filesystem::g_copy(const char * src,const char * dst,abort_callback & p_abort) { + service_ptr_t r_src,r_dst; + t_filesize size; + + g_open(r_src,src,open_mode_read,p_abort); + size = r_src->get_size_ex(p_abort); + g_open(r_dst,dst,open_mode_write_new,p_abort); + + if (size > 0) { + try { + file::g_transfer_object(r_src,r_dst,size,p_abort); + } catch(...) { + r_dst.release(); + try {g_remove(dst,fb2k::noAbort);} catch(...) {} + throw; + } + } + + try { + file::g_copy_timestamps(r_src, r_dst, p_abort); + } catch (exception_io) {} +} + +void stream_reader::read_object(void * p_buffer,t_size p_bytes,abort_callback & p_abort) { + if (read(p_buffer,p_bytes,p_abort) != p_bytes) throw exception_io_data_truncation(); +} + +t_filestats file::get_stats(abort_callback & p_abort) +{ + t_filestats temp; + temp.m_size = get_size(p_abort); + temp.m_timestamp = get_timestamp(p_abort); + return temp; +} + +t_filesize stream_reader::skip(t_filesize p_bytes,abort_callback & p_abort) +{ + t_uint8 temp[256]; + t_filesize todo = p_bytes, done = 0; + while(todo > 0) { + t_size delta,deltadone; + delta = sizeof(temp); + if (delta > todo) delta = (t_size) todo; + deltadone = read(temp,delta,p_abort); + done += deltadone; + todo -= deltadone; + if (deltadone < delta) break; + } + return done; +} + +void stream_reader::skip_object(t_filesize p_bytes,abort_callback & p_abort) { + if (skip(p_bytes,p_abort) != p_bytes) throw exception_io_data_truncation(); +} + +void filesystem::g_open_write_new(service_ptr_t & p_out,const char * p_path,abort_callback & p_abort) { + g_open(p_out,p_path,open_mode_write_new,p_abort); +} +void file::g_transfer_file(const service_ptr_t & p_from,const service_ptr_t & p_to,abort_callback & p_abort) { + t_filesize length = p_from->get_size(p_abort); + p_from->reopen( p_abort ); +// p_from->seek(0,p_abort); + p_to->seek(0,p_abort); + p_to->set_eof(p_abort); + if (length == filesize_invalid) { + g_transfer(p_from, p_to, ~0, p_abort); + } else if (length > 0) { + g_transfer_object(p_from,p_to,length,p_abort); + } +} + +void filesystem::g_open_temp(service_ptr_t & p_out,abort_callback & p_abort) { + g_open(p_out,"tempfile://",open_mode_write_new,p_abort); +} + +void filesystem::g_open_tempmem(service_ptr_t & p_out,abort_callback & p_abort) { + g_open(p_out,"tempmem://",open_mode_write_new,p_abort); +} + +file::ptr filesystem::g_open_tempmem() { + file::ptr f; g_open_tempmem(f, fb2k::noAbort); return f; +} + +void archive_impl::list_directory(const char * p_path,directory_callback & p_out,abort_callback & p_abort) { + throw exception_io_not_found(); +} + +void archive_impl::create_directory(const char * path,abort_callback &) { + throw exception_io_denied(); +} + +void filesystem::g_get_stats(const char * p_path,t_filestats & p_stats,bool & p_is_writeable,abort_callback & p_abort) { + TRACK_CALL_TEXT("filesystem::g_get_stats"); + return g_get_interface(p_path)->get_stats(p_path,p_stats,p_is_writeable,p_abort); +} + +void archive_impl::get_stats(const char * p_path,t_filestats & p_stats,bool & p_is_writeable,abort_callback & p_abort) { + pfc::string8 archive,file; + if (g_parse_unpack_path(p_path,archive,file)) { + if (g_is_remote(archive)) throw exception_io_object_is_remote(); + p_is_writeable = false; + p_stats = get_stats_in_archive(archive,file,p_abort); + } + else throw exception_io_not_found(); +} + + +bool file::is_eof(abort_callback & p_abort) { + t_filesize position,size; + position = get_position(p_abort); + size = get_size(p_abort); + if (size == filesize_invalid) return false; + return position >= size; +} + + +t_filetimestamp foobar2000_io::import_DOS_time(uint32_t v) { +#ifdef _WIN32 + FILETIME ft = {}; + if (DosDateTimeToFileTime(HIWORD(v), LOWORD(v), &ft)) { + FILETIME ft2 = {}; + if (LocalFileTimeToFileTime(&ft, &ft2)) { + return ((uint64_t)ft2.dwHighDateTime << 32) | (uint64_t)ft2.dwLowDateTime; + } + + } +#endif + // FIX ME + return filetimestamp_invalid; +} + +t_filetimestamp foobar2000_io::filetimestamp_from_system_timer() +{ + return pfc::fileTimeNow(); +} + +void stream_reader::read_string_ex(pfc::string_base & p_out,t_size p_bytes,abort_callback & p_abort) { + const t_size expBase = 64*1024; + if (p_bytes > expBase) { + pfc::array_t temp; + t_size allocWalk = expBase; + t_size done = 0; + for(;;) { + const t_size target = pfc::min_t(allocWalk, p_bytes); + temp.set_size(target); + read_object(temp.get_ptr() + done, target - done, p_abort); + if (target == p_bytes) break; + done = target; + allocWalk <<= 1; + } + p_out.set_string(temp.get_ptr(), p_bytes); + } else { + pfc::string_buffer buf(p_out, p_bytes); + read_object(buf.get_ptr(),p_bytes,p_abort); + } +} +void stream_reader::read_string(pfc::string_base & p_out,abort_callback & p_abort) +{ + t_uint32 length; + read_lendian_t(length,p_abort); + read_string_ex(p_out,length,p_abort); +} + +void stream_reader::read_string_raw(pfc::string_base & p_out,abort_callback & p_abort) { + enum {delta = 256}; + char buffer[delta]; + p_out.reset(); + for(;;) { + t_size delta_done; + delta_done = read(buffer,delta,p_abort); + p_out.add_string(buffer,delta_done); + if (delta_done < delta) break; + } +} +void stream_writer::write_string(const char * p_string,t_size p_len,abort_callback & p_abort) { + t_uint32 len = pfc::downcast_guarded(pfc::strlen_max(p_string,p_len)); + write_lendian_t(len,p_abort); + write_object(p_string,len,p_abort); +} + +void stream_writer::write_string(const char * p_string,abort_callback & p_abort) { + write_string(p_string,~0,p_abort); +} + +void stream_writer::write_string_raw(const char * p_string,abort_callback & p_abort) { + write_object(p_string,strlen(p_string),p_abort); +} + +void file::truncate(t_uint64 p_position,abort_callback & p_abort) { + if (p_position < get_size(p_abort)) resize(p_position,p_abort); +} + + +#ifdef _WIN32 +namespace { + //rare/weird win32 errors that didn't make it to the main API + PFC_DECLARE_EXCEPTION(exception_io_device_not_ready, exception_io,"Device not ready"); + PFC_DECLARE_EXCEPTION(exception_io_invalid_drive, exception_io_not_found,"Drive not found"); + PFC_DECLARE_EXCEPTION(exception_io_win32, exception_io,"Generic win32 I/O error"); + PFC_DECLARE_EXCEPTION(exception_io_buffer_overflow, exception_io,"The file name is too long"); + PFC_DECLARE_EXCEPTION(exception_io_invalid_path_syntax, exception_io,"Invalid path syntax"); + + class exception_io_win32_ex : public exception_io_win32 { + public: + static pfc::string8 format(DWORD code) { + pfc::string8 ret; + ret << "I/O error (win32 "; + if (code & 0x80000000) { + ret << "0x" << pfc::format_hex(code, 8); + } else { + ret << "#" << (uint32_t)code; + } + ret << ")"; + return ret; + } + exception_io_win32_ex(DWORD p_code) : m_msg(format(p_code)) {} + exception_io_win32_ex(const exception_io_win32_ex & p_other) {*this = p_other;} + const char * what() const throw() {return m_msg;} + private: + pfc::string8 m_msg; + }; +} + +PFC_NORETURN void foobar2000_io::win32_file_write_failure(DWORD p_code, const char * path) { + if (p_code == ERROR_ACCESS_DENIED) { + const DWORD attr = uGetFileAttributes(path); + if (attr != ~0 && (attr & FILE_ATTRIBUTE_READONLY) != 0) throw exception_io_denied_readonly(); + } + exception_io_from_win32(p_code); +} + +PFC_NORETURN void foobar2000_io::exception_io_from_win32(DWORD p_code) { +#if PFC_DEBUG + PFC_DEBUGLOG << "exception_io_from_win32: " << p_code; +#endif + //pfc::string_fixed_t<32> debugMsg; debugMsg << "Win32 I/O error #" << (t_uint32)p_code; + //TRACK_CALL_TEXT(debugMsg); + switch(p_code) { + case ERROR_ALREADY_EXISTS: + case ERROR_FILE_EXISTS: + throw exception_io_already_exists(); + case ERROR_NETWORK_ACCESS_DENIED: + case ERROR_ACCESS_DENIED: + throw exception_io_denied(); + case ERROR_WRITE_PROTECT: + throw exception_io_write_protected(); + case ERROR_BUSY: + case ERROR_PATH_BUSY: + case ERROR_SHARING_VIOLATION: + case ERROR_LOCK_VIOLATION: + throw exception_io_sharing_violation(); + case ERROR_HANDLE_DISK_FULL: + case ERROR_DISK_FULL: + throw exception_io_device_full(); + case ERROR_FILE_NOT_FOUND: + case ERROR_PATH_NOT_FOUND: + throw exception_io_not_found(); + case ERROR_BROKEN_PIPE: + case ERROR_NO_DATA: + throw exception_io_no_data(); + case ERROR_NETWORK_UNREACHABLE: + case ERROR_NETNAME_DELETED: + throw exception_io_network_not_reachable(); + case ERROR_NOT_READY: + throw exception_io_device_not_ready(); + case ERROR_INVALID_DRIVE: + throw exception_io_invalid_drive(); + case ERROR_CRC: + case ERROR_FILE_CORRUPT: + case ERROR_DISK_CORRUPT: + throw exception_io_file_corrupted(); + case ERROR_BUFFER_OVERFLOW: + throw exception_io_buffer_overflow(); + case ERROR_DISK_CHANGE: + throw exception_io_disk_change(); + case ERROR_DIR_NOT_EMPTY: + throw exception_io_directory_not_empty(); + case ERROR_INVALID_NAME: + throw exception_io_invalid_path_syntax(); + case ERROR_NO_SYSTEM_RESOURCES: + case ERROR_NONPAGED_SYSTEM_RESOURCES: + case ERROR_PAGED_SYSTEM_RESOURCES: + case ERROR_WORKING_SET_QUOTA: + case ERROR_PAGEFILE_QUOTA: + case ERROR_COMMITMENT_LIMIT: + throw exception_io("Insufficient system resources"); + case ERROR_IO_DEVICE: + throw exception_io("Device error"); + case ERROR_BAD_NETPATH: + // known to be inflicted by momentary net connectivity issues - NOT the same as exception_io_not_found + throw exception_io("Network path not found"); +#if FB2K_SUPPORT_TRANSACTED_FILESYSTEM + case ERROR_TRANSACTIONAL_OPEN_NOT_ALLOWED: + case ERROR_TRANSACTIONS_UNSUPPORTED_REMOTE: + case ERROR_RM_NOT_ACTIVE: + case ERROR_RM_METADATA_CORRUPT: + case ERROR_DIRECTORY_NOT_RM: + throw exception_io_transactions_unsupported(); + case ERROR_TRANSACTIONAL_CONFLICT: + throw exception_io_transactional_conflict(); + case ERROR_TRANSACTION_ALREADY_ABORTED: + throw exception_io_transaction_aborted(); + case ERROR_EFS_NOT_ALLOWED_IN_TRANSACTION: + throw exception_io("Transacted updates of encrypted content are not supported"); +#endif // FB2K_SUPPORT_TRANSACTED_FILESYSTEM + case ERROR_UNEXP_NET_ERR: + // QNAP threw this when messing with very long file paths and concurrent conversion, probably SMB daemon crashed + throw exception_io("Unexpected network error"); + case ERROR_NOT_SAME_DEVICE: + throw exception_io("Source and destination must be on the same device"); + case 0x80310000: + throw exception_io("Drive locked by BitLocker"); + default: + throw exception_io_win32_ex(p_code); + } +} +#endif + +t_filesize file::get_size_ex(abort_callback & p_abort) { + t_filesize temp = get_size(p_abort); + if (temp == filesize_invalid) throw exception_io_no_length(); + return temp; +} + +void file::ensure_local() { + if (is_remote()) throw exception_io_object_is_remote(); +} + +void file::ensure_seekable() { + if (!can_seek()) throw exception_io_object_not_seekable(); +} + +bool filesystem::g_is_recognized_path(const char * p_path) { + filesystem::ptr obj; + return g_get_interface(obj,p_path); +} + +t_filesize file::get_remaining(abort_callback & p_abort) { + t_filesize length = get_size_ex(p_abort); + t_filesize position = get_position(p_abort); + pfc::dynamic_assert(position <= length); + return length - position; +} + +void file::probe_remaining(t_filesize bytes, abort_callback & p_abort) { + t_filesize length = get_size(p_abort); + if (length != ~0) { + t_filesize remaining = length - get_position(p_abort); + if (remaining < bytes) throw exception_io_data_truncation(); + } +} + + +t_filesize file::g_transfer(service_ptr_t p_src,service_ptr_t p_dst,t_filesize p_bytes,abort_callback & p_abort) { + return g_transfer(pfc::implicit_cast(p_src.get_ptr()),pfc::implicit_cast(p_dst.get_ptr()),p_bytes,p_abort); +} + +void file::g_transfer_object(service_ptr_t p_src,service_ptr_t p_dst,t_filesize p_bytes,abort_callback & p_abort) { + if (p_bytes > 1024) /* don't bother on small objects */ + { + t_filesize srcFileSize = p_src->get_size(p_abort); // detect truncation + if (srcFileSize != ~0) { + t_filesize remaining = srcFileSize - p_src->get_position(p_abort); + if (p_bytes > remaining) throw exception_io_data_truncation(); + } + + t_filesize oldsize = p_dst->get_size(p_abort); // pre-resize the target file + if (oldsize != filesize_invalid) { + t_filesize newpos = p_dst->get_position(p_abort) + p_bytes; + if (newpos > oldsize) p_dst->resize(newpos ,p_abort); + } + + } + g_transfer_object(pfc::implicit_cast(p_src.get_ptr()),pfc::implicit_cast(p_dst.get_ptr()),p_bytes,p_abort); +} + + +void foobar2000_io::generate_temp_location_for_file(pfc::string_base & p_out, const char * p_origpath,const char * p_extension,const char * p_magic) { + hasher_md5_result hash; + { + auto hasher = hasher_md5::get(); + hasher_md5_state state; + hasher->initialize(state); + hasher->process(state,p_origpath,strlen(p_origpath)); + hasher->process(state,p_extension,strlen(p_extension)); + hasher->process(state,p_magic,strlen(p_magic)); + hash = hasher->get_result(state); + } + + p_out = p_origpath; + p_out.truncate(p_out.scan_filename()); + p_out += "temp-"; + p_out += pfc::format_hexdump(hash.m_data,sizeof(hash.m_data),""); + p_out += "."; + p_out += p_extension; +} + +t_filesize file::skip_seek(t_filesize p_bytes,abort_callback & p_abort) { + const t_filesize size = get_size(p_abort); + if (size != filesize_invalid) { + const t_filesize position = get_position(p_abort); + const t_filesize toskip = pfc::min_t( p_bytes, size - position ); + seek(position + toskip,p_abort); + return toskip; + } else { + this->seek_ex( p_bytes, seek_from_current, p_abort ); + return p_bytes; + } +} + +t_filesize file::skip(t_filesize p_bytes,abort_callback & p_abort) { + if (p_bytes > 1024 && can_seek()) { + const t_filesize size = get_size(p_abort); + if (size != filesize_invalid) { + const t_filesize position = get_position(p_abort); + const t_filesize toskip = pfc::min_t( p_bytes, size - position ); + seek(position + toskip,p_abort); + return toskip; + } + } + return stream_reader::skip(p_bytes,p_abort); +} + +bool foobar2000_io::is_native_filesystem( const char * p_fspath ) { + return _extract_native_path_ptr( p_fspath ); +} + +bool foobar2000_io::_extract_native_path_ptr(const char * & p_fspath) { + static const char header[] = "file://"; static const t_size headerLen = 7; + if (strncmp(p_fspath,header,headerLen) != 0) return false; + p_fspath += headerLen; + return true; +} +bool foobar2000_io::extract_native_path(const char * p_fspath,pfc::string_base & p_native) { + if (!_extract_native_path_ptr(p_fspath)) return false; + p_native = p_fspath; + return true; +} + +bool foobar2000_io::extract_native_path_ex(const char * p_fspath, pfc::string_base & p_native) { + if (!_extract_native_path_ptr(p_fspath)) return false; + if (p_fspath[0] != '\\' || p_fspath[1] != '\\') { + p_native = "\\\\?\\"; + p_native += p_fspath; + } else { + p_native = p_fspath; + } + return true; +} + +bool foobar2000_io::extract_native_path_archive_aware(const char * in, pfc::string_base & out) { + if (foobar2000_io::extract_native_path(in, out)) return true; + if (archive_impl::g_is_unpack_path(in)) { + pfc::string8 arc, dummy; + if (archive_impl::g_parse_unpack_path(in, arc, dummy)) { + return foobar2000_io::extract_native_path(arc, out); + } + } + return false; +} + +pfc::string stream_reader::read_string(abort_callback & p_abort) { + t_uint32 len; + read_lendian_t(len,p_abort); + return read_string_ex(len,p_abort); +} +pfc::string stream_reader::read_string_ex(t_size p_len,abort_callback & p_abort) { + pfc::rcptr_t temp; temp.new_t(); + read_object(temp->lock_buffer(p_len),p_len,p_abort); + temp->unlock_buffer(); + return pfc::string::t_data(temp); +} + + +void filesystem::remove_directory_content(const char * path, abort_callback & abort) { + class myCallback : public directory_callback { + public: + bool on_entry(filesystem * p_owner,abort_callback & p_abort,const char * p_url,bool p_is_subdirectory,const t_filestats & p_stats) { + if (p_is_subdirectory) p_owner->list_directory(p_url, *this, p_abort); + try { + p_owner->remove(p_url, p_abort); + } catch(exception_io_not_found) {} + return true; + } + }; + myCallback cb; + list_directory(path, cb, abort); +} +void filesystem::remove_object_recur(const char * path, abort_callback & abort) { + try { + remove_directory_content(path, abort); + } catch(exception_io_not_found) {} + remove(path, abort); +} + +void filesystem::g_remove_object_recur_timeout(const char * path, double timeout, abort_callback & abort) { + FB2K_RETRY_FILE_MOVE( g_remove_object_recur(path, abort), abort, timeout ); +} + +void filesystem::g_remove_object_recur(const char * path, abort_callback & abort) { + g_get_interface(path)->remove_object_recur(path, abort); +} + +void foobar2000_io::purgeOldFiles(const char * directory, t_filetimestamp period, abort_callback & abort) { + + class myCallback : public directory_callback { + public: + myCallback(t_filetimestamp period) : m_base(filetimestamp_from_system_timer() - period) {} + bool on_entry(filesystem * p_owner,abort_callback & p_abort,const char * p_url,bool p_is_subdirectory,const t_filestats & p_stats) { + if (!p_is_subdirectory && p_stats.m_timestamp < m_base) { + try { + filesystem::g_remove_timeout(p_url, 1, p_abort); + } catch(exception_io_not_found) {} + } + return true; + } + private: + const t_filetimestamp m_base; + }; + + myCallback cb(period); + filesystem::g_list_directory(directory, cb, abort); +} + +void stream_reader::read_string_nullterm( pfc::string_base & out, abort_callback & abort ) { + enum { bufCount = 256 }; + char buffer[bufCount]; + out.reset(); + size_t w = 0; + for(;;) { + char & c = buffer[w]; + this->read_object( &c, 1, abort ); + if (c == 0) { + out.add_string( buffer, w ); break; + } + if (++w == bufCount ) { + out.add_string( buffer, bufCount ); w = 0; + } + } +} + +t_filesize stream_reader::skip_till_eof(abort_callback & abort) { + t_filesize atOnce = 1024 * 1024; + t_filesize done = 0; + for (;; ) { + abort.check(); + t_filesize did = this->skip(atOnce, abort); + done += did; + if (did != atOnce) break; + } + return done; +} + +uint8_t stream_reader::read_byte( abort_callback & abort ) { + uint8_t b; + read_object(&b, 1, abort ); + return b; +} + +bool foobar2000_io::matchContentType(const char * fullString, const char * ourType) { + t_size lim = pfc::string_find_first(fullString, ';'); + if (lim != ~0) { + while(lim > 0 && fullString[lim-1] == ' ') --lim; + } + return pfc::stricmp_ascii_ex(fullString,lim, ourType, ~0) == 0; +} + +const char * foobar2000_io::contentTypeFromExtension( const char * ext ) { + if ( pfc::stringEqualsI_ascii( ext, "mp3" ) ) return "audio/mpeg"; + if ( pfc::stringEqualsI_ascii( ext, "flac" ) ) return "audio/flac"; + if ( pfc::stringEqualsI_ascii( ext, "mp4" ) ) return "application/mp4"; // We don't know if it's audio-only or other. + if ( pfc::stringEqualsI_ascii( ext, "m4a" ) ) return "audio/mp4"; + if ( pfc::stringEqualsI_ascii( ext, "mpc" ) ) return "audio/musepack"; + if ( pfc::stringEqualsI_ascii( ext, "ogg" ) ) return "audio/ogg"; + if ( pfc::stringEqualsI_ascii( ext, "opus" ) ) return "audio/opus"; + if ( pfc::stringEqualsI_ascii( ext, "wav" ) ) return "audio/vnd.wave"; + if ( pfc::stringEqualsI_ascii( ext, "wv" ) ) return "audio/wavpack"; + if ( pfc::stringEqualsI_ascii( ext, "txt" ) || pfc::stringEqualsI_ascii( ext, "cue" ) || pfc::stringEqualsI_ascii( ext, "log" ) ) return "text/plain"; + return "application/binary"; +} + +const char * foobar2000_io::extensionFromContentType( const char * contentType ) { + if (matchContentType_MP3( contentType )) return "mp3"; + if (matchContentType_FLAC( contentType )) return "flac"; + if (matchContentType_MP4audio( contentType)) return "m4a"; + if (matchContentType_MP4( contentType)) return "mp4"; + if (matchContentType_Musepack( contentType )) return "mpc"; + if (matchContentType_Ogg( contentType )) return "ogg"; + if (matchContentType_Opus( contentType )) return "opus"; + if (matchContentType_WAV( contentType )) return "wav"; + if (matchContentType_WavPack( contentType )) return "wv"; + if (matchContentType(contentType, "image/jpeg")) return "jpg"; + if (matchContentType(contentType, "image/png")) return "png"; + return ""; +} + +bool foobar2000_io::matchContentType_MP3( const char * type) { + return matchContentType(type,"audio/mp3") || matchContentType(type,"audio/mpeg") || matchContentType(type,"audio/mpg") || matchContentType(type,"audio/x-mp3") || matchContentType(type,"audio/x-mpeg") || matchContentType(type,"audio/x-mpg"); +} +bool foobar2000_io::matchContentType_MP4( const char * type ) { + return matchContentType(type, "audio/mp4") || matchContentType(type, "audio/x-mp4") + || matchContentType(type, "video/mp4") || matchContentType(type, "video/x-mp4") + || matchContentType(type, "application/mp4") || matchContentType(type, "application/x-mp4"); + +} +bool foobar2000_io::matchContentType_MP4audio( const char * type ) { + return matchContentType(type, "audio/mp4") || matchContentType(type, "audio/x-mp4"); +} +bool foobar2000_io::matchContentType_Ogg( const char * type) { + return matchContentType(type, "application/ogg") || matchContentType(type, "application/x-ogg") || matchContentType(type, "audio/ogg") || matchContentType(type, "audio/x-ogg"); +} +bool foobar2000_io::matchContentType_Opus( const char * type) { + return matchContentType(type, "audio/opus") || matchContentType(type, "audio/x-opus"); +} +bool foobar2000_io::matchContentType_WAV( const char * type ) { + return matchContentType(type, "audio/vnd.wave" ) || matchContentType(type, "audio/wav") || matchContentType(type, "audio/wave") || matchContentType(type, "audio/x-wav") || matchContentType(type, "audio/x-wave"); +} +bool foobar2000_io::matchContentType_FLAC( const char * type) { + return matchContentType(type, "audio/flac") || matchContentType(type, "audio/x-flac") || matchContentType(type, "application/flac") || matchContentType(type, "application/x-flac"); +} +bool foobar2000_io::matchContentType_WavPack( const char * type) { + return matchContentType( type, "audio/wavpack" ) || matchContentType( type, "audio/x-wavpack"); +} +bool foobar2000_io::matchContentType_Musepack( const char * type) { + return matchContentType(type,"audio/musepack") || matchContentType(type,"audio/x-musepack"); +} + +const char * foobar2000_io::afterProtocol( const char * fullString ) { + const char * s = strstr( fullString, "://" ); + if ( s != nullptr ) return s + 3; + s = strchr(fullString, ':' ); + if ( s != nullptr && s[1] != '\\' && s[1] != 0 ) return s + 1; + PFC_ASSERT(!"Should not get here"); + return fullString; +} + +bool foobar2000_io::matchProtocol(const char * fullString, const char * protocolName) { + const t_size len = strlen(protocolName); + if (pfc::stricmp_ascii_ex(fullString, len, protocolName, len) != 0) return false; + return fullString[len] == ':' && fullString[len+1] == '/' && fullString[len+2] == '/'; +} +void foobar2000_io::substituteProtocol(pfc::string_base & out, const char * fullString, const char * protocolName) { + const char * base = strstr(fullString, "://"); + if (base) { + out = protocolName; out << base; + } else { + PFC_ASSERT(!"Should not get here"); + out = fullString; + } +} + +void filesystem::move_overwrite(const char * src, const char * dst, abort_callback & abort) { + { + filesystem_v2::ptr v2; + if (v2 &= this) { + v2->move_overwrite(src, dst, abort); return; + } + } + try { + this->remove(dst, abort); + } catch (exception_io_not_found) {} + this->move(src, dst, abort); +} + +void filesystem::replace_file(const char * src, const char * dst, abort_callback & abort) { + filesystem_v2::ptr v2; + if ( v2 &= this ) { + v2->replace_file( src, dst, abort ); return; + } + move_overwrite( src, dst, abort ); +} + +void filesystem::make_directory(const char * path, abort_callback & abort, bool * didCreate) { + filesystem_v2::ptr v2; + if ( v2 &= this ) { + v2->make_directory( path, abort, didCreate ); + return; + } + bool rv = false; + try { + create_directory( path, abort ); + rv = true; + } catch(exception_io_already_exists) { + } + if (didCreate != nullptr) * didCreate = rv; +} + +bool filesystem::make_directory_check(const char * path, abort_callback & abort) { + bool rv = false; + make_directory(path, abort, &rv); + return rv; +} + +bool filesystem::directory_exists(const char * path, abort_callback & abort) { + filesystem_v2::ptr v2; + if ( v2 &= this ) { + return v2->directory_exists( path, abort ); + } + try { + directory_callback_dummy cb; + list_directory(path, cb, abort); + return true; + } catch (exception_io const &) { return false; } +} +bool filesystem::file_exists(const char * path, abort_callback & abort) { + filesystem_v2::ptr v2; + if ( v2 &= this ) { + return v2->file_exists( path, abort ); + } + try { + t_filestats stats; bool writable; + get_stats(path, stats, writable, abort ); + return true; + } catch(exception_io) { return false; } +} + +char filesystem::pathSeparator() { + filesystem_v2::ptr v2; + if ( v2 &= this ) return v2->pathSeparator(); + return '/'; +} + +void filesystem::extract_filename_ext(const char * path, pfc::string_base & outFN) { + filesystem_v2::ptr v2; + if ( v2 &= this ) { + v2->extract_filename_ext( path, outFN ); + return; + } + outFN = pfc::filename_ext_v2( path ); +} + +bool filesystem::get_parent_helper( const char * path, char separator, pfc::string_base & out ) { + auto proto = path; + path = afterProtocol(path); + + auto sep_ptr = strrchr( path, separator ); + if ( sep_ptr == path ) return false; + if ( sep_ptr >= path + 1 && sep_ptr[-1] == separator ) return false; + + out.set_string(proto, path - proto); + out.add_string(path, sep_ptr - path); + return true; +} + +bool filesystem::get_parent_path(const char * path, pfc::string_base & out) { + filesystem_v2::ptr v2; + if ( v2 &= this ) { + return v2->get_parent_path(path, out); + } + return get_parent_helper( path, '/', out ); +} + +void filesystem::read_whole_file(const char * path, mem_block_container & out, pfc::string_base & outContentType, size_t maxBytes, abort_callback & abort) { + filesystem_v2::ptr v2; + if ( v2 &= this ) { + v2->read_whole_file( path, out, outContentType, maxBytes, abort ); + return; + } + read_whole_file_fallback(path, out, outContentType, maxBytes, abort); +} + +void filesystem::read_whole_file_fallback(const char * path, mem_block_container & out, pfc::string_base & outContentType, size_t maxBytes, abort_callback & abort) { + auto f = this->openRead( path, abort, 0 ); + if (!f->get_content_type(outContentType)) outContentType = ""; + auto s64 = f->get_size( abort ); + if ( s64 == filesize_invalid ) { + // unknown length, perform streamed read + size_t done = 0, alloc = 0; + + while(alloc < maxBytes ) { + if ( alloc == 0 ) alloc = 4096; + else { + size_t next = alloc * 2; + if ( next <= alloc ) throw exception_io_data(); + alloc = next; + } + if ( alloc > maxBytes ) alloc = maxBytes; + + out.set_size( alloc ); + size_t delta = alloc - done; + size_t deltaGot = f->read( (uint8_t*) out.get_ptr() + done, delta, abort ); + PFC_ASSERT( deltaGot <= delta ); + done += deltaGot; + if ( deltaGot != delta ) { + out.set_size( done ); return; + } + } + // maxbytes reached + PFC_ASSERT( done == maxBytes ); + // corner case check + if ( f->skip(1, abort) != 0 ) throw exception_io_data(); + } else if ( s64 > maxBytes ) { + throw exception_io_data(); + } else { + size_t s = (size_t) s64; + out.set_size( s ); + if (s > 0) f->read_object( out.get_ptr(), s, abort); + } +} + +bool filesystem::is_transacted() { + filesystem_transacted::ptr p; + return ( p &= this ); +} + +void filesystem::rewrite_file(const char * path, abort_callback & abort, double opTimeout, std::function worker) { + if ( this->is_transacted() ) { + auto f = this->openWriteNew( path, abort, opTimeout ); + worker(f); + } else { + pfc::string_formatter temp(path); temp << ".new.tmp"; + try { + { + auto f = this->openWriteNew( temp, abort, opTimeout ); + worker(f); + f->flushFileBuffers_( abort ); + } + + retryOnSharingViolation(opTimeout, abort, [&] { + this->replace_file(temp, path, abort); + }); + + } catch(...) { + try { + retryOnSharingViolation(opTimeout, abort, [&] { this->remove(temp, fb2k::noAbort); } ); + } catch(...) {} + throw; + } + } +} + +void filesystem::rewrite_directory(const char * path, abort_callback & abort, double opTimeout, std::function worker) { + if ( this->is_transacted() ) { + // so simple + if ( ! this->make_directory_check( path, abort) ) { + retryFileDelete(opTimeout, abort, [&] { this->remove_directory_content(path, abort); }); + } + worker( path ); + } else { + // so complex + pfc::string8 fnNew( path ); fnNew += ".new.tmp"; + pfc::string8 fnOld( path ); fnOld += ".old.tmp"; + + if ( !this->make_directory_check( fnNew, abort ) ) { + // folder.new folder already existed? clear contents + try { + retryFileDelete(opTimeout, abort, [&] { this->remove_directory_content(fnNew, abort); }); + } catch(exception_io_not_found) {} + } + + // write to folder.new + worker( fnNew ); + + bool haveOld = false; + if ( directory_exists( path, abort ) ) { + // move folder to folder.old + if (this->directory_exists(fnOld, abort)) { + try { + retryFileDelete(opTimeout, abort, [&] { this->remove_object_recur(fnOld, abort); }); + } catch (exception_io_not_found) {} + } + try { + retryFileMove(opTimeout, abort, [&] { this->move( path, fnOld, abort ); } ) ; + haveOld = true; + } catch(exception_io_not_found) {} + } + + // move folder.new to folder + retryFileMove( opTimeout, abort, [&] { + this->move( fnNew, path, abort ); + } ); + + if ( haveOld ) { + // delete folder.old if we made one + try { + retryFileDelete( opTimeout, abort, [&] { this->remove_object_recur( fnOld, abort); } ); + } catch (exception_io_not_found) {} + } + } +} + +void filesystem_v2::list_directory(const char * p_path, directory_callback & p_out, abort_callback & p_abort) { + list_directory_ex(p_path, p_out, listMode::filesAndFolders | listMode::hidden, p_abort); +} + +void filesystem_v2::extract_filename_ext(const char * path, pfc::string_base & outFN) { + outFN = pfc::filename_ext_v2(path, this->pathSeparator() ); +} + +bool filesystem_v2::get_parent_path(const char * path, pfc::string_base & out) { + return get_parent_helper(path, pathSeparator(), out); +} + +void filesystem_v2::replace_file(const char * src, const char * dst, abort_callback & abort) { + this->move_overwrite( src, dst, abort ); +} + +void filesystem_v2::read_whole_file(const char * path, mem_block_container & out, pfc::string_base & outContentType, size_t maxBytes, abort_callback & abort) { + read_whole_file_fallback( path, out, outContentType, maxBytes, abort ); +} + +bool filesystem_v2::make_directory_check(const char * path, abort_callback & abort) { + bool rv = false; + make_directory(path, abort, &rv); + return rv; +} + + +filesystem_transacted::ptr filesystem_transacted::create( const char * pathFor ) { + service_enum_t e; + filesystem_transacted_entry::ptr p; + while(e.next(p)) { + if ( p->is_our_path( pathFor ) ) { + auto ret = p->create(pathFor); + if (ret.is_valid()) return ret; + } + } + return nullptr; +} + +bool filesystem::commit_if_transacted(abort_callback &abort) { + bool rv = false; + filesystem_transacted::ptr t; + if ( t &= this ) { + t->commit( abort ); rv = true; + } + return rv; +} + +t_filestats filesystem::get_stats(const char * path, abort_callback & abort) { + t_filestats s; bool dummy; + this->get_stats(path, s, dummy, abort); + return s; +} + +bool file_dynamicinfo_v2::get_dynamic_info(class file_info & p_out) { + t_filesize dummy = 0; + return this->get_dynamic_info_v2(p_out, dummy); +} + +void file::flushFileBuffers_(abort_callback&a) { + file_lowLevelIO::ptr f; + if ( f &= this ) f->flushFileBuffers(a); +} + +size_t file::lowLevelIO_(const GUID & guid, size_t arg1, void * arg2, size_t arg2size, abort_callback & abort) { + size_t retval = 0; + file_lowLevelIO::ptr f; + if (f &= this) retval = f->lowLevelIO(guid, arg1, arg2, arg2size, abort); + return retval; +} + +bool file_lowLevelIO::flushFileBuffers(abort_callback & abort) { + return this->lowLevelIO( guid_flushFileBuffers, 0, nullptr, 0, abort) != 0; +} + +bool file_lowLevelIO::getFileTimes(filetimes_t & out, abort_callback & a) { + return this->lowLevelIO(guid_getFileTimes, 0, &out, sizeof(out), a) != 0; +} + +bool file_lowLevelIO::setFileTimes(filetimes_t const & in, abort_callback & a) { + return this->lowLevelIO(guid_setFileTimes, 0, (void*)&in, sizeof(in), a) != 0; +} + +bool file::g_copy_creation_time(service_ptr_t from, service_ptr_t to, abort_callback& a) { + file_lowLevelIO::ptr llFrom, llTo; + bool rv = false; + if (llTo &= to) { + if (llFrom &= from) { + file_lowLevelIO::filetimes_t filetimes; + if (llFrom->getFileTimes(filetimes, a)) { + if (filetimes.creation != filetimestamp_invalid) { + file_lowLevelIO::filetimes_t ft2; + ft2.creation = filetimes.creation; + rv = llTo->setFileTimes(ft2, a); + } + } + } + } + return rv; +} +bool file::g_copy_timestamps(file::ptr from, file::ptr to, abort_callback& a) { + file_lowLevelIO::ptr llFrom, llTo; + if ( llTo &= to ) { + if (llFrom &= from) { + file_lowLevelIO::filetimes_t filetimes = {}; + if (llFrom->getFileTimes(filetimes, a)) { + return llTo->setFileTimes(filetimes, a); + } + } + file_lowLevelIO::filetimes_t filetimes = {}; + filetimes.lastWrite = from->get_timestamp(a); + if ( filetimes.lastWrite != filetimestamp_invalid ) { + return llTo->setFileTimes(filetimes, a); + } + } + return false; +} diff --git a/foobar2000/SDK/filesystem.h b/foobar2000/SDK/filesystem.h new file mode 100644 index 0000000..e13e61c --- /dev/null +++ b/foobar2000/SDK/filesystem.h @@ -0,0 +1,837 @@ +#pragma once +class file_info; +class mem_block_container; + +//! Contains various I/O related structures and interfaces. + +namespace foobar2000_io +{ + //! Type used for file size related variables. + typedef t_uint64 t_filesize; + //! Type used for file size related variables when a signed value is needed. + typedef t_int64 t_sfilesize; + //! Type used for file timestamp related variables. 64-bit value representing the number of 100-nanosecond intervals since January 1, 1601; 0 for invalid/unknown time. + typedef t_uint64 t_filetimestamp; + //! Invalid/unknown file timestamp constant. Also see: t_filetimestamp. + const t_filetimestamp filetimestamp_invalid = 0; + //! Invalid/unknown file size constant. Also see: t_filesize. + static const t_filesize filesize_invalid = (t_filesize)(~0); + + static const t_filetimestamp filetimestamp_1second_increment = 10000000; + + //! Generic I/O error. Root class for I/O failure exception. See relevant default message for description of each derived exception class. + PFC_DECLARE_EXCEPTION(exception_io, pfc::exception,"I/O error"); + //! Object not found. + PFC_DECLARE_EXCEPTION(exception_io_not_found, exception_io,"Object not found"); + //! Access denied. \n + //! Special Windows note: this MAY be thrown instead of exception_io_sharing_violation by operations that rename/move files due to Win32 MoveFile() bugs. + PFC_DECLARE_EXCEPTION(exception_io_denied, exception_io,"Access denied"); + //! Access denied. + PFC_DECLARE_EXCEPTION(exception_io_denied_readonly, exception_io_denied,"File is read-only"); + //! Unsupported format or corrupted file (unexpected data encountered). + PFC_DECLARE_EXCEPTION(exception_io_data, exception_io,"Unsupported format or corrupted file"); + //! Unsupported format or corrupted file (truncation encountered). + PFC_DECLARE_EXCEPTION(exception_io_data_truncation, exception_io_data,"Unsupported format or corrupted file"); + //! Unsupported format (a subclass of "unsupported format or corrupted file" exception). + PFC_DECLARE_EXCEPTION(exception_io_unsupported_format, exception_io_data,"Unsupported file format"); + //! Decode error - subsong index out of expected range + PFC_DECLARE_EXCEPTION(exception_io_bad_subsong_index, exception_io_data,"Unexpected subsong index"); + //! Object is remote, while specific operation is supported only for local objects. + PFC_DECLARE_EXCEPTION(exception_io_object_is_remote, exception_io,"This operation is not supported on remote objects"); + //! Sharing violation. + PFC_DECLARE_EXCEPTION(exception_io_sharing_violation, exception_io,"File is already in use"); + //! Device full. + PFC_DECLARE_EXCEPTION(exception_io_device_full, exception_io,"Device full"); + //! Attempt to seek outside valid range. + PFC_DECLARE_EXCEPTION(exception_io_seek_out_of_range, exception_io,"Seek offset out of range"); + //! This operation requires a seekable object. + PFC_DECLARE_EXCEPTION(exception_io_object_not_seekable, exception_io,"Object is not seekable"); + //! This operation requires an object with known length. + PFC_DECLARE_EXCEPTION(exception_io_no_length, exception_io,"Length of object is unknown"); + //! Invalid path. + PFC_DECLARE_EXCEPTION(exception_io_no_handler_for_path, exception_io,"Invalid path"); + //! Object already exists. + PFC_DECLARE_EXCEPTION(exception_io_already_exists, exception_io,"Object already exists"); + //! Pipe error. + PFC_DECLARE_EXCEPTION(exception_io_no_data, exception_io,"The process receiving or sending data has terminated"); + //! Network not reachable. + PFC_DECLARE_EXCEPTION(exception_io_network_not_reachable,exception_io,"Network not reachable"); + //! Media is write protected. + PFC_DECLARE_EXCEPTION(exception_io_write_protected, exception_io_denied,"The media is write protected"); + //! File is corrupted. This indicates filesystem call failure, not actual invalid data being read by the app. + PFC_DECLARE_EXCEPTION(exception_io_file_corrupted, exception_io,"The file is corrupted"); + //! The disc required for requested operation is not available. + PFC_DECLARE_EXCEPTION(exception_io_disk_change, exception_io,"Disc not available"); + //! The directory is not empty. + PFC_DECLARE_EXCEPTION(exception_io_directory_not_empty, exception_io,"Directory not empty"); + //! A network connectivity error + PFC_DECLARE_EXCEPTION( exception_io_net, exception_io, "Network error"); + //! A network security error + PFC_DECLARE_EXCEPTION( exception_io_net_security, exception_io_net, "Network security error"); + //! A network connectivity error, specifically a DNS query failure + PFC_DECLARE_EXCEPTION( exception_io_dns, exception_io_net, "DNS error"); + //! The path does not point to a directory. + PFC_DECLARE_EXCEPTION(exception_io_not_directory, exception_io, "Not a directory"); + + //! Stores file stats (size and timestamp). + struct t_filestats { + //! Size of the file. + t_filesize m_size; + //! Time of last file modification. + t_filetimestamp m_timestamp; + + inline bool operator==(const t_filestats & param) const {return m_size == param.m_size && m_timestamp == param.m_timestamp;} + inline bool operator!=(const t_filestats & param) const {return m_size != param.m_size || m_timestamp != param.m_timestamp;} + }; + + //! Invalid/unknown file stats constant. See: t_filestats. + static const t_filestats filestats_invalid = {filesize_invalid,filetimestamp_invalid}; + +#ifdef _WIN32 + PFC_NORETURN void exception_io_from_win32(DWORD p_code); +#define WIN32_IO_OP(X) {SetLastError(NO_ERROR); if (!(X)) exception_io_from_win32(GetLastError());} + + // SPECIAL WORKAROUND: throw "file is read-only" rather than "access denied" where appropriate + PFC_NORETURN void win32_file_write_failure(DWORD p_code, const char * path); +#endif + + //! Generic interface to read data from a nonseekable stream. Also see: stream_writer, file. \n + //! Error handling: all methods may throw exception_io or one of derivatives on failure; exception_aborted when abort_callback is signaled. + class NOVTABLE stream_reader { + public: + //! Attempts to reads specified number of bytes from the stream. + //! @param p_buffer Receives data being read. Must have at least p_bytes bytes of space allocated. + //! @param p_bytes Number of bytes to read. + //! @param p_abort abort_callback object signaling user aborting the operation. + //! @returns Number of bytes actually read. May be less than requested when EOF was reached. + virtual t_size read(void * p_buffer,t_size p_bytes,abort_callback & p_abort) = 0; + //! Reads specified number of bytes from the stream. If requested amount of bytes can't be read (e.g. EOF), throws exception_io_data_truncation. + //! @param p_buffer Receives data being read. Must have at least p_bytes bytes of space allocated. + //! @param p_bytes Number of bytes to read. + //! @param p_abort abort_callback object signaling user aborting the operation. + virtual void read_object(void * p_buffer,t_size p_bytes,abort_callback & p_abort); + //! Attempts to skip specified number of bytes in the stream. + //! @param p_bytes Number of bytes to skip. + //! @param p_abort abort_callback object signaling user aborting the operation. + //! @returns Number of bytes actually skipped, May be less than requested when EOF was reached. + virtual t_filesize skip(t_filesize p_bytes,abort_callback & p_abort); + //! Skips specified number of bytes in the stream. If requested amount of bytes can't be skipped (e.g. EOF), throws exception_io_data_truncation. + //! @param p_bytes Number of bytes to skip. + //! @param p_abort abort_callback object signaling user aborting the operation. + virtual void skip_object(t_filesize p_bytes,abort_callback & p_abort); + + //! Helper template built around read_object. Reads single raw object from the stream. + //! @param p_object Receives object read from the stream on success. + //! @param p_abort abort_callback object signaling user aborting the operation. + template inline void read_object_t(T& p_object,abort_callback & p_abort) {pfc::assert_raw_type(); read_object(&p_object,sizeof(p_object),p_abort);} + //! Helper template built around read_object. Reads single raw object from the stream; corrects byte order assuming stream uses little endian order. + //! @param p_object Receives object read from the stream on success. + //! @param p_abort abort_callback object signaling user aborting the operation. + template inline void read_lendian_t(T& p_object,abort_callback & p_abort) {read_object_t(p_object,p_abort); byte_order::order_le_to_native_t(p_object);} + //! Helper template built around read_object. Reads single raw object from the stream; corrects byte order assuming stream uses big endian order. + //! @param p_object Receives object read from the stream on success. + //! @param p_abort abort_callback object signaling user aborting the operation. + template inline void read_bendian_t(T& p_object,abort_callback & p_abort) {read_object_t(p_object,p_abort); byte_order::order_be_to_native_t(p_object);} + + //! Helper function; reads a string (with a 32-bit header indicating length in bytes followed by UTF-8 encoded data without a null terminator). + void read_string(pfc::string_base & p_out,abort_callback & p_abort); + //! Helper function; alternate way of storing strings; assumes string takes space up to end of stream. + void read_string_raw(pfc::string_base & p_out,abort_callback & p_abort); + //! Helper function; reads a string (with a 32-bit header indicating length in bytes followed by UTF-8 encoded data without a null terminator). + pfc::string read_string(abort_callback & p_abort); + + //! Helper function; reads a string of specified length from the stream. + void read_string_ex(pfc::string_base & p_out,t_size p_bytes,abort_callback & p_abort); + //! Helper function; reads a string of specified length from the stream. + pfc::string read_string_ex(t_size p_len,abort_callback & p_abort); + + void read_string_nullterm( pfc::string_base & out, abort_callback & abort ); + + t_filesize skip_till_eof(abort_callback & abort); + + template + void read_till_eof(t_outArray & out, abort_callback & abort) { + pfc::assert_raw_type(); + const t_size itemWidth = sizeof(typename t_outArray::t_item); + out.set_size(pfc::max_t(1,256 / itemWidth)); t_size done = 0; + for(;;) { + t_size delta = out.get_size() - done; + t_size delta2 = read(out.get_ptr() + done, delta * itemWidth, abort ) / itemWidth; + done += delta2; + if (delta2 != delta) break; + out.set_size(out.get_size() << 1); + } + out.set_size(done); + } + + uint8_t read_byte( abort_callback & abort ); + protected: + stream_reader() {} + ~stream_reader() {} + }; + + + //! Generic interface to write data to a nonseekable stream. Also see: stream_reader, file. \n + //! Error handling: all methods may throw exception_io or one of derivatives on failure; exception_aborted when abort_callback is signaled. + class NOVTABLE stream_writer { + public: + //! Writes specified number of bytes from specified buffer to the stream. + //! @param p_buffer Buffer with data to write. Must contain at least p_bytes bytes. + //! @param p_bytes Number of bytes to write. + //! @param p_abort abort_callback object signaling user aborting the operation. + virtual void write(const void * p_buffer,t_size p_bytes,abort_callback & p_abort) = 0; + + //! Helper. Same as write(), provided for consistency. + inline void write_object(const void * p_buffer,t_size p_bytes,abort_callback & p_abort) {write(p_buffer,p_bytes,p_abort);} + + //! Helper template. Writes single raw object to the stream. + //! @param p_object Object to write. + //! @param p_abort abort_callback object signaling user aborting the operation. + template inline void write_object_t(const T & p_object, abort_callback & p_abort) {pfc::assert_raw_type(); write_object(&p_object,sizeof(p_object),p_abort);} + //! Helper template. Writes single raw object to the stream; corrects byte order assuming stream uses little endian order. + //! @param p_object Object to write. + //! @param p_abort abort_callback object signaling user aborting the operation. + template inline void write_lendian_t(const T & p_object, abort_callback & p_abort) {T temp = p_object; byte_order::order_native_to_le_t(temp); write_object_t(temp,p_abort);} + //! Helper template. Writes single raw object to the stream; corrects byte order assuming stream uses big endian order. + //! @param p_object Object to write. + //! @param p_abort abort_callback object signaling user aborting the operation. + template inline void write_bendian_t(const T & p_object, abort_callback & p_abort) {T temp = p_object; byte_order::order_native_to_be_t(temp); write_object_t(temp,p_abort);} + + //! Helper function; writes string (with 32-bit header indicating length in bytes followed by UTF-8 encoded data without null terminator). + void write_string(const char * p_string,abort_callback & p_abort); + void write_string(const char * p_string,t_size p_len,abort_callback & p_abort); + + template + void write_string(const T& val,abort_callback & p_abort) {write_string(pfc::stringToPtr(val),p_abort);} + + //! Helper function; writes raw string to the stream, with no length info or null terminators. + void write_string_raw(const char * p_string,abort_callback & p_abort); + + void write_string_nullterm( const char * p_string, abort_callback & p_abort) {this->write( p_string, strlen(p_string)+1, p_abort); } + protected: + stream_writer() {} + ~stream_writer() {} + }; + + //! A class providing abstraction for an open file object, with reading/writing/seeking methods. See also: stream_reader, stream_writer (which it inherits read/write methods from). \n + //! Error handling: all methods may throw exception_io or one of derivatives on failure; exception_aborted when abort_callback is signaled. + class NOVTABLE file : public service_base, public stream_reader, public stream_writer { + public: + + //! Seeking mode constants. Note: these are purposedly defined to same values as standard C SEEK_* constants + enum t_seek_mode { + //! Seek relative to beginning of file (same as seeking to absolute offset). + seek_from_beginning = 0, + //! Seek relative to current position. + seek_from_current = 1, + //! Seek relative to end of file. + seek_from_eof = 2, + }; + + //! Retrieves size of the file. + //! @param p_abort abort_callback object signaling user aborting the operation. + //! @returns File size on success; filesize_invalid if unknown (nonseekable stream etc). + virtual t_filesize get_size(abort_callback & p_abort) = 0; + + + //! Retrieves read/write cursor position in the file. In case of non-seekable stream, this should return number of bytes read so far since open/reopen call. + //! @param p_abort abort_callback object signaling user aborting the operation. + //! @returns Read/write cursor position + virtual t_filesize get_position(abort_callback & p_abort) = 0; + + //! Resizes file to the specified size in bytes. + //! @param p_abort abort_callback object signaling user aborting the operation. + virtual void resize(t_filesize p_size,abort_callback & p_abort) = 0; + + //! Sets read/write cursor position to the specified offset. Throws exception_io_seek_out_of_range if the specified offset is outside the valid range. + //! @param p_position position to seek to. + //! @param p_abort abort_callback object signaling user aborting the operation. + virtual void seek(t_filesize p_position,abort_callback & p_abort) = 0; + + //! Same as seek() but throws exception_io_data instead of exception_io_seek_out_of_range. + void seek_probe(t_filesize p_position, abort_callback & p_abort); + + //! Sets read/write cursor position to the specified offset; extended form allowing seeking relative to current position or to end of file. + //! @param p_position Position to seek to; interpretation of this value depends on p_mode parameter. + //! @param p_mode Seeking mode; see t_seek_mode enum values for further description. + //! @param p_abort abort_callback object signaling user aborting the operation. + virtual void seek_ex(t_sfilesize p_position,t_seek_mode p_mode,abort_callback & p_abort); + + //! Returns whether the file is seekable or not. If can_seek() returns false, all seek() or seek_ex() calls will fail; reopen() is still usable on nonseekable streams. + virtual bool can_seek() = 0; + + //! Retrieves mime type of the file. + //! @param p_out Receives content type string on success. + virtual bool get_content_type(pfc::string_base & p_out) = 0; + + //! Hint, returns whether the file is already fully buffered into memory. + virtual bool is_in_memory() {return false;} + + //! Optional, called by owner thread before sleeping. + //! @param p_abort abort_callback object signaling user aborting the operation. + virtual void on_idle(abort_callback & p_abort) {(void)p_abort;} + + //! Retrieves last modification time of the file. + //! @param p_abort abort_callback object signaling user aborting the operation. + //! @returns Last modification time o fthe file; filetimestamp_invalid if N/A. + virtual t_filetimestamp get_timestamp(abort_callback & p_abort) {(void)p_abort;return filetimestamp_invalid;} + + //! Resets non-seekable stream, or seeks to zero on seekable file. + //! @param p_abort abort_callback object signaling user aborting the operation. + virtual void reopen(abort_callback & p_abort) = 0; + + //! Indicates whether the file is a remote resource and non-sequential access may be slowed down by lag. This is typically returns to true on non-seekable sources but may also return true on seekable sources indicating that seeking is supported but will be relatively slow. + virtual bool is_remote() = 0; + + //! Retrieves file stats structure. Uses get_size() and get_timestamp(). + t_filestats get_stats(abort_callback & p_abort); + + //! Returns whether read/write cursor position is at the end of file. + bool is_eof(abort_callback & p_abort); + + //! Truncates file to specified size (while preserving read/write cursor position if possible); uses set_eof(). + void truncate(t_filesize p_position,abort_callback & p_abort); + + //! Truncates the file at current read/write cursor position. + void set_eof(abort_callback & p_abort) {resize(get_position(p_abort),p_abort);} + + + //! Helper; retrieves size of the file. If size is not available (get_size() returns filesize_invalid), throws exception_io_no_length. + t_filesize get_size_ex(abort_callback & p_abort); + + //! Helper; retrieves amount of bytes between read/write cursor position and end of file. Fails when length can't be determined. + t_filesize get_remaining(abort_callback & p_abort); + + //! Security helper; fails early with exception_io_data_truncation if it is not possible to read this amount of bytes from this file at this position. + void probe_remaining(t_filesize bytes, abort_callback & p_abort); + + //! Helper; throws exception_io_object_not_seekable if file is not seekable. + void ensure_seekable(); + + //! Helper; throws exception_io_object_is_remote if the file is remote. + void ensure_local(); + + //! Helper; transfers specified number of bytes between streams. + //! @returns number of bytes actually transferred. May be less than requested if e.g. EOF is reached. + static t_filesize g_transfer(stream_reader * src,stream_writer * dst,t_filesize bytes,abort_callback & p_abort); + //! Helper; transfers specified number of bytes between streams. Throws exception if requested number of bytes could not be read (EOF). + static void g_transfer_object(stream_reader * src,stream_writer * dst,t_filesize bytes,abort_callback & p_abort); + //! Helper; transfers entire file content from one file to another, erasing previous content. + static void g_transfer_file(const service_ptr_t & p_from,const service_ptr_t & p_to,abort_callback & p_abort); + //! Helper; transfers file modification times from one file to another, if supported by underlying objects. Returns true on success, false if the operation doesn't appear to be supported. + static bool g_copy_timestamps(service_ptr_t from, service_ptr_t to, abort_callback& abort); + static bool g_copy_creation_time(service_ptr_t from, service_ptr_t to, abort_callback& abort); + + //! Helper; improved performance over g_transfer on streams (avoids disk fragmentation when transferring large blocks). + static t_filesize g_transfer(service_ptr_t p_src,service_ptr_t p_dst,t_filesize p_bytes,abort_callback & p_abort); + //! Helper; improved performance over g_transfer_file on streams (avoids disk fragmentation when transferring large blocks). + static void g_transfer_object(service_ptr_t p_src,service_ptr_t p_dst,t_filesize p_bytes,abort_callback & p_abort); + + //! file_lowLevelIO wrapper + void flushFileBuffers_(abort_callback &); + //! file_lowLevelIO wrapper + size_t lowLevelIO_(const GUID & guid, size_t arg1, void * arg2, size_t arg2size, abort_callback & abort); + + t_filesize skip(t_filesize p_bytes,abort_callback & p_abort); + t_filesize skip_seek(t_filesize p_bytes,abort_callback & p_abort); + + FB2K_MAKE_SERVICE_INTERFACE(file,service_base); + }; + + typedef service_ptr_t file_ptr; + + //! Extension for shoutcast dynamic metadata handling. + class file_dynamicinfo : public file { + FB2K_MAKE_SERVICE_INTERFACE(file_dynamicinfo,file); + public: + //! Retrieves "static" info that doesn't change in the middle of stream, such as station names etc. Returns true on success; false when static info is not available. + virtual bool get_static_info(class file_info & p_out) = 0; + //! Returns whether dynamic info is available on this stream or not. + virtual bool is_dynamic_info_enabled() = 0; + //! Retrieves dynamic stream info (e.g. online stream track titles). Returns true on success, false when info has not changed since last call. + virtual bool get_dynamic_info(class file_info & p_out) = 0; + }; + + //! \since 1.4.1 + //! Extended version of file_dynamicinfo + class file_dynamicinfo_v2 : public file_dynamicinfo { + FB2K_MAKE_SERVICE_INTERFACE(file_dynamicinfo_v2, file_dynamicinfo); + public: + virtual bool get_dynamic_info_v2( class file_info & out, t_filesize & outOffset ) = 0; + protected: + // Obsolete + bool get_dynamic_info(class file_info & p_out); + }; + + //! Extension for cached file access - allows callers to know that they're dealing with a cache layer, to prevent cache duplication. + class file_cached : public file { + FB2K_MAKE_SERVICE_INTERFACE(file_cached, file); + public: + virtual size_t get_cache_block_size() = 0; + virtual void suggest_grow_cache(size_t suggestSize) = 0; + + static file::ptr g_create(service_ptr_t p_base,abort_callback & p_abort, t_size blockSize); + static void g_create(service_ptr_t & p_out,service_ptr_t p_base,abort_callback & p_abort, t_size blockSize); + + static void g_decodeInitCache(file::ptr & theFile, abort_callback & abort, size_t blockSize); + }; + + //! \since 1.5 + //! Additional service implemented by standard file object providing access to low level OS specific APIs. + class file_lowLevelIO : public service_base { + FB2K_MAKE_SERVICE_INTERFACE(file_lowLevelIO, service_base ); + public: + //! @returns 0 if the command was not recognized, a command-defined non zero value otherwise. + virtual size_t lowLevelIO( const GUID & guid, size_t arg1, void * arg2, size_t arg2size, abort_callback & abort ) = 0; + + //! Win32 FlushFileBuffers() wrapper. \n + //! Throws exception_io_denied on a file opened for reading. \n + //! No arguments are defined. \n + //! Returns 1 if handled, 0 if unsupported. + static const GUID guid_flushFileBuffers; + //! Retrieves file creation / last access / last write times. \n + //! Parameters: arg2 points to a filetimes_t struct to receive the data; arg2size must be set to sizeof(filetimes_t). \n + //! If the filesystem does not support a specific portion of the information, relevant struct member will be set to filetimestamp_invalid. \n + //! Returns 1 if handled, 0 if unsupported. + static const GUID guid_getFileTimes; + //! Sets file creation / last access / last write times. \n + //! Parameters: arg2 points to a filetimes_t struct holding the new data; arg2size must be set to sizeof(filetimes_t). \n + //! Individual members of the filetimes_t struct can be set to filetimestamp_invalid, if not all of the values are to be altered on the file. \n + //! Returns 1 if handled, 0 if unsupported. + static const GUID guid_setFileTimes; + + //! Struct to be used with guid_getFileTimes / guid_setFileTimes. + struct filetimes_t { + t_filetimestamp creation = filetimestamp_invalid; + t_filetimestamp lastAccess = filetimestamp_invalid; + t_filetimestamp lastWrite = filetimestamp_invalid; + }; + + //! Helper + bool flushFileBuffers(abort_callback &); + //! Helper + bool getFileTimes( filetimes_t & out, abort_callback &); + //! Helper + bool setFileTimes( filetimes_t const & in, abort_callback &); + + }; + + //! Implementation helper - contains dummy implementations of methods that modify the file + template class file_readonly_t : public t_base { + public: + void resize(t_filesize p_size,abort_callback & p_abort) {throw exception_io_denied();} + void write(const void * p_buffer,t_size p_bytes,abort_callback & p_abort) {throw exception_io_denied();} + }; + typedef file_readonly_t file_readonly; + + class file_streamstub : public file_readonly { + public: + t_size read(void *,t_size,abort_callback &) {return 0;} + t_filesize get_size(abort_callback &) {return filesize_invalid;} + t_filesize get_position(abort_callback &) {return 0;} + bool get_content_type(pfc::string_base &) {return false;} + bool is_remote() {return true;} + void reopen(abort_callback&) {} + void seek(t_filesize,abort_callback &) {throw exception_io_object_not_seekable();} + bool can_seek() {return false;} + }; + + class filesystem; + + class NOVTABLE directory_callback { + public: + //! @returns true to continue enumeration, false to abort. + virtual bool on_entry(filesystem * p_owner,abort_callback & p_abort,const char * p_url,bool p_is_subdirectory,const t_filestats & p_stats)=0; + }; + + + //! Entrypoint service for all filesystem operations.\n + //! Implementation: standard implementations for local filesystem etc are provided by core.\n + //! Instantiation: use static helper functions rather than calling filesystem interface methods directly, e.g. filesystem::g_open() to open a file. + class NOVTABLE filesystem : public service_base { + FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(filesystem); + public: + //! Enumeration specifying how to open a file. See: filesystem::open(), filesystem::g_open(). + typedef uint32_t t_open_mode; + enum { + //! Opens an existing file for reading; if the file does not exist, the operation will fail. + open_mode_read, + //! Opens an existing file for writing; if the file does not exist, the operation will fail. + open_mode_write_existing, + //! Opens a new file for writing; if the file exists, its contents will be wiped. + open_mode_write_new, + + open_mode_mask = 0xFF, + }; + + virtual bool get_canonical_path(const char * p_path,pfc::string_base & p_out)=0; + virtual bool is_our_path(const char * p_path)=0; + virtual bool get_display_path(const char * p_path,pfc::string_base & p_out)=0; + + virtual void open(service_ptr_t & p_out,const char * p_path, t_open_mode p_mode,abort_callback & p_abort)=0; + virtual void remove(const char * p_path,abort_callback & p_abort)=0; + //! Moves/renames a file. Will fail if the destination file already exists. \n + //! Note that this function may throw exception_io_denied instead of exception_io_sharing_violation when the file is momentarily in use, due to bugs in Windows MoveFile() API. There is no legitimate way for us to distinguish between the two scenarios. + virtual void move(const char * p_src,const char * p_dst,abort_callback & p_abort)=0; + //! Queries whether a file at specified path belonging to this filesystem is a remote object or not. + virtual bool is_remote(const char * p_src) = 0; + + //! Retrieves stats of a file at specified path. + virtual void get_stats(const char * p_path,t_filestats & p_stats,bool & p_is_writeable,abort_callback & p_abort) = 0; + //! Helper + t_filestats get_stats( const char * path, abort_callback & abort ); + + virtual bool relative_path_create(const char * file_path,const char * playlist_path,pfc::string_base & out) {return false;} + virtual bool relative_path_parse(const char * relative_path,const char * playlist_path,pfc::string_base & out) {return false;} + + //! Creates a directory. + virtual void create_directory(const char * p_path,abort_callback & p_abort) = 0; + + virtual void list_directory(const char * p_path,directory_callback & p_out,abort_callback & p_abort)=0; + + //! Hint; returns whether this filesystem supports mime types. \n + //! When this returns false, all file::get_content_type() calls on files opened thru this filesystem implementation will return false; otherwise, file::get_content_type() calls may return true depending on the file. + virtual bool supports_content_types() = 0; + + static void g_get_canonical_path(const char * path,pfc::string_base & out); + static void g_get_display_path(const char * path,pfc::string_base & out); + //! Extracts the native filesystem path, sets out to the input path if native path cannot be extracted so the output is always set. + //! @returns True if native path was extracted successfully, false otherwise (but output is set anyway). + static bool g_get_native_path( const char * path, pfc::string_base & out); + + static bool g_get_interface(service_ptr_t & p_out,const char * path);//path is AFTER get_canonical_path + static filesystem::ptr g_get_interface(const char * path);// throws exception_io_no_handler_for_path on failure + static filesystem::ptr get( const char * path ) { return g_get_interface(path); } // shortened + static bool g_is_remote(const char * p_path);//path is AFTER get_canonical_path + static bool g_is_recognized_and_remote(const char * p_path);//path is AFTER get_canonical_path + static bool g_is_remote_safe(const char * p_path) {return g_is_recognized_and_remote(p_path);} + static bool g_is_remote_or_unrecognized(const char * p_path); + static bool g_is_recognized_path(const char * p_path); + + //! Opens file at specified path, with specified access privileges. + static void g_open(service_ptr_t & p_out,const char * p_path,t_open_mode p_mode,abort_callback & p_abort); + //! Attempts to open file at specified path; if the operation fails with sharing violation error, keeps retrying (with short sleep period between retries) for specified amount of time. + static void g_open_timeout(service_ptr_t & p_out,const char * p_path,t_open_mode p_mode,double p_timeout,abort_callback & p_abort); + static void g_open_write_new(service_ptr_t & p_out,const char * p_path,abort_callback & p_abort); + static void g_open_read(service_ptr_t & p_out,const char * path,abort_callback & p_abort) {return g_open(p_out,path,open_mode_read,p_abort);} + static void g_open_precache(service_ptr_t & p_out,const char * path,abort_callback & p_abort);//open only for precaching data (eg. will fail on http etc) + static bool g_exists(const char * p_path,abort_callback & p_abort); + static bool g_exists_writeable(const char * p_path,abort_callback & p_abort); + //! Removes file at specified path. + static void g_remove(const char * p_path,abort_callback & p_abort); + //! Attempts to remove file at specified path; if the operation fails with a sharing violation error, keeps retrying (with short sleep period between retries) for specified amount of time. + static void g_remove_timeout(const char * p_path,double p_timeout,abort_callback & p_abort); + //! Moves file from one path to another. + //! Note that this function may throw exception_io_denied instead of exception_io_sharing_violation when the file is momentarily in use, due to bugs in Windows MoveFile() API. There is no legitimate way for us to distinguish between the two scenarios. + static void g_move(const char * p_src,const char * p_dst,abort_callback & p_abort); + //! Attempts to move file from one path to another; if the operation fails with a sharing violation error, keeps retrying (with short sleep period between retries) for specified amount of time. + static void g_move_timeout(const char * p_src,const char * p_dst,double p_timeout,abort_callback & p_abort); + + static void g_link(const char * p_src,const char * p_dst,abort_callback & p_abort); + static void g_link_timeout(const char * p_src,const char * p_dst,double p_timeout,abort_callback & p_abort); + + static void g_copy(const char * p_src,const char * p_dst,abort_callback & p_abort);//needs canonical path + static void g_copy_timeout(const char * p_src,const char * p_dst,double p_timeout,abort_callback & p_abort);//needs canonical path + static void g_copy_directory(const char * p_src,const char * p_dst,abort_callback & p_abort);//needs canonical path + static void g_get_stats(const char * p_path,t_filestats & p_stats,bool & p_is_writeable,abort_callback & p_abort); + static bool g_relative_path_create(const char * p_file_path,const char * p_playlist_path,pfc::string_base & out); + static bool g_relative_path_parse(const char * p_relative_path,const char * p_playlist_path,pfc::string_base & out); + + static void g_create_directory(const char * p_path,abort_callback & p_abort); + + //! If for some bloody reason you ever need stream io compatibility, use this, INSTEAD of calling fopen() on the path string you've got; will only work with file:// (and not with http://, unpack:// or whatever) + static FILE * streamio_open(const char * p_path,const char * p_flags); + + static void g_open_temp(service_ptr_t & p_out,abort_callback & p_abort); + static void g_open_tempmem(service_ptr_t & p_out,abort_callback & p_abort); + static file::ptr g_open_tempmem(); + + static void g_list_directory(const char * p_path,directory_callback & p_out,abort_callback & p_abort);// path must be canonical + + static bool g_is_valid_directory(const char * path,abort_callback & p_abort); + static bool g_is_empty_directory(const char * path,abort_callback & p_abort); + + void remove_object_recur(const char * path, abort_callback & abort); + void remove_directory_content(const char * path, abort_callback & abort); + static void g_remove_object_recur(const char * path, abort_callback & abort); + static void g_remove_object_recur_timeout(const char * path, double timeout, abort_callback & abort); + + // Presumes both source and destination belong to this filesystem. + void copy_directory(const char * p_src, const char * p_dst, abort_callback & p_abort); + + //! Moves/renames a file, overwriting the destination atomically if exists. \n + //! Note that this function may throw exception_io_denied instead of exception_io_sharing_violation when the file is momentarily in use, due to bugs in Windows MoveFile() API. There is no legitimate way for us to distinguish between the two scenarios. + void move_overwrite( const char * src, const char * dst, abort_callback & abort); + //! Moves/renames a file, overwriting the destination atomically if exists. \n + //! Meant to retain destination attributes if feasible. Otherwise identical to move_overwrite(). \n + //! Note that this function may throw exception_io_denied instead of exception_io_sharing_violation when the file is momentarily in use, due to bugs in Windows MoveFile() API. There is no legitimate way for us to distinguish between the two scenarios. + void replace_file(const char * src, const char * dst, abort_callback & abort); + //! Create a directory, without throwing an exception if it already exists. + //! @param didCreate bool flag indicating whether a new directory was created or not. \n + //! This should be a retval, but because it's messy to obtain this information with certain APIs, the caller can opt out of receiving this information,. + void make_directory( const char * path, abort_callback & abort, bool * didCreate = nullptr ); + //! Bool retval version of make_directory(). + bool make_directory_check( const char * path, abort_callback & abort ); + + bool directory_exists(const char * path, abort_callback & abort); + bool file_exists( const char * path, abort_callback & abort ); + char pathSeparator(); + //! Extracts the filename.ext portion of the path. \n + //! The filename is ready to be presented to the user - URL decoding and such (similar to get_display_path()) is applied. + void extract_filename_ext(const char * path, pfc::string_base & outFN); + //! Retrieves the parent path. + bool get_parent_path(const char * path, pfc::string_base & out); + + file::ptr openWriteNew( const char * path, abort_callback & abort, double timeout ); + file::ptr openWriteExisting(const char * path, abort_callback & abort, double timeout); + file::ptr openRead( const char * path, abort_callback & abort, double timeout); + file::ptr openEx( const char * path, t_open_mode mode, abort_callback & abort, double timeout); + + void read_whole_file(const char * path, mem_block_container & out, pfc::string_base & outContentType, size_t maxBytes, abort_callback & abort ); + + bool is_transacted(); + bool commit_if_transacted(abort_callback &abort); + + //! Full file rewrite helper that automatically does the right thing to ensure atomic update. \n + //! If this is a transacted filesystem, a simple in-place rewrite is performed. \n + //! If this is not a transacted filesystem, your content first goes to a temporary file, which then replaces the original. \n + //! See also: filesystem_transacted. \n + //! In order to perform transacted operations, you must obtain a transacted filesystem explicitly, or get one passed down from a higher level context (example: in config_io_callback_v3). + void rewrite_file( const char * path, abort_callback & abort, double opTimeout, std::function worker ); + //! Full directory rewrite helper that automatically does the right thing to ensure atomic update. \n + //! If this is a transacted filesystem, a simple in-place rewrite is performed. \n + //! If this is not a transacted filesystem, your content first goes to a temporary folder, which then replaces the original. \n + //! It is encouraged to perform flushFileBuffers on all files accessed from within. \n + //! See also: filesystem_transacted. \n + //! In order to perform transacted operations, you must obtain a transacted filesystem explicitly, or get one passed down from a higher level context (example: in config_io_callback_v3). + void rewrite_directory(const char * path, abort_callback & abort, double opTimeout, std::function worker); + protected: + static bool get_parent_helper(const char * path, char separator, pfc::string_base & out); + void read_whole_file_fallback( const char * path, mem_block_container & out, pfc::string_base & outContentType, size_t maxBytes, abort_callback & abort ); + }; + + namespace listMode { + enum { + //! Return files + files = 1, + //! Return folders + folders = 2, + //! Return both files and flders + filesAndFolders = files | folders, + //! Return hidden files + hidden = 4, + //! Do not hand over filestats unless they come for free with folder enumeration + suppressStats = 8, + }; + } + + class filesystem_v2 : public filesystem { + FB2K_MAKE_SERVICE_INTERFACE( filesystem_v2, filesystem ) + public: + //! Moves/renames a file, overwriting the destination atomically if exists. \n + //! Note that this function may throw exception_io_denied instead of exception_io_sharing_violation when the file is momentarily in use, due to bugs in Windows MoveFile() API. There is no legitimate way for us to distinguish between the two scenarios. + virtual void move_overwrite(const char * src, const char * dst, abort_callback & abort) = 0; + //! Moves/renames a file, overwriting the destination atomically if exists. \n + //! Meant to retain destination attributes if feasible. Otherwise identical to move_overwrite(). \n + //! Note that this function may throw exception_io_denied instead of exception_io_sharing_violation when the file is momentarily in use, due to bugs in Windows MoveFile() API. There is no legitimate way for us to distinguish between the two scenarios. + virtual void replace_file(const char * src, const char * dst, abort_callback & abort); + //! Create a directory, without throwing an exception if it already exists. + //! @param didCreate bool flag indicating whether a new directory was created or not. \n + //! This should be a retval, but because it's messy to obtain this information with certain APIs, the caller can opt out of receiving this information,. + virtual void make_directory(const char * path, abort_callback & abort, bool * didCreate = nullptr) = 0; + virtual bool directory_exists(const char * path, abort_callback & abort) = 0; + virtual bool file_exists(const char * path, abort_callback & abort) = 0; + virtual char pathSeparator() = 0; + virtual void extract_filename_ext(const char * path, pfc::string_base & outFN); + virtual bool get_parent_path( const char * path, pfc::string_base & out); + virtual void list_directory_ex(const char * p_path, directory_callback & p_out, unsigned listMode, abort_callback & p_abort) = 0; + virtual void read_whole_file(const char * path, mem_block_container & out, pfc::string_base & outContentType, size_t maxBytes, abort_callback & abort); + + //! Wrapper to list_directory_ex + void list_directory( const char * p_path, directory_callback & p_out, abort_callback & p_abort ); + + bool make_directory_check(const char * path, abort_callback & abort); + }; + + class directory_callback_impl : public directory_callback + { + struct t_entry + { + pfc::string_simple m_path; + t_filestats m_stats; + t_entry(const char * p_path, const t_filestats & p_stats) : m_path(p_path), m_stats(p_stats) {} + }; + + + pfc::list_t > m_data; + bool m_recur; + + static int sortfunc(const pfc::rcptr_t & p1, const pfc::rcptr_t & p2) {return pfc::io::path::compare(p1->m_path,p2->m_path);} + public: + bool on_entry(filesystem * owner,abort_callback & p_abort,const char * url,bool is_subdirectory,const t_filestats & p_stats); + + directory_callback_impl(bool p_recur) : m_recur(p_recur) {} + t_size get_count() {return m_data.get_count();} + const char * operator[](t_size n) const {return m_data[n]->m_path;} + const char * get_item(t_size n) const {return m_data[n]->m_path;} + const t_filestats & get_item_stats(t_size n) const {return m_data[n]->m_stats;} + void sort() {m_data.sort_t(sortfunc);} + }; + + + t_filetimestamp filetimestamp_from_system_timer(); + t_filetimestamp import_DOS_time(uint32_t); + +#ifdef _WIN32 + inline t_filetimestamp import_filetimestamp(FILETIME ft) { + return *reinterpret_cast(&ft); + } +#endif + + void generate_temp_location_for_file(pfc::string_base & p_out, const char * p_origpath,const char * p_extension,const char * p_magic); + + + inline file_ptr fileOpen(const char * p_path,filesystem::t_open_mode p_mode,abort_callback & p_abort,double p_timeout) { + file_ptr temp; filesystem::g_open_timeout(temp,p_path,p_mode,p_timeout,p_abort); PFC_ASSERT(temp.is_valid()); return temp; + } + + inline file_ptr fileOpenReadExisting(const char * p_path,abort_callback & p_abort,double p_timeout = 0) { + return fileOpen(p_path,filesystem::open_mode_read,p_abort,p_timeout); + } + inline file_ptr fileOpenWriteExisting(const char * p_path,abort_callback & p_abort,double p_timeout = 0) { + return fileOpen(p_path,filesystem::open_mode_write_existing,p_abort,p_timeout); + } + inline file_ptr fileOpenWriteNew(const char * p_path,abort_callback & p_abort,double p_timeout = 0) { + return fileOpen(p_path,filesystem::open_mode_write_new,p_abort,p_timeout); + } + + template + class directory_callback_retrieveList : public directory_callback { + public: + directory_callback_retrieveList(t_list & p_list,bool p_getFiles,bool p_getSubDirectories) : m_list(p_list), m_getFiles(p_getFiles), m_getSubDirectories(p_getSubDirectories) {} + bool on_entry(filesystem * p_owner,abort_callback & p_abort,const char * p_url,bool p_is_subdirectory,const t_filestats & p_stats) { + p_abort.check(); + if (p_is_subdirectory ? m_getSubDirectories : m_getFiles) { + m_list.add_item(p_url); + } + return true; + } + private: + const bool m_getSubDirectories; + const bool m_getFiles; + t_list & m_list; + }; + template + class directory_callback_retrieveListEx : public directory_callback { + public: + directory_callback_retrieveListEx(t_list & p_files, t_list & p_directories) : m_files(p_files), m_directories(p_directories) {} + bool on_entry(filesystem * p_owner,abort_callback & p_abort,const char * p_url,bool p_is_subdirectory,const t_filestats & p_stats) { + p_abort.check(); + if (p_is_subdirectory) m_directories += p_url; + else m_files += p_url; + return true; + } + private: + t_list & m_files; + t_list & m_directories; + }; + template class directory_callback_retrieveListRecur : public directory_callback { + public: + directory_callback_retrieveListRecur(t_list & p_list) : m_list(p_list) {} + bool on_entry(filesystem * owner,abort_callback & p_abort,const char * path, bool isSubdir, const t_filestats&) { + if (isSubdir) { + try { owner->list_directory(path,*this,p_abort); } catch(exception_io) {} + } else { + m_list.add_item(path); + } + return true; + } + private: + t_list & m_list; + }; + + template + static void listFiles(const char * p_path,t_list & p_out,abort_callback & p_abort) { + directory_callback_retrieveList callback(p_out,true,false); + filesystem::g_list_directory(p_path,callback,p_abort); + } + template + static void listDirectories(const char * p_path,t_list & p_out,abort_callback & p_abort) { + directory_callback_retrieveList callback(p_out,false,true); + filesystem::g_list_directory(p_path,callback,p_abort); + } + template + static void listFilesAndDirectories(const char * p_path,t_list & p_files,t_list & p_directories,abort_callback & p_abort) { + directory_callback_retrieveListEx callback(p_files,p_directories); + filesystem::g_list_directory(p_path,callback,p_abort); + } + template + static void listFilesRecur(const char * p_path,t_list & p_out,abort_callback & p_abort) { + directory_callback_retrieveListRecur callback(p_out); + filesystem::g_list_directory(p_path,callback,p_abort); + } + + bool extract_native_path(const char * p_fspath,pfc::string_base & p_native); + bool _extract_native_path_ptr(const char * & p_fspath); + bool is_native_filesystem( const char * p_fspath ); + bool extract_native_path_ex(const char * p_fspath, pfc::string_base & p_native);//prepends \\?\ where needed + + bool extract_native_path_archive_aware( const char * fspatch, pfc::string_base & out ); + + template + pfc::string getPathDisplay(const T& source) { + const char * c = pfc::stringToPtr(source); + if ( *c == 0 ) return c; + pfc::string_formatter temp; + filesystem::g_get_display_path(c,temp); + return temp.toString(); + } + template + pfc::string getPathCanonical(const T& source) { + const char * c = pfc::stringToPtr(source); + if ( *c == 0 ) return c; + pfc::string_formatter temp; + filesystem::g_get_canonical_path(c,temp); + return temp.toString(); + } + + + bool matchContentType(const char * fullString, const char * ourType); + bool matchProtocol(const char * fullString, const char * protocolName); + const char * afterProtocol( const char * fullString ); + void substituteProtocol(pfc::string_base & out, const char * fullString, const char * protocolName); + + bool matchContentType_MP3( const char * fullString); + bool matchContentType_MP4audio( const char * fullString); + bool matchContentType_MP4( const char * fullString); + bool matchContentType_Ogg( const char * fullString); + bool matchContentType_Opus( const char * fullString); + bool matchContentType_FLAC( const char * fullString); + bool matchContentType_WavPack( const char * fullString); + bool matchContentType_WAV( const char * fullString); + bool matchContentType_Musepack( const char * fullString); + const char * extensionFromContentType( const char * contentType ); + const char * contentTypeFromExtension( const char * ext ); + + void purgeOldFiles(const char * directory, t_filetimestamp period, abort_callback & abort); + + //! \since 1.6 + class read_ahead_tools : public service_base { + FB2K_MAKE_SERVICE_COREAPI(read_ahead_tools); + public: + //! Turn any file object into asynchronous read-ahead-buffered file. + //! @param f File object to wrap. Do not call this object's method after a successful call to add_read_ahead; new file object takes over the ownership of it. + //! @param size Requested read-ahead bytes. Pass 0 to use user settings for local/remote playback. + virtual file::ptr add_read_ahead(file::ptr f, size_t size, abort_callback & aborter) = 0; + + //! A helper method to use prior to opening decoders. \n + //! May open the file if needed or leave it blank for the decoder to open. + //! @param f File object to open if needed (buffering mandated by user settings). May be valid or null prior to call. May be valid or null (no buffering) after call. + //! @param path Path to open. May be null if f is not null. At least one of f and path must be valid prior to call. + virtual void open_file_helper(file::ptr & f, const char * path, abort_callback & aborter) = 0; + }; +} + +using namespace foobar2000_io; + +#include "filesystem_helper.h" diff --git a/foobar2000/SDK/filesystem_helper.cpp b/foobar2000/SDK/filesystem_helper.cpp new file mode 100644 index 0000000..c0aa906 --- /dev/null +++ b/foobar2000/SDK/filesystem_helper.cpp @@ -0,0 +1,259 @@ +#include "foobar2000.h" + +void stream_writer_chunk::write(const void * p_buffer,t_size p_bytes,abort_callback & p_abort) { + t_size remaining = p_bytes, written = 0; + while(remaining > 0) { + t_size delta = sizeof(m_buffer) - m_buffer_state; + if (delta > remaining) delta = remaining; + memcpy(m_buffer,(const t_uint8*)p_buffer + written,delta); + written += delta; + remaining -= delta; + + if (m_buffer_state == sizeof(m_buffer)) { + m_writer->write_lendian_t((t_uint8)m_buffer_state,p_abort); + m_writer->write_object(m_buffer,m_buffer_state,p_abort); + m_buffer_state = 0; + } + } +} + +void stream_writer_chunk::flush(abort_callback & p_abort) +{ + m_writer->write_lendian_t((t_uint8)m_buffer_state,p_abort); + if (m_buffer_state > 0) { + m_writer->write_object(m_buffer,m_buffer_state,p_abort); + m_buffer_state = 0; + } +} + +/* + stream_writer * m_writer; + unsigned m_buffer_state; + unsigned char m_buffer[255]; +*/ + +t_size stream_reader_chunk::read(void * p_buffer,t_size p_bytes,abort_callback & p_abort) +{ + t_size todo = p_bytes, done = 0; + while(todo > 0) { + if (m_buffer_size == m_buffer_state) { + if (m_eof) break; + t_uint8 temp; + m_reader->read_lendian_t(temp,p_abort); + m_buffer_size = temp; + if (temp != sizeof(m_buffer)) m_eof = true; + m_buffer_state = 0; + if (m_buffer_size>0) { + m_reader->read_object(m_buffer,m_buffer_size,p_abort); + } + } + + + t_size delta = m_buffer_size - m_buffer_state; + if (delta > todo) delta = todo; + if (delta > 0) { + memcpy((unsigned char*)p_buffer + done,m_buffer + m_buffer_state,delta); + todo -= delta; + done += delta; + m_buffer_state += delta; + } + } + return done; +} + +void stream_reader_chunk::flush(abort_callback & p_abort) { + while(!m_eof) { + p_abort.check_e(); + t_uint8 temp; + m_reader->read_lendian_t(temp,p_abort); + m_buffer_size = temp; + if (temp != sizeof(m_buffer)) m_eof = true; + m_buffer_state = 0; + if (m_buffer_size>0) { + m_reader->skip_object(m_buffer_size,p_abort); + } + } +} + +/* + stream_reader * m_reader; + unsigned m_buffer_state, m_buffer_size; + bool m_eof; + unsigned char m_buffer[255]; +*/ + +void stream_reader_chunk::g_skip(stream_reader * p_stream,abort_callback & p_abort) { + stream_reader_chunk(p_stream).flush(p_abort); +} + + + +static void fileSanitySeek(file::ptr f, pfc::array_t const & content, size_t offset, abort_callback & aborter) { + const size_t readAmount = 64 * 1024; + pfc::array_staticsize_t buf; buf.set_size_discard(readAmount); + f->seek(offset, aborter); + t_filesize positionGot = f->get_position(aborter); + if (positionGot != offset) { + FB2K_console_formatter() << "File sanity: at " << offset << " reported position became " << positionGot; + throw std::runtime_error("Seek test failure"); + } + size_t did = f->read(buf.get_ptr(), readAmount, aborter); + size_t expected = pfc::min_t(readAmount, content.get_size() - offset); + if (expected != did) { + FB2K_console_formatter() << "File sanity: at " << offset << " bytes, expected read size of " << expected << ", got " << did; + if (did > expected) FB2K_console_formatter() << "Read past EOF"; + else FB2K_console_formatter() << "Premature EOF"; + throw std::runtime_error("Seek test failure"); + } + if (memcmp(buf.get_ptr(), content.get_ptr() + offset, did) != 0) { + FB2K_console_formatter() << "File sanity: data mismatch at " << offset << " - " << (offset + did) << " bytes"; + throw std::runtime_error("Seek test failure"); + } + positionGot = f->get_position(aborter); + if (positionGot != offset + did) { + FB2K_console_formatter() << "File sanity: at " << offset << "+" << did << "=" << (offset + did) << " reported position became " << positionGot; + throw std::runtime_error("Seek test failure"); + } +} + +bool fb2kFileSelfTest(file::ptr f, abort_callback & aborter) { + try { + pfc::array_t fileContent; + f->reopen(aborter); + f->read_till_eof(fileContent, aborter); + + { + t_filesize sizeClaimed = f->get_size(aborter); + if (sizeClaimed == filesize_invalid) { + FB2K_console_formatter() << "File sanity: file reports unknown size, actual size read is " << fileContent.get_size(); + } + else { + if (sizeClaimed != fileContent.get_size()) { + FB2K_console_formatter() << "File sanity: file reports size of " << sizeClaimed << ", actual size read is " << fileContent.get_size(); + throw std::runtime_error("File size mismatch"); + } + else { + FB2K_console_formatter() << "File sanity: file size check OK: " << sizeClaimed; + } + } + } + + { + FB2K_console_formatter() << "File sanity: testing N-first-bytes reads..."; + const size_t sizeUpTo = pfc::min_t(fileContent.get_size(), 1024 * 1024); + pfc::array_staticsize_t buf1; + buf1.set_size_discard(sizeUpTo); + + for (size_t w = 1; w <= sizeUpTo; w <<= 1) { + f->reopen(aborter); + size_t did = f->read(buf1.get_ptr(), w, aborter); + if (did != w) { + FB2K_console_formatter() << "File sanity: premature EOF reading first " << w << " bytes, got " << did; + throw std::runtime_error("Premature EOF"); + } + if (memcmp(fileContent.get_ptr(), buf1.get_ptr(), did) != 0) { + FB2K_console_formatter() << "File sanity: file content mismatch reading first " << w << " bytes"; + throw std::runtime_error("File content mismatch"); + } + } + } + if (f->can_seek()) { + FB2K_console_formatter() << "File sanity: testing random access..."; + + { + size_t sizeUpTo = pfc::min_t(fileContent.get_size(), 1024 * 1024); + for (size_t w = 1; w < sizeUpTo; w <<= 1) { + fileSanitySeek(f, fileContent, w, aborter); + } + fileSanitySeek(f, fileContent, fileContent.get_size(), aborter); + for (size_t w = 1; w < sizeUpTo; w <<= 1) { + fileSanitySeek(f, fileContent, fileContent.get_size() - w, aborter); + } + fileSanitySeek(f, fileContent, fileContent.get_size() / 2, aborter); + } + } + FB2K_console_formatter() << "File sanity test: all OK"; + return true; + } + catch (std::exception const & e) { + FB2K_console_formatter() << "File sanity test failure: " << e.what(); + return false; + } +} + + +namespace foobar2000_io { + void retryFileDelete(double timeout, abort_callback & a, std::function f) { + FB2K_RETRY_ON_EXCEPTION3(f(), a, timeout, exception_io_sharing_violation, exception_io_denied, exception_io_directory_not_empty); + } + void retryFileMove(double timeout, abort_callback & a, std::function f) { + FB2K_RETRY_FILE_MOVE( f(), a, timeout ); + } + void retryOnSharingViolation(double timeout, abort_callback & a, std::function f) { + FB2K_RETRY_ON_SHARING_VIOLATION(f(), a, timeout); + } + void retryOnSharingViolation(std::function f, double timeout, abort_callback & a) { + FB2K_RETRY_ON_SHARING_VIOLATION( f(), a, timeout ); + } + void listDirectory( const char * path, abort_callback & aborter, listDirectoryFunc_t func) { + listDirectoryCallbackImpl cb; cb.m_func = func; + filesystem::g_list_directory(path, cb, aborter); + } + +#ifdef _WIN32 + pfc::string8 stripParentFolders( const char * inPath ) { + PFC_ASSERT( strstr(inPath, "://" ) == nullptr || matchProtocol( inPath, "file" ) ); + size_t prefixLen = pfc::string_find_first(inPath, "://"); + if ( prefixLen != pfc_infinite ) prefixLen += 3; + else prefixLen = 0; + + pfc::chain_list_v2_t segments; + pfc::splitStringByChar(segments, inPath + prefixLen, '\\' ); + for ( auto i = segments.first(); i.is_valid(); ) { + auto n = i; ++n; + if ( i->equals( "." ) ) { + segments.remove_single( i ); + } else if ( i->equals( ".." ) ) { + auto p = i; --p; + if ( p.is_valid() ) segments.remove_single( p ); + segments.remove_single( i ); + } + i = n; + } + pfc::string8 ret; + if ( prefixLen > 0 ) ret.add_string( inPath, prefixLen ); + bool bFirst = true; + for ( auto i = segments.first(); i.is_valid(); ++ i ) { + if (!bFirst) ret << "\\"; + ret << *i; + bFirst = false; + } + return ret; + } + + + + pfc::string8 winGetVolumePath(const char * fb2kPath) { + PFC_ASSERT(matchProtocol(fb2kPath, "file")); + pfc::string8 native; + if (!filesystem::g_get_native_path(fb2kPath, native)) throw pfc::exception_invalid_params(); + + TCHAR outBuffer[MAX_PATH+1] = {}; + WIN32_IO_OP( GetVolumePathName( pfc::stringcvt::string_os_from_utf8( native ), outBuffer, MAX_PATH ) ); + return pfc::stringcvt::string_utf8_from_os( outBuffer ).get_ptr(); + } + + DWORD winVolumeFlags( const char * fb2kPath ) { + PFC_ASSERT(matchProtocol(fb2kPath, "file")); + pfc::string8 native; + if (!filesystem::g_get_native_path(fb2kPath, native)) throw pfc::exception_invalid_params(); + + TCHAR outBuffer[MAX_PATH + 1] = {}; + WIN32_IO_OP(GetVolumePathName(pfc::stringcvt::string_os_from_utf8(native), outBuffer, MAX_PATH)); + + DWORD flags = 0; + WIN32_IO_OP(GetVolumeInformation(outBuffer, nullptr, 0, nullptr, nullptr, &flags, nullptr, 0)); + return flags; + } +#endif +} diff --git a/foobar2000/SDK/filesystem_helper.h b/foobar2000/SDK/filesystem_helper.h new file mode 100644 index 0000000..214faa7 --- /dev/null +++ b/foobar2000/SDK/filesystem_helper.h @@ -0,0 +1,616 @@ +#pragma once + +#include + +namespace foobar2000_io { + typedef std::function< void (const char *, t_filestats const & , bool ) > listDirectoryFunc_t; + void listDirectory( const char * path, abort_callback & aborter, listDirectoryFunc_t func); + +#ifdef _WIN32 + pfc::string8 stripParentFolders( const char * inPath ); +#endif + + void retryOnSharingViolation( std::function f, double timeout, abort_callback & a); + void retryOnSharingViolation( double timeout, abort_callback & a, std::function f); + + // **** WINDOWS SUCKS **** + // Special version of retryOnSharingViolation with workarounds for known MoveFile() bugs. + void retryFileMove( double timeout, abort_callback & a, std::function f); + + // **** WINDOWS SUCKS **** + // Special version of retryOnSharingViolation with workarounds for known idiotic problems with folder removal. + void retryFileDelete( double timeout, abort_callback & a, std::function f); + + class listDirectoryCallbackImpl : public directory_callback { + public: + listDirectoryCallbackImpl() {} + listDirectoryCallbackImpl( listDirectoryFunc_t f ) : m_func(f) {} + bool on_entry(filesystem * p_owner, abort_callback & p_abort, const char * p_url, bool p_is_subdirectory, const t_filestats & p_stats) { + m_func(p_url, p_stats, p_is_subdirectory); + return true; + } + listDirectoryFunc_t m_func; + }; + +#ifdef _WIN32 + pfc::string8 winGetVolumePath(const char * fb2kPath ); + DWORD winVolumeFlags( const char * fb2kPath ); +#endif +} + + +//helper +class file_path_canonical { +public: + file_path_canonical(const char * src) {filesystem::g_get_canonical_path(src,m_data);} + operator const char * () const {return m_data.get_ptr();} + const char * get_ptr() const {return m_data.get_ptr();} + t_size get_length() const {return m_data.get_length();} +private: + pfc::string8 m_data; +}; + +class file_path_display { +public: + file_path_display(const char * src) {filesystem::g_get_display_path(src,m_data);} + operator const char * () const {return m_data.get_ptr();} + const char * get_ptr() const {return m_data.get_ptr();} + t_size get_length() const {return m_data.get_length();} +private: + pfc::string8 m_data; +}; + + + +class stream_reader_memblock_ref : public stream_reader +{ +public: + template stream_reader_memblock_ref(const t_array & p_array) : m_data(p_array.get_ptr()), m_data_size(p_array.get_size()), m_pointer(0) { + pfc::assert_byte_type(); + } + stream_reader_memblock_ref(const void * p_data,t_size p_data_size) : m_data((const unsigned char*)p_data), m_data_size(p_data_size), m_pointer(0) {} + stream_reader_memblock_ref() : m_data(NULL), m_data_size(0), m_pointer(0) {} + + template void set_data(const t_array & data) { + pfc::assert_byte_type(); + set_data(data.get_ptr(), data.get_size()); + } + + void set_data(const void * data, t_size dataSize) { + m_pointer = 0; + m_data = reinterpret_cast(data); + m_data_size = dataSize; + } + + t_size read(void * p_buffer,t_size p_bytes,abort_callback & p_abort) { + t_size delta = pfc::min_t(p_bytes, get_remaining()); + memcpy(p_buffer,m_data+m_pointer,delta); + m_pointer += delta; + return delta; + } + void read_object(void * p_buffer,t_size p_bytes,abort_callback & p_abort) { + if (p_bytes > get_remaining()) throw exception_io_data_truncation(); + memcpy(p_buffer,m_data+m_pointer,p_bytes); + m_pointer += p_bytes; + } + t_filesize skip(t_filesize p_bytes,abort_callback & p_abort) { + t_size remaining = get_remaining(); + if (p_bytes >= remaining) { + m_pointer = m_data_size; return remaining; + } else { + m_pointer += (t_size)p_bytes; return p_bytes; + } + } + void skip_object(t_filesize p_bytes,abort_callback & p_abort) { + if (p_bytes > get_remaining()) { + throw exception_io_data_truncation(); + } else { + m_pointer += (t_size)p_bytes; + } + } + void seek_(t_size offset) { + PFC_ASSERT( offset <= m_data_size ); + m_pointer = offset; + } + const void * get_ptr_() const {return m_data + m_pointer;} + t_size get_remaining() const {return m_data_size - m_pointer;} + void reset() {m_pointer = 0;} +private: + const unsigned char * m_data; + t_size m_data_size,m_pointer; +}; + +class stream_writer_buffer_simple : public stream_writer { +public: + void write(const void * p_buffer,t_size p_bytes,abort_callback & p_abort) { + p_abort.check(); + t_size base = m_buffer.get_size(); + if (base + p_bytes < base) throw std::bad_alloc(); + m_buffer.set_size(base + p_bytes); + memcpy( (t_uint8*) m_buffer.get_ptr() + base, p_buffer, p_bytes ); + } + + typedef pfc::array_t t_buffer; + + pfc::array_t m_buffer; +}; + +template +class stream_writer_buffer_append_ref_t : public stream_writer +{ +public: + stream_writer_buffer_append_ref_t(t_storage & p_output) : m_output(p_output) {} + void write(const void * p_buffer,t_size p_bytes,abort_callback & p_abort) { + PFC_STATIC_ASSERT( sizeof(m_output[0]) == 1 ); + p_abort.check(); + t_size base = m_output.get_size(); + if (base + p_bytes < base) throw std::bad_alloc(); + m_output.set_size(base + p_bytes); + memcpy( (t_uint8*) m_output.get_ptr() + base, p_buffer, p_bytes ); + } +private: + t_storage & m_output; +}; + +class stream_reader_limited_ref : public stream_reader { +public: + stream_reader_limited_ref(stream_reader * p_reader,t_filesize p_limit) : m_reader(p_reader), m_remaining(p_limit) {} + + t_size read(void * p_buffer,t_size p_bytes,abort_callback & p_abort) { + if (p_bytes > m_remaining) p_bytes = (t_size)m_remaining; + + t_size done = m_reader->read(p_buffer,p_bytes,p_abort); + m_remaining -= done; + return done; + } + + inline t_filesize get_remaining() const {return m_remaining;} + + t_filesize skip(t_filesize p_bytes,abort_callback & p_abort) { + if (p_bytes > m_remaining) p_bytes = m_remaining; + t_filesize done = m_reader->skip(p_bytes,p_abort); + m_remaining -= done; + return done; + } + + void flush_remaining(abort_callback & p_abort) { + if (m_remaining > 0) skip_object(m_remaining,p_abort); + } + +private: + stream_reader * m_reader; + t_filesize m_remaining; +}; + +class stream_writer_chunk_dwordheader : public stream_writer +{ +public: + stream_writer_chunk_dwordheader(const service_ptr_t & p_writer) : m_writer(p_writer) {} + + void initialize(abort_callback & p_abort) { + m_headerposition = m_writer->get_position(p_abort); + m_written = 0; + m_writer->write_lendian_t((t_uint32)0,p_abort); + } + + void finalize(abort_callback & p_abort) { + t_filesize end_offset; + end_offset = m_writer->get_position(p_abort); + m_writer->seek(m_headerposition,p_abort); + m_writer->write_lendian_t(pfc::downcast_guarded(m_written),p_abort); + m_writer->seek(end_offset,p_abort); + } + + void write(const void * p_buffer,t_size p_bytes,abort_callback & p_abort) { + m_writer->write(p_buffer,p_bytes,p_abort); + m_written += p_bytes; + } + +private: + service_ptr_t m_writer; + t_filesize m_headerposition; + t_filesize m_written; +}; + +class stream_writer_chunk : public stream_writer +{ +public: + stream_writer_chunk(stream_writer * p_writer) : m_writer(p_writer), m_buffer_state(0) {} + + void write(const void * p_buffer,t_size p_bytes,abort_callback & p_abort); + + void flush(abort_callback & p_abort);//must be called after writing before object is destroyed + +private: + stream_writer * m_writer; + unsigned m_buffer_state; + unsigned char m_buffer[255]; +}; + +class stream_reader_chunk : public stream_reader +{ +public: + stream_reader_chunk(stream_reader * p_reader) : m_reader(p_reader), m_buffer_state(0), m_buffer_size(0), m_eof(false) {} + + t_size read(void * p_buffer,t_size p_bytes,abort_callback & p_abort); + + void flush(abort_callback & p_abort);//must be called after reading before object is destroyed + + static void g_skip(stream_reader * p_stream,abort_callback & p_abort); + +private: + stream_reader * m_reader; + t_size m_buffer_state, m_buffer_size; + bool m_eof; + unsigned char m_buffer[255]; +}; + +class stream_reader_dummy : public stream_reader { t_size read(void * p_buffer,t_size p_bytes,abort_callback & p_abort) {return 0;} }; + + + + + + + + + + + + + + + + + + +template class stream_reader_formatter { +public: + stream_reader_formatter(stream_reader & p_stream,abort_callback & p_abort) : m_stream(p_stream), m_abort(p_abort) {} + + template void read_int(t_int & p_out) { + if (isBigEndian) m_stream.read_bendian_t(p_out,m_abort); + else m_stream.read_lendian_t(p_out,m_abort); + } + + void read_raw(void * p_buffer,t_size p_bytes) { + m_stream.read_object(p_buffer,p_bytes,m_abort); + } + + void skip(t_size p_bytes) {m_stream.skip_object(p_bytes,m_abort);} + + template void read_raw(TArray& data) { + pfc::assert_byte_type(); + read_raw(data.get_ptr(),data.get_size()); + } + template void read_byte_block(TArray & data) { + pfc::assert_byte_type(); + t_uint32 size; read_int(size); data.set_size(size); + read_raw(data); + } + template void read_array(TArray & data) { + t_uint32 size; *this >> size; data.set_size(size); + for(t_uint32 walk = 0; walk < size; ++walk) *this >> data[walk]; + } + void read_string_nullterm( pfc::string_base & out ) { + m_stream.read_string_nullterm( out, m_abort ); + } + stream_reader & m_stream; + abort_callback & m_abort; +}; + +template class stream_writer_formatter { +public: + stream_writer_formatter(stream_writer & p_stream,abort_callback & p_abort) : m_stream(p_stream), m_abort(p_abort) {} + + template void write_int(t_int p_int) { + if (isBigEndian) m_stream.write_bendian_t(p_int,m_abort); + else m_stream.write_lendian_t(p_int,m_abort); + } + + void write_raw(const void * p_buffer,t_size p_bytes) { + m_stream.write_object(p_buffer,p_bytes,m_abort); + } + template void write_raw(const TArray& data) { + pfc::assert_byte_type(); + write_raw(data.get_ptr(),data.get_size()); + } + + template void write_byte_block(const TArray& data) { + pfc::assert_byte_type(); + write_int( pfc::downcast_guarded(data.get_size()) ); + write_raw( data ); + } + template void write_array(const TArray& data) { + const t_uint32 size = pfc::downcast_guarded(data.get_size()); + *this << size; + for(t_uint32 walk = 0; walk < size; ++walk) *this << data[walk]; + } + + void write_string(const char * str) { + const t_size len = strlen(str); + *this << pfc::downcast_guarded(len); + write_raw(str, len); + } + void write_string(const char * str, t_size len_) { + const t_size len = pfc::strlen_max(str, len_); + *this << pfc::downcast_guarded(len); + write_raw(str, len); + } + void write_string_nullterm( const char * str ) { + this->write_raw( str, strlen(str)+1 ); + } + + stream_writer & m_stream; + abort_callback & m_abort; +}; + + +#define __DECLARE_INT_OVERLOADS(TYPE) \ + template inline stream_reader_formatter & operator>>(stream_reader_formatter & p_stream,TYPE & p_int) {typename pfc::sized_int_t::t_unsigned temp;p_stream.read_int(temp); p_int = (TYPE) temp; return p_stream;} \ + template inline stream_writer_formatter & operator<<(stream_writer_formatter & p_stream,TYPE p_int) {p_stream.write_int((typename pfc::sized_int_t::t_unsigned)p_int); return p_stream;} + +__DECLARE_INT_OVERLOADS(char); +__DECLARE_INT_OVERLOADS(signed char); +__DECLARE_INT_OVERLOADS(unsigned char); +__DECLARE_INT_OVERLOADS(signed short); +__DECLARE_INT_OVERLOADS(unsigned short); + +__DECLARE_INT_OVERLOADS(signed int); +__DECLARE_INT_OVERLOADS(unsigned int); + +__DECLARE_INT_OVERLOADS(signed long); +__DECLARE_INT_OVERLOADS(unsigned long); + +__DECLARE_INT_OVERLOADS(signed long long); +__DECLARE_INT_OVERLOADS(unsigned long long); + +__DECLARE_INT_OVERLOADS(wchar_t); + + +#undef __DECLARE_INT_OVERLOADS + +template class _IsTypeByte { +public: + enum {value = pfc::is_same_type::value || pfc::is_same_type::value || pfc::is_same_type::value}; +}; + +template stream_reader_formatter & operator>>(stream_reader_formatter & p_stream,TVal (& p_array)[Count]) { + if (_IsTypeByte::value) { + p_stream.read_raw(p_array,Count); + } else { + for(t_size walk = 0; walk < Count; ++walk) p_stream >> p_array[walk]; + } + return p_stream; +} + +template stream_writer_formatter & operator<<(stream_writer_formatter & p_stream,TVal const (& p_array)[Count]) { + if (_IsTypeByte::value) { + p_stream.write_raw(p_array,Count); + } else { + for(t_size walk = 0; walk < Count; ++walk) p_stream << p_array[walk]; + } + return p_stream; +} + +#define FB2K_STREAM_READER_OVERLOAD(type) \ + template stream_reader_formatter & operator>>(stream_reader_formatter & stream,type & value) + +#define FB2K_STREAM_WRITER_OVERLOAD(type) \ + template stream_writer_formatter & operator<<(stream_writer_formatter & stream,const type & value) + +FB2K_STREAM_READER_OVERLOAD(GUID) { + return stream >> value.Data1 >> value.Data2 >> value.Data3 >> value.Data4; +} + +FB2K_STREAM_WRITER_OVERLOAD(GUID) { + return stream << value.Data1 << value.Data2 << value.Data3 << value.Data4; +} + +FB2K_STREAM_READER_OVERLOAD(pfc::string) { + t_uint32 len; stream >> len; + value = stream.m_stream.read_string_ex(len,stream.m_abort); + return stream; +} + +FB2K_STREAM_WRITER_OVERLOAD(pfc::string) { + stream << pfc::downcast_guarded(value.length()); + stream.write_raw(value.ptr(),value.length()); + return stream; +} + +FB2K_STREAM_READER_OVERLOAD(pfc::string_base) { + stream.m_stream.read_string(value, stream.m_abort); + return stream; +} +FB2K_STREAM_WRITER_OVERLOAD(pfc::string_base) { + const char * val = value.get_ptr(); + const t_size len = strlen(val); + stream << pfc::downcast_guarded(len); + stream.write_raw(val,len); + return stream; +} + + +FB2K_STREAM_WRITER_OVERLOAD(float) { + union { + float f; t_uint32 i; + } u; u.f = value; + return stream << u.i; +} + +FB2K_STREAM_READER_OVERLOAD(float) { + union { float f; t_uint32 i;} u; + stream >> u.i; value = u.f; + return stream; +} + +FB2K_STREAM_WRITER_OVERLOAD(double) { + union { + double f; t_uint64 i; + } u; u.f = value; + return stream << u.i; +} + +FB2K_STREAM_READER_OVERLOAD(double) { + union { double f; t_uint64 i;} u; + stream >> u.i; value = u.f; + return stream; +} + +FB2K_STREAM_WRITER_OVERLOAD(bool) { + t_uint8 temp = value ? 1 : 0; + return stream << temp; +} +FB2K_STREAM_READER_OVERLOAD(bool) { + t_uint8 temp; stream >> temp; value = temp != 0; + return stream; +} + +template +class stream_writer_formatter_simple : public stream_writer_formatter { +public: + stream_writer_formatter_simple() : stream_writer_formatter(_m_stream,fb2k::noAbort), m_buffer(_m_stream.m_buffer) {} + + typedef stream_writer_buffer_simple::t_buffer t_buffer; + t_buffer & m_buffer; +private: + stream_writer_buffer_simple _m_stream; +}; + +template +class stream_reader_formatter_simple_ref : public stream_reader_formatter { +public: + stream_reader_formatter_simple_ref(const void * source, t_size sourceSize) : stream_reader_formatter(_m_stream,fb2k::noAbort), _m_stream(source,sourceSize) {} + template stream_reader_formatter_simple_ref(const TSource& source) : stream_reader_formatter(_m_stream,fb2k::noAbort), _m_stream(source) {} + stream_reader_formatter_simple_ref() : stream_reader_formatter(_m_stream,fb2k::noAbort) {} + + void set_data(const void * source, t_size sourceSize) {_m_stream.set_data(source,sourceSize);} + template void set_data(const TSource & source) {_m_stream.set_data(source);} + + void reset() {_m_stream.reset();} + t_size get_remaining() {return _m_stream.get_remaining();} + + const void * get_ptr_() const {return _m_stream.get_ptr_();} +private: + stream_reader_memblock_ref _m_stream; +}; + +template +class stream_reader_formatter_simple : public stream_reader_formatter_simple_ref { +public: + stream_reader_formatter_simple() {} + stream_reader_formatter_simple(const void * source, t_size sourceSize) {set_data(source,sourceSize);} + template stream_reader_formatter_simple(const TSource & source) {set_data(source);} + + void set_data(const void * source, t_size sourceSize) { + m_content.set_data_fromptr(reinterpret_cast(source), sourceSize); + onContentChange(); + } + template void set_data(const TSource & source) { + m_content = source; + onContentChange(); + } +private: + void onContentChange() { + stream_reader_formatter_simple_ref::set_data(m_content); + } + pfc::array_t m_content; +}; + + + + + + +template class _stream_reader_formatter_translator { +public: + _stream_reader_formatter_translator(stream_reader_formatter & stream) : m_stream(stream) {} + typedef _stream_reader_formatter_translator t_self; + template t_self & operator||(t_what & out) {m_stream >> out; return *this;} +private: + stream_reader_formatter & m_stream; +}; +template class _stream_writer_formatter_translator { +public: + _stream_writer_formatter_translator(stream_writer_formatter & stream) : m_stream(stream) {} + typedef _stream_writer_formatter_translator t_self; + template t_self & operator||(const t_what & in) {m_stream << in; return *this;} +private: + stream_writer_formatter & m_stream; +}; + +#define FB2K_STREAM_RECORD_OVERLOAD(type, code) \ + FB2K_STREAM_READER_OVERLOAD(type) { \ + _stream_reader_formatter_translator streamEx(stream); \ + streamEx || code; \ + return stream; \ + } \ + FB2K_STREAM_WRITER_OVERLOAD(type) { \ + _stream_writer_formatter_translator streamEx(stream); \ + streamEx || code; \ + return stream; \ + } + + + + +#define FB2K_RETRY_ON_EXCEPTION(OP, ABORT, TIMEOUT, EXCEPTION) \ + { \ + pfc::lores_timer timer; timer.start(); \ + for(;;) { \ + try { {OP;} break; } \ + catch(EXCEPTION) { if (timer.query() > TIMEOUT) throw;} \ + ABORT.sleep(0.05); \ + } \ + } + +#define FB2K_RETRY_ON_EXCEPTION2(OP, ABORT, TIMEOUT, EXCEPTION1, EXCEPTION2) \ + { \ + pfc::lores_timer timer; timer.start(); \ + for(;;) { \ + try { {OP;} break; } \ + catch(EXCEPTION1) { if (timer.query() > TIMEOUT) throw;} \ + catch(EXCEPTION2) { if (timer.query() > TIMEOUT) throw;} \ + ABORT.sleep(0.05); \ + } \ + } + +#define FB2K_RETRY_ON_EXCEPTION3(OP, ABORT, TIMEOUT, EXCEPTION1, EXCEPTION2, EXCEPTION3) \ + { \ + pfc::lores_timer timer; timer.start(); \ + for(;;) { \ + try { {OP;} break; } \ + catch(EXCEPTION1) { if (timer.query() > TIMEOUT) throw;} \ + catch(EXCEPTION2) { if (timer.query() > TIMEOUT) throw;} \ + catch(EXCEPTION3) { if (timer.query() > TIMEOUT) throw;} \ + ABORT.sleep(0.05); \ + } \ + } + +#define FB2K_RETRY_ON_SHARING_VIOLATION(OP, ABORT, TIMEOUT) FB2K_RETRY_ON_EXCEPTION(OP, ABORT, TIMEOUT, exception_io_sharing_violation) + +// **** WINDOWS SUCKS **** +// File move ops must be retried on all these because you get access-denied when someone is holding open handles to something you're trying to move, or already-exists on something you just told Windows to move away +#define FB2K_RETRY_FILE_MOVE(OP, ABORT, TIMEOUT) FB2K_RETRY_ON_EXCEPTION3(OP, ABORT, TIMEOUT, exception_io_sharing_violation, exception_io_denied, exception_io_already_exists) + +class fileRestorePositionScope { +public: + fileRestorePositionScope(file::ptr f, abort_callback & a) : m_file(f), m_abort(a) { + m_offset = f->get_position(a); + } + ~fileRestorePositionScope() { + try { + if (!m_abort.is_aborting()) m_file->seek(m_offset, m_abort); + } catch(...) {} + } +private: + file::ptr m_file; + t_filesize m_offset; + abort_callback & m_abort; +}; + + +//! Debug self-test function for testing a file object implementation, performs various behavior validity checks, random access etc. Output goes to fb2k console. +//! Returns true on success, false on failure (buggy file object implementation). +bool fb2kFileSelfTest(file::ptr f, abort_callback & aborter); diff --git a/foobar2000/SDK/filesystem_transacted.h b/foobar2000/SDK/filesystem_transacted.h new file mode 100644 index 0000000..ee945d8 --- /dev/null +++ b/foobar2000/SDK/filesystem_transacted.h @@ -0,0 +1,44 @@ +#pragma once + +#include "filesystem.h" + +//! Object cannot be opened in transacted mode. +PFC_DECLARE_EXCEPTION(exception_io_transactions_unsupported, exception_io, "Transactions unsupported on this volume"); +PFC_DECLARE_EXCEPTION(exception_io_transactional_conflict, exception_io, "Transactional conflict"); +PFC_DECLARE_EXCEPTION(exception_io_transaction_aborted, exception_io, "Transaction aborted"); + +//! An instance of a filesystem transaction. Inherits from filesystem API and provides all the methods. \n +//! To perform a transacted filesystem update, you must call methods on this object specifically - not static methods of filesystem class, not methods of a filesystem instance obtained from someplace else. \n +//! Call commit() when done, then release the object. If you release the object without having called commit(), the update will be rolled back. \n +//! Please keep in mind that you must not explicitly rely on this API and always provide a fallback mechanism. \n +//! A transacted operation may be impossible for the following reasons: \n +//! Too old foobar2000 version - filesystem_transacted was first published at version 1.4 beta 7 - obtaining a filesystem_transacted instance will fail. \n +//! Too old Windows OS - transacted APIs are available starting from Vista, not available on XP - obtaining a filesystem_transacted instance will fail. \n +//! Functionality disabled by user - obtaining a filesystem_transacted instance will fail. \n +//! The volume you're trying to work with does not support transacted updates - network share, non-NTFS USB stick, etc - create() will succeed but operations will fail with exception_io_transactions_unsupported. \n +class filesystem_transacted : public filesystem_v2 { + FB2K_MAKE_SERVICE_INTERFACE(filesystem_transacted, filesystem_v2); +public: + //! Commits the transaction. You should release this filesystem_transacted object when done. \n + //! If you don't call commit, all operations made with this filesystem_transacted instance will be rolled back. + virtual void commit(abort_callback & abort) = 0; + + //! Helper to obtain a new instance. Will return null if filesystem_transacted is unavailable. + static filesystem_transacted::ptr create( const char * pathFor ); +}; + +//! \since 1.4 +//! An entrypoint interface to create filesystem_transacted instances. Use filesystem_transacted::create() instead of calling this directly. +class filesystem_transacted_entry : public service_base { + FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(filesystem_transacted_entry); +public: + //! May return null if transacted ops are not available in this location for one reason or another. + virtual filesystem_transacted::ptr create( const char * pathFor ) = 0; + + virtual bool is_our_path( const char * path ) = 0; +}; + +// Since 1.5, transacted filesystem is no longer supported +// as it adds extra complexity without actually solving any problems. +// Even Microsoft recommends not to use this API. +#define FB2K_SUPPORT_TRANSACTED_FILESYSTEM 0 diff --git a/foobar2000/SDK/foobar2000-dsp.h b/foobar2000/SDK/foobar2000-dsp.h new file mode 100644 index 0000000..5c58539 --- /dev/null +++ b/foobar2000/SDK/foobar2000-dsp.h @@ -0,0 +1,5 @@ +#pragma once + +// placeholder added for foobar2000 mobile source compatibility + +#include "foobar2000.h" \ No newline at end of file diff --git a/foobar2000/SDK/foobar2000-pfc.h b/foobar2000/SDK/foobar2000-pfc.h new file mode 100644 index 0000000..fb64b96 --- /dev/null +++ b/foobar2000/SDK/foobar2000-pfc.h @@ -0,0 +1,14 @@ +#pragma once + +#include "../../pfc/pfc.h" + +// These were in a global namespace before and are commonly referenced as such. +using pfc::bit_array; +using pfc::bit_array_var; +using pfc::bit_array_true; +using pfc::bit_array_false; +using pfc::bit_array_val; +using pfc::bit_array_bittable; +using pfc::bit_array_one; +using pfc::bit_array_range; +using pfc::LastErrorRevertScope; diff --git a/foobar2000/SDK/foobar2000-winver.h b/foobar2000/SDK/foobar2000-winver.h new file mode 100644 index 0000000..b93249c --- /dev/null +++ b/foobar2000/SDK/foobar2000-winver.h @@ -0,0 +1,15 @@ +#pragma once + +#define FOOBAR2000_DESKTOP +#define FOOBAR2000_DESKTOP_WINDOWS +#define FOOBAR2000_DESKTOP_WINDOWS_OR_BOOM + +#define FOOBAR2000_HAVE_FILE_FORMAT_SANITIZER +#define FOOBAR2000_HAVE_CHAPTERIZER +#define FOOBAR2000_HAVE_ALBUM_ART +#define FOOBAR2000_DECLARE_FILE_TYPES +#define FOOBAR2000_HAVE_DSP +#define FOOBAR2000_HAVE_CONSOLE +#define FOOBAR2000_INTERACTIVE +#define FOOBAR2000_WINAPI_CLASSIC +#define FOOBAR2000_HAVE_METADB \ No newline at end of file diff --git a/foobar2000/SDK/foobar2000.h b/foobar2000/SDK/foobar2000.h new file mode 100644 index 0000000..96b6882 --- /dev/null +++ b/foobar2000/SDK/foobar2000.h @@ -0,0 +1,130 @@ +// This is the master foobar2000 SDK header file; it includes headers for all functionality exposed through the SDK project. #include this in your source code, never reference any of the other headers directly. + +#ifndef _FOOBAR2000_H_ +#define _FOOBAR2000_H_ + +#include "foobar2000-winver.h" + +// #define FOOBAR2000_TARGET_VERSION 75 // 0.9.6 +// #define FOOBAR2000_TARGET_VERSION 76 // 1.0 +// #define FOOBAR2000_TARGET_VERSION 77 // 1.1, 1.2 +// #define FOOBAR2000_TARGET_VERSION 78 // 1.3 +#define FOOBAR2000_TARGET_VERSION 79 // 1.4 +// #define FOOBAR2000_TARGET_VERSION 80 // 1.5, 1.6 + +// Use this to determine what foobar2000 SDK version is in use, undefined for releases older than 2018 +#define FOOBAR2000_SDK_VERSION 20210223 + + +#include "foobar2000-pfc.h" +#include "../shared/shared.h" + +#ifndef NOTHROW +#ifdef _MSC_VER +#define NOTHROW __declspec(nothrow) +#else +#define NOTHROW +#endif +#endif + +#define FB2KAPI /*NOTHROW*/ + +typedef const char * pcchar; + +#include "core_api.h" +#include "service.h" +#include "service_impl.h" +#include "service_by_guid.h" +#include "service_compat.h" + +#include "completion_notify.h" +#include "abort_callback.h" +#include "componentversion.h" +#include "preferences_page.h" +#include "coreversion.h" +#include "filesystem.h" +#include "filesystem_transacted.h" +#include "archive.h" +#include "audio_chunk.h" +#include "cfg_var.h" +#include "mem_block_container.h" +#include "audio_postprocessor.h" +#include "playable_location.h" +#include "file_info.h" +#include "file_info_impl.h" +#include "hasher_md5.h" +#include "metadb_handle.h" +#include "metadb.h" +#include "file_info_filter.h" +#include "console.h" +#include "dsp.h" +#include "dsp_manager.h" +#include "initquit.h" +#include "event_logger.h" +#include "input.h" +#include "input_impl.h" +#include "decode_postprocessor.h" +#include "menu.h" +#include "contextmenu.h" +#include "contextmenu_manager.h" +#include "menu_helpers.h" +#include "modeless_dialog.h" +#include "playback_control.h" +#include "play_callback.h" +#include "playlist.h" +#include "playlist_loader.h" +#include "replaygain.h" +#include "resampler.h" +#include "tag_processor.h" +#include "titleformat.h" +#include "ui.h" +#include "unpack.h" +#include "vis.h" +#include "packet_decoder.h" +#include "commandline.h" +#include "genrand.h" +#include "file_operation_callback.h" +#include "library_manager.h" +#include "config_io_callback.h" +#include "popup_message.h" +#include "app_close_blocker.h" +#include "config_object.h" +#include "config_object_impl.h" +#include "threaded_process.h" +#include "message_loop.h" +#include "input_file_type.h" +#include "chapterizer.h" +#include "link_resolver.h" +#include "main_thread_callback.h" +#include "advconfig.h" +#include "info_lookup_handler.h" +#include "track_property.h" + +#include "album_art.h" +#include "album_art_helpers.h" +#include "icon_remap.h" +#include "ui_element.h" +#include "ole_interaction.h" +#include "search_tools.h" +#include "autoplaylist.h" +#include "replaygain_scanner.h" +#include "ui_edit_context.h" + +#include "system_time_keeper.h" +#include "playback_stream_capture.h" +#include "http_client.h" +#include "exceptions.h" + +#include "progress_meter.h" + +#include "output.h" + +#include "file_format_sanitizer.h" + +#include "commonObjects.h" + +#include "file_lock_manager.h" +#include "imageLoaderLite.h" +#include "imageViewer.h" + +#endif //_FOOBAR2000_H_ diff --git a/foobar2000/SDK/foobar2000_SDK.vcxproj b/foobar2000/SDK/foobar2000_SDK.vcxproj new file mode 100644 index 0000000..1772538 --- /dev/null +++ b/foobar2000/SDK/foobar2000_SDK.vcxproj @@ -0,0 +1,292 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + + {E8091321-D79D-4575-86EF-064EA1A4A20D} + foobar2000_SDK + + + + StaticLibrary + false + Unicode + true + v141 + + + StaticLibrary + false + Unicode + v141 + + + + + + + + + + + + + <_ProjectFileVersion>10.0.30319.1 + + + + Disabled + EnableFastChecks + Use + foobar2000.h + Level3 + true + ProgramDatabase + MultiThreadedDebugDLL + 4715 + true + + + _DEBUG;%(PreprocessorDefinitions) + 0x0409 + + + true + + + + + MinSpace + true + false + Fast + false + Use + foobar2000.h + Level3 + true + ProgramDatabase + MultiThreadedDLL + /d2notypeopt %(AdditionalOptions) + 4715 + true + true + true + NDEBUG;_UNICODE;UNICODE;%(PreprocessorDefinitions) + + + NDEBUG;%(PreprocessorDefinitions) + 0x0409 + + + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Create + Create + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/foobar2000/SDK/foobar2000_SDK.vcxproj.filters b/foobar2000/SDK/foobar2000_SDK.vcxproj.filters new file mode 100644 index 0000000..db996c1 --- /dev/null +++ b/foobar2000/SDK/foobar2000_SDK.vcxproj.filters @@ -0,0 +1,488 @@ + + + + + {6c35c7a7-723a-401f-acdc-c63af942abae} + *.h + + + {3b2ccd60-8f0c-4241-830a-fda069a5d440} + *.cpp + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + \ No newline at end of file diff --git a/foobar2000/SDK/foosort.cpp b/foobar2000/SDK/foosort.cpp new file mode 100644 index 0000000..31c0489 --- /dev/null +++ b/foobar2000/SDK/foosort.cpp @@ -0,0 +1,154 @@ +#include "foobar2000.h" +#include "foosort.h" + +#define FOOSORT_PROFILING 0 + +namespace { +#if FOOSORT_PROFILING + typedef pfc::hires_timer foosort_timer; +#endif + + class foosort { + public: + foosort(pfc::sort_callback & cb, abort_callback & a) : m_abort(a), p_callback(cb) {} + + foosort fork() { + foosort ret ( * this ); + ret.genrand = genrand_service::g_create(); + return ret; + } + + size_t myrand(size_t cnt) { + return genrand->genrand(cnt); + } + + void squaresort(t_size const p_base, t_size const p_count) { + const t_size max = p_base + p_count; + for (t_size walk = p_base + 1; walk < max; ++walk) { + for (t_size prev = p_base; prev < walk; ++prev) { + p_callback.swap_check(prev, walk); + } + } + } + + + void _sort_2elem_helper(t_size & p_elem1, t_size & p_elem2) { + if (p_callback.compare(p_elem1, p_elem2) > 0) pfc::swap_t(p_elem1, p_elem2); + } + + t_size _pivot_helper(t_size const p_base, t_size const p_count) { + PFC_ASSERT(p_count > 2); + + //t_size val1 = p_base, val2 = p_base + (p_count / 2), val3 = p_base + (p_count - 1); + + t_size val1 = myrand(p_count), val2 = myrand(p_count - 1), val3 = myrand(p_count - 2); + if (val2 >= val1) val2++; + if (val3 >= val1) val3++; + if (val3 >= val2) val3++; + + val1 += p_base; val2 += p_base; val3 += p_base; + + _sort_2elem_helper(val1, val2); + _sort_2elem_helper(val1, val3); + _sort_2elem_helper(val2, val3); + + return val2; + } + + void newsort(t_size const p_base, t_size const p_count, size_t concurrency) { + if (p_count <= 4) { + squaresort(p_base, p_count); + return; + } + + this->m_abort.check(); +#if FOOSORT_PROFILING + foosort_timer t; + if ( concurrency > 1 ) t.start(); +#endif + + t_size pivot = _pivot_helper(p_base, p_count); + + { + const t_size target = p_base + p_count - 1; + if (pivot != target) { + p_callback.swap(pivot, target); pivot = target; + } + } + + + t_size partition = p_base; + { + bool asdf = false; + for (t_size walk = p_base; walk < pivot; ++walk) { + const int comp = p_callback.compare(walk, pivot); + bool trigger = false; + if (comp == 0) { + trigger = asdf; + asdf = !asdf; + } else if (comp < 0) { + trigger = true; + } + if (trigger) { + if (partition != walk) p_callback.swap(partition, walk); + partition++; + } + } + } + if (pivot != partition) { + p_callback.swap(pivot, partition); pivot = partition; + } + + const auto base1 = p_base, count1 = pivot - p_base; + const auto base2 = pivot + 1, count2 = p_count - (pivot + 1 - p_base); + + if (concurrency > 1) { + + size_t total = count1 + count2; + size_t con1 = (size_t)((uint64_t)count1 * (uint64_t)concurrency / (uint64_t)total); + if (con1 < 1) con1 = 1; + if (con1 > concurrency - 1) con1 = concurrency - 1; + size_t con2 = concurrency - con1; + +#if FOOSORT_PROFILING + FB2K_console_formatter() << "foosort pass: " << p_base << "+" << p_count << "(" << concurrency << ") took " << t.queryString(); + FB2K_console_formatter() << "foosort forking: " << base1 << "+" << count1 << "(" << con1 << ") + " << base2 << "+" << count2 << "(" << con2 << ")"; +#endif + pfc::thread2 other; + other.startHere([&] { + try { + foosort subsort = fork(); + subsort.newsort(base1, count1, con1); + } catch (exception_aborted) {} + }); + try { + newsort(base2, count2, con2); + } catch (exception_aborted) {} + other.waitTillDone(); + m_abort.check(); + } else { + newsort(base1, count1, 1); + newsort(base2, count2, 1); + } + } + private: + abort_callback & m_abort; + pfc::sort_callback & p_callback; + genrand_service::ptr genrand = genrand_service::g_create(); + }; + + + +} +namespace fb2k { + void sort(pfc::sort_callback & cb, size_t count, size_t concurrency, abort_callback & aborter) { +#if FOOSORT_PROFILING + foosort_timer t; t.start(); +#endif + foosort theFooSort(cb, aborter); + theFooSort.newsort(0, count, concurrency); +#if FOOSORT_PROFILING + FB2K_console_formatter() << "foosort took: " << t.queryString(); +#endif + } +} diff --git a/foobar2000/SDK/foosort.h b/foobar2000/SDK/foosort.h new file mode 100644 index 0000000..83a87ce --- /dev/null +++ b/foobar2000/SDK/foosort.h @@ -0,0 +1,10 @@ +#pragma once + +namespace fb2k { + + // foosort + // abortable multithreaded quicksort + // expects cb to handle concurrent calls as long as they do not touch the same items concurrently + void sort(pfc::sort_callback & cb, size_t count, size_t concurrency, abort_callback & aborter); + +} diff --git a/foobar2000/SDK/genrand.h b/foobar2000/SDK/genrand.h new file mode 100644 index 0000000..27518cf --- /dev/null +++ b/foobar2000/SDK/genrand.h @@ -0,0 +1,42 @@ +//! PRNG service. Implemented by the core, do not reimplement. Use g_create() helper function to instantiate. +class NOVTABLE genrand_service : public service_base +{ +public: + //! Seeds the PRNG with specified value. + virtual void seed(unsigned val) = 0; + //! Returns random value N, where 0 <= N < range. + virtual unsigned genrand(unsigned range)=0; + + double genrand_f() { return (double)genrand(0xFFFFFFFF) / (double)0xFFFFFFFF; } + + void genrand_blob( void * out, size_t bytes ) { + size_t dwords = bytes/4; + uint32_t * out32 = (uint32_t*) out; + for(size_t w = 0; w < dwords; ++w ) { + out32[w] = genrand32(); + } + size_t left = bytes % 4; + if (left > 0) { + auto leftptr = (uint8_t*) out + (bytes-left); + for( size_t w = 0; w < left; ++w) leftptr[w] = genrand8(); + } + } + + uint32_t genrand32() { + return (uint32_t) genrand(0xFFFFFFFF); + } + uint8_t genrand8() { + return (uint8_t) genrand(0x100); + } + + static service_ptr_t g_create() {return standard_api_create_t();} + + void generate_random_order(t_size * out, t_size count) { + unsigned genrandMax = (unsigned) pfc::min_t(count, 0xFFFFFFFF); + t_size n; + for(n=0;n= 80 +FOOGUIDDECL const GUID ui_control_v2::class_guid = { 0x6fc5d5c3, 0x13aa, 0x4f7f, { 0x97, 0x8d, 0x89, 0x6b, 0x9b, 0x90, 0x4a, 0x5 } }; +#endif // FOOBAR2000_TARGET_VERSION >= 80 + + +// {392B88DE-50FC-43b0-9F03-2D79B071CAF6} +FOOGUIDDECL const GUID ui_status_text_override::class_guid = +{ 0x392b88de, 0x50fc, 0x43b0, { 0x9f, 0x3, 0x2d, 0x79, 0xb0, 0x71, 0xca, 0xf6 } }; + +// {52BD7A17-540C-4a97-B812-72BC84EC4FF5} +FOOGUIDDECL const GUID ui_drop_item_callback::class_guid = +{ 0x52bd7a17, 0x540c, 0x4a97, { 0xb8, 0x12, 0x72, 0xbc, 0x84, 0xec, 0x4f, 0xf5 } }; + +// {550B3A19-42A4-4c0f-91F2-90550189CBFF} +FOOGUIDDECL const GUID commandline_handler::class_guid = +{ 0x550b3a19, 0x42a4, 0x4c0f, { 0x91, 0xf2, 0x90, 0x55, 0x1, 0x89, 0xcb, 0xff } }; + +// {C71B99BD-12C5-48fe-A9C0-469F6FEA88BF} +FOOGUIDDECL const GUID modeless_dialog_manager::class_guid = +{ 0xc71b99bd, 0x12c5, 0x48fe, { 0xa9, 0xc0, 0x46, 0x9f, 0x6f, 0xea, 0x88, 0xbf } }; + +// {78BCBFA1-DFB9-487f-AB16-CD82BF90CCF7} +FOOGUIDDECL const GUID play_callback_manager::class_guid = +{ 0x78bcbfa1, 0xdfb9, 0x487f, { 0xab, 0x16, 0xcd, 0x82, 0xbf, 0x90, 0xcc, 0xf7 } }; + +// {8E4EED7A-C6B8-49c7-99FE-97E04AAA63A8} +FOOGUIDDECL const GUID play_callback_static::class_guid = +{ 0x8e4eed7a, 0xc6b8, 0x49c7, { 0x99, 0xfe, 0x97, 0xe0, 0x4a, 0xaa, 0x63, 0xa8 } }; + +// {BF803668-2977-4c71-B9AB-5C77C338C970} +FOOGUIDDECL const GUID playback_control::class_guid = +{ 0xbf803668, 0x2977, 0x4c71, { 0xb9, 0xab, 0x5c, 0x77, 0xc3, 0x38, 0xc9, 0x70 } }; + +// {242D9341-211A-4637-A69F-F6684B52F9D6} +FOOGUIDDECL const GUID playlist_callback_static::class_guid = +{ 0x242d9341, 0x211a, 0x4637, { 0xa6, 0x9f, 0xf6, 0x68, 0x4b, 0x52, 0xf9, 0xd6 } }; + +// {95E9F11B-4C99-4d0a-AB9F-367196B10925} +FOOGUIDDECL const GUID playlist_callback_single_static::class_guid = +{ 0x95e9f11b, 0x4c99, 0x4d0a, { 0xab, 0x9f, 0x36, 0x71, 0x96, 0xb1, 0x9, 0x25 } }; + +// {88D7EDB1-A850-42a4-BBAB-49E955F4B81F} +FOOGUIDDECL const GUID playlist_lock::class_guid = +{ 0x88d7edb1, 0xa850, 0x42a4, { 0xbb, 0xab, 0x49, 0xe9, 0x55, 0xf4, 0xb8, 0x1f } }; + +// {2FBCE1E5-902E-49e0-B9CF-CE0FBA765348} +FOOGUIDDECL const GUID filesystem::class_guid = +{ 0x2fbce1e5, 0x902e, 0x49e0, { 0xb9, 0xcf, 0xce, 0xf, 0xba, 0x76, 0x53, 0x48 } }; + +// {9098AF12-61A3-4caa-8AA9-BB95C2EF8346} +FOOGUIDDECL const GUID unpacker::class_guid = +{ 0x9098af12, 0x61a3, 0x4caa, { 0x8a, 0xa9, 0xbb, 0x95, 0xc2, 0xef, 0x83, 0x46 } }; + +// {EC707440-FA3E-4d12-9876-FC369F04D4A4} +FOOGUIDDECL const GUID archive::class_guid = +{ 0xec707440, 0xfa3e, 0x4d12, { 0x98, 0x76, 0xfc, 0x36, 0x9f, 0x4, 0xd4, 0xa4 } }; + +// {69897890-90CB-4F7D-9969-1A9DCC5D9DDB} +FOOGUIDDECL const GUID archive_v2::class_guid = +{ 0x69897890, 0x90cb, 0x4f7d, { 0x99, 0x69, 0x1a, 0x9d, 0xcc, 0x5d, 0x9d, 0xdb } }; + +// {8D3F8B2D-A866-4F1F-9A4F-FF23929ED6DA} +FOOGUIDDECL const GUID archive_v3::class_guid = +{ 0x8d3f8b2d, 0xa866, 0x4f1f, { 0x9a, 0x4f, 0xff, 0x23, 0x92, 0x9e, 0xd6, 0xda } }; + +// {B2F9FC40-3E55-4b23-A2C9-22BAAD8795B1} +FOOGUIDDECL const GUID file::class_guid = +{ 0xb2f9fc40, 0x3e55, 0x4b23, { 0xa2, 0xc9, 0x22, 0xba, 0xad, 0x87, 0x95, 0xb1 } }; + +// {6374340F-82D4-4471-A24B-A754B1398285} +FOOGUIDDECL const GUID file_dynamicinfo::class_guid = +{ 0x6374340f, 0x82d4, 0x4471, { 0xa2, 0x4b, 0xa7, 0x54, 0xb1, 0x39, 0x82, 0x85 } }; + +// {E171F954-4D7D-4F6F-9CC4-8A5B580BD410} +FOOGUIDDECL const GUID file_dynamicinfo_v2::class_guid = +{ 0xe171f954, 0x4d7d, 0x4f6f, { 0x9c, 0xc4, 0x8a, 0x5b, 0x58, 0xb, 0xd4, 0x10 } }; + +// {89FC86C9-D3DB-4ABB-B3B7-705BE43C86DB} +FOOGUIDDECL const GUID file_cached::class_guid = +{ 0x67fc5d4c, 0x50ed, 0x4da3, { 0x95, 0x42, 0x9b, 0x30, 0xc4, 0xa4, 0x42, 0x9a } }; + +// {A00CB77D-ED72-4031-806B-4E45AF995241} +FOOGUIDDECL const GUID replaygain_manager::class_guid = +{ 0xa00cb77d, 0xed72, 0x4031, { 0x80, 0x6b, 0x4e, 0x45, 0xaf, 0x99, 0x52, 0x41 } }; + +// {2DD58954-09E0-4B3B-AEE6-484E83111A73} +FOOGUIDDECL const GUID replaygain_manager_v2::class_guid = +{ 0x2dd58954, 0x9e0, 0x4b3b,{ 0xae, 0xe6, 0x48, 0x4e, 0x83, 0x11, 0x1a, 0x73 } }; + +// {3F7674AB-044C-4796-8801-6C443C244D88} +FOOGUIDDECL const GUID titleformat_compiler::class_guid = +{ 0x3f7674ab, 0x44c, 0x4796, { 0x88, 0x1, 0x6c, 0x44, 0x3c, 0x24, 0x4d, 0x88 } }; + +FOOGUIDDECL const GUID user_interface::class_guid = { 0x1add4dc4, 0xb278, 0x4a0c, { 0xa1, 0x5, 0x26, 0x29, 0xf4, 0xb3, 0x12, 0xf4 } }; +FOOGUIDDECL const GUID user_interface_v2::class_guid = { 0x5f5ac82b, 0x44a7, 0x4872,{ 0xb7, 0x86, 0x1c, 0x1f, 0xc6, 0x56, 0x6b, 0x60 } }; + +FOOGUIDDECL const GUID user_interface_v2::cap_suppress_core_shellhook = { 0xe5950632, 0x750d, 0x4265,{ 0x8c, 0xea, 0x6c, 0xf2, 0x77, 0x91, 0x44, 0x40 } }; +FOOGUIDDECL const GUID user_interface_v2::cap_suppress_core_uvc = { 0xe35156b2, 0xfbac, 0x450d,{ 0xa3, 0xf7, 0xf1, 0xda, 0x46, 0xb6, 0xdf, 0x8d } }; + + +// {994C0D0E-319E-45f3-92FC-518616E73ADC} +FOOGUIDDECL const GUID contextmenu_item::caller_now_playing = +{ 0x994c0d0e, 0x319e, 0x45f3, { 0x92, 0xfc, 0x51, 0x86, 0x16, 0xe7, 0x3a, 0xdc } }; + +// {47502BA1-816D-4a3e-ADE5-A7A9860A67DB} +FOOGUIDDECL const GUID contextmenu_item::caller_active_playlist_selection = +{ 0x47502ba1, 0x816d, 0x4a3e, { 0xad, 0xe5, 0xa7, 0xa9, 0x86, 0xa, 0x67, 0xdb } }; + +// Deprecated. +FOOGUIDDECL const GUID contextmenu_item::caller_playlist = caller_active_playlist_selection; + +// {B3CC1030-EF26-45cf-A84A-7FC169BC9FFB} +FOOGUIDDECL const GUID contextmenu_item::caller_active_playlist = +{ 0xb3cc1030, 0xef26, 0x45cf, { 0xa8, 0x4a, 0x7f, 0xc1, 0x69, 0xbc, 0x9f, 0xfb } }; + +// {5FDCD5E8-6EB2-4454-9EDA-527522893BED} +FOOGUIDDECL const GUID contextmenu_item::caller_playlist_manager = +{ 0x5fdcd5e8, 0x6eb2, 0x4454, { 0x9e, 0xda, 0x52, 0x75, 0x22, 0x89, 0x3b, 0xed } }; + +// {00000000-0000-0000-0000-000000000000} +FOOGUIDDECL const GUID contextmenu_item::caller_undefined = +{ 0x00000000, 0x0000, 0x0000, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }; + +// {FABEE3E9-8901-4df4-A2D7-B9898D86C39B} +FOOGUIDDECL const GUID contextmenu_item::caller_keyboard_shortcut_list = +{ 0xfabee3e9, 0x8901, 0x4df4, { 0xa2, 0xd7, 0xb9, 0x89, 0x8d, 0x86, 0xc3, 0x9b } }; + +// {FDA07C56-05D0-4b84-9FBD-A8BE556D474D} +FOOGUIDDECL const GUID contextmenu_item::caller_media_library_viewer = +{ 0xfda07c56, 0x5d0, 0x4b84, { 0x9f, 0xbd, 0xa8, 0xbe, 0x55, 0x6d, 0x47, 0x4d } }; + +// {95DE5842-30F5-4f72-B40C-191663782F80} +FOOGUIDDECL const GUID keyboard_shortcut_manager::class_guid = +{ 0x95de5842, 0x30f5, 0x4f72, { 0xb4, 0xc, 0x19, 0x16, 0x63, 0x78, 0x2f, 0x80 } }; + +// {CD19C870-DA6A-4b8a-A118-732A8102D07D} +FOOGUIDDECL const GUID keyboard_shortcut_manager_v2::class_guid = +{ 0xcd19c870, 0xda6a, 0x4b8a, { 0xa1, 0x18, 0x73, 0x2a, 0x81, 0x2, 0xd0, 0x7d } }; + +// {30F95BEB-FDF4-4a75-B597-60CAF93B39C4} +FOOGUIDDECL const GUID packet_decoder::owner_MP4 = +{ 0x30f95beb, 0xfdf4, 0x4a75, { 0xb5, 0x97, 0x60, 0xca, 0xf9, 0x3b, 0x39, 0xc4 } }; + +// {8F450CB3-A083-4b83-8D85-ADCE5EA6D57F} +FOOGUIDDECL const GUID packet_decoder::owner_MP4_ALAC = +{ 0x8f450cb3, 0xa083, 0x4b83, { 0x8d, 0x85, 0xad, 0xce, 0x5e, 0xa6, 0xd5, 0x7f } }; + +// {62263199-48C2-4777-B33A-87ED161A1630} +FOOGUIDDECL const GUID packet_decoder::owner_MP4_AC3 = +{ 0x62263199, 0x48c2, 0x4777, { 0xb3, 0x3a, 0x87, 0xed, 0x16, 0x1a, 0x16, 0x30 } }; + +// {AE6325BE-6C65-4B8F-B60D-5C803763BFB5} +FOOGUIDDECL const GUID packet_decoder::owner_MP4_EAC3 = +{ 0xae6325be, 0x6c65, 0x4b8f, { 0xb6, 0xd, 0x5c, 0x80, 0x37, 0x63, 0xbf, 0xb5 } }; + + +// {40017871-50A9-48b6-BF60-BD181A227F9B} +FOOGUIDDECL const GUID packet_decoder::owner_MP4_AMR = +{ 0x40017871, 0x50a9, 0x48b6, { 0xbf, 0x60, 0xbd, 0x18, 0x1a, 0x22, 0x7f, 0x9b } }; + +// {2E729EA0-6BEB-4392-BF24-75C69B60166D} +FOOGUIDDECL const GUID packet_decoder::owner_MP4_AMR_WB = +{ 0x2e729ea0, 0x6beb, 0x4392, { 0xbf, 0x24, 0x75, 0xc6, 0x9b, 0x60, 0x16, 0x6d } }; + +// {AF5B7CB0-A08E-404a-A3C0-5C5EA1A8A05C} +FOOGUIDDECL const GUID packet_decoder::owner_ADTS = +{ 0xaf5b7cb0, 0xa08e, 0x404a, { 0xa3, 0xc0, 0x5c, 0x5e, 0xa1, 0xa8, 0xa0, 0x5c } }; + +// {F72D2EAE-835C-4dfb-97C6-624343EFAFB0} +FOOGUIDDECL const GUID packet_decoder::owner_ADIF = +{ 0xf72d2eae, 0x835c, 0x4dfb, { 0x97, 0xc6, 0x62, 0x43, 0x43, 0xef, 0xaf, 0xb0 } }; + +// {5C2DE804-EAEE-4b8e-8C14-9207A2549BBE} +FOOGUIDDECL const GUID packet_decoder::owner_matroska = +{ 0x5c2de804, 0xeaee, 0x4b8e, { 0x8c, 0x14, 0x92, 0x7, 0xa2, 0x54, 0x9b, 0xbe } }; + +// {7B741A69-1AC7-440d-A01D-88536DD4DE1C} +FOOGUIDDECL const GUID packet_decoder::owner_MP3 = +{ 0x7b741a69, 0x1ac7, 0x440d, { 0xa0, 0x1d, 0x88, 0x53, 0x6d, 0xd4, 0xde, 0x1c } }; + +// {17B300A0-3110-4400-A434-C18FBEEABA81} +FOOGUIDDECL const GUID packet_decoder::owner_MP2 = +{ 0x17b300a0, 0x3110, 0x4400, { 0xa4, 0x34, 0xc1, 0x8f, 0xbe, 0xea, 0xba, 0x81 } }; + +// {1C068E5E-DD65-4639-BF85-78B297C8FFAC} +FOOGUIDDECL const GUID packet_decoder::owner_MP1 = +{ 0x1c068e5e, 0xdd65, 0x4639, { 0xbf, 0x85, 0x78, 0xb2, 0x97, 0xc8, 0xff, 0xac } }; + +// {BC73F9FC-0107-480e-BF0E-BE58AF7AF328} +FOOGUIDDECL const GUID packet_decoder::property_samplerate = +{ 0xbc73f9fc, 0x107, 0x480e, { 0xbf, 0xe, 0xbe, 0x58, 0xaf, 0x7a, 0xf3, 0x28 } }; + +// {E5D19AAD-931B-48ac-AA6E-95E2C23BEC37} +FOOGUIDDECL const GUID packet_decoder::property_bitspersample = +{ 0xe5d19aad, 0x931b, 0x48ac, { 0xaa, 0x6e, 0x95, 0xe2, 0xc2, 0x3b, 0xec, 0x37 } }; + +// {1AFA1145-E774-4c26-91D6-3F5DD61E260E} +FOOGUIDDECL const GUID packet_decoder::property_channels = +{ 0x1afa1145, 0xe774, 0x4c26, { 0x91, 0xd6, 0x3f, 0x5d, 0xd6, 0x1e, 0x26, 0xe } }; + +// {29C556DA-065A-4d24-8A11-0F9DBC05A817} +FOOGUIDDECL const GUID packet_decoder::property_byteorder = +{ 0x29c556da, 0x65a, 0x4d24, { 0x8a, 0x11, 0xf, 0x9d, 0xbc, 0x5, 0xa8, 0x17 } }; + +// {0759C32F-E78E-4479-B0C0-B653DFA014D8} +FOOGUIDDECL const GUID packet_decoder::property_signed = +{ 0x759c32f, 0xe78e, 0x4479, { 0xb0, 0xc0, 0xb6, 0x53, 0xdf, 0xa0, 0x14, 0xd8 } }; + +// {BB31669E-0C30-4c5f-AAF0-20CD49D46058} +FOOGUIDDECL const GUID packet_decoder::property_channelmask = +{ 0xbb31669e, 0xc30, 0x4c5f, { 0xaa, 0xf0, 0x20, 0xcd, 0x49, 0xd4, 0x60, 0x58 } }; + +// {E2C734FD-D4C2-467E-B5D3-4C13D9BD300E} +FOOGUIDDECL const GUID packet_decoder::property_bufferpadding = +{ 0xe2c734fd, 0xd4c2, 0x467e, { 0xb5, 0xd3, 0x4c, 0x13, 0xd9, 0xbd, 0x30, 0xe } }; + +// {61FD52AD-5146-417E-8662-36152E60A7D0} +FOOGUIDDECL const GUID packet_decoder::property_checkingintegrity = +{ 0x61fd52ad, 0x5146, 0x417e, { 0x86, 0x62, 0x36, 0x15, 0x2e, 0x60, 0xa7, 0xd0 } }; + +// {809A5B7F-99D3-4848-AFC9-7E02B287E33F} +FOOGUIDDECL const GUID packet_decoder::property_eventlogger = +{ 0x809a5b7f, 0x99d3, 0x4848, { 0xaf, 0xc9, 0x7e, 0x2, 0xb2, 0x87, 0xe3, 0x3f } }; + +// {7AD5E747-CBF3-487D-884F-D93CF8B86EB0} +FOOGUIDDECL const GUID packet_decoder::property_mp3_delayless = +{ 0x7ad5e747, 0xcbf3, 0x487d, { 0x88, 0x4f, 0xd9, 0x3c, 0xf8, 0xb8, 0x6e, 0xb0 } }; + +// {CC2741CF-B57F-43A7-83C9-01C8628E6FDC} +FOOGUIDDECL const GUID packet_decoder::property_query_delay_samples = +{ 0xcc2741cf, 0xb57f, 0x43a7, { 0x83, 0xc9, 0x1, 0xc8, 0x62, 0x8e, 0x6f, 0xdc } }; + +// {69DD756A-1396-4D69-A08D-82B34B033141} +FOOGUIDDECL const GUID packet_decoder::property_query_mp4_use_elst = +{ 0x69dd756a, 0x1396, 0x4d69, { 0xa0, 0x8d, 0x82, 0xb3, 0x4b, 0x3, 0x31, 0x41 } }; + +// {6F441057-1D18-4a58-9AC4-8F409CDA7DFD} +FOOGUIDDECL const GUID standard_commands::guid_context_file_properties = +{ 0x6f441057, 0x1d18, 0x4a58, { 0x9a, 0xc4, 0x8f, 0x40, 0x9c, 0xda, 0x7d, 0xfd } }; + +// {EFC1E9C8-EEEF-427a-8F42-E5781605846D} +FOOGUIDDECL const GUID standard_commands::guid_context_file_open_directory = +{ 0xefc1e9c8, 0xeeef, 0x427a, { 0x8f, 0x42, 0xe5, 0x78, 0x16, 0x5, 0x84, 0x6d } }; + +// {FFE18008-BCA2-4b29-AB88-8816B492C434} +FOOGUIDDECL const GUID standard_commands::guid_context_copy_names = +{ 0xffe18008, 0xbca2, 0x4b29, { 0xab, 0x88, 0x88, 0x16, 0xb4, 0x92, 0xc4, 0x34 } }; + +// {44B8F02B-5408-4361-8240-18DEC881B95E} +FOOGUIDDECL const GUID standard_commands::guid_context_send_to_playlist = +{ 0x44b8f02b, 0x5408, 0x4361, { 0x82, 0x40, 0x18, 0xde, 0xc8, 0x81, 0xb9, 0x5e } }; + +// {8C3BA2CB-BC4D-4752-8282-C6F9AED75A78} +FOOGUIDDECL const GUID standard_commands::guid_context_reload_info = +{ 0x8c3ba2cb, 0xbc4d, 0x4752, { 0x82, 0x82, 0xc6, 0xf9, 0xae, 0xd7, 0x5a, 0x78 } }; + +// {BD045EA4-E5E9-4206-8FF9-12AD9F5DCDE1} +FOOGUIDDECL const GUID standard_commands::guid_context_reload_info_if_changed = +{ 0xbd045ea4, 0xe5e9, 0x4206, { 0x8f, 0xf9, 0x12, 0xad, 0x9f, 0x5d, 0xcd, 0xe1 } }; + +// {684D9FBB-4383-44a2-9789-7EE1376209C6} +FOOGUIDDECL const GUID standard_commands::guid_context_rewrite_info = +{ 0x684d9fbb, 0x4383, 0x44a2, { 0x97, 0x89, 0x7e, 0xe1, 0x37, 0x62, 0x9, 0xc6 } }; + +// {860179B7-962F-4340-ACAD-0DDAF060A6B8} +FOOGUIDDECL const GUID standard_commands::guid_context_remove_tags = +{ 0x860179b7, 0x962f, 0x4340, { 0xac, 0xad, 0xd, 0xda, 0xf0, 0x60, 0xa6, 0xb8 } }; + +// {A7E7ECB7-1943-4907-83B3-60E92353C7FA} +FOOGUIDDECL const GUID standard_commands::guid_context_convert_run = +{ 0xa7e7ecb7, 0x1943, 0x4907, { 0x83, 0xb3, 0x60, 0xe9, 0x23, 0x53, 0xc7, 0xfa } }; + +// {BED4FB6E-C4F2-40dd-B528-1DE1450DDFE9} +FOOGUIDDECL const GUID standard_commands::guid_context_convert_run_withcue = +{ 0xbed4fb6e, 0xc4f2, 0x40dd, { 0xb5, 0x28, 0x1d, 0xe1, 0x45, 0xd, 0xdf, 0xe9 } }; + +// {A58AE6EA-A34F-4879-B25C-F31F2CBC4DA9} +FOOGUIDDECL const GUID standard_commands::guid_context_convert_run_singlefile = +{ 0xa58ae6ea, 0xa34f, 0x4879, { 0xb2, 0x5c, 0xf3, 0x1f, 0x2c, 0xbc, 0x4d, 0xa9 } }; + +// {A8EFA42D-76CB-42bc-8803-70516567B13D} +FOOGUIDDECL const GUID standard_commands::guid_context_write_cd = +{ 0xa8efa42d, 0x76cb, 0x42bc, { 0x88, 0x3, 0x70, 0x51, 0x65, 0x67, 0xb1, 0x3d } }; + +// {487DAED1-02FB-4336-A813-5E90317AB041} +FOOGUIDDECL const GUID standard_commands::guid_context_rg_scan_track = +{ 0x487daed1, 0x2fb, 0x4336, { 0xa8, 0x13, 0x5e, 0x90, 0x31, 0x7a, 0xb0, 0x41 } }; + +// {3850F34C-F619-4296-B8A0-26C617B1D16C} +FOOGUIDDECL const GUID standard_commands::guid_context_rg_scan_album = +{ 0x3850f34c, 0xf619, 0x4296, { 0xb8, 0xa0, 0x26, 0xc6, 0x17, 0xb1, 0xd1, 0x6c } }; + +// {6A2DBA02-260C-436f-8F41-0190A4298266} +FOOGUIDDECL const GUID standard_commands::guid_context_rg_scan_album_multi = +{ 0x6a2dba02, 0x260c, 0x436f, { 0x8f, 0x41, 0x1, 0x90, 0xa4, 0x29, 0x82, 0x66 } }; + +// {54C82A92-5824-4381-8D1B-79FBB1E2ABB8} +FOOGUIDDECL const GUID standard_commands::guid_context_rg_remove = +{ 0x54c82a92, 0x5824, 0x4381, { 0x8d, 0x1b, 0x79, 0xfb, 0xb1, 0xe2, 0xab, 0xb8 } }; + +// {69EAA594-13D9-4237-9BD7-11A39FDD1454} +FOOGUIDDECL const GUID standard_commands::guid_context_save_playlist = +{ 0x69eaa594, 0x13d9, 0x4237, { 0x9b, 0xd7, 0x11, 0xa3, 0x9f, 0xdd, 0x14, 0x54 } }; + +// {2BEB6836-C657-40ef-BB6E-D5B222AB89CE} +FOOGUIDDECL const GUID standard_commands::guid_context_masstag_edit = +{ 0x2beb6836, 0xc657, 0x40ef, { 0xbb, 0x6e, 0xd5, 0xb2, 0x22, 0xab, 0x89, 0xce } }; + +// {A579FF07-5D0B-48ed-A071-B6FCE4385AA9} +FOOGUIDDECL const GUID standard_commands::guid_context_masstag_rename = +{ 0xa579ff07, 0x5d0b, 0x48ed, { 0xa0, 0x71, 0xb6, 0xfc, 0xe4, 0x38, 0x5a, 0xa9 } }; + +// {77CFBCD0-98DC-4015-B327-D7142C664806} +FOOGUIDDECL const GUID standard_commands::guid_main_always_on_top = +{ 0x77cfbcd0, 0x98dc, 0x4015, { 0xb3, 0x27, 0xd7, 0x14, 0x2c, 0x66, 0x48, 0x6 } }; + +// {11213A01-9F36-4e69-A1BB-7A72F418DE3A} +FOOGUIDDECL const GUID standard_commands::guid_main_preferences = +{ 0x11213a01, 0x9f36, 0x4e69, { 0xa1, 0xbb, 0x7a, 0x72, 0xf4, 0x18, 0xde, 0x3a } }; + +// {EDA23441-5D38-4499-A22C-FE0CE0A987D9} +FOOGUIDDECL const GUID standard_commands::guid_main_about = +{ 0xeda23441, 0x5d38, 0x4499, { 0xa2, 0x2c, 0xfe, 0xc, 0xe0, 0xa9, 0x87, 0xd9 } }; + +// {6D38C73A-15D8-472c-8E68-6F946B82ECB4} +FOOGUIDDECL const GUID standard_commands::guid_main_exit = +{ 0x6d38c73a, 0x15d8, 0x472c, { 0x8e, 0x68, 0x6f, 0x94, 0x6b, 0x82, 0xec, 0xb4 } }; + +// {EF9B60FE-CB03-4c40-A8FD-3F1821020B37} +FOOGUIDDECL const GUID standard_commands::guid_main_restart = +{ 0xef9b60fe, 0xcb03, 0x4c40, { 0xa8, 0xfd, 0x3f, 0x18, 0x21, 0x2, 0xb, 0x37 } }; + +// {90500F09-F16F-415e-A047-AC5045C95FFE} +FOOGUIDDECL const GUID standard_commands::guid_main_activate = +{ 0x90500f09, 0xf16f, 0x415e, { 0xa0, 0x47, 0xac, 0x50, 0x45, 0xc9, 0x5f, 0xfe } }; + +// {13C17E8D-0D6F-41a4-B24A-59371043E925} +FOOGUIDDECL const GUID standard_commands::guid_main_hide = +{ 0x13c17e8d, 0xd6f, 0x41a4, { 0xb2, 0x4a, 0x59, 0x37, 0x10, 0x43, 0xe9, 0x25 } }; + +// {D9473FB2-BF11-4be0-972F-0E43F166A118} +FOOGUIDDECL const GUID standard_commands::guid_main_activate_or_hide = +{ 0xd9473fb2, 0xbf11, 0x4be0, { 0x97, 0x2f, 0xe, 0x43, 0xf1, 0x66, 0xa1, 0x18 } }; + +// {91E349DA-8800-42e5-BC8C-F3A92577AE84} +FOOGUIDDECL const GUID standard_commands::guid_main_titleformat_help = +{ 0x91e349da, 0x8800, 0x42e5, { 0xbc, 0x8c, 0xf3, 0xa9, 0x25, 0x77, 0xae, 0x84 } }; + +// {FBCFE01C-6C57-4e6a-A9F1-62334640DC91} +FOOGUIDDECL const GUID standard_commands::guid_main_playback_follows_cursor= +{ 0xfbcfe01c, 0x6c57, 0x4e6a, { 0xa9, 0xf1, 0x62, 0x33, 0x46, 0x40, 0xdc, 0x91 } }; + +// {0E1C730A-1EA9-41cc-9C30-25700ABDD9FA} +FOOGUIDDECL const GUID standard_commands::guid_main_cursor_follows_playback= +{ 0xe1c730a, 0x1ea9, 0x41cc, { 0x9c, 0x30, 0x25, 0x70, 0xa, 0xbd, 0xd9, 0xfa } }; + +// {E58895A0-A2F0-45b6-8799-1440E4DB7284} +FOOGUIDDECL const GUID standard_commands::guid_main_next = +{ 0xe58895a0, 0xa2f0, 0x45b6, { 0x87, 0x99, 0x14, 0x40, 0xe4, 0xdb, 0x72, 0x84 } }; + +// {059C4566-4708-4ebf-8139-6A8EA5A9DFC8} +FOOGUIDDECL const GUID standard_commands::guid_main_previous = +{ 0x59c4566, 0x4708, 0x4ebf, { 0x81, 0x39, 0x6a, 0x8e, 0xa5, 0xa9, 0xdf, 0xc8 } }; + +// {18B1278A-F1BB-4b48-BC3D-6EC9EF80AD19} +FOOGUIDDECL const GUID standard_commands::guid_main_next_or_random = +{ 0x18b1278a, 0xf1bb, 0x4b48, { 0xbc, 0x3d, 0x6e, 0xc9, 0xef, 0x80, 0xad, 0x19 } }; + +// {42BDA765-30A8-40fa-BFA4-6A4E2F2B2CE9} +FOOGUIDDECL const GUID standard_commands::guid_main_random = +{ 0x42bda765, 0x30a8, 0x40fa, { 0xbf, 0xa4, 0x6a, 0x4e, 0x2f, 0x2b, 0x2c, 0xe9 } }; + +// {FCEF5262-7FA5-452e-A527-C14E0CB582DE} +FOOGUIDDECL const GUID standard_commands::guid_main_pause = +{ 0xfcef5262, 0x7fa5, 0x452e, { 0xa5, 0x27, 0xc1, 0x4e, 0xc, 0xb5, 0x82, 0xde } }; + +// {D3F83B15-D4AF-4586-8BD0-4EA415E31FE1} +FOOGUIDDECL const GUID standard_commands::guid_main_play = +{ 0xd3f83b15, 0xd4af, 0x4586, { 0x8b, 0xd0, 0x4e, 0xa4, 0x15, 0xe3, 0x1f, 0xe1 } }; + +// {8DEBC44E-EDA2-48d4-8696-31FC29D1F383} +FOOGUIDDECL const GUID standard_commands::guid_main_play_or_pause = +{ 0x8debc44e, 0xeda2, 0x48d4, { 0x86, 0x96, 0x31, 0xfc, 0x29, 0xd1, 0xf3, 0x83 } }; + +// {2DF17F25-80B9-4a43-B21D-620458FDDE1E} +FOOGUIDDECL const GUID standard_commands::guid_main_rg_set_album = +{ 0x2df17f25, 0x80b9, 0x4a43, { 0xb2, 0x1d, 0x62, 0x4, 0x58, 0xfd, 0xde, 0x1e } }; + +// {C26F1955-5753-4836-887F-84A563DD6DD9} +FOOGUIDDECL const GUID standard_commands::guid_main_rg_set_track = +{ 0xc26f1955, 0x5753, 0x4836, { 0x88, 0x7f, 0x84, 0xa5, 0x63, 0xdd, 0x6d, 0xd9 } }; + +// {8D2D808E-6AA2-455b-A2F1-CDB019328140} +FOOGUIDDECL const GUID standard_commands::guid_main_rg_disable = +{ 0x8d2d808e, 0x6aa2, 0x455b, { 0xa2, 0xf1, 0xcd, 0xb0, 0x19, 0x32, 0x81, 0x40 } }; + +// {17D95800-D2B6-4160-A227-7E57A8069619} +FOOGUIDDECL const GUID standard_commands::guid_main_rg_byorder = +{ 0x17d95800, 0xd2b6, 0x4160, { 0xa2, 0x27, 0x7e, 0x57, 0xa8, 0x6, 0x96, 0x19 } }; + + +// {C3378028-165F-4374-966C-2FA2E0FCD3A8} +FOOGUIDDECL const GUID standard_commands::guid_main_stop = +{ 0xc3378028, 0x165f, 0x4374, { 0x96, 0x6c, 0x2f, 0xa2, 0xe0, 0xfc, 0xd3, 0xa8 } }; + +// {EE057982-22F9-4862-A986-859E463316FB} +FOOGUIDDECL const GUID standard_commands::guid_main_stop_after_current = +{ 0xee057982, 0x22f9, 0x4862, { 0xa9, 0x86, 0x85, 0x9e, 0x46, 0x33, 0x16, 0xfb } }; + +// {11DC6526-73C4-44f0-91B1-DE5C2D26B0C7} +FOOGUIDDECL const GUID standard_commands::guid_main_volume_down = +{ 0x11dc6526, 0x73c4, 0x44f0, { 0x91, 0xb1, 0xde, 0x5c, 0x2d, 0x26, 0xb0, 0xc7 } }; + +// {A313E630-A04A-4ae8-B5B4-0A944AC964FF} +FOOGUIDDECL const GUID standard_commands::guid_main_volume_up = +{ 0xa313e630, 0xa04a, 0x4ae8, { 0xb5, 0xb4, 0xa, 0x94, 0x4a, 0xc9, 0x64, 0xff } }; + +// {4A36285B-B4AF-46ed-A1AA-6333057F485B} +FOOGUIDDECL const GUID standard_commands::guid_main_volume_mute = +{ 0x4a36285b, 0xb4af, 0x46ed, { 0xa1, 0xaa, 0x63, 0x33, 0x5, 0x7f, 0x48, 0x5b } }; + +// {2DC43C22-CA09-4ef9-A61E-7A0C1DAAE21E} +FOOGUIDDECL const GUID standard_commands::guid_main_add_directory = +{ 0x2dc43c22, 0xca09, 0x4ef9, { 0xa6, 0x1e, 0x7a, 0xc, 0x1d, 0xaa, 0xe2, 0x1e } }; + +// {FC89C278-4475-4853-96C9-F7F05E8CC837} +FOOGUIDDECL const GUID standard_commands::guid_main_add_files = +{ 0xfc89c278, 0x4475, 0x4853, { 0x96, 0xc9, 0xf7, 0xf0, 0x5e, 0x8c, 0xc8, 0x37 } }; + +// {9CA39D38-AC9B-4cca-B0CE-C0F62D188114} +FOOGUIDDECL const GUID standard_commands::guid_main_add_location = +{ 0x9ca39d38, 0xac9b, 0x4cca, { 0xb0, 0xce, 0xc0, 0xf6, 0x2d, 0x18, 0x81, 0x14 } }; + +// {73D6E69D-0DC9-4d5c-A7EE-FF4BE3896B08} +FOOGUIDDECL const GUID standard_commands::guid_main_add_playlist = +{ 0x73d6e69d, 0xdc9, 0x4d5c, { 0xa7, 0xee, 0xff, 0x4b, 0xe3, 0x89, 0x6b, 0x8 } }; + +// {55559142-7AEA-4c20-9B72-1D48E970A274} +FOOGUIDDECL const GUID standard_commands::guid_main_clear_playlist = +{ 0x55559142, 0x7aea, 0x4c20, { 0x9b, 0x72, 0x1d, 0x48, 0xe9, 0x70, 0xa2, 0x74 } }; + +// {BF72488F-36AC-46b3-A36D-193E60C79BC5} +FOOGUIDDECL const GUID standard_commands::guid_main_create_playlist = +{ 0xbf72488f, 0x36ac, 0x46b3, { 0xa3, 0x6d, 0x19, 0x3e, 0x60, 0xc7, 0x9b, 0xc5 } }; + +// {59E99BEE-42A3-4526-B06D-56C0C49F0BC1} +FOOGUIDDECL const GUID standard_commands::guid_main_highlight_playing = +{ 0x59e99bee, 0x42a3, 0x4526, { 0xb0, 0x6d, 0x56, 0xc0, 0xc4, 0x9f, 0xb, 0xc1 } }; + +// {D94393D4-9DBB-4e5c-BE8C-BE9CA80E214D} +FOOGUIDDECL const GUID standard_commands::guid_main_load_playlist = +{ 0xd94393d4, 0x9dbb, 0x4e5c, { 0xbe, 0x8c, 0xbe, 0x9c, 0xa8, 0xe, 0x21, 0x4d } }; + +// {EE1308C5-EBD2-48f1-959D-2627069C2E0F} +FOOGUIDDECL const GUID standard_commands::guid_main_next_playlist = +{ 0xee1308c5, 0xebd2, 0x48f1, { 0x95, 0x9d, 0x26, 0x27, 0x6, 0x9c, 0x2e, 0xf } }; + +// {486ECDA3-7BA2-49e9-BB44-4DB9DF9320C7} +FOOGUIDDECL const GUID standard_commands::guid_main_previous_playlist = +{ 0x486ecda3, 0x7ba2, 0x49e9, { 0xbb, 0x44, 0x4d, 0xb9, 0xdf, 0x93, 0x20, 0xc7 } }; + +// {69C778AA-B836-40a0-89CD-7A2E50C102CB} +FOOGUIDDECL const GUID standard_commands::guid_main_open = +{ 0x69c778aa, 0xb836, 0x40a0, { 0x89, 0xcd, 0x7a, 0x2e, 0x50, 0xc1, 0x2, 0xcb } }; + +// {EB7FB5A4-5904-4d2c-B66C-D882A3B15277} +FOOGUIDDECL const GUID standard_commands::guid_main_remove_playlist = +{ 0xeb7fb5a4, 0x5904, 0x4d2c, { 0xb6, 0x6c, 0xd8, 0x82, 0xa3, 0xb1, 0x52, 0x77 } }; + +// {C297BADB-8098-45a9-A5E8-B53A0D780CE3} +FOOGUIDDECL const GUID standard_commands::guid_main_remove_dead_entries = +{ 0xc297badb, 0x8098, 0x45a9, { 0xa5, 0xe8, 0xb5, 0x3a, 0xd, 0x78, 0xc, 0xe3 } }; + +// {D08C2921-7750-4979-98F9-3A513A31FF96} +FOOGUIDDECL const GUID standard_commands::guid_main_remove_duplicates = +{ 0xd08c2921, 0x7750, 0x4979, { 0x98, 0xf9, 0x3a, 0x51, 0x3a, 0x31, 0xff, 0x96 } }; + +// {D3A25E47-BA98-4e6b-95AD-A7502919EB75} +FOOGUIDDECL const GUID standard_commands::guid_main_rename_playlist = +{ 0xd3a25e47, 0xba98, 0x4e6b, { 0x95, 0xad, 0xa7, 0x50, 0x29, 0x19, 0xeb, 0x75 } }; + +// {0FDCFC65-9B39-445a-AA88-4D245F217480} +FOOGUIDDECL const GUID standard_commands::guid_main_save_all_playlists = +{ 0xfdcfc65, 0x9b39, 0x445a, { 0xaa, 0x88, 0x4d, 0x24, 0x5f, 0x21, 0x74, 0x80 } }; + +// {370B720B-4CF7-465b-908C-2D2ADD027900} +FOOGUIDDECL const GUID standard_commands::guid_main_save_playlist = +{ 0x370b720b, 0x4cf7, 0x465b, { 0x90, 0x8c, 0x2d, 0x2a, 0xdd, 0x2, 0x79, 0x0 } }; + +// {A6CFC2A8-56B3-4d12-88E7-0237961AC47E} +FOOGUIDDECL const GUID standard_commands::guid_main_playlist_search = +{ 0xa6cfc2a8, 0x56b3, 0x4d12, { 0x88, 0xe7, 0x2, 0x37, 0x96, 0x1a, 0xc4, 0x7e } }; + +// {383D4E8D-7E30-4fb8-B5DD-8C975D89E58E} +FOOGUIDDECL const GUID standard_commands::guid_main_playlist_sel_crop = +{ 0x383d4e8d, 0x7e30, 0x4fb8, { 0xb5, 0xdd, 0x8c, 0x97, 0x5d, 0x89, 0xe5, 0x8e } }; + +// {E0EEA319-E282-4e6c-8B82-4DFD42A1D4ED} +FOOGUIDDECL const GUID standard_commands::guid_main_playlist_sel_remove = +{ 0xe0eea319, 0xe282, 0x4e6c, { 0x8b, 0x82, 0x4d, 0xfd, 0x42, 0xa1, 0xd4, 0xed } }; + +// {F0845920-7F6D-40ac-B2EB-3D00C2C8260B} +FOOGUIDDECL const GUID standard_commands::guid_main_playlist_sel_invert = +{ 0xf0845920, 0x7f6d, 0x40ac, { 0xb2, 0xeb, 0x3d, 0x0, 0xc2, 0xc8, 0x26, 0xb } }; + +// {29910B33-79E9-40da-992B-5A4AA4281F78} +FOOGUIDDECL const GUID standard_commands::guid_main_playlist_undo = +{ 0x29910b33, 0x79e9, 0x40da, { 0x99, 0x2b, 0x5a, 0x4a, 0xa4, 0x28, 0x1f, 0x78 } }; + +// {7A9D9450-A8BF-4a88-B44F-DCD83A49DD7A} +FOOGUIDDECL const GUID standard_commands::guid_main_playlist_redo = +{ 0x7a9d9450, 0xa8bf, 0x4a88, { 0xb4, 0x4f, 0xdc, 0xd8, 0x3a, 0x49, 0xdd, 0x7a } }; + +// {02D89A8A-5F7D-41c3-A215-6731D8621036} +FOOGUIDDECL const GUID standard_commands::guid_main_show_console = +{ 0x5b652d25, 0xce44, 0x4737, { 0x99, 0xbb, 0xa3, 0xcf, 0x2a, 0xeb, 0x35, 0xcc } }; + +// {E6970E91-33BE-4288-AC01-4B02F07B5D38} +FOOGUIDDECL const GUID standard_commands::guid_main_play_cd = +{ 0xe6970e91, 0x33be, 0x4288, { 0xac, 0x1, 0x4b, 0x2, 0xf0, 0x7b, 0x5d, 0x38 } }; + +// {1073AB1D-38ED-4957-8B06-38BC878C1F40} +FOOGUIDDECL const GUID standard_commands::guid_main_restart_resetconfig = +{ 0x1073ab1d, 0x38ed, 0x4957, { 0x8b, 0x6, 0x38, 0xbc, 0x87, 0x8c, 0x1f, 0x40 } }; + +// {FDC8A1C2-93EF-4415-8C20-60B6517F0B5F} +FOOGUIDDECL const GUID standard_commands::guid_main_record = +{ 0xfdc8a1c2, 0x93ef, 0x4415, { 0x8c, 0x20, 0x60, 0xb6, 0x51, 0x7f, 0xb, 0x5f } }; + +// {45EB37D2-3CD9-4f0a-9B20-8EAE649D7A9F} +FOOGUIDDECL const GUID standard_commands::guid_main_playlist_moveback = +{ 0x45eb37d2, 0x3cd9, 0x4f0a, { 0x9b, 0x20, 0x8e, 0xae, 0x64, 0x9d, 0x7a, 0x9f } }; + +// {02298938-596A-41f7-A398-19766A06E6EB} +FOOGUIDDECL const GUID standard_commands::guid_main_playlist_moveforward = +{ 0x2298938, 0x596a, 0x41f7, { 0xa3, 0x98, 0x19, 0x76, 0x6a, 0x6, 0xe6, 0xeb } }; + +// {E910ACC6-A81A-4a20-9F62-19015BCD1013} +FOOGUIDDECL const GUID standard_commands::guid_main_saveconfig = +{ 0xe910acc6, 0xa81a, 0x4a20, { 0x9f, 0x62, 0x19, 0x1, 0x5b, 0xcd, 0x10, 0x13 } }; + +// {03445FCC-71D9-4e01-AE02-A557D30B4CD7} +FOOGUIDDECL const GUID standard_commands::guid_main_playlist_select_all = +{ 0x3445fcc, 0x71d9, 0x4e01, { 0xae, 0x2, 0xa5, 0x57, 0xd3, 0xb, 0x4c, 0xd7 } }; + +// {919FA72B-1DF9-49ee-A8F1-D8BA1DEAA7E9} +FOOGUIDDECL const GUID standard_commands::guid_main_show_now_playing = +{ 0x919fa72b, 0x1df9, 0x49ee, { 0xa8, 0xf1, 0xd8, 0xba, 0x1d, 0xea, 0xa7, 0xe9 } }; + +// {D8D51855-D79D-49b8-97A8-065785FA2426} +FOOGUIDDECL const GUID playlist_manager::class_guid= +{ 0xd8d51855, 0xd79d, 0x49b8, { 0x97, 0xa8, 0x6, 0x57, 0x85, 0xfa, 0x24, 0x26 } }; + +// {C303A0F1-8E88-4539-87E5-9E455B7D548C} +FOOGUIDDECL const GUID genrand_service::class_guid= +{ 0xc303a0f1, 0x8e88, 0x4539, { 0x87, 0xe5, 0x9e, 0x45, 0x5b, 0x7d, 0x54, 0x8c } }; + +// {EB8FA333-F365-46ad-8621-345671567BAA} +FOOGUIDDECL const GUID playlist_incoming_item_filter::class_guid= +{ 0xeb8fa333, 0xf365, 0x46ad, { 0x86, 0x21, 0x34, 0x56, 0x71, 0x56, 0x7b, 0xaa } }; + +// {FEBD85B5-C12D-45b5-B55D-0D3F432B0C6B} +FOOGUIDDECL const GUID library_manager::class_guid= +{ 0xfebd85b5, 0xc12d, 0x45b5, { 0xb5, 0x5d, 0xd, 0x3f, 0x43, 0x2b, 0xc, 0x6b } }; + +// {9A08B50F-E8C6-40c0-88C3-7530CF2C3115} +FOOGUIDDECL const GUID library_callback::class_guid= +{ 0x9a08b50f, 0xe8c6, 0x40c0, { 0x88, 0xc3, 0x75, 0x30, 0xcf, 0x2c, 0x31, 0x15 } }; + +// {D5CAC75A-3023-4dce-8B0D-B502BD056A7C} +FOOGUIDDECL const GUID popup_message::class_guid= +{ 0xd5cac75a, 0x3023, 0x4dce, { 0x8b, 0xd, 0xb5, 0x2, 0xbd, 0x5, 0x6a, 0x7c } }; + +// {13F9CB11-0EAE-4417-AF0C-8894DEB65E8B} +FOOGUIDDECL const GUID app_close_blocker::class_guid= +{ 0x13f9cb11, 0xeae, 0x4417, { 0xaf, 0xc, 0x88, 0x94, 0xde, 0xb6, 0x5e, 0x8b } }; + +// {5570A2D2-8F9E-48a7-AFAC-DAC00A3C1636} +FOOGUIDDECL const GUID file_operation_callback::class_guid= +{ 0x5570a2d2, 0x8f9e, 0x48a7, { 0xaf, 0xac, 0xda, 0xc0, 0xa, 0x3c, 0x16, 0x36 } }; + +// {340099D1-7BEC-4ac6-92A4-77FF01507891} +FOOGUIDDECL const GUID config_object::class_guid= +{ 0x340099d1, 0x7bec, 0x4ac6, { 0x92, 0xa4, 0x77, 0xff, 0x1, 0x50, 0x78, 0x91 } }; + +// {1BA04122-DE9A-4a90-9FC3-A6A7EC8F9626} +FOOGUIDDECL const GUID config_object_notify_manager::class_guid= +{ 0x1ba04122, 0xde9a, 0x4a90, { 0x9f, 0xc3, 0xa6, 0xa7, 0xec, 0x8f, 0x96, 0x26 } }; + +// {AD5138E6-CF8F-4482-89B2-B2EAAEDF3B4C} +FOOGUIDDECL const GUID standard_config_objects::bool_show_keyboard_shortcuts_in_menus= +{ 0xad5138e6, 0xcf8f, 0x4482, { 0x89, 0xb2, 0xb2, 0xea, 0xae, 0xdf, 0x3b, 0x4c } }; + +// {E91F618C-5741-470f-A178-21272C3ECA90} +FOOGUIDDECL const GUID standard_config_objects::bool_remember_window_positions= +{ 0xe91f618c, 0x5741, 0x470f, { 0xa1, 0x78, 0x21, 0x27, 0x2c, 0x3e, 0xca, 0x90 } }; + +// {5497FAA9-690C-4fb4-8BC0-678CEBCBBDC4} +FOOGUIDDECL const GUID standard_config_objects::bool_playback_follows_cursor= +{ 0x5497faa9, 0x690c, 0x4fb4, { 0x8b, 0xc0, 0x67, 0x8c, 0xeb, 0xcb, 0xbd, 0xc4 } }; + +// {58D3E2D6-DD6D-48dd-98EB-4EABF0ACFEB8} +FOOGUIDDECL const GUID standard_config_objects::bool_cursor_follows_playback= +{ 0x58d3e2d6, 0xdd6d, 0x48dd, { 0x98, 0xeb, 0x4e, 0xab, 0xf0, 0xac, 0xfe, 0xb8 } }; + +// {2DF7194D-86FC-4312-98DA-E820DA3E710D} +FOOGUIDDECL const GUID standard_config_objects::bool_ui_always_on_top= +{ 0x2df7194d, 0x86fc, 0x4312, { 0x98, 0xda, 0xe8, 0x20, 0xda, 0x3e, 0x71, 0xd } }; + +// {B572C86F-4206-40a0-8476-C7B27E74DB2D} +FOOGUIDDECL const GUID standard_config_objects::bool_playlist_stop_after_current= +{ 0xb572c86f, 0x4206, 0x40a0, { 0x84, 0x76, 0xc7, 0xb2, 0x7e, 0x74, 0xdb, 0x2d } }; + +// {FFDFDA96-699C-4617-A977-22DC2F039B00} +FOOGUIDDECL const GUID standard_config_objects::string_gui_last_directory_media= +{ 0xffdfda96, 0x699c, 0x4617, { 0xa9, 0x77, 0x22, 0xdc, 0x2f, 0x3, 0x9b, 0x0 } }; + +// {915D3B21-81CB-4b96-9762-1C90FBF371DF} +FOOGUIDDECL const GUID standard_config_objects::string_gui_last_directory_playlists= +{ 0x915d3b21, 0x81cb, 0x4b96, { 0x97, 0x62, 0x1c, 0x90, 0xfb, 0xf3, 0x71, 0xdf } }; + +// {338B6871-EB1F-4384-BF83-6BFACE5B66BC} +FOOGUIDDECL const GUID standard_config_objects::int32_dynamic_bitrate_display_rate= +{ 0x338b6871, 0xeb1f, 0x4384, { 0xbf, 0x83, 0x6b, 0xfa, 0xce, 0x5b, 0x66, 0xbc } }; + +// {3E35D949-DCDB-44e1-8013-9D1633C09756} +FOOGUIDDECL const GUID config_object_notify::class_guid= +{ 0x3e35d949, 0xdcdb, 0x44e1, { 0x80, 0x13, 0x9d, 0x16, 0x33, 0xc0, 0x97, 0x56 } }; + +// {4AC9408E-4D9C-4135-ACB5-2CAB35376FC9} +FOOGUIDDECL const GUID titleformat_object::class_guid= +{ 0x4ac9408e, 0x4d9c, 0x4135, { 0xac, 0xb5, 0x2c, 0xab, 0x35, 0x37, 0x6f, 0xc9 } }; + +// {25B0D20D-9BA3-4a7b-8D0E-89FAF75F916F} +FOOGUIDDECL const GUID tag_processor_id3v2::class_guid= +{ 0x25b0d20d, 0x9ba3, 0x4a7b, { 0x8d, 0xe, 0x89, 0xfa, 0xf7, 0x5f, 0x91, 0x6f } }; + +// {AD537D40-499D-4c29-81D4-C0FA496DD58C} +FOOGUIDDECL const GUID tag_processor_trailing::class_guid= +{ 0xad537d40, 0x499d, 0x4c29, { 0x81, 0xd4, 0xc0, 0xfa, 0x49, 0x6d, 0xd5, 0x8c } }; + +// {160885C6-3AA3-4f60-8718-1240615E6414} +FOOGUIDDECL const GUID metadb_handle::class_guid= +{ 0x160885c6, 0x3aa3, 0x4f60, { 0x87, 0x18, 0x12, 0x40, 0x61, 0x5e, 0x64, 0x14 } }; + +// {9DBC262F-4795-4292-824B-23A655011A3E} +FOOGUIDDECL const GUID threaded_process::class_guid= +{ 0x9dbc262f, 0x4795, 0x4292, { 0x82, 0x4b, 0x23, 0xa6, 0x55, 0x1, 0x1a, 0x3e } }; + +// {7B69141B-4271-4070-8998-10CD39249C12} +FOOGUIDDECL const GUID threaded_process_callback::class_guid= +{ 0x7b69141b, 0x4271, 0x4070, { 0x89, 0x98, 0x10, 0xcd, 0x39, 0x24, 0x9c, 0x12 } }; + + +// {64D18B80-5E97-40e4-A30C-A4640C60BCE5} +FOOGUIDDECL const GUID metadb_io::class_guid= +{ 0x64d18b80, 0x5e97, 0x40e4, { 0xa3, 0xc, 0xa4, 0x64, 0xc, 0x60, 0xbc, 0xe5 } }; + +FOOGUIDDECL const GUID preferences_page::guid_tagging = { 0x563107c3, 0xfc7d, 0x4022, { 0xa8, 0x72, 0xa8, 0x2a, 0x2b, 0x3f, 0xd5, 0x25 } }; + +// {B9218D23-F73D-4b61-A1D9-BFD420CDAC77} +FOOGUIDDECL const GUID preferences_page::guid_root= +{ 0xb9218d23, 0xf73d, 0x4b61, { 0xa1, 0xd9, 0xbf, 0xd4, 0x20, 0xcd, 0xac, 0x77 } }; + +// {2F0E2232-A5FD-43e4-B6AB-3839B8D1A707} +FOOGUIDDECL const GUID preferences_page::guid_hidden= +{ 0x2f0e2232, 0xa5fd, 0x43e4, { 0xb6, 0xab, 0x38, 0x39, 0xb8, 0xd1, 0xa7, 0x7 } }; + +// {627C0767-0793-44f8-8087-BE4934311282} +FOOGUIDDECL const GUID preferences_page::guid_tools= +{ 0x627c0767, 0x793, 0x44f8, { 0x80, 0x87, 0xbe, 0x49, 0x34, 0x31, 0x12, 0x82 } }; + +// {6AAA67B6-732F-4967-899A-18C5F8C70017} +FOOGUIDDECL const GUID preferences_page::guid_display= +{ 0x6aaa67b6, 0x732f, 0x4967, { 0x89, 0x9a, 0x18, 0xc5, 0xf8, 0xc7, 0x0, 0x17 } }; + +// {67508677-CFCC-4a1c-921C-49B39CC5B986} +FOOGUIDDECL const GUID preferences_page::guid_playback= +{ 0x67508677, 0xcfcc, 0x4a1c, { 0x92, 0x1c, 0x49, 0xb3, 0x9c, 0xc5, 0xb9, 0x86 } }; + +// {494326C8-88FF-4265-B2B2-E6470BEE13B3} +FOOGUIDDECL const GUID preferences_page::guid_visualisations= +{ 0x494326c8, 0x88ff, 0x4265, { 0xb2, 0xb2, 0xe6, 0x47, 0xb, 0xee, 0x13, 0xb3 } }; + +// {FC01B529-4BD7-47cd-BAF7-2FB632F0DBB6} +FOOGUIDDECL const GUID preferences_page::guid_input= +{ 0xfc01b529, 0x4bd7, 0x47cd, { 0xba, 0xf7, 0x2f, 0xb6, 0x32, 0xf0, 0xdb, 0xb6 } }; + +// {2E8E9647-4174-41b2-9EC4-910BE128082E} +FOOGUIDDECL const GUID preferences_page::guid_core= +{ 0x2e8e9647, 0x4174, 0x41b2, { 0x9e, 0xc4, 0x91, 0xb, 0xe1, 0x28, 0x8, 0x2e } }; + +// {7D0334E5-ADD7-4ff5-9DB8-A618B4824028} +FOOGUIDDECL const GUID preferences_page::guid_tag_writing= +{ 0x7d0334e5, 0xadd7, 0x4ff5, { 0x9d, 0xb8, 0xa6, 0x18, 0xb4, 0x82, 0x40, 0x28 } }; + +// {2D269FA9-6F78-4cec-9F1F-0A176EFCE77A} +FOOGUIDDECL const GUID preferences_page::guid_media_library= +{ 0x2d269fa9, 0x6f78, 0x4cec, { 0x9f, 0x1f, 0xa, 0x17, 0x6e, 0xfc, 0xe7, 0x7a } }; + +FOOGUIDDECL const GUID preferences_page::guid_output = { 0xa9038870, 0xdc08, 0x431d, { 0x8c, 0x91, 0x3b, 0x4e, 0x41, 0xd2, 0x43, 0x6d } }; +FOOGUIDDECL const GUID preferences_page::guid_advanced = { 0x56eb56ab, 0xedfe, 0x4853, { 0x90, 0xf7, 0xc6, 0xb8, 0x34, 0xfa, 0x2f, 0x6b } }; +FOOGUIDDECL const GUID preferences_page::guid_components = { 0xe966267, 0x7dfb, 0x433b, { 0xa0, 0x7c, 0x3f, 0x8c, 0xdd, 0x31, 0xa2, 0x58 } }; +// {B8C5CEEA-A5F4-4278-AA2D-798E4403001F} +FOOGUIDDECL const GUID library_viewer::class_guid= +{ 0xb8c5ceea, 0xa5f4, 0x4278, { 0xaa, 0x2d, 0x79, 0x8e, 0x44, 0x3, 0x0, 0x1f } }; + +// {5CD49B5D-D604-4c07-A8FA-FFD8512AFD2B} +FOOGUIDDECL const GUID message_loop::class_guid= +{ 0x5cd49b5d, 0xd604, 0x4c07, { 0xa8, 0xfa, 0xff, 0xd8, 0x51, 0x2a, 0xfd, 0x2b } }; + +FOOGUIDDECL const GUID input_decoder::class_guid = { 0x7eb442cd, 0xfad7, 0x4a26, { 0xad, 0x7e, 0x16, 0xf6, 0xfc, 0x89, 0x20, 0x7b } }; +FOOGUIDDECL const GUID input_decoder_v2::class_guid = { 0x2c5489eb, 0xd3c1, 0x4ad4,{ 0xbe, 0xa3, 0xcf, 0x40, 0x8e, 0x16, 0x1b, 0x9b } }; +FOOGUIDDECL const GUID input_decoder_v3::class_guid = { 0x953c71ed, 0xd3a2, 0x43f4,{ 0xa3, 0x3a, 0x55, 0x94, 0xd6, 0x73, 0x96, 0xe5 } }; +FOOGUIDDECL const GUID input_decoder_v4::class_guid = { 0x7bbf10b5, 0x2064, 0x4da2,{ 0x8a, 0x72, 0xeb, 0x4c, 0xe9, 0xb3, 0xa0, 0xb4 } }; +FOOGUIDDECL const GUID input_params::seeking_expensive = { 0x10973582, 0x1aae, 0x4169, { 0xb9, 0x76, 0xb5, 0xbe, 0xf9, 0x4b, 0x7a, 0x71 } }; +FOOGUIDDECL const GUID input_params::set_preferred_sample_rate = { 0x7dc7f926, 0x9b9f, 0x4a73, { 0xa2, 0x37, 0x83, 0xe9, 0x93, 0x38, 0x41, 0x75 } }; +FOOGUIDDECL const GUID input_params::query_position = { 0xa9d79933, 0x5438, 0x480f, { 0x85, 0xf3, 0xb6, 0xb8, 0xee, 0x1e, 0xfa, 0xe9 } }; +FOOGUIDDECL const GUID input_params::continue_stream = { 0x673050df, 0x11f6, 0x4039, { 0x8b, 0x4, 0x6c, 0x31, 0x98, 0x91, 0x4b, 0x89 } }; + + +// {8E9BB1D4-A52B-4df6-A929-1AAE4075388A} +FOOGUIDDECL const GUID input_info_reader::class_guid = +{ 0x8e9bb1d4, 0xa52b, 0x4df6, { 0xa9, 0x29, 0x1a, 0xae, 0x40, 0x75, 0x38, 0x8a } }; + +// {FE40FF66-64C9-4234-B639-028DC8060CF7} +FOOGUIDDECL const GUID input_info_writer::class_guid = +{ 0xfe40ff66, 0x64c9, 0x4234, { 0xb6, 0x39, 0x2, 0x8d, 0xc8, 0x6, 0xc, 0xf7 } }; + +// {436547FC-C4EF-4322-B59E-E696A25FAB2C} +FOOGUIDDECL const GUID input_entry::class_guid = +{ 0x436547fc, 0xc4ef, 0x4322, { 0xb5, 0x9e, 0xe6, 0x96, 0xa2, 0x5f, 0xab, 0x2c } }; + +// {7037DF35-D246-4CDD-B224-08CF444CE4CE} +FOOGUIDDECL const GUID input_entry_v2::class_guid = +{ 0x7037df35, 0xd246, 0x4cdd,{ 0xb2, 0x24, 0x8, 0xcf, 0x44, 0x4c, 0xe4, 0xce } }; + +FOOGUIDDECL const GUID input_entry_v3::class_guid = { 0x7099fac8, 0x10ee, 0x4906, { 0xa3, 0x13, 0xb9, 0x8c, 0x69, 0x74, 0xe7, 0xb9 } }; + +#ifdef FOOBAR2000_DESKTOP +FOOGUIDDECL const GUID input_manager::class_guid = { 0x291fccfd, 0xe7de, 0x4e08,{ 0x8d, 0xef, 0x6b, 0xe5, 0xf8, 0x23, 0xc4, 0x9a } }; +FOOGUIDDECL const GUID input_manager_v2::class_guid = { 0x81b36d9e, 0x4fb6, 0x4788, { 0xa1, 0x2c, 0x40, 0xc0, 0x64, 0x82, 0xe5, 0xc6 } }; +FOOGUIDDECL const GUID input_manager_v3::class_guid = { 0xc1c414be, 0x74f, 0x41f0, { 0xb8, 0x7f, 0x29, 0x2e, 0xad, 0x50, 0xc0, 0x76 } }; +FOOGUIDDECL const GUID input_stream_selector::class_guid = { 0x2a511979, 0x2e57, 0x438a,{ 0x9a, 0x6e, 0x37, 0x3c, 0x25, 0x2, 0xd7, 0x63 } }; +FOOGUIDDECL const GUID input_stream_info_reader::class_guid = { 0x934f1361, 0x9c18, 0x497a,{ 0xb6, 0xef, 0xc0, 0x4e, 0xa1, 0x66, 0x3e, 0xab } }; +FOOGUIDDECL const GUID input_stream_info_reader_entry::class_guid = { 0xb53b66f2, 0x7cd3, 0x4ce2,{ 0xbf, 0x63, 0x7b, 0xcf, 0xcf, 0xa1, 0xb2, 0x8f } }; +FOOGUIDDECL const GUID input_stream_manipulator::class_guid = { 0xc258aa65, 0x961a, 0x4e8e,{ 0x83, 0x98, 0xcc, 0x88, 0xd2, 0xba, 0xe0, 0x5d } }; +FOOGUIDDECL const GUID input_stream_manipulator_callback::class_guid = { 0x88ce44b6, 0x8721, 0x440f,{ 0x9e, 0xab, 0xbd, 0xc6, 0x63, 0xed, 0xe1, 0x73 } }; +FOOGUIDDECL const GUID input_info_filter::class_guid = { 0xd64af9aa, 0x1c0d, 0x4dd9, { 0xa3, 0x49, 0x2, 0xa3, 0x77, 0xbd, 0x7a, 0xf2 } }; +FOOGUIDDECL const GUID input_info_filter_v2::class_guid = { 0x7a2023cb, 0xfb7c, 0x4f04, { 0xa5, 0x45, 0x7f, 0xbd, 0x52, 0x92, 0x31, 0x92 } }; +FOOGUIDDECL const GUID input_stream_info_filter::class_guid = { 0xfd056bba, 0xc6a1, 0x40b5, { 0xa6, 0x17, 0x37, 0xb9, 0x71, 0x34, 0xb4, 0xc6 } }; +FOOGUIDDECL const GUID input_playback_shim::class_guid = { 0xaf8aea76, 0x144f, 0x4770, { 0x8d, 0x84, 0x7, 0x27, 0x22, 0x8f, 0x29, 0x8 } }; + +FOOGUIDDECL const GUID preferences_page::guid_input_info_filter = { 0xf9de8ee1, 0x9a5b, 0x4bbd, { 0xb4, 0x62, 0xef, 0x7b, 0x3a, 0xa5, 0x0, 0x24 } }; +#endif + +// {3296219B-EBA5-4c32-A193-C9BB174801DA} +FOOGUIDDECL const GUID link_resolver::class_guid = +{ 0x3296219b, 0xeba5, 0x4c32, { 0xa1, 0x93, 0xc9, 0xbb, 0x17, 0x48, 0x1, 0xda } }; + +// {A49E087E-D064-4b2e-A08D-11264F691FFE} +FOOGUIDDECL const GUID playback_queue_callback::class_guid = +{ 0xa49e087e, 0xd064, 0x4b2e, { 0xa0, 0x8d, 0x11, 0x26, 0x4f, 0x69, 0x1f, 0xfe } }; + +// {1131D64B-04CB-43ee-95EB-24D18B753248} +FOOGUIDDECL const GUID main_thread_callback_manager::class_guid = +{ 0x1131d64b, 0x4cb, 0x43ee, { 0x95, 0xeb, 0x24, 0xd1, 0x8b, 0x75, 0x32, 0x48 } }; + +// {87D2C583-7AFB-4e58-B21E-FBD3E6E8F5E7} +FOOGUIDDECL const GUID main_thread_callback::class_guid = +{ 0x87d2c583, 0x7afb, 0x4e58, { 0xb2, 0x1e, 0xfb, 0xd3, 0xe6, 0xe8, 0xf5, 0xe7 } }; + + + +// {21656AB9-0255-4f8c-8FB9-1A7748BCE93A} +FOOGUIDDECL const GUID mainmenu_group::class_guid = +{ 0x21656ab9, 0x255, 0x4f8c, { 0x8f, 0xb9, 0x1a, 0x77, 0x48, 0xbc, 0xe9, 0x3a } }; + +// {2E673A2E-A4EE-419c-94C8-9C838660414C} +FOOGUIDDECL const GUID mainmenu_group_popup::class_guid = +{ 0x2e673a2e, 0xa4ee, 0x419c, { 0x94, 0xc8, 0x9c, 0x83, 0x86, 0x60, 0x41, 0x4c } }; + +// {77D2B792-BF39-4B80-AE34-3BF69DA70160} +FOOGUIDDECL const GUID mainmenu_group_popup_v2::class_guid = +{ 0x77d2b792, 0xbf39, 0x4b80,{ 0xae, 0x34, 0x3b, 0xf6, 0x9d, 0xa7, 0x1, 0x60 } }; + +// {35077B8C-6E70-47ba-B9DD-D51500E12F2E} +FOOGUIDDECL const GUID mainmenu_commands::class_guid = +{ 0x35077b8c, 0x6e70, 0x47ba, { 0xb9, 0xdd, 0xd5, 0x15, 0x0, 0xe1, 0x2f, 0x2e } }; + +// {350B3EA8-6B3E-4346-B6D2-799E98EFC920} +FOOGUIDDECL const GUID mainmenu_manager::class_guid = +{ 0x350b3ea8, 0x6b3e, 0x4346, { 0xb6, 0xd2, 0x79, 0x9e, 0x98, 0xef, 0xc9, 0x20 } }; + + +// {8F487F1F-419F-47a7-8ECF-EC44AF4449A3} +FOOGUIDDECL const GUID mainmenu_groups::file = +{ 0x8f487f1f, 0x419f, 0x47a7, { 0x8e, 0xcf, 0xec, 0x44, 0xaf, 0x44, 0x49, 0xa3 } }; + +// {F8BE5604-165F-4bb9-B6A9-15E55E0E0D3A} +FOOGUIDDECL const GUID mainmenu_groups::view = +{ 0xf8be5604, 0x165f, 0x4bb9, { 0xb6, 0xa9, 0x15, 0xe5, 0x5e, 0xe, 0xd, 0x3a } }; + +// {8CDA6B10-0613-4cfd-8730-3B9CBF4C6A39} +FOOGUIDDECL const GUID mainmenu_groups::edit = +{ 0x8cda6b10, 0x613, 0x4cfd, { 0x87, 0x30, 0x3b, 0x9c, 0xbf, 0x4c, 0x6a, 0x39 } }; + +// {D8A4FC46-5E3D-47aa-97B7-947988228246} +FOOGUIDDECL const GUID mainmenu_groups::edit_part1 = +{ 0xd8a4fc46, 0x5e3d, 0x47aa, { 0x97, 0xb7, 0x94, 0x79, 0x88, 0x22, 0x82, 0x46 } }; + +// {007682CE-2A99-4b70-8F63-DE765D1C5555} +FOOGUIDDECL const GUID mainmenu_groups::edit_part2 = +{ 0x7682ce, 0x2a99, 0x4b70, { 0x8f, 0x63, 0xde, 0x76, 0x5d, 0x1c, 0x55, 0x55 } }; + +// {1F572090-D620-4940-85EC-0EFE499FAC03} +FOOGUIDDECL const GUID mainmenu_groups::edit_part3 = +{ 0x1f572090, 0xd620, 0x4940, { 0x85, 0xec, 0xe, 0xfe, 0x49, 0x9f, 0xac, 0x3 } }; + +// {65FA047A-1599-4b9c-B53D-C3FEB716339D} +FOOGUIDDECL const GUID mainmenu_groups::edit_part2_selection = +{ 0x65fa047a, 0x1599, 0x4b9c, { 0xb5, 0x3d, 0xc3, 0xfe, 0xb7, 0x16, 0x33, 0x9d } }; + +// {5B8AEF2C-6E1A-420d-B488-3E3A00E39E28} +FOOGUIDDECL const GUID mainmenu_groups::edit_part2_sort = +{ 0x5b8aef2c, 0x6e1a, 0x420d, { 0xb4, 0x88, 0x3e, 0x3a, 0x0, 0xe3, 0x9e, 0x28 } }; + +// {EE9D6F72-7BC7-4a60-8C28-B96DED252BD3} +FOOGUIDDECL const GUID mainmenu_groups::edit_part2_selection_sort = +{ 0xee9d6f72, 0x7bc7, 0x4a60, { 0x8c, 0x28, 0xb9, 0x6d, 0xed, 0x25, 0x2b, 0xd3 } }; + +// {53FA5B8A-FCBC-4296-B968-45BAE6888845} +FOOGUIDDECL const GUID mainmenu_groups::playback = +{ 0x53fa5b8a, 0xfcbc, 0x4296, { 0xb9, 0x68, 0x45, 0xba, 0xe6, 0x88, 0x88, 0x45 } }; + +// {15D22753-9D30-4929-AA14-5124016F7E68} +FOOGUIDDECL const GUID mainmenu_groups::library = +{ 0x15d22753, 0x9d30, 0x4929, { 0xaa, 0x14, 0x51, 0x24, 0x1, 0x6f, 0x7e, 0x68 } }; + +// {25DC3DB7-996A-4f48-AF53-712032EFA04F} +FOOGUIDDECL const GUID mainmenu_groups::help = +{ 0x25dc3db7, 0x996a, 0x4f48, { 0xaf, 0x53, 0x71, 0x20, 0x32, 0xef, 0xa0, 0x4f } }; + +// {8EED252D-0A0F-4fc9-9D81-8CF7209A8BF2} +FOOGUIDDECL const GUID mainmenu_groups::file_open = +{ 0x8eed252d, 0xa0f, 0x4fc9, { 0x9d, 0x81, 0x8c, 0xf7, 0x20, 0x9a, 0x8b, 0xf2 } }; + +// {9E650B8E-C3F3-4495-AF15-6B656060C3B9} +FOOGUIDDECL const GUID mainmenu_groups::file_add = +{ 0x9e650b8e, 0xc3f3, 0x4495, { 0xaf, 0x15, 0x6b, 0x65, 0x60, 0x60, 0xc3, 0xb9 } }; + +// {9995FAE6-B1C4-457f-A747-5BD120930210} +FOOGUIDDECL const GUID mainmenu_groups::file_playlist = +{ 0x9995fae6, 0xb1c4, 0x457f, { 0xa7, 0x47, 0x5b, 0xd1, 0x20, 0x93, 0x2, 0x10 } }; + +// {8A324F18-6173-42ec-A640-5E296AD446B3} +FOOGUIDDECL const GUID mainmenu_groups::file_etc = +{ 0x8a324f18, 0x6173, 0x42ec, { 0xa6, 0x40, 0x5e, 0x29, 0x6a, 0xd4, 0x46, 0xb3 } }; + +// {12F5E247-5A81-4734-8119-8F9BC114FE74} +FOOGUIDDECL const GUID mainmenu_groups::playback_controls = +{ 0x12f5e247, 0x5a81, 0x4734, { 0x81, 0x19, 0x8f, 0x9b, 0xc1, 0x14, 0xfe, 0x74 } }; + +// {1169B3EB-81B5-4199-8929-7D3EAFD4809F} +FOOGUIDDECL const GUID mainmenu_groups::playback_etc = +{ 0x1169b3eb, 0x81b5, 0x4199, { 0x89, 0x29, 0x7d, 0x3e, 0xaf, 0xd4, 0x80, 0x9f } }; + +// {29272FEC-21C7-4b00-A9A7-01A8CE675EBF} +FOOGUIDDECL const GUID mainmenu_groups::view_visualisations = +{ 0x29272fec, 0x21c7, 0x4b00, { 0xa9, 0xa7, 0x1, 0xa8, 0xce, 0x67, 0x5e, 0xbf } }; + +// {8AF6EE1A-18D5-4B1C-AA2D-E50EAE3DC117} +FOOGUIDDECL const GUID mainmenu_groups::view_dsp = +{ 0x8af6ee1a, 0x18d5, 0x4b1c,{ 0xaa, 0x2d, 0xe5, 0xe, 0xae, 0x3d, 0xc1, 0x17 } }; + +// {2DA055D4-0257-40b2-8281-7967866B485C} +FOOGUIDDECL const GUID mainmenu_groups::file_etc_preferences = +{ 0x2da055d4, 0x257, 0x40b2, { 0x82, 0x81, 0x79, 0x67, 0x86, 0x6b, 0x48, 0x5c } }; + +// {517BFAE8-A148-4cb2-8CCE-1AD2179FB910} +FOOGUIDDECL const GUID mainmenu_groups::file_etc_exit = +{ 0x517bfae8, 0xa148, 0x4cb2, { 0x8c, 0xce, 0x1a, 0xd2, 0x17, 0x9f, 0xb9, 0x10 } }; + +// {61190F78-3B3A-4fda-A431-2CF7DA1C96CE} +FOOGUIDDECL const GUID mainmenu_groups::view_alwaysontop = +{ 0x61190f78, 0x3b3a, 0x4fda, { 0xa4, 0x31, 0x2c, 0xf7, 0xda, 0x1c, 0x96, 0xce } }; + +// {E55F8D65-AE51-47bf-99F9-BB113421CB19} +FOOGUIDDECL const GUID mainmenu_groups::help_about = +{ 0xe55f8d65, 0xae51, 0x47bf, { 0x99, 0xf9, 0xbb, 0x11, 0x34, 0x21, 0xcb, 0x19 } }; + +// {696DF823-B7AE-4b73-95C8-C1E9A9410726} +FOOGUIDDECL const GUID mainmenu_groups::library_refresh= +{ 0x696df823, 0xb7ae, 0x4b73, { 0x95, 0xc8, 0xc1, 0xe9, 0xa9, 0x41, 0x7, 0x26 } }; + +// {004BF6ED-2F88-464f-BDC1-278F6E610C2F} +FOOGUIDDECL const GUID standard_commands::guid_seek_ahead_1s = +{ 0x4bf6ed, 0x2f88, 0x464f, { 0xbd, 0xc1, 0x27, 0x8f, 0x6e, 0x61, 0xc, 0x2f } }; + +// {5B56D124-2ECA-4b0f-9895-2A533B31D29E} +FOOGUIDDECL const GUID standard_commands::guid_seek_ahead_5s = +{ 0x5b56d124, 0x2eca, 0x4b0f, { 0x98, 0x95, 0x2a, 0x53, 0x3b, 0x31, 0xd2, 0x9e } }; + +// {B582E3CA-D86D-4568-8380-68BC9C93ED0E} +FOOGUIDDECL const GUID standard_commands::guid_seek_ahead_10s = +{ 0xb582e3ca, 0xd86d, 0x4568, { 0x83, 0x80, 0x68, 0xbc, 0x9c, 0x93, 0xed, 0xe } }; + +// {DE6B96B7-3074-4da9-A260-988E31CEE0F9} +FOOGUIDDECL const GUID standard_commands::guid_seek_ahead_30s = +{ 0xde6b96b7, 0x3074, 0x4da9, { 0xa2, 0x60, 0x98, 0x8e, 0x31, 0xce, 0xe0, 0xf9 } }; + +// {FEED4AD7-13D2-4846-8833-D91B5F8B4E94} +FOOGUIDDECL const GUID standard_commands::guid_seek_ahead_1min = +{ 0xfeed4ad7, 0x13d2, 0x4846, { 0x88, 0x33, 0xd9, 0x1b, 0x5f, 0x8b, 0x4e, 0x94 } }; + +// {ECCF4904-03CF-429a-9D99-7A87FA62FD10} +FOOGUIDDECL const GUID standard_commands::guid_seek_ahead_2min = +{ 0xeccf4904, 0x3cf, 0x429a, { 0x9d, 0x99, 0x7a, 0x87, 0xfa, 0x62, 0xfd, 0x10 } }; + +// {5F0F0AF7-F519-41e6-A8DB-04DF1390E69D} +FOOGUIDDECL const GUID standard_commands::guid_seek_ahead_5min = +{ 0x5f0f0af7, 0xf519, 0x41e6, { 0xa8, 0xdb, 0x4, 0xdf, 0x13, 0x90, 0xe6, 0x9d } }; + +// {9ABA4292-1B8F-42ac-93AC-34B2A74C6320} +FOOGUIDDECL const GUID standard_commands::guid_seek_ahead_10min = +{ 0x9aba4292, 0x1b8f, 0x42ac, { 0x93, 0xac, 0x34, 0xb2, 0xa7, 0x4c, 0x63, 0x20 } }; + +// {2F89AB1C-5646-4997-8E3F-92BEE0322A41} +FOOGUIDDECL const GUID standard_commands::guid_seek_back_1s = +{ 0x2f89ab1c, 0x5646, 0x4997, { 0x8e, 0x3f, 0x92, 0xbe, 0xe0, 0x32, 0x2a, 0x41 } }; + +// {0CE84538-2E21-4482-BFE1-BCEC471467CC} +FOOGUIDDECL const GUID standard_commands::guid_seek_back_5s = +{ 0xce84538, 0x2e21, 0x4482, { 0xbf, 0xe1, 0xbc, 0xec, 0x47, 0x14, 0x67, 0xcc } }; + +// {9F504218-AF5D-41a8-BCE3-26313FAE65EE} +FOOGUIDDECL const GUID standard_commands::guid_seek_back_10s = +{ 0x9f504218, 0xaf5d, 0x41a8, { 0xbc, 0xe3, 0x26, 0x31, 0x3f, 0xae, 0x65, 0xee } }; + +// {98239B89-F66E-4160-B650-D9B068C59E63} +FOOGUIDDECL const GUID standard_commands::guid_seek_back_30s = +{ 0x98239b89, 0xf66e, 0x4160, { 0xb6, 0x50, 0xd9, 0xb0, 0x68, 0xc5, 0x9e, 0x63 } }; + +// {6633226B-9AA9-4810-AFDA-185A281D9FE2} +FOOGUIDDECL const GUID standard_commands::guid_seek_back_1min = +{ 0x6633226b, 0x9aa9, 0x4810, { 0xaf, 0xda, 0x18, 0x5a, 0x28, 0x1d, 0x9f, 0xe2 } }; + +// {E2F8B8BB-C539-44f3-A300-13185DE52227} +FOOGUIDDECL const GUID standard_commands::guid_seek_back_2min = +{ 0xe2f8b8bb, 0xc539, 0x44f3, { 0xa3, 0x0, 0x13, 0x18, 0x5d, 0xe5, 0x22, 0x27 } }; + +// {7B41A130-01D2-4a1b-9CAD-6314633C61C4} +FOOGUIDDECL const GUID standard_commands::guid_seek_back_5min = +{ 0x7b41a130, 0x1d2, 0x4a1b, { 0x9c, 0xad, 0x63, 0x14, 0x63, 0x3c, 0x61, 0xc4 } }; + +// {0B012852-BAF9-4f6b-B947-FAB89AE76B79} +FOOGUIDDECL const GUID standard_commands::guid_seek_back_10min = +{ 0xb012852, 0xbaf9, 0x4f6b, { 0xb9, 0x47, 0xfa, 0xb8, 0x9a, 0xe7, 0x6b, 0x79 } }; + +// {97215C5E-7228-4237-B52C-A2B5504EF726} +FOOGUIDDECL const GUID playback_statistics_collector::class_guid = +{ 0x97215c5e, 0x7228, 0x4237, { 0xb5, 0x2c, 0xa2, 0xb5, 0x50, 0x4e, 0xf7, 0x26 } }; + + +FOOGUIDDECL const GUID advconfig_entry::guid_root = { 0x34949f34, 0xe655, 0x4f09, { 0xba, 0x50, 0xfa, 0xeb, 0x4d, 0x9b, 0x77, 0x69 } }; +FOOGUIDDECL const GUID advconfig_entry::class_guid = { 0x7e84602e, 0xdc49, 0x4047, { 0xaa, 0xee, 0x63, 0x71, 0x8b, 0xbc, 0x5a, 0x1f } }; +FOOGUIDDECL const GUID advconfig_branch::class_guid = { 0x6a60b472, 0x91ac, 0x4681, { 0x9d, 0xc2, 0x76, 0xc9, 0x94, 0x0, 0x5b, 0x63 } }; +FOOGUIDDECL const GUID advconfig_entry_checkbox::class_guid = { 0x5243aba4, 0x2a3d, 0x4627, { 0x88, 0xef, 0xce, 0xe3, 0x76, 0x1c, 0x7, 0x9c } }; +FOOGUIDDECL const GUID advconfig_entry::guid_branch_tagging = { 0xe8fe273f, 0xdd00, 0x476e, { 0xa7, 0x90, 0xe5, 0x9d, 0xf6, 0xb8, 0xf8, 0xd4 } }; +FOOGUIDDECL const GUID advconfig_entry::guid_branch_decoding = { 0x904c272b, 0x2317, 0x4c3c, { 0xb2, 0xff, 0xc5, 0xa0, 0x12, 0x5e, 0x2c, 0xc2 } }; +FOOGUIDDECL const GUID advconfig_entry_string::class_guid = { 0x185d582d, 0xfbd8, 0x4db3, { 0xbe, 0x23, 0x47, 0xaa, 0xc6, 0x75, 0xfc, 0x11 } }; +FOOGUIDDECL const GUID advconfig_entry::guid_branch_tools = { 0x35365484, 0xcc58, 0x4926, { 0x97, 0xe1, 0x5e, 0x63, 0xf3, 0xab, 0xb9, 0xe2 } }; +FOOGUIDDECL const GUID advconfig_entry::guid_branch_playback = { 0xc48d430d, 0x112, 0x4922, { 0x97, 0x23, 0x28, 0x38, 0xc7, 0xd9, 0x7d, 0xd7 } }; +FOOGUIDDECL const GUID advconfig_entry::guid_branch_display = { 0x6c4bc1c8, 0xbaf4, 0x40c3, { 0x9d, 0xb1, 0x9, 0x50, 0x7f, 0xc, 0xc, 0xb9 } }; +FOOGUIDDECL const GUID advconfig_entry::guid_branch_debug = { 0xc375447d, 0x58e6, 0x4fd5, { 0xbd, 0xd8, 0x99, 0xfa, 0xef, 0x7b, 0x30, 0x9f } }; +FOOGUIDDECL const GUID advconfig_entry::guid_branch_tagging_general = { 0x1a7757de, 0x55bd, 0x4c25, { 0xb2, 0xf9, 0xc6, 0x3a, 0x85, 0x0, 0xba, 0xed } }; + + + + +FOOGUIDDECL const GUID playback_control_v2::class_guid = { 0x1eb67bda, 0x1345, 0x49ae, { 0x8e, 0x89, 0x50, 0x5, 0xd9, 0x1, 0x62, 0x89 } }; + +FOOGUIDDECL const GUID preferences_page_v2::class_guid = { 0xce4ebc9e, 0xab20, 0x46f9, { 0x92, 0x5f, 0x88, 0x3b, 0x8, 0x4f, 0x5, 0x69 } }; +FOOGUIDDECL const GUID preferences_branch_v2::class_guid = { 0x167ebeb9, 0x8334, 0x4b21, { 0xaf, 0x58, 0xa7, 0x40, 0xa5, 0xd5, 0xb6, 0x66 } }; + + + + +FOOGUIDDECL const GUID advconfig_entry_enum::class_guid = { 0xb1451540, 0x98ec, 0x4d36, { 0x9f, 0x19, 0xe3, 0x10, 0xfb, 0xa7, 0xab, 0x5a } }; + +FOOGUIDDECL const GUID info_lookup_handler::class_guid = { 0x4fcfdab7, 0x55b5, 0x47d6, { 0xb1, 0x9d, 0xa4, 0xdc, 0x9f, 0xd7, 0x69, 0x4c } }; +FOOGUIDDECL const GUID info_lookup_handler_v2::class_guid = { 0x3c9728a5, 0x89e5, 0x4560, { 0xb6, 0x8b, 0x9b, 0x9b, 0x90, 0x1f, 0x0, 0x7b } }; + +FOOGUIDDECL const GUID completion_notify::class_guid = { 0xdf26d586, 0xf7ec, 0x40c3, { 0x9f, 0xe8, 0x2e, 0xa0, 0x72, 0x5d, 0x76, 0xc0 } }; + +FOOGUIDDECL const GUID metadb_io_v2::class_guid = { 0x265c4ece, 0xffb2, 0x4299, { 0x91, 0xb5, 0x6c, 0xa6, 0x60, 0x3d, 0xa1, 0x53 } }; + +FOOGUIDDECL const GUID file_info_filter::class_guid = { 0x45d0b800, 0xde83, 0x4a77, { 0xad, 0x34, 0x3f, 0x84, 0x2d, 0x40, 0xe7, 0x95 } }; +FOOGUIDDECL const GUID file_info_filter_v2::class_guid = { 0x9bb4da92, 0xd334, 0x4f18, { 0x91, 0xc1, 0x91, 0x9f, 0xb2, 0xf4, 0x4f, 0x30 } }; + + + +FOOGUIDDECL const GUID metadb_hint_list::class_guid = { 0x719dc072, 0x8d4d, 0x4aa6, { 0xa6, 0xf3, 0x90, 0x73, 0x7, 0xe5, 0xbc, 0xee } }; + +FOOGUIDDECL const GUID playlist_incoming_item_filter_v2::class_guid = { 0xf335802b, 0xa522, 0x434d, { 0xbe, 0x89, 0xe8, 0x6d, 0x59, 0x56, 0x16, 0x48 } }; + +FOOGUIDDECL const GUID process_locations_notify::class_guid = { 0x7d7eddac, 0xf93e, 0x4707, { 0x96, 0x9b, 0xcf, 0x44, 0xa9, 0xe1, 0xfb, 0x78 } }; + +FOOGUIDDECL const GUID library_manager_v2::class_guid = { 0x900eae79, 0x68d0, 0x4900, { 0xa4, 0xd8, 0x18, 0x20, 0x5, 0xae, 0x33, 0x7e } }; + +FOOGUIDDECL const GUID packet_decoder::owner_Ogg = { 0x8fa27457, 0xa021, 0x43b9, { 0xad, 0x20, 0xa7, 0x96, 0xdb, 0x94, 0x7c, 0xd1 } }; + +FOOGUIDDECL const GUID packet_decoder::property_mp4_esds = { 0xe8504271, 0xaec0, 0x4805, { 0xbb, 0x69, 0x71, 0xcc, 0xa3, 0xbc, 0x2, 0xdd } }; + +FOOGUIDDECL const GUID packet_decoder::property_ogg_header = { 0xbeb22c78, 0xeb49, 0x4f9e, { 0x9e, 0x37, 0x57, 0xd8, 0x87, 0x40, 0xfb, 0x4c } }; + +FOOGUIDDECL const GUID packet_decoder::property_ogg_query_sample_rate = { 0x6226bf73, 0x1c37, 0x4aa1, { 0xae, 0xb1, 0x18, 0x8b, 0x4a, 0xf3, 0xae, 0xf7 } }; + +FOOGUIDDECL const GUID packet_decoder::property_ogg_packet = { 0xade740be, 0x8e2e, 0x4d0b, { 0xa4, 0x0, 0x3f, 0x6e, 0x8a, 0xf7, 0x71, 0xe4 } }; + +FOOGUIDDECL const GUID packet_decoder::property_ogg_query_preskip = { 0xfae837d4, 0xc396, 0x4ccd, { 0xa7, 0xa5, 0xeb, 0x1e, 0x7e, 0x86, 0x32, 0xfb } }; + +FOOGUIDDECL const GUID packet_decoder::property_allow_delayed_output = { 0x985b25a5, 0x5e79, 0x4dda, { 0x8b, 0xdd, 0x53, 0xb4, 0x25, 0x76, 0xe2, 0x2d } }; + +FOOGUIDDECL const GUID track_property_provider::class_guid = { 0xefcdd365, 0x81fc, 0x4c82, { 0x96, 0x1c, 0xa2, 0xb5, 0x76, 0x1a, 0xf8, 0xb7 } }; + +FOOGUIDDECL const GUID track_property_provider_v2::class_guid = { 0x78e86595, 0xcffd, 0x4d10, { 0x85, 0x66, 0xbc, 0xf6, 0x6c, 0x89, 0x11, 0x16 } }; + + +FOOGUIDDECL const GUID library_manager_v3::class_guid = { 0x33dc0f1f, 0x16f0, 0x4e27, { 0xa7, 0x3, 0x63, 0x57, 0x72, 0x35, 0xb0, 0x1c } }; + +FOOGUIDDECL const GUID metadb_io_v3::class_guid = { 0x10e80560, 0xb1ef, 0x4e5e, { 0xb4, 0xc, 0x5d, 0xfc, 0x2e, 0x9a, 0xf1, 0x11 } }; + +FOOGUIDDECL const GUID search_filter::class_guid = { 0x4134bb34, 0xed5, 0x49f3, { 0x9c, 0xef, 0x43, 0xe3, 0xa4, 0xa3, 0xa8, 0xae } }; + +FOOGUIDDECL const GUID search_filter_manager::class_guid = { 0xad0baa03, 0xebd4, 0x4a72, { 0xa0, 0xa7, 0x98, 0x3, 0xa9, 0xe1, 0xe4, 0xf4 } }; + +FOOGUIDDECL const GUID playlist_manager_v2::class_guid = { 0x7fe9052a, 0x8ec2, 0x4054, { 0xa8, 0xcf, 0x77, 0xef, 0xb5, 0x2f, 0xe4, 0x8 } }; + +FOOGUIDDECL const GUID playlist_manager_v3::class_guid = { 0x3b6575c8, 0x21a, 0x4474, { 0xbb, 0x60, 0x6c, 0xfc, 0xfe, 0x33, 0xc3, 0x4f } }; + +FOOGUIDDECL const GUID titleformat_common_methods::class_guid = { 0x432fe059, 0xf087, 0x4785, { 0xa5, 0x18, 0xcb, 0xc1, 0x8b, 0x84, 0x9a, 0xc1 } }; + +FOOGUIDDECL const GUID core_version_info_v2::class_guid = { 0x273704d7, 0x99ba, 0x4b58, { 0xa0, 0x60, 0x82, 0xab, 0x1, 0xb4, 0x92, 0x25 } }; + +FOOGUIDDECL const GUID visualisation_stream_v2::class_guid = { 0xeb5b35b5, 0x267b, 0x443c, { 0xbe, 0x37, 0xc, 0x25, 0x73, 0x81, 0xa8, 0x6a } }; + +FOOGUIDDECL const GUID visualisation_stream_v3::class_guid = { 0xe1fd8e8b, 0x68f1, 0x4cea, { 0xa0, 0xe6, 0x61, 0x91, 0x1d, 0x14, 0x65, 0x42 } }; + +FOOGUIDDECL const GUID album_art_data::class_guid = { 0x9ddce05c, 0xaa3f, 0x4565, { 0xb3, 0x3a, 0xbd, 0x6a, 0xdc, 0xdd, 0x90, 0x37 } }; +FOOGUIDDECL const GUID album_art_extractor::class_guid = { 0xe19ca3d3, 0x6267, 0x4271, { 0xb7, 0x89, 0x6a, 0x3f, 0x87, 0xfb, 0x3e, 0xbf } }; +FOOGUIDDECL const GUID album_art_extractor_instance::class_guid = { 0xf673700e, 0x3b6e, 0x4f70, { 0xa1, 0x6, 0xab, 0x74, 0x5c, 0x20, 0x20, 0x60 } }; +FOOGUIDDECL const GUID album_art_editor::class_guid = { 0x36b34487, 0xd7b9, 0x4533, { 0xa7, 0x3f, 0x39, 0x6e, 0x2d, 0x9f, 0x41, 0x5e } }; +FOOGUIDDECL const GUID album_art_editor_instance::class_guid = { 0x642a2ae1, 0x2259, 0x48f5, { 0xab, 0xdc, 0x63, 0xed, 0x4e, 0xb5, 0x90, 0x76 } }; + + +FOOGUIDDECL const GUID icon_remapping::class_guid = { 0x7c6aea96, 0x4a19, 0x471b, { 0x92, 0x5a, 0xa7, 0xc8, 0xb2, 0x8a, 0x59, 0xe7 } }; + +FOOGUIDDECL const GUID file_operation_callback_dynamic_manager::class_guid = { 0x1cb3a4dc, 0x7d79, 0x45cf, { 0x82, 0x2e, 0xf8, 0x67, 0x7b, 0x8d, 0xde, 0x37 } }; + +FOOGUIDDECL const GUID ui_selection_holder::class_guid = { 0x2ec9b7a, 0x93a5, 0x434d, { 0x85, 0x46, 0xed, 0x1d, 0xbb, 0x24, 0x35, 0xd0 } }; +FOOGUIDDECL const GUID ui_selection_manager::class_guid = { 0xd8ee46c7, 0x27ad, 0x4881, { 0xb1, 0x33, 0x14, 0x96, 0xc0, 0xe7, 0xee, 0x67 } }; +FOOGUIDDECL const GUID ui_selection_manager_v2::class_guid = { 0x2740b6c1, 0x449d, 0x40cc, { 0x8a, 0x79, 0x38, 0xc, 0x77, 0xe8, 0xdf, 0xc2 } }; + + +FOOGUIDDECL const GUID ole_interaction::class_guid = { 0xfbee40c9, 0xef36, 0x410b, { 0x9d, 0x52, 0x7e, 0x56, 0x39, 0x59, 0xf3, 0xd1 } }; + +FOOGUIDDECL const GUID tag_processor_album_art_utils::class_guid = { 0x58768713, 0xc13c, 0x4406, { 0x97, 0x98, 0x21, 0x47, 0xcb, 0x97, 0x33, 0x2a } }; + +FOOGUIDDECL const GUID playlist_incoming_item_filter_v3::class_guid = { 0xd3332054, 0xbccd, 0x45cb, { 0xbc, 0x2, 0x0, 0xa1, 0x3a, 0x80, 0x25, 0x9f } }; + +FOOGUIDDECL const GUID menu_item_resolver::class_guid = { 0xac70ecdc, 0xe1d, 0x4db2, { 0x9c, 0xd0, 0xc9, 0xb8, 0xa9, 0xcd, 0x28, 0xfa } }; + +FOOGUIDDECL const GUID app_close_blocking_task_manager::class_guid = { 0x213f1454, 0x8a62, 0x44b6, { 0xb0, 0xcb, 0xc1, 0xe1, 0x5d, 0xa7, 0x3b, 0xc8 } }; + +FOOGUIDDECL const GUID message_loop_v2::class_guid = { 0x5d438080, 0xb269, 0x406d, { 0x94, 0xaf, 0xef, 0xd9, 0x29, 0x9f, 0x32, 0x5c } }; + + +FOOGUIDDECL const GUID autoplaylist_client::class_guid = { 0x7c90bda0, 0xf93d, 0x4a73, { 0x98, 0x51, 0xa0, 0x33, 0x6, 0x7, 0xcb, 0x28 } }; + +FOOGUIDDECL const GUID autoplaylist_client_factory::class_guid = { 0x764200cb, 0xe283, 0x4efd, { 0x88, 0xa5, 0x80, 0x38, 0xdd, 0xee, 0x77, 0xdb } }; + +FOOGUIDDECL const GUID autoplaylist_manager::class_guid = { 0x1571d0d, 0xc1e1, 0x44bf, { 0xb5, 0x29, 0x7a, 0xe, 0x4e, 0x89, 0x15, 0xbc } }; + + +FOOGUIDDECL const GUID replaygain_result::class_guid = { 0xe7f43102, 0xbc6e, 0x400b, { 0xbb, 0x81, 0x2d, 0x9b, 0xe4, 0x30, 0x3e, 0xcc } }; +FOOGUIDDECL const GUID replaygain_scanner::class_guid = { 0x2a40fcf9, 0xf1f7, 0x41da, { 0xad, 0x76, 0x29, 0xf7, 0x51, 0xed, 0xd3, 0x8a } }; +FOOGUIDDECL const GUID replaygain_scanner_entry::class_guid = { 0x278380f3, 0x6fc0, 0x4176, { 0x8a, 0x98, 0x1f, 0x66, 0x3a, 0x94, 0x79, 0xc5 } }; +FOOGUIDDECL const GUID replaygain_scanner_entry_v2::class_guid = { 0xb1ba75ca, 0xd600, 0x4b32,{ 0xa6, 0xeb, 0x93, 0xca, 0x73, 0xcd, 0xb4, 0x5c } }; + +#ifdef FOOBAR2000_DESKTOP +FOOGUIDDECL const GUID replaygain_alter_stream::class_guid = { 0x4141712a, 0xccd6, 0x45e4,{ 0x82, 0x70, 0x8c, 0xb2, 0x20, 0x26, 0x56, 0x8a } }; +FOOGUIDDECL const GUID replaygain_alter_stream_entry::class_guid = { 0x6e36b9cb, 0xb7d4, 0x4bde,{ 0xa4, 0xd1, 0xcc, 0xd2, 0x58, 0xa4, 0x7a, 0xcd } }; +#endif + +FOOGUIDDECL const GUID search_filter_manager_v2::class_guid = { 0xac62a380, 0x7771, 0x4dc9, { 0x8a, 0x26, 0x41, 0x41, 0x39, 0xb4, 0x3e, 0xe2 } }; +FOOGUIDDECL const GUID search_filter_v2::class_guid = { 0xb7ca3c8, 0xaf23, 0x4a52, { 0x82, 0x66, 0xfe, 0x5, 0x4c, 0x49, 0xeb, 0x23 } }; + + +FOOGUIDDECL const GUID autoplaylist_client_v2::class_guid = { 0xfaa716f7, 0xebb1, 0x473c, { 0xbc, 0xf1, 0xb0, 0x1b, 0x8c, 0x9f, 0x1b, 0x95 } }; + +FOOGUIDDECL const GUID library_search_ui::class_guid = { 0xe5eb14fa, 0xbb08, 0x4564, { 0xaf, 0xfa, 0x6f, 0x27, 0x3f, 0x37, 0xbd, 0x3d } }; + +FOOGUIDDECL const GUID search_filter_v3::class_guid = { 0xdd6bc035, 0x7e33, 0x425a, { 0x89, 0x32, 0x6, 0xb5, 0xea, 0xb0, 0x39, 0xbc } }; + +FOOGUIDDECL const GUID event_logger::class_guid = { 0x5d336782, 0x69d6, 0x4225, { 0x9e, 0xa8, 0xcc, 0x29, 0x35, 0x7, 0xf9, 0xfe } }; +FOOGUIDDECL const GUID event_logger_recorder::class_guid = { 0xdfcdc841, 0xa295, 0x46aa, { 0x85, 0x8c, 0xf2, 0x1f, 0xf8, 0x33, 0x8b, 0x78 } }; + + +FOOGUIDDECL const GUID playlist_manager_v4::class_guid = { 0xcea7b49e, 0xacf2, 0x4ea2, { 0x99, 0x86, 0x95, 0xa, 0x56, 0xa2, 0xc0, 0xef } }; + +FOOGUIDDECL const GUID ole_interaction_v2::class_guid = { 0x55738cab, 0x311d, 0x4dbe, { 0xa0, 0xbc, 0x51, 0xc1, 0xba, 0x27, 0xfe, 0x95 } }; + +FOOGUIDDECL const GUID autoplaylist_manager_v2::class_guid = { 0x67882220, 0xeea5, 0x4dbc, { 0x9a, 0xa3, 0x63, 0xde, 0x8c, 0xdd, 0x40, 0x82 } }; + +FOOGUIDDECL const GUID library_file_move_scope::class_guid = { 0x345fae5, 0x8093, 0x45f6, { 0x94, 0x2e, 0xd6, 0xe, 0xbf, 0x7, 0xe9, 0x52 } }; + +FOOGUIDDECL const GUID library_file_move_manager::class_guid = { 0xc4cd4818, 0xe3f8, 0x4061, { 0x99, 0xf1, 0x18, 0x66, 0x4e, 0x6c, 0x28, 0x57 } }; + +FOOGUIDDECL const GUID library_file_move_notify::class_guid = { 0xd8ae7613, 0x7577, 0x4192, { 0x8f, 0xa4, 0x2d, 0x8f, 0x7e, 0xc6, 0x6, 0x38 } }; + +FOOGUIDDECL const GUID library_meta_autocomplete::class_guid = { 0x4b976e34, 0xf05a, 0x4da4, { 0xad, 0x65, 0x71, 0x9c, 0xdf, 0xd, 0xed, 0xae } }; +FOOGUIDDECL const GUID library_meta_autocomplete_v2::class_guid = { 0xd60d585e, 0xb42e, 0x4a6e, { 0x8f, 0x24, 0x27, 0x50, 0x71, 0x1a, 0x87, 0x30 } }; + + +FOOGUIDDECL const GUID input_protocol_type::class_guid = { 0x6a03c4ee, 0xf87b, 0x49d7, { 0x81, 0xdb, 0x66, 0xb, 0xe8, 0xc1, 0x0, 0x7e } }; + +FOOGUIDDECL const GUID metadb_hint_list_v2::class_guid = { 0x983a971a, 0x596b, 0x478f, { 0x9c, 0x38, 0x36, 0xb0, 0x2b, 0xd6, 0x39, 0xd9 } }; + +#if FOOBAR2000_TARGET_VERSION >= 76 +FOOGUIDDECL const GUID playlist_loader::class_guid = { 0x7103cae9, 0x91c, 0x4d80, { 0xbc, 0x1d, 0x28, 0x4a, 0xc1, 0x3f, 0x1e, 0x8c } }; +FOOGUIDDECL const GUID playlist_loader_callback::class_guid = { 0x924470a0, 0x1478, 0x4141, { 0xa7, 0x5a, 0xc5, 0x2f, 0x1f, 0xfa, 0xef, 0xea } }; +FOOGUIDDECL const GUID playlist_loader_callback_v2::class_guid = { 0xcb5941bb, 0x9085, 0x4d7e, { 0x9b, 0xc2, 0x19, 0x71, 0xd3, 0x47, 0x96, 0x2a } }; +#endif + +FOOGUIDDECL const GUID album_art_manager_v2::class_guid = { 0xb420664f, 0x4033, 0x465e, { 0x81, 0xb1, 0xce, 0x42, 0x43, 0x89, 0x1f, 0x59 } }; +FOOGUIDDECL const GUID album_art_extractor_instance_v2::class_guid = { 0x951bbeb1, 0xa94a, 0x4d9a, { 0xa2, 0x85, 0xd6, 0x1e, 0xe4, 0x66, 0xe8, 0xcc } }; +FOOGUIDDECL const GUID album_art_path_list::class_guid = { 0xbb26233f, 0x1051, 0x4b01, { 0x9f, 0xd8, 0xf0, 0xe4, 0x20, 0x7, 0xd7, 0xe6 } }; +FOOGUIDDECL const GUID album_art_fallback::class_guid = { 0x45481581, 0x40b3, 0x4930, { 0xab, 0x6d, 0x4e, 0x6e, 0x56, 0x58, 0x6c, 0x82 } }; + + +FOOGUIDDECL const GUID preferences_page_callback::class_guid = { 0x3d26e08e, 0x861c, 0x4599, { 0x9c, 0x89, 0xaa, 0xa7, 0x19, 0xaf, 0x50, 0x70 } }; +FOOGUIDDECL const GUID preferences_page_instance::class_guid = { 0x6893a996, 0xa816, 0x49fe, { 0x82, 0xce, 0xc, 0xb8, 0x4, 0xa4, 0xcf, 0xec } }; +FOOGUIDDECL const GUID preferences_page_v3::class_guid = { 0xd6d0f741, 0x9f17, 0x4df8, { 0x9d, 0x5c, 0x87, 0xf2, 0x8b, 0x1f, 0xe, 0x64 } }; +FOOGUIDDECL const GUID preferences_page_v4::class_guid = { 0x76227dab, 0xc740, 0x4d49, { 0xa2, 0xe2, 0x50, 0x80, 0x13, 0xe, 0xf6, 0xba } }; + +FOOGUIDDECL const GUID advconfig_entry_string_v2::class_guid = { 0x2ec9b1fa, 0xe1e4, 0x42f0, { 0x87, 0x97, 0x4a, 0x63, 0x16, 0x94, 0x86, 0xbc } }; +FOOGUIDDECL const GUID advconfig_entry_checkbox_v2::class_guid = { 0xe29b37d0, 0xa957, 0x4a85, { 0x82, 0x40, 0x1e, 0x96, 0xc7, 0x29, 0xb6, 0x69 } }; + +FOOGUIDDECL const GUID config_io_callback_v2::class_guid = { 0xfe784bf0, 0x9504, 0x4e35, { 0x85, 0xe4, 0x72, 0x53, 0x82, 0x62, 0xa1, 0x99 } }; + +FOOGUIDDECL const GUID contextmenu_item_v2::class_guid = { 0x4bd830ca, 0xe3e6, 0x404e, { 0x95, 0x44, 0xc9, 0xb7, 0xd1, 0x5a, 0x3f, 0x49 } }; +FOOGUIDDECL const GUID contextmenu_group_manager::class_guid = { 0x92ba0c5, 0x5572, 0x48cd, { 0xa4, 0xca, 0x7b, 0x73, 0xde, 0xb, 0x2a, 0xec } }; +FOOGUIDDECL const GUID contextmenu_group::class_guid = { 0x9dcfc219, 0x779, 0x4669, { 0x98, 0xc1, 0x83, 0x6d, 0xf6, 0x7, 0xc5, 0xd4 } }; +FOOGUIDDECL const GUID contextmenu_group_popup::class_guid = { 0x97636ad5, 0x5b2d, 0x4ad6, { 0x9f, 0x79, 0xc9, 0x64, 0x63, 0x88, 0xc8, 0x29 } }; + +FOOGUIDDECL const GUID contextmenu_groups::root = { 0xe6e7dc71, 0x114b, 0x4848, { 0x92, 0x8c, 0x2a, 0xdc, 0x3f, 0x86, 0xbe, 0xb4 } }; +FOOGUIDDECL const GUID contextmenu_groups::tagging = { 0xc24b5125, 0xad58, 0x4dfd, { 0x99, 0x76, 0xff, 0xbe, 0xba, 0xad, 0xc9, 0x79 } }; +FOOGUIDDECL const GUID contextmenu_groups::tagging_pictures = { 0x7ba1a031, 0xe710, 0x48dc, { 0xbb, 0x11, 0xe0, 0xbe, 0xd8, 0x33, 0x66, 0x9e } }; + +FOOGUIDDECL const GUID contextmenu_groups::utilities = { 0xfb0a55d6, 0xe693, 0x4c4a, { 0x8c, 0x62, 0xf2, 0x4e, 0xa0, 0xce, 0xf8, 0xb7 } }; +FOOGUIDDECL const GUID contextmenu_groups::replaygain = { 0x9640f207, 0x9c98, 0x47b5, { 0x85, 0x7, 0x6c, 0x9c, 0x14, 0x3e, 0x64, 0x15 } }; +FOOGUIDDECL const GUID contextmenu_groups::fileoperations = { 0x62a0e2a4, 0x251d, 0x4315, { 0x88, 0x9b, 0x1, 0x8f, 0x30, 0x8c, 0x7, 0x7a } }; +FOOGUIDDECL const GUID contextmenu_groups::convert = { 0x70429638, 0x502b, 0x466d, { 0xbf, 0x24, 0x46, 0xd, 0xae, 0x23, 0x10, 0x91 } }; +FOOGUIDDECL const GUID contextmenu_groups::playbackstatistics = { 0x12ad3123, 0xd934, 0x4241, { 0xa7, 0x1, 0x92, 0x7e, 0x87, 0x7, 0xd1, 0xdc } }; +FOOGUIDDECL const GUID contextmenu_groups::properties = { 0xb38cb9f, 0xa92d, 0x4fa4, { 0xb4, 0x58, 0x70, 0xd2, 0xfd, 0x39, 0x25, 0xba } }; +FOOGUIDDECL const GUID contextmenu_groups::legacy = { 0xbebb1711, 0x20e, 0x46ed, { 0xa7, 0xf8, 0xa3, 0x26, 0x27, 0x18, 0x4a, 0x88 } }; + +FOOGUIDDECL const GUID mainmenu_commands_v2::class_guid = { 0xe682e810, 0x41f3, 0x434d, { 0xb0, 0xc7, 0xd4, 0x96, 0x90, 0xe6, 0x5f, 0x87 } }; +FOOGUIDDECL const GUID mainmenu_node::class_guid = { 0xabba6512, 0x377d, 0x414f, { 0x81, 0x3e, 0x68, 0x6, 0xc2, 0x2d, 0x4d, 0xb1 } }; + +FOOGUIDDECL const GUID system_time_keeper::class_guid = { 0xdc5d4938, 0x7927, 0x48ba, { 0xbf, 0x84, 0xda, 0x2f, 0xb1, 0xb, 0x36, 0xf2 } }; + +FOOGUIDDECL const GUID component_installation_validator::class_guid = { 0x639283e, 0x3004, 0x4e5c, { 0xb1, 0xb3, 0x6d, 0xff, 0x8, 0xa7, 0x92, 0x84 } }; + +FOOGUIDDECL const GUID playback_stream_capture::class_guid = { 0x9423439e, 0x8cd5, 0x45d7, { 0xaa, 0x6d, 0x4b, 0x98, 0xc, 0x22, 0x93, 0x3e } }; + +FOOGUIDDECL const GUID http_request::class_guid = { 0x48580056, 0x2c5f, 0x45a8, { 0xb8, 0x6e, 0x5, 0x83, 0x55, 0x3e, 0xaa, 0x4f } }; +FOOGUIDDECL const GUID http_request_post::class_guid = { 0xe254b804, 0xeac5, 0x4be0, { 0x99, 0x4d, 0x53, 0x1c, 0x17, 0xea, 0xfd, 0x37 } }; +FOOGUIDDECL const GUID http_request_post_v2::class_guid = { 0x95309335, 0xd301, 0x4946, { 0x99, 0x27, 0x2d, 0xb7, 0x9b, 0x22, 0x46, 0x9c } }; +FOOGUIDDECL const GUID http_client::class_guid = { 0x3b5ffe0c, 0xd75a, 0x491e, { 0xbb, 0x6f, 0x10, 0x3f, 0x73, 0x1e, 0x81, 0x84 } }; +FOOGUIDDECL const GUID http_reply::class_guid = { 0x7f02bf78, 0x5c98, 0x4d6d, { 0x83, 0x6b, 0xb7, 0x77, 0xa4, 0xa3, 0x3e, 0xe5 } }; + +FOOGUIDDECL const GUID popup_message_v2::class_guid = { 0x18b74f55, 0x10e1, 0x4645, { 0x91, 0xf6, 0xb0, 0xe0, 0x4c, 0x28, 0x21, 0xe9 } }; +FOOGUIDDECL const GUID metadb_index_client::class_guid = { 0x52637e7d, 0x7f3, 0x49bf, { 0x90, 0x19, 0x36, 0x20, 0x83, 0xcc, 0x7e, 0x59 } }; +FOOGUIDDECL const GUID metadb_index_manager::class_guid = { 0xb9633863, 0x2683, 0x4163, { 0xa5, 0x8d, 0x26, 0x47, 0x5d, 0xb0, 0xe7, 0x9b } }; +FOOGUIDDECL const GUID init_stage_callback::class_guid = { 0xaf51c159, 0x6326, 0x4da5, { 0x90, 0xb0, 0xf1, 0x1f, 0x99, 0x64, 0xcc, 0x2e } }; +FOOGUIDDECL const GUID metadb_io_edit_callback::class_guid = { 0x2388a50f, 0x33d, 0x4b7c, { 0x91, 0xf2, 0x51, 0x53, 0x69, 0x43, 0xe9, 0xed } }; + +FOOGUIDDECL const GUID progress_meter_instance::class_guid = { 0x13915b24, 0xefef, 0x42b5, { 0xae, 0x1d, 0x55, 0x24, 0x5e, 0x22, 0x22, 0xc5 } }; +FOOGUIDDECL const GUID progress_meter::class_guid = { 0x5e98da34, 0xa9c7, 0x4925, { 0xb0, 0xec, 0x90, 0x9d, 0xe0, 0x16, 0xfa, 0x68 } }; + +FOOGUIDDECL const GUID album_art_manager_config::class_guid = { 0xffa6f79b, 0x6e58, 0x459b, { 0xb7, 0x82, 0xc8, 0x61, 0xe6, 0x3d, 0xb0, 0x75 } }; +FOOGUIDDECL const GUID album_art_manager_v3::class_guid = { 0x125841a4, 0x9507, 0x4d93, { 0x98, 0x60, 0x15, 0xd3, 0xc3, 0x45, 0x47, 0xd6 } }; + +FOOGUIDDECL const GUID now_playing_album_art_notify_manager::class_guid = { 0x79a1aab7, 0xb1a1, 0x463c,{ 0xb8, 0x86, 0x32, 0xb6, 0x79, 0x56, 0xd9, 0xd2 } }; + +FOOGUIDDECL const GUID album_art_editor_instance_v2::class_guid = { 0xcf0f46ca, 0xe34a, 0x4be7, { 0xaf, 0x40, 0xf, 0x31, 0x5c, 0xa1, 0x23, 0x98 } }; + +FOOGUIDDECL const GUID input_info_writer_v2::class_guid = { 0x601c0b4c, 0xf915, 0x486c, { 0xa3, 0x77, 0x0, 0xe3, 0x9c, 0x4d, 0xb7, 0xe4 } }; + +FOOGUIDDECL const GUID playback_control_v3::class_guid = { 0x67008b05, 0x2792, 0x43b5, { 0x80, 0xbb, 0xf9, 0x41, 0xd7, 0xc3, 0xc9, 0x2 } }; + +FOOGUIDDECL const GUID metadb_info_container::class_guid = { 0xd1fdcc01, 0x1e84, 0x48e6, { 0xbf, 0x5f, 0x37, 0x74, 0xd1, 0xcd, 0xc0, 0xe } }; + +FOOGUIDDECL const GUID metadb_hint_list_v3::class_guid = { 0x3e7d9438, 0x7be9, 0x437b, { 0x8a, 0xc8, 0x2b, 0xc2, 0x88, 0xce, 0x1e, 0x22 } }; +FOOGUIDDECL const GUID track_property_provider_v3::class_guid = { 0xdbbce29c, 0x6431, 0x464b, { 0x9c, 0x68, 0x7f, 0x38, 0xfc, 0x34, 0xaf, 0xe7 } }; + + +FOOGUIDDECL const GUID output::class_guid = { 0xb9632c4f, 0x596e, 0x43ee, { 0xb2, 0x14, 0x71, 0x4, 0x48, 0x4b, 0x65, 0xf1 } }; +FOOGUIDDECL const GUID output_entry::class_guid = { 0xe7480b4f, 0x4941, 0x4dd2, { 0xad, 0xbf, 0x96, 0x6c, 0x76, 0x63, 0x43, 0x92 } }; +FOOGUIDDECL const GUID output_entry_v2::class_guid = { 0xaacc17f3, 0xb7cc, 0x48c2, { 0x95, 0x6e, 0x52, 0xba, 0x72, 0x89, 0x42, 0xe5 } }; +FOOGUIDDECL const GUID output_entry_v3::class_guid = { 0xfa18060e, 0xfc84, 0x4f53, { 0xa8, 0xe3, 0x60, 0xc5, 0xf5, 0x22, 0x50, 0x7a } }; +FOOGUIDDECL const GUID volume_control::class_guid = { 0x39f9fc0c, 0x4dc9, 0x4a7a, { 0xb9, 0xad, 0x75, 0x8b, 0x78, 0x57, 0x78, 0xad } }; +FOOGUIDDECL const GUID output_v2::class_guid = { 0x4f679e4b, 0x79e0, 0x4fc9, { 0x90, 0x27, 0x55, 0x49, 0x85, 0x72, 0x26, 0xbf } }; +FOOGUIDDECL const GUID output_v3::class_guid = { 0x3b764d8e, 0x6c1c, 0x40bd, { 0x9a, 0x7e, 0xac, 0x3, 0x2a, 0x3e, 0x25, 0xf1 } }; +FOOGUIDDECL const GUID output_v4::class_guid = { 0x2c7a21a, 0xcc12, 0x48f3, { 0x89, 0x2e, 0xa7, 0x98, 0xb0, 0xc8, 0xaa, 0x49 } }; +FOOGUIDDECL const GUID output_v5::class_guid = { 0x735e6e3f, 0x95d8, 0x45ca, { 0xad, 0x1a, 0xca, 0x88, 0x1f, 0x1d, 0x5c, 0xfa } }; + + + +FOOGUIDDECL const GUID output_manager::class_guid = { 0x6cc5827e, 0x2c89, 0x42ff, { 0x83, 0x51, 0x76, 0xa9, 0x2e, 0x2f, 0x34, 0x50 } }; +FOOGUIDDECL const GUID output_manager_v2::class_guid = { 0xcc8aa352, 0x7af1, 0x41d2, { 0x94, 0x7e, 0xa2, 0x65, 0x17, 0x5b, 0x68, 0x96 } }; + + +FOOGUIDDECL const GUID ui_element_instance::class_guid = { 0xb55d4525, 0xddc8, 0x40d7,{ 0xb9, 0x19, 0x6d, 0x7c, 0x48, 0x38, 0xf2, 0xdb } }; +FOOGUIDDECL const GUID ui_element::class_guid = { 0xb52c703, 0x1586, 0x42f7,{ 0xa8, 0x4c, 0x70, 0x54, 0xcd, 0xc8, 0x22, 0x55 } }; +FOOGUIDDECL const GUID ui_element_v2::class_guid = { 0x2e1fe21e, 0x8e0f, 0x43be,{ 0x9f, 0xdb, 0xd5, 0xdd, 0xf4, 0xc9, 0xba, 0xba } }; +FOOGUIDDECL const GUID ui_element_instance_callback::class_guid = { 0xcd3647c6, 0x12d9, 0x4122,{ 0xa5, 0x28, 0x4a, 0xba, 0x34, 0x90, 0x89, 0x5c } }; +FOOGUIDDECL const GUID ui_element_instance_callback_v2::class_guid = { 0x5b11faa3, 0x48ee, 0x41a1,{ 0xb7, 0xf9, 0x16, 0x7a, 0xba, 0x6c, 0x60, 0x41 } }; +FOOGUIDDECL const GUID ui_element_children_enumerator::class_guid = { 0x6c7a3a46, 0xdc54, 0x4499,{ 0x98, 0x66, 0xae, 0x3, 0x55, 0xe, 0xf3, 0x1c } }; +FOOGUIDDECL const GUID ui_element_common_methods::class_guid = { 0xedee8cd9, 0x3072, 0x410e,{ 0xb2, 0x66, 0x37, 0x5d, 0x9f, 0x6f, 0xb0, 0x36 } }; +FOOGUIDDECL const GUID ui_element_common_methods_v2::class_guid = { 0x2dc90e34, 0x38fc, 0x4ad1,{ 0x92, 0x80, 0xff, 0x1f, 0xac, 0x14, 0x52, 0xd0 } }; +FOOGUIDDECL const GUID ui_element_common_methods_v3::class_guid = { 0xc911b54, 0xe6f9, 0x463c,{ 0x9b, 0xb1, 0xbd, 0xf3, 0xa, 0xec, 0xac, 0x97 } }; +FOOGUIDDECL const GUID ui_element_replace_dialog_notify::class_guid = { 0x95f9259e, 0x821e, 0x425f,{ 0x95, 0x57, 0x3b, 0xba, 0x63, 0x52, 0xf0, 0xc0 } }; +FOOGUIDDECL const GUID ui_element_popup_host::class_guid = { 0xfcc381e9, 0xe527, 0x4887,{ 0xae, 0x63, 0x27, 0xc0, 0x3f, 0x4, 0xd, 0x1 } }; +FOOGUIDDECL const GUID ui_element_popup_host_callback::class_guid = { 0x2993a043, 0x2e70, 0x4d8f,{ 0x81, 0xb, 0x41, 0x3, 0x37, 0x73, 0x97, 0xcd } }; +FOOGUIDDECL const GUID ui_element_config::class_guid = { 0xd34bba46, 0x1bad, 0x4547,{ 0xba, 0xb4, 0x17, 0xe2, 0x44, 0xd5, 0xeb, 0x94 } }; +FOOGUIDDECL const GUID ui_element_instance_callback_v3::class_guid = { 0x6d15c0c6, 0x90b6, 0x4c7e,{ 0xbf, 0x39, 0xe9, 0x39, 0xf2, 0xdf, 0x9b, 0x91 } }; +FOOGUIDDECL const GUID ui_element_popup_host_v2::class_guid = { 0x8caac11e, 0x52b6, 0x47f7,{ 0x97, 0xc9, 0x2c, 0x87, 0xdb, 0xdb, 0x2e, 0x5b } }; + + + + +FOOGUIDDECL const GUID ui_edit_context::class_guid = { 0xf9ba651b, 0x52dd, 0x466f,{ 0xaa, 0x77, 0xa9, 0x7a, 0x74, 0x98, 0x80, 0x7e } }; +FOOGUIDDECL const GUID ui_edit_context_manager::class_guid = { 0x3807f161, 0xaa17, 0x47df,{ 0x80, 0xf1, 0xe, 0xfc, 0xd2, 0x19, 0xb7, 0xa1 } }; +FOOGUIDDECL const GUID ui_edit_context_playlist::class_guid = { 0x6dec364d, 0x29f2, 0x47c8,{ 0xaf, 0x93, 0xbd, 0x35, 0x56, 0x3f, 0xa2, 0x25 } }; + +#ifdef FOOBAR2000_HAVE_FILE_FORMAT_SANITIZER +FOOGUIDDECL const GUID file_format_sanitizer::class_guid = { 0x933dc1b8, 0xbce8, 0x4a0a, { 0xa4, 0x53, 0xbf, 0x4e, 0x6b, 0xcd, 0xb0, 0xf9 } }; +FOOGUIDDECL const GUID file_format_sanitizer_stdtags::class_guid = { 0x613bceb4, 0x54c5, 0x43e8, { 0x9e, 0xc, 0xb7, 0xec, 0x15, 0x9b, 0x85, 0x11 } }; +#endif // FOOBAR2000_HAVE_FILE_FORMAT_SANITIZER + + +FOOGUIDDECL const GUID filesystem_transacted::class_guid = { 0x35637f92, 0x6b65, 0x47b8,{ 0x9e, 0xe, 0x48, 0x47, 0x97, 0x62, 0x34, 0x3 } }; +FOOGUIDDECL const GUID filesystem_transacted_entry::class_guid = { 0xfd6e6849, 0x7006, 0x44db,{ 0x9b, 0x87, 0x45, 0x58, 0x3c, 0xf7, 0x5d, 0x18 } }; +FOOGUIDDECL const GUID config_io_callback_v3::class_guid = { 0x8e633dd, 0x625e, 0x402c,{ 0xbe, 0xab, 0x74, 0x9, 0xd0, 0x1d, 0x41, 0xdf } }; +FOOGUIDDECL const GUID filesystem_v2::class_guid = { 0xdaf04ce2, 0x36a5, 0x4346,{ 0x80, 0x7f, 0x77, 0x8c, 0x5b, 0x5c, 0x26, 0xaa } }; + +FOOGUIDDECL const GUID playlist_incoming_item_filter_v4::class_guid = { 0x9bd438f5, 0x91e9, 0x463f,{ 0xbd, 0xcc, 0x36, 0x30, 0xf6, 0x8e, 0xf0, 0x5 } }; + +FOOGUIDDECL const GUID file_lock::class_guid = { 0xb2f0b2f8, 0x1ccf, 0x438e, { 0xb9, 0x75, 0x9f, 0x5b, 0x75, 0x5a, 0xa3, 0x2e } }; +FOOGUIDDECL const GUID file_lock_manager::class_guid = { 0xa808fe53, 0xd36, 0x42fb, { 0xac, 0x2, 0x6a, 0xc8, 0x9c, 0xcb, 0x24, 0xbe } }; +FOOGUIDDECL const GUID file_lock_manager_v2::class_guid = { 0x30c07c6a, 0xde51, 0x4094, { 0x82, 0xe6, 0xf1, 0x57, 0xd1, 0xb2, 0x3a, 0xe8 } }; +FOOGUIDDECL const GUID file_lock_interrupt::class_guid = { 0x73ebabc8, 0xfe66, 0x4dae, { 0x95, 0x90, 0x3d, 0xa3, 0x9f, 0x17, 0x19, 0x15 } }; + +FOOGUIDDECL const GUID track_property_provider_v4::class_guid = { 0x707abb57, 0x35f7, 0x41c3, { 0xac, 0x43, 0xc9, 0xb6, 0xc, 0xc6, 0xa9, 0xae } }; + + +FOOGUIDDECL const GUID album_art_extractor_v2::class_guid = { 0x3aa31001, 0xaf5b, 0x497a, { 0xbd, 0xdc, 0xa9, 0x3f, 0x23, 0xb2, 0x1b, 0xc2 } }; +FOOGUIDDECL const GUID album_art_editor_v2::class_guid = { 0xf16827d3, 0xca20, 0x44fe, { 0x94, 0xe0, 0x56, 0xd7, 0x2d, 0x35, 0x81, 0x6 } }; + + +FOOGUIDDECL const GUID replaygain_scanner_config::class_guid = { 0x210970e1, 0xa478, 0x4d76, { 0xa5, 0x7c, 0x95, 0x9, 0xae, 0x82, 0xae, 0x41 } }; + +#if FOOBAR2000_TARGET_VERSION >= 80 +FOOGUIDDECL const GUID metadb_io_v4::class_guid = { 0x6ec07034, 0xd5c2, 0x4fb5, { 0xb7, 0x59, 0x7d, 0x12, 0x4f, 0xa7, 0x27, 0xf4 } }; +FOOGUIDDECL const GUID popup_message_v3::class_guid = { 0xef3c83fd, 0x1144, 0x4edb, { 0xb8, 0x43, 0xcf, 0xba, 0xc6, 0xe5, 0xe1, 0x8b } }; +#endif // FOOBAR2000_TARGET_VERSION >= 80 + +FOOGUIDDECL const GUID file_lowLevelIO::class_guid = { 0xbcacb272, 0x8e6c, 0x4d23, { 0x91, 0x9a, 0xe, 0xaa, 0x55, 0x2e, 0xc9, 0x70 } }; +FOOGUIDDECL const GUID file_lowLevelIO::guid_flushFileBuffers = { 0xace5356b, 0x8c72, 0x408a, { 0x8e, 0x32, 0x78, 0x1e, 0x7d, 0x7f, 0xe9, 0x97 } }; +FOOGUIDDECL const GUID file_lowLevelIO::guid_getFileTimes = { 0xb5a3cd80, 0x23ae, 0x4c51, { 0x83, 0x3b, 0xa5, 0x98, 0x6b, 0xe1, 0xec, 0x59 } }; +FOOGUIDDECL const GUID file_lowLevelIO::guid_setFileTimes = { 0x46501e0d, 0x644d, 0x4d00, { 0xaf, 0x8c, 0xc5, 0xc1, 0xb, 0x34, 0xae, 0x37 } }; + + +FOOGUIDDECL const GUID async_task_manager::class_guid = { 0xea055f49, 0x7c6d, 0x4695, { 0x8c, 0xf, 0xeb, 0xbd, 0x92, 0xdb, 0xa7, 0xa7 } }; + +FOOGUIDDECL const GUID read_ahead_tools::class_guid = { 0x709671bf, 0x449a, 0x4dc8, { 0x9f, 0xcf, 0x84, 0xb3, 0xbc, 0xec, 0x98, 0x4d } }; + +FOOGUIDDECL const GUID fb2k::imageLoaderLite::class_guid = { 0xbe06ead9, 0x1c9, 0x42e0, { 0x9f, 0x9f, 0x12, 0xb2, 0xde, 0x95, 0xca, 0x96 } }; +FOOGUIDDECL const GUID fb2k::imageViewer::class_guid = { 0xdbdaaa24, 0x2f90, 0x426c, { 0x86, 0x3, 0x1c, 0x5a, 0xe5, 0xf9, 0x82, 0x21 } }; diff --git a/foobar2000/SDK/hasher_md5.cpp b/foobar2000/SDK/hasher_md5.cpp new file mode 100644 index 0000000..a81812b --- /dev/null +++ b/foobar2000/SDK/hasher_md5.cpp @@ -0,0 +1,43 @@ +#include "foobar2000.h" + +GUID hasher_md5::guid_from_result(const hasher_md5_result & param) +{ + static_assert(sizeof(GUID) == sizeof(hasher_md5_result), "sanity"); + GUID temp = * reinterpret_cast(¶m); + byte_order::order_le_to_native_t(temp); + return temp; +} + +GUID hasher_md5_result::asGUID() const { + return hasher_md5::guid_from_result( *this ); +} + +hasher_md5_result hasher_md5::process_single(const void * p_buffer,t_size p_bytes) +{ + hasher_md5_state state; + initialize(state); + process(state,p_buffer,p_bytes); + return get_result(state); +} + +GUID hasher_md5::process_single_guid(const void * p_buffer,t_size p_bytes) +{ + return guid_from_result(process_single(p_buffer,p_bytes)); +} + +t_uint64 hasher_md5_result::xorHalve() const { +#if PFC_BYTE_ORDER_IS_BIG_ENDIAN + t_uint64 ret = 0; + for(int walk = 0; walk < 8; ++walk) { + ret |= (t_uint64)((t_uint8)m_data[walk] ^ (t_uint8)m_data[walk+8]) << (walk * 8); + } + return ret; +#else + const t_uint64 * v = reinterpret_cast(&m_data); + return v[0] ^ v[1]; +#endif +} + +pfc::string8 hasher_md5_result::asString() const { + return pfc::format_hexdump( this->m_data, sizeof(m_data), "").get_ptr(); +} diff --git a/foobar2000/SDK/hasher_md5.h b/foobar2000/SDK/hasher_md5.h new file mode 100644 index 0000000..a83882d --- /dev/null +++ b/foobar2000/SDK/hasher_md5.h @@ -0,0 +1,94 @@ +#pragma once + +struct hasher_md5_state { + char m_data[128]; +}; + +struct hasher_md5_result { + char m_data[16]; + + t_uint64 xorHalve() const; + GUID asGUID() const; + pfc::string8 asString() const; + + static hasher_md5_result null() {hasher_md5_result h = {}; return h;} +}; + +FB2K_STREAM_READER_OVERLOAD(hasher_md5_result) { + stream.read_raw(&value, sizeof(value)); return stream; +} +FB2K_STREAM_WRITER_OVERLOAD(hasher_md5_result) { + stream.write_raw(&value, sizeof(value)); return stream; +} + +inline bool operator==(const hasher_md5_result & p_item1,const hasher_md5_result & p_item2) {return memcmp(&p_item1,&p_item2,sizeof(hasher_md5_result)) == 0;} +inline bool operator!=(const hasher_md5_result & p_item1,const hasher_md5_result & p_item2) {return memcmp(&p_item1,&p_item2,sizeof(hasher_md5_result)) != 0;} + +namespace pfc { + template<> class traits_t : public traits_rawobject {}; + template<> class traits_t : public traits_rawobject {}; + + template<> inline int compare_t(const hasher_md5_result & p_item1, const hasher_md5_result & p_item2) { + return memcmp(&p_item1, &p_item2, sizeof(hasher_md5_result)); + } + +} + +class NOVTABLE hasher_md5 : public service_base +{ +public: + + virtual void initialize(hasher_md5_state & p_state) = 0; + virtual void process(hasher_md5_state & p_state,const void * p_buffer,t_size p_bytes) = 0; + virtual hasher_md5_result get_result(const hasher_md5_state & p_state) = 0; + + + static GUID guid_from_result(const hasher_md5_result & param); + + hasher_md5_result process_single(const void * p_buffer,t_size p_bytes); + hasher_md5_result process_single_string(const char * str) {return process_single(str, strlen(str));} + GUID process_single_guid(const void * p_buffer,t_size p_bytes); + GUID get_result_guid(const hasher_md5_state & p_state) {return guid_from_result(get_result(p_state));} + + + //! Helper + void process_string(hasher_md5_state & p_state,const char * p_string,t_size p_length = ~0) {return process(p_state,p_string,pfc::strlen_max(p_string,p_length));} + + FB2K_MAKE_SERVICE_COREAPI(hasher_md5); +}; + + +class stream_writer_hasher_md5 : public stream_writer { +public: + stream_writer_hasher_md5() { + m_hasher->initialize(m_state); + } + void write(const void * p_buffer,t_size p_bytes,abort_callback & p_abort) { + p_abort.check(); + m_hasher->process(m_state,p_buffer,p_bytes); + } + hasher_md5_result result() const { + return m_hasher->get_result(m_state); + } + GUID resultGuid() const { + return hasher_md5::guid_from_result(result()); + } +private: + hasher_md5_state m_state; + const hasher_md5::ptr m_hasher = hasher_md5::get(); +}; + +template +class stream_formatter_hasher_md5 : public stream_writer_formatter { +public: + stream_formatter_hasher_md5() : stream_writer_formatter(_m_stream,fb2k::noAbort) {} + + hasher_md5_result result() const { + return _m_stream.result(); + } + GUID resultGuid() const { + return hasher_md5::guid_from_result(result()); + } +private: + stream_writer_hasher_md5 _m_stream; +}; diff --git a/foobar2000/SDK/http_client.h b/foobar2000/SDK/http_client.h new file mode 100644 index 0000000..4bba21b --- /dev/null +++ b/foobar2000/SDK/http_client.h @@ -0,0 +1,57 @@ +#pragma once + +//! Implemented by file object returned by http_request::run methods. Allows you to retrieve various additional information returned by the server. \n +//! Warning: reply status may change when seeking on the file object since seek operations often require a new HTTP request to be fired. +class NOVTABLE http_reply : public service_base { + FB2K_MAKE_SERVICE_INTERFACE(http_reply, service_base) +public: + //! Retrieves the status line, eg. "200 OK". + virtual void get_status(pfc::string_base & out) = 0; + //! Retrieves a HTTP header value, eg. "content-type". Note that get_http_header("content-type", out) is equivalent to get_content_type(out). If there are multiple matching header entries, value of the first one will be returned. + virtual bool get_http_header(const char * name, pfc::string_base & out) = 0; + //! Retrieves a HTTP header value, eg. "content-type". If there are multiple matching header entries, this will return all their values, delimited by \r\n. + virtual bool get_http_header_multi(const char * name, pfc::string_base & out) = 0; +}; + +class NOVTABLE http_request : public service_base { + FB2K_MAKE_SERVICE_INTERFACE(http_request, service_base) +public: + //! Adds a HTTP request header line. + //! @param line Request to be added, without trailing \r\n. + virtual void add_header(const char * line) = 0; + //! Runs the request on the specified URL. Throws an exception on failure (connection error, invalid response from the server, reply code other than 2XX), returns a file::ptr interface to the stream on success. + virtual file::ptr run(const char * url, abort_callback & abort) = 0; + //! Runs the request on the specified URL. Throws an exception on failure but returns normally if the HTTP server returned a valid response other than 2XX, so the caller can still parse the received data stream if the server has returned an error. + virtual file::ptr run_ex(const char * url, abort_callback & abort) = 0; + + void add_header(const char * name, const char * value) { + add_header(PFC_string_formatter() << name << ": " << value); + } +}; + +class NOVTABLE http_request_post : public http_request { + FB2K_MAKE_SERVICE_INTERFACE(http_request_post, http_request); +public: + //! Adds a HTTP POST field. + //! @param name Field name. + //! @param fileName File name to be included in the POST request; leave empty ("") not to send a file name. + //! @param contentType Content type of the entry; leave empty ("") not to send content type. + virtual void add_post_data(const char * name, const void * data, t_size dataSize, const char * fileName, const char * contentType) = 0; + + void add_post_data(const char * name, const char * value) { add_post_data(name, value, strlen(value), "", ""); } +}; + +//! \since 1.5 +class NOVTABLE http_request_post_v2 : public http_request_post { + FB2K_MAKE_SERVICE_INTERFACE(http_request_post_v2, http_request_post); +public: + virtual void set_post_data(const void* blob, size_t bytes, const char* contentType) = 0; +}; + +class NOVTABLE http_client : public service_base { + FB2K_MAKE_SERVICE_COREAPI(http_client) +public: + //! Creates a HTTP request object. + //! @param type Request type. Currently supported: "GET" and "POST". Throws pfc::exception_not_implemented for unsupported values. + virtual http_request::ptr create_request(const char * type) = 0; +}; diff --git a/foobar2000/SDK/icon_remap.h b/foobar2000/SDK/icon_remap.h new file mode 100644 index 0000000..bbec9b9 --- /dev/null +++ b/foobar2000/SDK/icon_remap.h @@ -0,0 +1,28 @@ +#pragma once + +//! New in 0.9.5; allows your file format to use another icon than .ico when registering the file type with Windows shell. \n +//! Implementation: use icon_remapping_impl, or simply: static service_factory_single_t myicon("ext","iconname.ico"); +class icon_remapping : public service_base { +public: + //! @param p_extension File type extension being queried. + //! @param p_iconname Receives the icon name to use, including the .ico extension. + //! @returns True when p_iconname has been set, false if we don't recognize the specified extension. + virtual bool query(const char * p_extension,pfc::string_base & p_iconname) = 0; + + FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(icon_remapping); +}; + +//! Standard implementation of icon_remapping. +class icon_remapping_impl : public icon_remapping { +public: + icon_remapping_impl(const char * p_extension,const char * p_iconname) : m_extension(p_extension), m_iconname(p_iconname) {} + bool query(const char * p_extension,pfc::string_base & p_iconname) { + if (pfc::stricmp_ascii(p_extension,m_extension) == 0) { + p_iconname = m_iconname; return true; + } else { + return false; + } + } +private: + pfc::string8 m_extension,m_iconname; +}; diff --git a/foobar2000/SDK/imageLoaderLite.h b/foobar2000/SDK/imageLoaderLite.h new file mode 100644 index 0000000..c12e7e7 --- /dev/null +++ b/foobar2000/SDK/imageLoaderLite.h @@ -0,0 +1,52 @@ +#pragma once + +#ifdef _WIN32 +namespace Gdiplus { + class Image; +} +#endif +namespace fb2k { +#ifdef _WIN32 + typedef Gdiplus::Image * nativeImage_t; +#else + typedef void * nativeImage_t; +#endif + + struct imageInfo_t { + uint32_t width, height, bitDepth; + bool haveAlpha; + const char * formatName; + const char * mime; + }; + + //! \since 1.6 + //! Interface to common image loader routines that turn a bytestream into a image that can be drawn in a window. \n + //! Windows: Using imageLoaderLite methods initializes gdiplus if necessary, leaving gdiplus initialized for the rest of app lifetime. \n + //! If your component supports running on foobar2000 older than 1.6, use tryGet() to gracefully fall back to your own image loader: \n + //! auto api = fb2k::imageLoaderLite::tryGet(); if (api.is_valid()) { do stuff with api; } else { use fallbacks; } + class imageLoaderLite : public service_base { + FB2K_MAKE_SERVICE_COREAPI(imageLoaderLite); + public: + //! Throws excpetions on failure, returns valid image otherwise.\n + //! Caller takes ownership of the returned object. \n + //! @param outInfo Optional struct to receive information about the loaded image. + virtual nativeImage_t load(const void * data, size_t bytes, imageInfo_t * outInfo = nullptr, abort_callback & aborter = fb2k::noAbort) = 0; + + //! Parses the image data just enough to hand over basic info about what's inside. \n + //! Much faster than load(). \n + //! Supports all formats recognized by load(). + virtual imageInfo_t getInfo(const void * data, size_t bytes, abort_callback & aborter = fb2k::noAbort) = 0; + + //! Helper - made virtual so it can be possibly specialized in the future + virtual nativeImage_t load(album_art_data_ptr data, imageInfo_t * outInfo = nullptr, abort_callback & aborter = fb2k::noAbort) { + return load(data->get_ptr(), data->get_size(), outInfo, aborter); + } + //! Helper - made virtual so it can be possibly specialized in the future + virtual imageInfo_t getInfo(album_art_data_ptr data, abort_callback & aborter = fb2k::noAbort) { + return getInfo(data->get_ptr(), data->get_size(), aborter); + } + }; +} + +#define FB2K_GETOPENFILENAME_PICTUREFILES "Picture files|*.jpg;*.jpeg;*.png;*.bmp;*.gif;*.webp" +#define FB2K_GETOPENFILENAME_PICTUREFILES_ALL FB2K_GETOPENFILENAME_PICTUREFILES "|All files|*.*" \ No newline at end of file diff --git a/foobar2000/SDK/imageViewer.h b/foobar2000/SDK/imageViewer.h new file mode 100644 index 0000000..ccf3d07 --- /dev/null +++ b/foobar2000/SDK/imageViewer.h @@ -0,0 +1,15 @@ +#pragma once + +namespace fb2k { + //! \since 1.6.2 + class imageViewer : public service_base { + FB2K_MAKE_SERVICE_COREAPI(imageViewer); + public: + //! Spawns an image viewer window, showing the specified picture already loaded into application memory. + virtual void show(HWND parent, fb2k::memBlockRef data) = 0; + //! Spawns an image viewer window, showing album art from the specified list of items. + //! @param aaType Type of picture to load, front cover, back cover or other. + //! @param pageno Reserved for future use, set to 0. + virtual void load_and_show(HWND parent, metadb_handle_list_cref items, const GUID & aaType, unsigned pageno = 0) = 0; + }; +} \ No newline at end of file diff --git a/foobar2000/SDK/info_lookup_handler.h b/foobar2000/SDK/info_lookup_handler.h new file mode 100644 index 0000000..08e1562 --- /dev/null +++ b/foobar2000/SDK/info_lookup_handler.h @@ -0,0 +1,32 @@ +//! Service used to access various external (online) track info lookup services, such as freedb, to update file tags with info retrieved from those services. +class NOVTABLE info_lookup_handler : public service_base { +public: + enum { + flag_album_lookup = 1 << 0, + flag_track_lookup = 1 << 1, + }; + + //! Retrieves human-readable name of the lookup handler to display in user interface. + virtual void get_name(pfc::string_base & p_out) = 0; + + //! Returns one or more of flag_track_lookup, and flag_album_lookup. + virtual t_uint32 get_flags() = 0; + + virtual HICON get_icon(int p_width, int p_height) = 0; + + //! Performs a lookup. Creates a modeless dialog and returns immediately. + //! @param p_items Items to look up. + //! @param p_notify Callback to notify caller when the operation has completed. Call on_completion with status code 0 to signal failure/abort, or with code 1 to signal success / new infos in metadb. + //! @param p_parent Parent window for the lookup dialog. Caller will typically disable the window while lookup is in progress and enable it back when completion is signaled. + virtual void lookup(metadb_handle_list_cref items,completion_notify::ptr notify,HWND parent) = 0; + + FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(info_lookup_handler); +}; + + +class NOVTABLE info_lookup_handler_v2 : public info_lookup_handler { + FB2K_MAKE_SERVICE_INTERFACE(info_lookup_handler_v2, info_lookup_handler); +public: + virtual double merit() {return 0;} + virtual void lookup_noninteractive(metadb_handle_list_cref items, completion_notify::ptr notify, HWND parent) = 0; +}; diff --git a/foobar2000/SDK/initquit.h b/foobar2000/SDK/initquit.h new file mode 100644 index 0000000..107ce50 --- /dev/null +++ b/foobar2000/SDK/initquit.h @@ -0,0 +1,41 @@ +#pragma once +//! Basic callback startup/shutdown callback, on_init is called after the main window has been created, on_quit is called before the main window is destroyed. \n +//! To register: static initquit_factory_t myclass_factory; \n +//! Note that you should be careful with calling other components during on_init/on_quit or \n +//! initializing services that are possibly used by other components by on_init/on_quit - \n +//! initialization order of components is undefined. +//! If some other service that you publish is not properly functional before you receive an on_init() call, \n +//! someone else might call this service before >your< on_init is invoked. +class NOVTABLE initquit : public service_base { +public: + virtual void on_init() {} + virtual void on_quit() {} + + FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(initquit); +}; + +template +class initquit_factory_t : public service_factory_single_t {}; + + +//! \since 1.1 +namespace init_stages { + enum { + before_config_read = 10, + after_config_read = 20, + before_library_init = 30, + // Since foobar2000 v2.0, after_library_init is fired OUT OF ORDER with the rest, after ASYNCHRONOUS library init has completed. + after_library_init = 40, + before_ui_init = 50, + after_ui_init = 60, + }; +}; + +//! \since 1.1 +class NOVTABLE init_stage_callback : public service_base { + FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(init_stage_callback) +public: + virtual void on_init_stage(t_uint32 stage) = 0; + + static void dispatch(t_uint32 stage) {FB2K_FOR_EACH_SERVICE(init_stage_callback, on_init_stage(stage));} +}; diff --git a/foobar2000/SDK/input.cpp b/foobar2000/SDK/input.cpp new file mode 100644 index 0000000..d4551ae --- /dev/null +++ b/foobar2000/SDK/input.cpp @@ -0,0 +1,450 @@ +#include "foobar2000.h" // PCH +#ifdef FOOBAR2000_MODERN +#include "foobar2000-input.h" +#include +#include +#endif +#include +#include "album_art.h" +#include "file_info_impl.h" + +service_ptr input_entry::open(const GUID & whatFor, file::ptr hint, const char * path, event_logger::ptr logger, abort_callback & aborter) { + +#ifdef FOOBAR2000_DESKTOP + if ( whatFor == input_stream_info_reader::class_guid ) { + input_entry_v2::ptr v2; + if ( v2 &= this ) { + GUID g = v2->get_guid(); + service_enum_t e; + service_ptr_t p; + while (e.next(p)) { + if (p->get_guid() == g) { + return p->open( path, hint, aborter ); + } + } + } + throw exception_io_unsupported_format(); + } +#endif + if ( whatFor == album_art_extractor_instance::class_guid ) { + input_entry_v2::ptr v2; + if (v2 &= this) { + GUID g = v2->get_guid(); + service_enum_t e; + album_art_extractor_v2::ptr p; + while( e.next(p) ) { + if ( p->get_guid() == g ) { + return p->open( hint, path, aborter ); + } + } + } + throw exception_io_unsupported_format(); + } + if ( whatFor == album_art_editor_instance::class_guid ) { + input_entry_v2::ptr v2; + if (v2 &= this) { + GUID g = v2->get_guid(); + service_enum_t e; + album_art_editor_v2::ptr p; + while( e.next(p) ) { + if ( p->get_guid() == g ) { + return p->open( hint, path, aborter ); + } + } + } + throw exception_io_unsupported_format(); + } + + input_entry_v3::ptr v3; + + if (v3 &= this) { + return v3->open_v3( whatFor, hint, path, logger, aborter ); + } else { + if (whatFor == input_decoder::class_guid) { + input_decoder::ptr obj; + open(obj, hint, path, aborter); + if ( logger.is_valid() ) { + input_decoder_v2::ptr v2; + if (v2 &= obj) v2->set_logger(logger); + } + return obj; + } + if (whatFor == input_info_reader::class_guid) { + input_info_reader::ptr obj; + open(obj, hint, path, aborter); + return obj; + } + if (whatFor == input_info_writer::class_guid) { + input_info_writer::ptr obj; + open(obj, hint, path, aborter); + return obj; + } + } + + throw pfc::exception_not_implemented(); +} + +bool input_entry::g_find_service_by_path(service_ptr_t & p_out,const char * p_path) +{ + auto ext = pfc::string_extension(p_path); + return g_find_service_by_path(p_out, p_path, ext ); +} + +bool input_entry::g_find_service_by_path(service_ptr_t & p_out,const char * p_path, const char * p_ext) +{ + service_ptr_t ptr; + service_enum_t e; + while(e.next(ptr)) + { + if (ptr->is_our_path(p_path,p_ext)) + { + p_out = ptr; + return true; + } + } + return false; +} + +bool input_entry::g_find_service_by_content_type(service_ptr_t & p_out,const char * p_content_type) +{ + service_ptr_t ptr; + service_enum_t e; + while(e.next(ptr)) + { + if (ptr->is_our_content_type(p_content_type)) + { + p_out = ptr; + return true; + } + } + return false; +} + + +#if 0 +static void prepare_for_open(service_ptr_t & p_service,service_ptr_t & p_file,const char * p_path,filesystem::t_open_mode p_open_mode,abort_callback & p_abort,bool p_from_redirect) +{ + if (p_file.is_empty()) + { + service_ptr_t fs; + if (filesystem::g_get_interface(fs,p_path)) { + if (fs->supports_content_types()) { + fs->open(p_file,p_path,p_open_mode,p_abort); + } + } + } + + if (p_file.is_valid()) + { + pfc::string8 content_type; + if (p_file->get_content_type(content_type)) + { + if (input_entry::g_find_service_by_content_type(p_service,content_type)) + return; + } + } + + if (input_entry::g_find_service_by_path(p_service,p_path)) + { + if (p_from_redirect && p_service->is_redirect()) throw exception_io_unsupported_format(); + return; + } + + throw exception_io_unsupported_format(); +} +#endif + +bool input_entry::g_find_inputs_by_content_type(pfc::list_base_t > & p_out, const char * p_content_type, bool p_from_redirect) { + auto filter = [=] (input_entry::ptr p) { + return !(p_from_redirect && p->is_redirect()); + }; + return g_find_inputs_by_content_type_ex(p_out, p_content_type, filter ); +} + +bool input_entry::g_find_inputs_by_path(pfc::list_base_t > & p_out, const char * p_path, bool p_from_redirect) { + auto filter = [=] (input_entry::ptr p) { + return !(p_from_redirect && p->is_redirect()); + }; + return g_find_inputs_by_path_ex(p_out, p_path, filter); +} + +bool input_entry::g_find_inputs_by_content_type_ex(pfc::list_base_t > & p_out, const char * p_content_type, input_filter_t filter ) { + service_enum_t e; + service_ptr_t ptr; + bool ret = false; + while (e.next(ptr)) { + if ( filter(ptr) ) { + if (ptr->is_our_content_type(p_content_type)) { p_out.add_item(ptr); ret = true; } + } + } + return ret; +} + +bool input_entry::g_find_inputs_by_path_ex(pfc::list_base_t > & p_out, const char * p_path, input_filter_t filter ) { + service_enum_t e; + service_ptr_t ptr; + auto extension = pfc::string_extension(p_path); + bool ret = false; + while (e.next(ptr)) { + GUID guid = pfc::guid_null; + input_entry_v3::ptr ex; + if ( ex &= ptr ) guid = ex->get_guid(); + if ( filter(ptr) ) { + if (ptr->is_our_path(p_path, extension)) { p_out.add_item(ptr); ret = true; } + } + } + return ret; +} + +static GUID input_get_guid( input_entry::ptr e ) { +#ifdef FOOBAR2000_DESKTOP + input_entry_v2::ptr p; + if ( p &= e ) return p->get_guid(); +#endif + return pfc::guid_null; +} + +service_ptr input_entry::g_open_from_list(input_entry_list_t const & p_list, const GUID & whatFor, service_ptr_t p_filehint, const char * p_path, event_logger::ptr logger, abort_callback & p_abort, GUID * outGUID) { + const t_size count = p_list.get_count(); + if ( count == 0 ) { + // sanity + throw exception_io_unsupported_format(); + } else if (count == 1) { + auto ret = p_list[0]->open(whatFor, p_filehint, p_path, logger, p_abort); + if ( outGUID != nullptr ) * outGUID = input_get_guid( p_list[0] ); + return ret; + } else { + std::exception_ptr errData, errUnsupported; + for (t_size n = 0; n < count; n++) { + try { + auto ret = p_list[n]->open(whatFor, p_filehint, p_path, logger, p_abort); + if (outGUID != nullptr) * outGUID = input_get_guid(p_list[n]); + return ret; + } catch (exception_io_no_handler_for_path) { + //do nothing, skip over + } catch(exception_io_unsupported_format) { + if (!errUnsupported) errUnsupported = std::current_exception(); + } catch (exception_io_data) { + if (!errData) errData = std::current_exception(); + } + } + if (errData) std::rethrow_exception(errData); + if (errUnsupported) std::rethrow_exception(errUnsupported); + throw exception_io_unsupported_format(); + } +} + +#ifdef FOOBAR2000_DESKTOP +service_ptr input_manager::open_v2(const GUID & whatFor, file::ptr hint, const char * path, bool fromRedirect, event_logger::ptr logger, abort_callback & aborter, GUID * outUsedEntry) { + // We're wrapping open_v2() on top of old open(). + // Assert on GUIDs that old open() is known to recognize. + PFC_ASSERT(whatFor == input_decoder::class_guid || whatFor == input_info_reader::class_guid || whatFor == input_info_writer::class_guid || whatFor == input_stream_selector::class_guid); + + { + input_manager_v2::ptr v2; + if ( v2 &= this ) { + return v2->open_v2( whatFor, hint, path, fromRedirect, logger, aborter, outUsedEntry ); + } + } + + auto ret = open( whatFor, hint, path, fromRedirect, aborter, outUsedEntry ); + +#ifdef FB2K_HAVE_EVENT_LOGGER + if ( logger.is_valid() ) { + input_decoder_v2::ptr dec; + if (dec &= ret) { + dec->set_logger(logger); + } + } +#endif + return ret; +} +#endif + +service_ptr input_entry::g_open(const GUID & whatFor, file::ptr p_filehint, const char * p_path, event_logger::ptr logger, abort_callback & p_abort, bool p_from_redirect) { + +#ifdef FOOBAR2000_DESKTOP + + // #define rationale: not all FOOBAR2000_MODERN flavours come with input_manager implementation, but classic 1.4+ does +#if !defined(FOOBAR2000_MODERN) && FOOBAR2000_TARGET_VERSION >= 79 + +#if FOOBAR2000_TARGET_VERSION > 79 + return input_manager_v2::get()->open_v2(whatFor, p_filehint, p_path, p_from_redirect, logger, p_abort); +#else + return input_manager::get()->open_v2(whatFor, p_filehint, p_path, p_from_redirect, logger, p_abort); +#endif + +#endif + { + input_manager::ptr m; + service_enum_t e; + if (e.next(m)) { + return m->open_v2(whatFor, p_filehint, p_path, p_from_redirect, logger, p_abort); + } + } +#endif + + const bool needWriteAcecss = !!(whatFor == input_info_writer::class_guid); + + service_ptr_t l_file = p_filehint; + if (l_file.is_empty()) { + service_ptr_t fs; + if (filesystem::g_get_interface(fs, p_path)) { + if (fs->supports_content_types()) { + fs->open(l_file, p_path, needWriteAcecss ? filesystem::open_mode_write_existing : filesystem::open_mode_read, p_abort); + } + } + } + + if (l_file.is_valid()) { + pfc::string8 content_type; + if (l_file->get_content_type(content_type)) { + pfc::list_t< input_entry::ptr > list; +#if PFC_DEBUG + FB2K_DebugLog() << "attempting input open by content type: " << content_type; +#endif + if (g_find_inputs_by_content_type(list, content_type, p_from_redirect)) { + try { + return g_open_from_list(list, whatFor, l_file, p_path, logger, p_abort); + } catch (exception_io_unsupported_format) { +#if PFC_DEBUG + FB2K_DebugLog() << "Failed to open by content type, using fallback"; +#endif + } + } + } + } + +#if PFC_DEBUG + FB2K_DebugLog() << "attempting input open by path: " << p_path; +#endif + { + pfc::list_t< input_entry::ptr > list; + if (g_find_inputs_by_path(list, p_path, p_from_redirect)) { + return g_open_from_list(list, whatFor, l_file, p_path, logger, p_abort); + } + } + + throw exception_io_unsupported_format(); +} + +void input_entry::g_open_for_decoding(service_ptr_t & p_instance,service_ptr_t p_filehint,const char * p_path,abort_callback & p_abort,bool p_from_redirect) { + TRACK_CALL_TEXT("input_entry::g_open_for_decoding"); + p_instance ^= g_open(input_decoder::class_guid, p_filehint, p_path, nullptr, p_abort, p_from_redirect); +} + +void input_entry::g_open_for_info_read(service_ptr_t & p_instance,service_ptr_t p_filehint,const char * p_path,abort_callback & p_abort,bool p_from_redirect) { + TRACK_CALL_TEXT("input_entry::g_open_for_info_read"); + p_instance ^= g_open(input_info_reader::class_guid, p_filehint, p_path, nullptr, p_abort, p_from_redirect); +} + +void input_entry::g_open_for_info_write(service_ptr_t & p_instance,service_ptr_t p_filehint,const char * p_path,abort_callback & p_abort,bool p_from_redirect) { + TRACK_CALL_TEXT("input_entry::g_open_for_info_write"); + p_instance ^= g_open(input_info_writer::class_guid, p_filehint, p_path, nullptr, p_abort, p_from_redirect); +} + +void input_entry::g_open_for_info_write_timeout(service_ptr_t & p_instance,service_ptr_t p_filehint,const char * p_path,abort_callback & p_abort,double p_timeout,bool p_from_redirect) { + pfc::lores_timer timer; + timer.start(); + for(;;) { + try { + g_open_for_info_write(p_instance,p_filehint,p_path,p_abort,p_from_redirect); + break; + } catch(exception_io_sharing_violation) { + if (timer.query() > p_timeout) throw; + p_abort.sleep(0.01); + } + } +} + +bool input_entry::g_is_supported_path(const char * p_path) +{ + service_ptr_t ptr; + service_enum_t e; + auto ext = pfc::string_extension (p_path); + while(e.next(ptr)) + { + if (ptr->is_our_path(p_path,ext)) return true; + } + return false; +} + + + +void input_open_file_helper(service_ptr_t & p_file,const char * p_path,t_input_open_reason p_reason,abort_callback & p_abort) +{ + if (p_file.is_empty()) { + switch(p_reason) { + default: + uBugCheck(); + case input_open_info_read: + case input_open_decode: + filesystem::g_open(p_file,p_path,filesystem::open_mode_read,p_abort); + break; + case input_open_info_write: + filesystem::g_open(p_file,p_path,filesystem::open_mode_write_existing,p_abort); + break; + } + } else { + p_file->reopen(p_abort); + } +} + +uint32_t input_entry::g_flags_for_path( const char * path, uint32_t mask ) { +#if FOOBAR2000_TARGET_VERSION >= 80 + return input_manager_v3::get()->flags_for_path(path, mask); +#else + input_manager_v3::ptr api; + if ( input_manager_v3::tryGet(api) ) { + return api->flags_for_path(path, mask); + } + uint32_t ret = 0; + service_enum_t e; input_entry::ptr p; + auto ext = pfc::string_extension(path); + while(e.next(p)) { + uint32_t f = p->get_flags() & mask; + if ( f != 0 && p->is_our_path( path, ext ) ) ret |= f;; + } + return ret; +#endif +} +uint32_t input_entry::g_flags_for_content_type( const char * ct, uint32_t mask ) { +#if FOOBAR2000_TARGET_VERSION >= 80 + return input_manager_v3::get()->flags_for_content_type(ct, mask); +#else + input_manager_v3::ptr api; + if ( input_manager_v3::tryGet(api) ) { + return api->flags_for_content_type( ct, mask ); + } + uint32_t ret = 0; + service_enum_t e; input_entry::ptr p; + while(e.next(p)) { + uint32_t f = p->get_flags() & mask; + if ( f != 0 && p->is_our_content_type(ct) ) ret |= f; + } + return ret; +#endif +} + +bool input_entry::g_are_parallel_reads_slow(const char * path) { + return g_flags_for_path(path, flag_parallel_reads_slow) != 0; +} + +void input_entry_v3::open_for_decoding(service_ptr_t & p_instance, service_ptr_t p_filehint, const char * p_path, abort_callback & p_abort) { + p_instance ^= open_v3( input_decoder::class_guid, p_filehint, p_path, nullptr, p_abort ); +} +void input_entry_v3::open_for_info_read(service_ptr_t & p_instance, service_ptr_t p_filehint, const char * p_path, abort_callback & p_abort) { + p_instance ^= open_v3(input_info_reader::class_guid, p_filehint, p_path, nullptr, p_abort); +} +void input_entry_v3::open_for_info_write(service_ptr_t & p_instance, service_ptr_t p_filehint, const char * p_path, abort_callback & p_abort) { + p_instance ^= open_v3(input_info_writer::class_guid, p_filehint, p_path, nullptr, p_abort); +} + +void input_info_writer::remove_tags_fallback(abort_callback & abort) { + uint32_t total = this->get_subsong_count(); + file_info_impl blank; + for( uint32_t walk = 0; walk < total; ++ walk ) { + this->set_info( this->get_subsong(walk), blank, abort ); + } + this->commit( abort ); +} diff --git a/foobar2000/SDK/input.h b/foobar2000/SDK/input.h new file mode 100644 index 0000000..5169d28 --- /dev/null +++ b/foobar2000/SDK/input.h @@ -0,0 +1,545 @@ +#pragma once +#include +#include "event_logger.h" +#include "audio_chunk.h" + +PFC_DECLARE_EXCEPTION(exception_tagging_unsupported, exception_io_data, "Tagging of this file format is not supported") + +enum { + input_flag_no_seeking = 1 << 0, + input_flag_no_looping = 1 << 1, + input_flag_playback = 1 << 2, + input_flag_testing_integrity = 1 << 3, + input_flag_allow_inaccurate_seeking = 1 << 4, + input_flag_no_postproc = 1 << 5, + + input_flag_simpledecode = input_flag_no_seeking|input_flag_no_looping, +}; + +//! Class providing interface for retrieval of information (metadata, duration, replaygain, other tech infos) from files. Also see: file_info. \n +//! Instantiating: see input_entry.\n +//! Implementing: see input_impl. + +class NOVTABLE input_info_reader : public service_base +{ +public: + //! Retrieves count of subsongs in the file. 1 for non-multisubsong-enabled inputs. + //! Note: multi-subsong handling is disabled for remote files (see: filesystem::is_remote) for performance reasons. Remote files are always assumed to be single-subsong, with null index. + virtual t_uint32 get_subsong_count() = 0; + + //! Retrieves identifier of specified subsong; this identifier is meant to be used in playable_location as well as a parameter for input_info_reader::get_info(). + //! @param p_index Index of subsong to query. Must be >=0 and < get_subsong_count(). + virtual t_uint32 get_subsong(t_uint32 p_index) = 0; + + //! Retrieves information about specified subsong. + //! @param p_subsong Identifier of the subsong to query. See: input_info_reader::get_subsong(), playable_location. + //! @param p_info file_info object to fill. Must be empty on entry. + //! @param p_abort abort_callback object signaling user aborting the operation. + virtual void get_info(t_uint32 p_subsong,file_info & p_info,abort_callback & p_abort) = 0; + + //! Retrieves file stats. Equivalent to calling get_stats() on file object. + virtual t_filestats get_file_stats(abort_callback & p_abort) = 0; + + FB2K_MAKE_SERVICE_INTERFACE(input_info_reader,service_base); +}; + +//! Class providing interface for retrieval of PCM audio data from files.\n +//! Instantiating: see input_entry.\n +//! Implementing: see input_impl. + +class NOVTABLE input_decoder : public input_info_reader +{ +public: + //! Prepares to decode specified subsong; resets playback position to the beginning of specified subsong. This must be called first, before any other input_decoder methods (other than those inherited from input_info_reader). \n + //! It is legal to set initialize() more than once, with same or different subsong, to play either the same subsong again or another subsong from same file without full reopen.\n + //! Warning: this interface inherits from input_info_reader, it is legal to call any input_info_reader methods even during decoding! Call order is not defined, other than initialize() requirement before calling other input_decoder methods.\n + //! @param p_subsong Subsong to decode. Should always be 0 for non-multi-subsong-enabled inputs. + //! @param p_flags Specifies additional hints for decoding process. It can be null, or a combination of one or more following constants: \n + //! input_flag_no_seeking - Indicates that seek() will never be called. Can be used to avoid building potentially expensive seektables when only sequential reading is needed.\n + //! input_flag_no_looping - Certain input implementations can be configured to utilize looping info from file formats they process and keep playing single file forever, or keep repeating it specified number of times. This flag indicates that such features should be disabled, for e.g. ReplayGain scan or conversion.\n + //! input_flag_playback - Indicates that decoding process will be used for realtime playback rather than conversion. This can be used to reconfigure features that are relevant only for conversion and take a lot of resources, such as very slow secure CDDA reading. \n + //! input_flag_testing_integrity - Indicates that we're testing integrity of the file. Any recoverable problems where decoding would normally continue should cause decoder to fail with exception_io_data. + //! @param p_abort abort_callback object signaling user aborting the operation. + virtual void initialize(t_uint32 p_subsong,unsigned p_flags,abort_callback & p_abort) = 0; + + //! Reads/decodes one chunk of audio data. Use false return value to signal end of file (no more data to return). Before calling run(), decoding must be initialized by initialize() call. + //! @param p_chunk audio_chunk object receiving decoded data. Contents are valid only the method returns true. + //! @param p_abort abort_callback object signaling user aborting the operation. + //! @returns true on success (new data decoded), false on EOF. + virtual bool run(audio_chunk & p_chunk,abort_callback & p_abort) = 0; + + //! Seeks to specified time offset. Before seeking or other decoding calls, decoding must be initialized with initialize() call. + //! @param p_seconds Time to seek to, in seconds. If p_seconds exceeds length of the object being decoded, succeed, and then return false from next run() call. + //! @param p_abort abort_callback object signaling user aborting the operation. + virtual void seek(double p_seconds,abort_callback & p_abort) = 0; + + //! Queries whether resource being read/decoded is seekable. If p_value is set to false, all seek() calls will fail. Before calling can_seek() or other decoding calls, decoding must be initialized with initialize() call. + virtual bool can_seek() = 0; + + //! This function is used to signal dynamic VBR bitrate, etc. Called after each run() (or not called at all if caller doesn't care about dynamic info). + //! @param p_out Initially contains currently displayed info (either last get_dynamic_info result or current cached info), use this object to return changed info. + //! @param p_timestamp_delta Indicates when returned info should be displayed (in seconds, relative to first sample of last decoded chunk), initially set to 0. + //! @returns false to keep old info, or true to indicate that changes have been made to p_info and those should be displayed. + virtual bool get_dynamic_info(file_info & p_out, double & p_timestamp_delta) = 0; + + //! This function is used to signal dynamic live stream song titles etc. Called after each run() (or not called at all if caller doesn't care about dynamic info). The difference between this and get_dynamic_info() is frequency and relevance of dynamic info changes - get_dynamic_info_track() returns new info only on track change in the stream, returning new titles etc. + //! @param p_out Initially contains currently displayed info (either last get_dynamic_info_track result or current cached info), use this object to return changed info. + //! @param p_timestamp_delta Indicates when returned info should be displayed (in seconds, relative to first sample of last decoded chunk), initially set to 0. + //! @returns false to keep old info, or true to indicate that changes have been made to p_info and those should be displayed. + virtual bool get_dynamic_info_track(file_info & p_out, double & p_timestamp_delta) = 0; + + //! Called from playback thread before sleeping. + //! @param p_abort abort_callback object signaling user aborting the operation. + virtual void on_idle(abort_callback & p_abort) = 0; + + + FB2K_MAKE_SERVICE_INTERFACE(input_decoder,input_info_reader); +}; + + +class NOVTABLE input_decoder_v2 : public input_decoder { + FB2K_MAKE_SERVICE_INTERFACE(input_decoder_v2, input_decoder) +public: + + //! OPTIONAL, throws pfc::exception_not_implemented() when not supported by this implementation. + //! Special version of run(). Returns an audio_chunk object as well as a raw data block containing original PCM stream. This is mainly used for MD5 checks on lossless formats. \n + //! If you set a "MD5" tech info entry in get_info(), you should make sure that run_raw() returns data stream that can be used to verify it. \n + //! Returned raw data should be possible to cut into individual samples; size in bytes should be divisible by audio_chunk's sample count for splitting in case partial output is needed (with cuesheets etc). + virtual bool run_raw(audio_chunk & out, mem_block_container & outRaw, abort_callback & abort) = 0; + + //! OBSOLETE since 1.5 \n + //! Specify logger when opening to reliably get info generated during input open operation. + virtual void set_logger(event_logger::ptr ptr) = 0; +}; + +class NOVTABLE input_decoder_v3 : public input_decoder_v2 { + FB2K_MAKE_SERVICE_INTERFACE(input_decoder_v3, input_decoder_v2); +public: + //! OBSOLETE, functionality implemented by core. + virtual void set_pause(bool paused) = 0; + //! OPTIONAL, should return false in most cases; return true to force playback buffer flush on unpause. Valid only after initialize() with input_flag_playback. + virtual bool flush_on_pause() = 0; +}; + +class NOVTABLE input_decoder_v4 : public input_decoder_v3 { + FB2K_MAKE_SERVICE_INTERFACE( input_decoder_v4, input_decoder_v3 ); +public: + //! OPTIONAL, return 0 if not implemented. \n + //! Provides means for communication of context specific data with the decoder. The decoder should do nothing and return 0 if it does not recognize the passed arguments. + virtual size_t extended_param( const GUID & type, size_t arg1, void * arg2, size_t arg2size) = 0; +}; + +//! Parameter GUIDs for input_decoder_v3::extended_param(). +class input_params { +public: + //! Signals whether unnecessary seeking should be avoided with this decoder for performance reasons. \n + //! Arguments disregarded, return value 1 or 0. + static const GUID seeking_expensive; + + //! Tells the decoder to output at this sample rate if the decoder's sample rate is adjustable. \n + //! Sample rate signaled in arg1. + static const GUID set_preferred_sample_rate; + + //! Retrieves logical decode position from the decoder. Implemented only in some rare cases where logical position does not match duration of returned data so far. + //! arg2 points to double position in seconds. + //! Return 1 if position was written to arg2, 0 if n/a. + static const GUID query_position; + + struct continue_stream_t { + file::ptr reader; + const char * path; + }; + //! Tells the decoder to continue decoding from another URL, without flushing etc. Mainly used by HLS streams. + //! arg2: continue_stream_t + //! Return 1 to acknowledge, 0 if unsupported. + //! A call to decode_initialize() will follow if you return 1; perform actual file open from there. + static const GUID continue_stream; +}; + +//! Class providing interface for writing metadata and replaygain info to files. Also see: file_info. \n +//! Instantiating: see input_entry.\n +//! Implementing: see input_impl. +class NOVTABLE input_info_writer : public input_info_reader +{ +public: + //! Tells the service to update file tags with new info for specified subsong. + //! @param p_subsong Subsong to update. Should be always 0 for non-multisubsong-enabled inputs. + //! @param p_info New info to write. Sometimes not all contents of p_info can be written. Caller will typically read info back after successful write, so e.g. tech infos that change with retag are properly maintained. + //! @param p_abort abort_callback object signaling user aborting the operation. WARNING: abort_callback object is provided for consistency; if writing tags actually gets aborted, user will be likely left with corrupted file. Anything calling this should make sure that aborting is either impossible, or gives appropriate warning to the user first. + virtual void set_info(t_uint32 p_subsong,const file_info & p_info,abort_callback & p_abort) = 0; + + //! Commits pending updates. In case of multisubsong inputs, set_info should queue the update and perform actual file access in commit(). Otherwise, actual writing can be done in set_info() and then commit() can just do nothing and always succeed. + //! @param p_abort abort_callback object signaling user aborting the operation. WARNING: abort_callback object is provided for consistency; if writing tags actually gets aborted, user will be likely left with corrupted file. Anything calling this should make sure that aborting is either impossible, or gives appropriate warning to the user first. + virtual void commit(abort_callback & p_abort) = 0; + + //! Helper for writers not implementing input_info_writer_v2::remove_tags(). + void remove_tags_fallback(abort_callback & abort); + + FB2K_MAKE_SERVICE_INTERFACE(input_info_writer,input_info_reader); +}; + +//! Extended input_info_writer. Not every input implements it. \n +//! Provides an explicit remove_tags(), which erases all supported tags from the file. +class NOVTABLE input_info_writer_v2 : public input_info_writer { +public: + //! Removes all tags from this file. Cancels any set_info() requests on this object. Does not require a commit() afterwards. + //! If no input_info_writer_v2 is provided, similar affect can be achieved by set_info()+commit() with blank file_info, but may not be as thorough; will typically result in blank tags rather than total removal fo tags. + virtual void remove_tags(abort_callback & abort) = 0; + + FB2K_MAKE_SERVICE_INTERFACE(input_info_writer_v2, input_info_writer); +}; + +class NOVTABLE input_entry : public service_base { + FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(input_entry); +public: + //! Determines whether specified content type can be handled by this input. + //! @param p_type Content type string to test. + virtual bool is_our_content_type(const char * p_type)=0; + + //! Determines whether specified file type can be handled by this input. This must not use any kind of file access; the result should be only based on file path / extension. + //! @param p_full_path Full URL of file being tested. + //! @param p_extension Extension of file being tested, provided by caller for performance reasons. + virtual bool is_our_path(const char * p_full_path,const char * p_extension)=0; + + //! Opens specified resource for decoding. + //! @param p_instance Receives new input_decoder instance if successful. + //! @param p_filehint Optional; passes file object to use for the operation; if set to null, the service will handle opening file by itself. Note that not all inputs operate on physical files that can be reached through filesystem API, some of them require this parameter to be set to null (tone and silence generators for an example). + //! @param p_path URL of resource being opened. + //! @param p_abort abort_callback object signaling user aborting the operation. + virtual void open_for_decoding(service_ptr_t & p_instance,service_ptr_t p_filehint,const char * p_path,abort_callback & p_abort) = 0; + + //! Opens specified file for reading info. + //! @param p_instance Receives new input_info_reader instance if successful. + //! @param p_filehint Optional; passes file object to use for the operation; if set to null, the service will handle opening file by itself. Note that not all inputs operate on physical files that can be reached through filesystem API, some of them require this parameter to be set to null (tone and silence generators for an example). + //! @param p_path URL of resource being opened. + //! @param p_abort abort_callback object signaling user aborting the operation. + virtual void open_for_info_read(service_ptr_t & p_instance,service_ptr_t p_filehint,const char * p_path,abort_callback & p_abort) = 0; + + //! Opens specified file for writing info. + //! @param p_instance Receives new input_info_writer instance if successful. + //! @param p_filehint Optional; passes file object to use for the operation; if set to null, the service will handle opening file by itself. Note that not all inputs operate on physical files that can be reached through filesystem API, some of them require this parameter to be set to null (tone and silence generators for an example). + //! @param p_path URL of resource being opened. + //! @param p_abort abort_callback object signaling user aborting the operation. + virtual void open_for_info_write(service_ptr_t & p_instance,service_ptr_t p_filehint,const char * p_path,abort_callback & p_abort) = 0; + + //! Reserved for future use. Do nothing and return until specifications are finalized. + virtual void get_extended_data(service_ptr_t p_filehint,const playable_location & p_location,const GUID & p_guid,mem_block_container & p_out,abort_callback & p_abort) = 0; + + enum { + //! Indicates that this service implements some kind of redirector that opens another input for decoding, used to avoid circular call possibility. + flag_redirect = 1, + //! Indicates that multi-CPU optimizations should not be used. + flag_parallel_reads_slow = 2, + }; + //! See flag_* enums. + virtual unsigned get_flags() = 0; + + inline bool is_redirect() {return (get_flags() & flag_redirect) != 0;} + inline bool are_parallel_reads_slow() {return (get_flags() & flag_parallel_reads_slow) != 0;} + + static bool g_find_service_by_path(service_ptr_t & p_out,const char * p_path); + static bool g_find_service_by_path(service_ptr_t & p_out,const char * p_path, const char * p_ext); + static bool g_find_service_by_content_type(service_ptr_t & p_out,const char * p_content_type); + static void g_open_for_decoding(service_ptr_t & p_instance,service_ptr_t p_filehint,const char * p_path,abort_callback & p_abort,bool p_from_redirect = false); + static void g_open_for_info_read(service_ptr_t & p_instance,service_ptr_t p_filehint,const char * p_path,abort_callback & p_abort,bool p_from_redirect = false); + static void g_open_for_info_write(service_ptr_t & p_instance,service_ptr_t p_filehint,const char * p_path,abort_callback & p_abort,bool p_from_redirect = false); + static void g_open_for_info_write_timeout(service_ptr_t & p_instance,service_ptr_t p_filehint,const char * p_path,abort_callback & p_abort,double p_timeout,bool p_from_redirect = false); + static bool g_is_supported_path(const char * p_path); + typedef std::function input_filter_t; + static bool g_find_inputs_by_content_type(pfc::list_base_t > & p_out, const char * p_content_type, bool p_from_redirect); + static bool g_find_inputs_by_path(pfc::list_base_t > & p_out, const char * p_path, bool p_from_redirect ); + static bool g_find_inputs_by_content_type_ex(pfc::list_base_t > & p_out, const char * p_content_type, input_filter_t filter ); + static bool g_find_inputs_by_path_ex(pfc::list_base_t > & p_out, const char * p_path, input_filter_t filter ); + static service_ptr g_open(const GUID & whatFor, file::ptr hint, const char * path, event_logger::ptr logger, abort_callback & aborter, bool fromRedirect = false); + + void open(service_ptr_t & p_instance,service_ptr_t const & p_filehint,const char * p_path,abort_callback & p_abort) {open_for_decoding(p_instance,p_filehint,p_path,p_abort);} + void open(service_ptr_t & p_instance,service_ptr_t const & p_filehint,const char * p_path,abort_callback & p_abort) {open_for_info_read(p_instance,p_filehint,p_path,p_abort);} + void open(service_ptr_t & p_instance,service_ptr_t const & p_filehint,const char * p_path,abort_callback & p_abort) {open_for_info_write(p_instance,p_filehint,p_path,p_abort);} + service_ptr open(const GUID & whatFor, file::ptr hint, const char * path, event_logger::ptr logger, abort_callback & aborter); + + typedef pfc::list_base_const_t< input_entry::ptr > input_entry_list_t; + + static service_ptr g_open_from_list(input_entry_list_t const & list, const GUID & whatFor, file::ptr hint, const char * path, event_logger::ptr logger, abort_callback & aborter, GUID * outGUID = nullptr); + static bool g_are_parallel_reads_slow( const char * path ); + + static uint32_t g_flags_for_path( const char * pathFor, uint32_t mask = UINT32_MAX ); + static uint32_t g_flags_for_content_type( const char * ct, uint32_t mask = UINT32_MAX ); +}; + +//! \since 1.4 +//! Extended input_entry methods provided by decoders. \n +//! Can be implemented by 1.3-compatible components but will not be called in fb2k versions prior to 1.4. +class input_entry_v2 : public input_entry { + FB2K_MAKE_SERVICE_INTERFACE(input_entry_v2, input_entry); +public: + //! @returns GUID used to identify us among other decoders in the decoder priority table. + virtual GUID get_guid() = 0; + //! @returns Name to present to the user in the decoder priority table. + virtual const char * get_name() = 0; + //! @returns GUID of this decoder's preferences page (optional), null guid if there's no page to present + virtual GUID get_preferences_guid() = 0; + //! @returns true if the decoder should be put at the end of the list when it's first sighted, false otherwise (will be put at the beginning of the list). + virtual bool is_low_merit() = 0; +}; + +//! \since 1.5 +class input_entry_v3 : public input_entry_v2 { + FB2K_MAKE_SERVICE_INTERFACE(input_entry_v3, input_entry_v2); +public: + //! New unified open() function for all supported interfaces. Supports any future interfaces via alternate GUIDs, as well as allows the event logger to be set prior to the open() call. + //! @param whatFor The class GUID of the service we want. \n + //! Currently allowed are: input_decoder::class_guid, input_info_reader::class_guid, input_info_writer::class_guid. \n + //! This method must throw pfc::exception_not_implemented for any GUIDs it does not recognize. + virtual service_ptr open_v3( const GUID & whatFor, file::ptr hint, const char * path, event_logger::ptr logger, abort_callback & aborter ) = 0; + + + void open_for_decoding(service_ptr_t & p_instance, service_ptr_t p_filehint, const char * p_path, abort_callback & p_abort) ; + void open_for_info_read(service_ptr_t & p_instance, service_ptr_t p_filehint, const char * p_path, abort_callback & p_abort); + void open_for_info_write(service_ptr_t & p_instance, service_ptr_t p_filehint, const char * p_path, abort_callback & p_abort); +}; + +#ifdef FOOBAR2000_DESKTOP +//! \since 1.4 +//! Core API to perform input open operations respecting user settings for decoder priority. \n +//! Unavailable prior to 1.4. +class input_manager : public service_base { + FB2K_MAKE_SERVICE_COREAPI(input_manager); +public: + virtual service_ptr open(const GUID & whatFor, file::ptr hint, const char * path, bool fromRedirect, abort_callback & aborter, GUID * outUsedEntry = nullptr) = 0; + + //! input_manager_v2 wrapper. + service_ptr open_v2(const GUID & whatFor, file::ptr hint, const char * path, bool fromRedirect, event_logger::ptr logger, abort_callback & aborter, GUID * outUsedEntry = nullptr); +}; + +//! \since 1.5 +//! Extension of input_manager. \n +//! Extended open_v2() supports album_art_extractor and album_art_editor. It reliably throws pfc::exception_not_implemented() for unsupported GUIDs (old version would bugcheck). \n +//! It also allows event_logger to be specified in advance so open() implementation can already use it. +class input_manager_v2 : public input_manager { + FB2K_MAKE_SERVICE_COREAPI_EXTENSION(input_manager_v2, input_manager) +public: + virtual service_ptr open_v2(const GUID & whatFor, file::ptr hint, const char * path, bool fromRedirect, event_logger::ptr logger, abort_callback & aborter, GUID * outUsedEntry = nullptr) = 0; +}; + +//! \since 1.5 +class input_manager_v3 : public input_manager_v2 { + FB2K_MAKE_SERVICE_COREAPI_EXTENSION(input_manager_v3, input_manager_v2); +public: + //! Retrieves list of enabled inputs, in user-specified order. \n + //! This is rarely needed. If you need this function, consider redesigning your code to call input_manager open methods instead. + virtual void get_enabled_inputs( pfc::list_base_t & out ) = 0; + //! Returns input_entry get_flags() values for this path, as returned by enabled inputs. + virtual uint32_t flags_for_path( const char * pathFor, uint32_t mask = UINT32_MAX ) = 0; + //! Returns input_entry get_flags() values for this content type, as returned by enabled inputs. + virtual uint32_t flags_for_content_type( const char * ct, uint32_t mask = UINT32_MAX ) = 0; + + + enum { + flagFromRedirect = 1 << 0, + flagSuppressFilters = 1 << 1, + }; + + virtual service_ptr open_v3(const GUID & whatFor, file::ptr hint, const char * path, uint32_t flags, event_logger::ptr logger, abort_callback & aborter, GUID * outUsedEntry = nullptr) = 0; +}; + +//! \since 1.4 +//! Core API for determining which audio stream to decode, in a multi-stream enabled input. \n +//! Unavailable prior to 1.4 - decode the default stream if input_stream_selector isn't present. \n +//! In foobar2000 v1.4 and up, this API allows decoders to determine which stream the user opted to decode for a specific file. \n +//! Use input_stream_selector::tryGet() to safely instantiate. +class input_stream_selector : public service_base { + FB2K_MAKE_SERVICE_COREAPI(input_stream_selector); +public: + //! Returns index of stream that should be presented for this file. \n + //! If not set by user, 0xFFFFFFFF will be returned and the default stream should be presented. \n + //! @param guid GUID of the input asking for the stream. + virtual uint32_t select_stream( const GUID & guid, const char * path ) = 0; +}; + +//! \since 1.4 +//! Interface provided by multi-stream enabled inputs to let the stream picker dialog show available streams. \n +//! Can be implemented by 1.3-compatible components but will not be called in fb2k versions prior to 1.4. +class input_stream_info_reader : public service_base { + FB2K_MAKE_SERVICE_INTERFACE(input_stream_info_reader, service_base); +public: + //! @returns Number of audio streams found. + virtual uint32_t get_stream_count() = 0; + //! Retrieves information about the specified stream; most importantly the codec name and bitrate. + virtual void get_stream_info(uint32_t index, file_info & out, abort_callback & aborter) = 0; + //! @returns Index of default stream to decode if there is no user preference. + virtual uint32_t get_default_stream() = 0; +}; + +//! \since 1.4 +//! Entrypoint interface for spawning input_stream_info_reader. \n +//! Can be implemented by 1.3-compatible components but will not be called in fb2k versions prior to 1.4. +class input_stream_info_reader_entry : public service_base { + FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(input_stream_info_reader_entry); +public: + //! Open file for reading stream infos. + virtual input_stream_info_reader::ptr open( const char * path, file::ptr fileHint, abort_callback & abort ) = 0; + + //! Return GUID of the matching input_entry. + virtual GUID get_guid() = 0; + +}; + +//! \since 1.4 +//! Callback for input_stream_manipulator \n +//! Used for applying ReplayGain to encoded audio streams. +class input_stream_manipulator_callback : public service_base { + FB2K_MAKE_SERVICE_INTERFACE(input_stream_manipulator_callback, service_base); +public: + //! Called first before other methods. Throw an exception if the file cannot be processed. \n + //! The arguments are the same as packet_decoder open() arguments. + virtual void set_decode_info(const GUID & p_owner, t_size p_param1, const void * p_param2, t_size p_param2size ) = 0; + + virtual void first_frame( const void * data, size_t bytes ) = 0; + //! Called with progress value, in 0..1 range. + virtual void on_progress( float progress ) = 0; + //! @returns true if the frame has been altered and should be written back, false otherwise. + virtual bool process_frame( void * data, size_t size ) = 0; +}; + +//! \since 1.4 +//! Manipulate audio stream payload in files. \n +//! Used for applying ReplayGain to encoded audio streams. +class input_stream_manipulator : public service_base { + FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(input_stream_manipulator); +public: + enum op_t { + //! Probe the file for codec information; calls set_decode_info() + first_frame() only. + op_probe = 0, + //! Read the entire stream - same as op_probe but then calls on_progress() + process_frame() with the entire file payload. \n + //! No writing to the file is performed - process_frame() results are disregarded. + op_read, + //! Rewrite the stream. Similar to op_read, but frames altered by process_frame() are written back to the file. + op_rewrite + }; + //! @param path Path of file to process. + //! @param fileHint optional file object, must be opened for read+write if bWalk is true. + //! @param callback Callback object for this operation. + //! @param opType Operation to perform, see op_t enum for details. + //! @param abort abort_callback object for this operating. Aborting with bWalk set to true will leave the file partially altered, use with caution! + virtual void process( const char * path, file::ptr fileHint, input_stream_manipulator_callback::ptr callback, op_t opType, abort_callback & abort ) = 0; + //! Return GUID of the matching input_entry. + virtual GUID get_guid() = 0; +}; + +//! \since 1.5 +//! An input_info_filter lets you hook into all performed tag read & write operations. \n +//! Your tag manipulations will be transparent to all fb2k components, as if the tags were read/written by relevant inputs. \n +//! Your input_info_filter needs to be enabled in Preferences in order to become active. Newly added ones are inactive by default. +class input_info_filter : public service_base { + FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT( input_info_filter ); +public: + //! Tags are being read from a file. + virtual void filter_info_read( const playable_location & loc,file_info & info,abort_callback & abort ) = 0; + //! Tags are being written to a file. \n + //! Return true to continue, false to suppress writing of tags. + virtual bool filter_info_write( const playable_location & loc, file_info & info, abort_callback & abort ) = 0; + //! Tags are being removed from a file. + virtual void on_info_remove( const char * path, abort_callback & abort ) = 0; + //! Return GUID of your filter. + virtual GUID get_guid() = 0; + //! Return preferences page or advconfig branch GUID of your filter. + virtual GUID get_preferences_guid() = 0; + //! Return user-friendly name of your filter to be shown in preferences. + virtual const char * get_name() = 0; + //! Optional backwards compatibility method. \n + //! If you also provide input services for old foobar2000 versions which don't recognize input_info_filter, report their GUIDs here so they can be ignored. \n + //! @param outGUIDs empty on entry, contains GUIDs of ignored inputs (if any) on return. + virtual void get_suppressed_inputs( pfc::list_base_t & outGUIDs ) {outGUIDs.remove_all();} + //! write_fallback() supported or not? \n + //! Used if your filter can store tags for untaggable files. + virtual bool supports_fallback() = 0; + //! Optional; called when user attempted to tag an untaggable/readonly file. \n + //! Used if your filter can store tags for untaggable files. + virtual bool write_fallback( const playable_location & loc, file_info const & info, abort_callback & abort ) = 0; + //! Optional; called when user attempted to remove tags from an untaggable/readonly file.\ n + //! Used if your filter can store tags for untaggable files. + virtual void remove_tags_fallback( const char * path, abort_callback & abort ) = 0; +}; + +//! \since 1.5 +class input_stream_info_filter : public service_base { + FB2K_MAKE_SERVICE_INTERFACE( input_stream_info_filter, service_base ); +public: + virtual void filter_dynamic_info( file_info & info ) = 0; + virtual void filter_dynamic_info_track( file_info & info ) = 0; +}; + +class album_art_data; + +//! \since 1.5 +//! Extended input_info_filter. +class input_info_filter_v2 : public input_info_filter { + FB2K_MAKE_SERVICE_INTERFACE( input_info_filter_v2, input_info_filter ); +public: + //! Creates an object which then can work with dynamic track titles etc of a decoded track. \n + //! Returning null to filter the info is allowed. + virtual input_stream_info_filter::ptr open_stream(playable_location const & loc, abort_callback & abort) = 0; + + + typedef service_ptr_t aaptr_t; + + //! Album art is being read from the file. \n + //! info may be null if file had no such picture. \n + //! Return passed info, altered info or null. + virtual aaptr_t filter_album_art_read( const char * path, const GUID & type, aaptr_t info, abort_callback & aborter ) = 0; + //! Album art is being written to the file. \n + //! Return passed info, altered info or null to suppress writing. + virtual aaptr_t filter_album_art_write( const char * path, const GUID & type, aaptr_t info, abort_callback & aborter ) = 0; + //! Specific album art is being removed from the file. \n + //! Return true to go on, false to suppress file update. + virtual bool filter_album_art_remove( const char * path, const GUID & type, abort_callback & aborter ) = 0; + //! All album art is being removed from the file. \n + //! Return true to go on, false to suppress file update. + virtual bool filter_album_art_remove_all( const char * path, abort_callback & aborter ) = 0; + + //! Valid with supports_fallback() = true \n + //! Album art is being written to an untaggable file. + virtual void write_album_art_fallback( const char * path, const GUID & type, aaptr_t info, abort_callback & aborter ) = 0; + //! Valid with supports_fallback() = true \n + //! Specific album art is being removed from an untaggable file. + virtual void remove_album_art_fallback( const char * path, const GUID & type, abort_callback & aborter ) = 0; + //! Valid with supports_fallback() = true \n + //! All album art is being removed from an untaggable file. + virtual void remove_all_album_art_fallback( const char * path, abort_callback & aborter ) = 0; +}; + +class dsp_preset; + +//! \since 1.5 +//! An input_playback_shim adds additional functionality to a DSP, allowing full control of the decoder. \n +//! Currently, input_playback_shim can only exist alongside a DSP, must have the same GUID as a DSP. \n +//! It will only be used in supported scenarios when the user has put your DSP in the chain. \n +//! Your DSP will be deactivated in such case when your input_playback_shim is active. \n +//! input_playback_shim is specifically intended to be instantiated for playback. Do not call this service from your component. \n/ +//! Implement this service ONLY IF NECESSARY. Very few tasks really need it, primarily DSPs that manipulate logical playback time & seeking. +class input_playback_shim : public service_base { + FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT( input_playback_shim ); +public: + //! Same GUID as your DSP. + virtual GUID get_guid() = 0; + //! Preferences page / advconfig branch GUID of your shim, pfc::guid_null if none. \n + //! This is currently unused / reserved for future use. + virtual GUID get_preferences_guid() = 0; + //! Same as your DSP. \n + //! This is currently unused / reserved for future use. + virtual const char * get_name() = 0; + //! Instantiates your shim on top of existing input_decoder. \n + //! If you don't want to do anything with this specific decoder, just return the passed decoder. + virtual input_decoder::ptr shim( input_decoder::ptr dec, const char * path, dsp_preset const & preset, abort_callback & aborter ) = 0; + //! Optional backwards compatibility method. \n + //! If you also provide input services for old versions of foobar2000 which don't recognize input_playback_shim, report their GUIDs here so they can be ignored. \n + //! @param outGUIDs empty on entry, contains GUIDs of ignored inputs (if any) on return. + virtual void get_suppressed_inputs( pfc::list_base_t & outGUIDs ) {outGUIDs.remove_all();} +}; + +#endif // #ifdef FOOBAR2000_DESKTOP + + +typedef input_info_writer_v2 input_info_writer_vhighest; +typedef input_decoder_v4 input_decoder_vhighest; +typedef input_info_reader input_info_reader_vhighest; diff --git a/foobar2000/SDK/input_file_type.cpp b/foobar2000/SDK/input_file_type.cpp new file mode 100644 index 0000000..1e7dda0 --- /dev/null +++ b/foobar2000/SDK/input_file_type.cpp @@ -0,0 +1,130 @@ +#include "foobar2000.h" + +#if FOOBAR2000_TARGET_VERSION >= 76 + +typedef pfc::avltree_t t_fnList; + +static void formatMaskList(pfc::string_base & out, t_fnList const & in) { + auto walk = in.cfirst(); + if (walk.is_valid()) { + out << *walk; ++walk; + while(walk.is_valid()) { + out << ";" << *walk; ++walk; + } + } +} +static void formatMaskList(pfc::string_base & out, t_fnList const & in, const char * label) { + if (in.get_count() > 0) { + out << label << "|"; + formatMaskList(out,in); + out << "|"; + } +} + +void input_file_type::make_filetype_support_fingerprint(pfc::string_base & str) { + pfc::string_formatter out; + pfc::avltree_t names; + + { + componentversion::ptr ptr; service_enum_t e; + pfc::string_formatter name; + while(e.next(ptr)) { + name = ""; + ptr->get_component_name(name); + if (strstr(name, "decoder") != NULL || strstr(name, "Decoder") != NULL) names += name; + } + } + + + make_extension_support_fingerprint(out); + for(auto walk = names.cfirst(); walk.is_valid(); ++walk) { + if (!out.is_empty()) str << "|"; + out << *walk; + } + str = out; +} +void input_file_type::make_extension_support_fingerprint(pfc::string_base & str) { + pfc::avltree_t masks; + { + service_enum_t e; + service_ptr_t ptr; + pfc::string_formatter mask; + while(e.next(ptr)) { + const unsigned count = ptr->get_count(); + for(unsigned n=0;nget_mask(n,mask)) { + if (strchr(mask,'|') == NULL) masks += mask; + } + } + } + } + pfc::string_formatter out; + for(auto walk = masks.cfirst(); walk.is_valid(); ++walk) { + if (!out.is_empty()) out << "|"; + out << *walk; + } + str = out; +} + +void input_file_type::build_openfile_mask(pfc::string_base & out, bool b_include_playlists, bool b_include_archives) +{ + t_fnList extensionsAll, extensionsPl, extensionsArc; + + if (b_include_playlists) { + service_enum_t e; service_ptr_t ptr; + while(e.next(ptr)) { + if (ptr->is_associatable()) { + pfc::string_formatter temp; temp << "*." << ptr->get_extension(); + extensionsPl += temp; + extensionsAll += temp; + } + } + } + if (b_include_archives) { + service_enum_t e; + archive_v3::ptr p; + pfc::string_formatter temp; + while (e.next(p)) { + p->list_extensions(temp); + pfc::chain_list_v2_t lst; + pfc::splitStringByChar(lst, temp, ','); + for (auto iter = lst.first(); iter.is_valid(); ++iter) { + extensionsArc += PFC_string_formatter() << "*." << *iter; + } + } + } + + typedef pfc::map_t t_masks; + t_masks masks; + { + service_enum_t e; + service_ptr_t ptr; + pfc::string_formatter name, mask; + while(e.next(ptr)) { + const unsigned count = ptr->get_count(); + for(unsigned n=0;nget_name(n,name) && ptr->get_mask(n,mask)) { + if (!strchr(name,'|') && !strchr(mask,'|')) { + masks.find_or_add(name) += mask; + extensionsAll += mask; + } + } + } + } + } + pfc::string_formatter outBuf; + outBuf << "All files|*.*|"; + formatMaskList(outBuf, extensionsAll, "All supported media types"); + formatMaskList(outBuf, extensionsPl, "Playlists"); + formatMaskList(outBuf, extensionsArc, "Archives"); + + + for(auto walk = masks.cfirst(); walk.is_valid(); ++walk) { + formatMaskList(outBuf,walk->m_value,walk->m_key); + } + out = outBuf; +} +#endif \ No newline at end of file diff --git a/foobar2000/SDK/input_file_type.h b/foobar2000/SDK/input_file_type.h new file mode 100644 index 0000000..32664f8 --- /dev/null +++ b/foobar2000/SDK/input_file_type.h @@ -0,0 +1,109 @@ +//! Entrypoint interface for registering media file types that can be opened through "open file" dialogs or associated with foobar2000 application in Windows shell. \n +//! Instead of implementing this directly, use DECLARE_FILE_TYPE() / DECLARE_FILE_TYPE_EX() macros. +class input_file_type : public service_base { +public: + virtual unsigned get_count()=0; + virtual bool get_name(unsigned idx,pfc::string_base & out)=0;//eg. "MPEG files" + virtual bool get_mask(unsigned idx,pfc::string_base & out)=0;//eg. "*.MP3;*.MP2"; separate with semicolons + virtual bool is_associatable(unsigned idx) = 0; + +#if FOOBAR2000_TARGET_VERSION >= 76 + static void build_openfile_mask(pfc::string_base & out,bool b_include_playlists=true, bool b_include_archives = false); + static void make_extension_support_fingerprint(pfc::string_base & str); + static void make_filetype_support_fingerprint(pfc::string_base & str); +#endif + + FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(input_file_type); +}; + +//! Extended interface for registering media file types that can be associated with foobar2000 application in Windows shell. \n +//! Instead of implementing this directly, use DECLARE_FILE_TYPE() / DECLARE_FILE_TYPE_EX() macros. +class input_file_type_v2 : public input_file_type { +public: + virtual void get_format_name(unsigned idx, pfc::string_base & out, bool isPlural) = 0; + virtual void get_extensions(unsigned idx, pfc::string_base & out) = 0; + + //Deprecated input_file_type method implementations: + bool get_name(unsigned idx, pfc::string_base & out) {get_format_name(idx, out, true); return true;} + bool get_mask(unsigned idx, pfc::string_base & out) { + pfc::string_formatter temp; get_extensions(idx,temp); + pfc::chain_list_v2_t exts; pfc::splitStringSimple_toList(exts,";",temp); + if (exts.get_count() == 0) return false;//should not happen + temp.reset(); + for(auto walk = exts.cfirst(); walk.is_valid(); ++walk) { + if (!temp.is_empty()) temp << ";"; + temp << "*." << walk->get_ptr(); + } + out = temp; + return true; + } + + FB2K_MAKE_SERVICE_INTERFACE(input_file_type_v2,input_file_type) +}; + + +//! Implementation helper. +class input_file_type_impl : public service_impl_single_t +{ + const char * name, * mask; + bool m_associatable; +public: + input_file_type_impl(const char * p_name, const char * p_mask,bool p_associatable) : name(p_name), mask(p_mask), m_associatable(p_associatable) {} + unsigned get_count() {return 1;} + bool get_name(unsigned idx,pfc::string_base & out) {if (idx==0) {out = name; return true;} else return false;} + bool get_mask(unsigned idx,pfc::string_base & out) {if (idx==0) {out = mask; return true;} else return false;} + bool is_associatable(unsigned idx) {return m_associatable;} +}; + + +//! Helper macro for registering our media file types. +//! Usage: DECLARE_FILE_TYPE("Blah files","*.blah;*.bleh"); +#define DECLARE_FILE_TYPE(NAME,MASK) \ + namespace { static input_file_type_impl g_filetype_instance(NAME,MASK,true); \ + static service_factory_single_ref_t g_filetype_service(g_filetype_instance); } + + + + +//! Implementation helper. +//! Usage: static input_file_type_factory mytype("blah type","*.bla;*.meh",true); +class input_file_type_factory : private service_factory_single_transparent_t +{ +public: + input_file_type_factory(const char * p_name,const char * p_mask,bool p_associatable) + : service_factory_single_transparent_t(p_name,p_mask,p_associatable) {} +}; + + + +class input_file_type_v2_impl : public input_file_type_v2 { +public: + input_file_type_v2_impl(const char * extensions,const char * name, const char * namePlural) : m_extensions(extensions), m_name(name), m_namePlural(namePlural) {} + unsigned get_count() {return 1;} + bool is_associatable(unsigned idx) {return true;} + void get_format_name(unsigned idx, pfc::string_base & out, bool isPlural) { + out = isPlural ? m_namePlural : m_name; + } + void get_extensions(unsigned idx, pfc::string_base & out) { + out = m_extensions; + } + +private: + const pfc::string8 m_name, m_namePlural, m_extensions; +}; + +//! Helper macro for registering our media file types, extended version providing separate singular/plural type names. +//! Usage: DECLARE_FILE_TYPE_EX("mp1;mp2;mp3","MPEG file","MPEG files") +#define DECLARE_FILE_TYPE_EX(extensions, name, namePlural) \ + namespace { static service_factory_single_t g_myfiletype(extensions, name, namePlural); } + + +//! Service for registering protocol types that can be associated with foobar2000. +class input_protocol_type : public service_base { + FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(input_protocol_type) +public: + //! Returns the name of the protocol, such as "ftp" or "http". + virtual void get_protocol_name(pfc::string_base & out) = 0; + //! Returns a human-readable description of the protocol. + virtual void get_description(pfc::string_base & out) = 0; +}; diff --git a/foobar2000/SDK/input_impl.h b/foobar2000/SDK/input_impl.h new file mode 100644 index 0000000..44261cc --- /dev/null +++ b/foobar2000/SDK/input_impl.h @@ -0,0 +1,450 @@ +#pragma once + +#include "input.h" +#include "mem_block_container.h" + +enum t_input_open_reason { + input_open_info_read, + input_open_decode, + input_open_info_write +}; + +//! Helper function for input implementation use; ensures that file is open with relevant access mode. This is typically called from input_impl::open() and such. +//! @param p_file File object pointer to process. If passed pointer is non-null, the function does nothing and always succeeds; otherwise it attempts to open the file using filesystem API methods. +//! @param p_path Path to the file. +//! @param p_reason Type of input operation requested. See: input_impl::open() parameters. +//! @param p_abort abort_callback object signaling user aborting the operation. +void input_open_file_helper(service_ptr_t & p_file,const char * p_path,t_input_open_reason p_reason,abort_callback & p_abort); + + +//! This is a class that just declares prototypes of functions that each input needs to implement. See input_decoder / input_info_reader / input_info_writer interfaces for full descriptions of member functions. Since input implementation class is instantiated using a template, you don't need to derive from input_impl as virtual functions are not used on implementation class level. Use input_factory_t template to register input class based on input_impl. +class input_impl +{ +public: + //! Opens specified file for info read / decoding / info write. This is called only once, immediately after object creation, before any other methods, and no other methods are called if open() fails. + //! @param p_filehint Optional; passes file object to use for the operation; if set to null, the implementation should handle opening file by itself. Note that not all inputs operate on physical files that can be reached through filesystem API, some of them require this parameter to be set to null (tone and silence generators for an example). Typically, an input implementation that requires file access calls input_open_file_helper() function to ensure that file is open with relevant access mode (read or read/write). + //! @param p_path URL of resource being opened. + //! @param p_reason Type of operation requested. Possible values are: \n + //! - input_open_info_read - info retrieval methods are valid; \n + //! - input_open_decode - info retrieval and decoding methods are valid; \n + //! - input_open_info_write - info retrieval and retagging methods are valid; \n + //! Note that info retrieval methods are valid in all cases, and they may be called at any point of decoding/retagging process. Results of info retrieval methods (other than get_subsong_count() / get_subsong()) between retag_set_info() and retag_commit() are undefined however; those should not be called during that period. + //! @param p_abort abort_callback object signaling user aborting the operation. + void open(service_ptr_t p_filehint,const char * p_path,t_input_open_reason p_reason,abort_callback & p_abort); + + //! See: input_info_reader::get_subsong_count(). Valid after open() with any reason. + unsigned get_subsong_count(); + //! See: input_info_reader::get_subsong(). Valid after open() with any reason. + t_uint32 get_subsong(unsigned p_index); + //! See: input_info_reader::get_info(). Valid after open() with any reason. + void get_info(t_uint32 p_subsong,file_info & p_info,abort_callback & p_abort); + //! See: input_info_reader::get_file_stats(). Valid after open() with any reason. + t_filestats get_file_stats(abort_callback & p_abort); + + //! See: input_decoder::initialize(). Valid after open() with input_open_decode reason. + void decode_initialize(t_uint32 p_subsong,unsigned p_flags,abort_callback & p_abort); + //! See: input_decoder::run(). Valid after decode_initialize(). + bool decode_run(audio_chunk & p_chunk,abort_callback & p_abort); + //! See: input_decoder::seek(). Valid after decode_initialize(). + void decode_seek(double p_seconds,abort_callback & p_abort); + //! See: input_decoder::can_seek(). Valid after decode_initialize(). + bool decode_can_seek(); + //! See: input_decoder::get_dynamic_info(). Valid after decode_initialize(). + bool decode_get_dynamic_info(file_info & p_out, double & p_timestamp_delta); + //! See: input_decoder::get_dynamic_info_track(). Valid after decode_initialize(). + bool decode_get_dynamic_info_track(file_info & p_out, double & p_timestamp_delta); + //! See: input_decoder::on_idle(). Valid after decode_initialize(). + void decode_on_idle(abort_callback & p_abort); + + //! See: input_info_writer::set_info(). Valid after open() with input_open_info_write reason. + void retag_set_info(t_uint32 p_subsong,const file_info & p_info,abort_callback & p_abort); + //! See: input_info_writer::commit(). Valid after open() with input_open_info_write reason. + void retag_commit(abort_callback & p_abort); + + //! See: input_entry::is_our_content_type(). + static bool g_is_our_content_type(const char * p_content_type); + //! See: input_entry::is_our_path(). + static bool g_is_our_path(const char * p_path,const char * p_extension); + + //! See: input_entry::get_guid(). + static GUID g_get_guid(); + //! See: input_entry::get_name(). + static const char * g_get_name(); + //! See: input_entry::get_preferences_guid(). + static GUID g_get_preferences_guid(); + //! See: input_entry::is_low_merit(). + static bool g_is_low_merit(); + + //! See: input_decoder_v2::run_raw(). Relevant only when implementing input_decoder_v2. Valid after decode_initialize(). + bool decode_run_raw(audio_chunk & p_chunk, mem_block_container & p_raw, abort_callback & p_abort); + + //! OLD way: may be called at any time from input_decoder_v2 \n + //! NEW way (1.5): called prior to open(). + void set_logger(event_logger::ptr ptr); +protected: + input_impl() {} + ~input_impl() {} +}; + +//! A base class that provides stub implementations of all optional input methods. \n +//! Inherit from this and you implement input_decoder_v4 without having to provide all the methods you don't actually need. +class input_stubs { +public: + bool decode_run_raw(audio_chunk & p_chunk, mem_block_container & p_raw, abort_callback & p_abort) { throw pfc::exception_not_implemented(); } + void set_logger(event_logger::ptr ptr) {} + void set_pause(bool paused) {} + bool flush_on_pause() { return false; } + size_t extended_param(const GUID & type, size_t arg1, void * arg2, size_t arg2size) { return 0; } + static GUID g_get_preferences_guid() {return pfc::guid_null;} + static bool g_is_low_merit() { return false; } + + bool decode_get_dynamic_info(file_info & p_out, double & p_timestamp_delta) { return false; } + bool decode_get_dynamic_info_track(file_info & p_out, double & p_timestamp_delta) { return false; } + void decode_on_idle(abort_callback & p_abort) { } + + + //! These typedefs indicate which interfaces your class actually supports. You can override them to support non default input API interfaces without specifying input_factory parameters. + typedef input_decoder_v4 interface_decoder_t; + typedef input_info_reader interface_info_reader_t; + typedef input_info_writer interface_info_writer_t; +}; + +//! This is a class that just declares prototypes of functions that each non-multitrack-enabled input needs to implement. See input_decoder / input_info_reader / input_info_writer interfaces for full descriptions of member functions. Since input implementation class is instantiated using a template, you don't need to derive from input_singletrack_impl as virtual functions are not used on implementation class level. Use input_singletrack_factory_t template to register input class based on input_singletrack_impl. +class input_singletrack_impl +{ +public: + //! Opens specified file for info read / decoding / info write. This is called only once, immediately after object creation, before any other methods, and no other methods are called if open() fails. + //! @param p_filehint Optional; passes file object to use for the operation; if set to null, the implementation should handle opening file by itself. Note that not all inputs operate on physical files that can be reached through filesystem API, some of them require this parameter to be set to null (tone and silence generators for an example). Typically, an input implementation that requires file access calls input_open_file_helper() function to ensure that file is open with relevant access mode (read or read/write). + //! @param p_path URL of resource being opened. + //! @param p_reason Type of operation requested. Possible values are: \n + //! - input_open_info_read - info retrieval methods are valid; \n + //! - input_open_decode - info retrieval and decoding methods are valid; \n + //! - input_open_info_write - info retrieval and retagging methods are valid; \n + //! Note that info retrieval methods are valid in all cases, and they may be called at any point of decoding/retagging process. Results of info retrieval methods (other than get_subsong_count() / get_subsong()) between retag_set_info() and retag_commit() are undefined however; those should not be called during that period. + //! @param p_abort abort_callback object signaling user aborting the operation. + void open(service_ptr_t p_filehint,const char * p_path,t_input_open_reason p_reason,abort_callback & p_abort); + + //! See: input_info_reader::get_info(). Valid after open() with any reason. \n + //! Implementation warning: this is typically also called immediately after tag update and should return newly written content then. + void get_info(file_info & p_info,abort_callback & p_abort); + //! See: input_info_reader::get_file_stats(). Valid after open() with any reason. \n + //! Implementation warning: this is typically also called immediately after tag update and should return new values then. + t_filestats get_file_stats(abort_callback & p_abort); + + //! See: input_decoder::initialize(). Valid after open() with input_open_decode reason. + void decode_initialize(unsigned p_flags,abort_callback & p_abort); + //! See: input_decoder::run(). Valid after decode_initialize(). + bool decode_run(audio_chunk & p_chunk,abort_callback & p_abort); + //! See: input_decoder::seek(). Valid after decode_initialize(). + void decode_seek(double p_seconds,abort_callback & p_abort); + //! See: input_decoder::can_seek(). Valid after decode_initialize(). + bool decode_can_seek(); + //! See: input_decoder::get_dynamic_info(). Valid after decode_initialize(). + bool decode_get_dynamic_info(file_info & p_out, double & p_timestamp_delta); + //! See: input_decoder::get_dynamic_info_track(). Valid after decode_initialize(). + bool decode_get_dynamic_info_track(file_info & p_out, double & p_timestamp_delta); + //! See: input_decoder::on_idle(). Valid after decode_initialize(). + void decode_on_idle(abort_callback & p_abort); + + //! See: input_info_writer::set_info(). Note that input_info_writer::commit() call isn't forwarded because it's useless in case of non-multitrack-enabled inputs. Valid after open() with input_open_info_write reason. + void retag(const file_info & p_info,abort_callback & p_abort); + + //! See: input_entry::is_our_content_type(). + static bool g_is_our_content_type(const char * p_content_type); + //! See: input_entry::is_our_path(). + static bool g_is_our_path(const char * p_path,const char * p_extension); + +protected: + input_singletrack_impl() {} + ~input_singletrack_impl() {} +}; + + +//! Used internally by standard input_entry implementation; do not use directly. Translates input_decoder / input_info_reader / input_info_writer calls to input_impl calls. +template +class input_impl_interface_wrapper_t : public interface_t +{ +public: + template + void open( args_t && ... args) { + m_instance.open(std::forward(args) ... ); + } + + // input_info_reader methods + + t_uint32 get_subsong_count() { + return m_instance.get_subsong_count(); + } + + t_uint32 get_subsong(t_uint32 p_index) { + return m_instance.get_subsong(p_index); + } + + + void get_info(t_uint32 p_subsong,file_info & p_info,abort_callback & p_abort) { + m_instance.get_info(p_subsong,p_info,p_abort); + } + + t_filestats get_file_stats(abort_callback & p_abort) { + return m_instance.get_file_stats(p_abort); + } + + // input_decoder methods + + void initialize(t_uint32 p_subsong,unsigned p_flags,abort_callback & p_abort) { + m_instance.decode_initialize(p_subsong,p_flags,p_abort); +#if PFC_DEBUG + m_eof = false; +#endif + } + + bool run(audio_chunk & p_chunk,abort_callback & p_abort) { +#if PFC_DEBUG + PFC_ASSERT( !m_eof ); + // Complain if run()/run_raw() gets called again after having returned EOF, this means a logic error on caller's side +#endif + bool ret = m_instance.decode_run(p_chunk,p_abort); +#if PFC_DEBUG + if ( !ret ) m_eof = true; +#endif + return ret; + } + + bool run_raw(audio_chunk & p_chunk, mem_block_container & p_raw, abort_callback & p_abort) { +#if PFC_DEBUG + // Complain if run()/run_raw() gets called again after having returned EOF, this means a logic error on caller's side + PFC_ASSERT(!m_eof); +#endif + bool ret = m_instance.decode_run_raw(p_chunk, p_raw, p_abort); +#if PFC_DEBUG + if ( !ret ) m_eof = true; +#endif + return ret; + } + + void seek(double p_seconds,abort_callback & p_abort) { + m_instance.decode_seek(p_seconds,p_abort); +#if PFC_DEBUG + m_eof = false; +#endif + } + + bool can_seek() { + return m_instance.decode_can_seek(); + } + + bool get_dynamic_info(file_info & p_out, double & p_timestamp_delta) { + return m_instance.decode_get_dynamic_info(p_out,p_timestamp_delta); + } + + bool get_dynamic_info_track(file_info & p_out, double & p_timestamp_delta) { + return m_instance.decode_get_dynamic_info_track(p_out,p_timestamp_delta); + } + + void on_idle(abort_callback & p_abort) { + m_instance.decode_on_idle(p_abort); + } + + void set_logger(event_logger::ptr ptr) { + m_instance.set_logger(ptr); + } + + void set_pause(bool paused) { + // obsolete + // m_instance.set_pause(paused); + } + + bool flush_on_pause() { + return m_instance.flush_on_pause(); + } + + size_t extended_param(const GUID & type, size_t arg1, void * arg2, size_t arg2size) { + return m_instance.extended_param(type, arg1, arg2, arg2size); + } + + // input_info_writer methods + + void set_info(t_uint32 p_subsong,const file_info & p_info,abort_callback & p_abort) { + m_instance.retag_set_info(p_subsong,p_info,p_abort); + } + + void commit(abort_callback & p_abort) { + m_instance.retag_commit(p_abort); + } + void remove_tags(abort_callback & p_abort) { + m_instance.remove_tags(p_abort); + } + +private: + I m_instance; + +#if PFC_DEBUG + // Report illegal API calls in debug build + bool m_eof = false; +#endif +}; + +template +class input_forward_static_methods : public input_stubs { +public: + static bool g_is_our_content_type(const char * p_content_type) { return input_t::g_is_our_content_type(p_content_type); } + static bool g_is_our_path(const char * p_path, const char * p_extension) { return input_t::g_is_our_path(p_path, p_extension); } + static GUID g_get_preferences_guid() { return input_t::g_get_preferences_guid(); } + static GUID g_get_guid() { return input_t::g_get_guid(); } + static const char * g_get_name() { return input_t::g_get_name(); } + static bool g_is_low_merit() { return input_t::g_is_low_merit(); } + + typedef typename input_t::interface_decoder_t interface_decoder_t; + typedef typename input_t::interface_info_reader_t interface_info_reader_t; + typedef typename input_t::interface_info_writer_t interface_info_writer_t; + +}; + +//! Helper used by input_singletrack_factory_t, do not use directly. Translates input_impl calls to input_singletrack_impl calls. +template +class input_wrapper_singletrack_t : public input_forward_static_methods +{ +public: + input_wrapper_singletrack_t() {} + + template + void open( args_t && ... args) { + m_instance.open(std::forward(args) ... ); + } + + void get_info(t_uint32 p_subsong,file_info & p_info,abort_callback & p_abort) { + if (p_subsong != 0) throw exception_io_bad_subsong_index(); + m_instance.get_info(p_info,p_abort); + } + + t_uint32 get_subsong_count() { + return 1; + } + + t_uint32 get_subsong(t_uint32 p_index) { + PFC_ASSERT(p_index == 0); + return 0; + } + + t_filestats get_file_stats(abort_callback & p_abort) { + return m_instance.get_file_stats(p_abort); + } + + void decode_initialize(t_uint32 p_subsong,unsigned p_flags,abort_callback & p_abort) { + if (p_subsong != 0) throw exception_io_bad_subsong_index(); + m_instance.decode_initialize(p_flags,p_abort); + } + + bool decode_run(audio_chunk & p_chunk,abort_callback & p_abort) {return m_instance.decode_run(p_chunk,p_abort);} + void decode_seek(double p_seconds,abort_callback & p_abort) {m_instance.decode_seek(p_seconds,p_abort);} + bool decode_can_seek() {return m_instance.decode_can_seek();} + bool decode_get_dynamic_info(file_info & p_out, double & p_timestamp_delta) {return m_instance.decode_get_dynamic_info(p_out,p_timestamp_delta);} + bool decode_get_dynamic_info_track(file_info & p_out, double & p_timestamp_delta) {return m_instance.decode_get_dynamic_info_track(p_out,p_timestamp_delta);} + void decode_on_idle(abort_callback & p_abort) {m_instance.decode_on_idle(p_abort);} + + void retag_set_info(t_uint32 p_subsong,const file_info & p_info,abort_callback & p_abort) { + if (p_subsong != 0) throw exception_io_bad_subsong_index(); + m_instance.retag(p_info,p_abort); + } + + bool decode_run_raw(audio_chunk & p_chunk, mem_block_container & p_raw, abort_callback & p_abort) { + return m_instance.decode_run_raw(p_chunk, p_raw, p_abort); + } + + void set_logger(event_logger::ptr ptr) {m_instance.set_logger(ptr);} + + void set_pause(bool paused) { + // m_instance.set_pause(paused); + } + bool flush_on_pause() { + return m_instance.flush_on_pause(); + } + size_t extended_param(const GUID & type, size_t arg1, void * arg2, size_t arg2size) { + return m_instance.extended_param(type, arg1, arg2, arg2size); + } + void retag_commit(abort_callback & p_abort) {} + + void remove_tags(abort_callback & p_abort) { + m_instance.remove_tags(p_abort); + } +private: + I m_instance; +}; + +//! Helper; standard input_entry implementation. Do not instantiate this directly, use input_factory_t or one of other input_*_factory_t helpers instead. +template +class input_entry_impl_t : public input_entry_v3 +{ +public: + bool is_our_content_type(const char * p_type) {return I::g_is_our_content_type(p_type);} + bool is_our_path(const char * p_full_path,const char * p_extension) {return I::g_is_our_path(p_full_path,p_extension);} + + template + void open_ex(service_ptr_t & p_instance,args_t && ... args) + { + auto temp = fb2k::service_new >(); + temp->open(std::forward(args) ... ); + p_instance = temp.get_ptr(); + } + + service_ptr open_v3(const GUID & whatFor, file::ptr hint, const char * path, event_logger::ptr logger, abort_callback & aborter) { + if ( whatFor == input_decoder::class_guid ) { + auto obj = fb2k::service_new< input_impl_interface_wrapper_t > (); + if ( logger.is_valid() ) obj->set_logger(logger); + obj->open( hint, path, input_open_decode, aborter ); + return obj; + } + if ( whatFor == input_info_reader::class_guid ) { + auto obj = fb2k::service_new < input_impl_interface_wrapper_t >(); + if (logger.is_valid()) obj->set_logger(logger); + obj->open(hint, path, input_open_info_read, aborter); + return obj; + } + if ( whatFor == input_info_writer::class_guid ) { + auto obj = fb2k::service_new < input_impl_interface_wrapper_t >(); + if (logger.is_valid()) obj->set_logger(logger); + obj->open(hint, path, input_open_info_write, aborter); + return obj; + } + throw pfc::exception_not_implemented(); + } + + void get_extended_data(service_ptr_t p_filehint,const playable_location & p_location,const GUID & p_guid,mem_block_container & p_out,abort_callback & p_abort) { + p_out.reset(); + } + + unsigned get_flags() {return t_flags;} + + GUID get_guid() { + return I::g_get_guid(); + } + const char * get_name() { + return I::g_get_name(); + } + + GUID get_preferences_guid() { + return I::g_get_preferences_guid(); + } + bool is_low_merit() { + return I::g_is_low_merit(); + } +}; + + +//! Stardard input factory. For reference of functions that must be supported by registered class, see input_impl.\n Usage: static input_factory_t g_myinputclass_factory;\n Note that input classes can't be registered through service_factory_t template directly. +template +class input_factory_t : public service_factory_single_t > {}; + +//! Non-multitrack-enabled input factory (helper) - hides multitrack management functions from input implementation; use this for inputs that handle file types where each physical file can contain only one user-visible playable track. For reference of functions that must be supported by registered class, see input_singletrack_impl.\n Usage: static input_singletrack_factory_t g_myinputclass_factory;\n Note that input classes can't be registered through service_factory_t template directly.template +template +class input_singletrack_factory_t : public service_factory_single_t,t_flags> > {}; + +//! Extended version of input_factory_t, with non-default flags and supported interfaces. See: input_factory_t, input_entry::get_flags(). \n +//! This is obsolete and provided for backwards compatibility. Use interface_decoder_t + interface_info_reader_t + interface_info_writer_t typedefs in your input class to specify supported interfaces. +template +class input_factory_ex_t : public service_factory_single_t > {}; + +//! Extended version of input_singletrack_factory_t, with non-default flags and supported interfaces. See: input_singletrack_factory_t, input_entry::get_flags(). +//! This is obsolete and provided for backwards compatibility. Use interface_decoder_t + interface_info_reader_t + interface_info_writer_t typedefs in your input class to specify supported interfaces. +template +class input_singletrack_factory_ex_t : public service_factory_single_t, t_flags, t_decoder, t_inforeader, t_infowriter> > {}; diff --git a/foobar2000/SDK/library_manager.h b/foobar2000/SDK/library_manager.h new file mode 100644 index 0000000..a85a907 --- /dev/null +++ b/foobar2000/SDK/library_manager.h @@ -0,0 +1,206 @@ +/*! +This service implements methods allowing you to interact with the Media Library.\n +All methods are valid from main thread only, unless noted otherwise.\n +Usage: Use library_manager::get() to instantiate. +*/ + +class NOVTABLE library_manager : public service_base { + FB2K_MAKE_SERVICE_COREAPI(library_manager); +public: + //! Interface for use with library_manager::enum_items(). + class NOVTABLE enum_callback { + public: + //! Return true to continue enumeration, false to abort. + virtual bool on_item(const metadb_handle_ptr & p_item) = 0; + }; + + //! Returns whether the specified item is in the Media Library or not. + virtual bool is_item_in_library(const metadb_handle_ptr & p_item) = 0; + //! Returns whether current user settings allow the specified item to be added to the Media Library or not. + virtual bool is_item_addable(const metadb_handle_ptr & p_item) = 0; + //! Returns whether current user settings allow the specified item path to be added to the Media Library or not. + virtual bool is_path_addable(const char * p_path) = 0; + //! Retrieves path of the specified item relative to the Media Library folder it is in. Returns true on success, false when the item is not in the Media Library. + //! SPECIAL WARNING: to allow multi-CPU optimizations to parse relative track paths, this API works in threads other than the main app thread. Main thread MUST be blocked while working in such scenarios, it's NOT safe to call from worker threads while the Media Library content/configuration might be getting altered. + virtual bool get_relative_path(const metadb_handle_ptr & p_item,pfc::string_base & p_out) = 0; + //! Calls callback method for every item in the Media Library. Note that order of items in Media Library is undefined. + virtual void enum_items(enum_callback & p_callback) = 0; +protected: + //! OBSOLETE, do not call, does nothing. + __declspec(deprecated) virtual void add_items(const pfc::list_base_const_t & p_data) = 0; + //! OBSOLETE, do not call, does nothing. + __declspec(deprecated) virtual void remove_items(const pfc::list_base_const_t & p_data) = 0; + //! OBSOLETE, do not call, does nothing. + __declspec(deprecated) virtual void add_items_async(const pfc::list_base_const_t & p_data) = 0; + + //! OBSOLETE, do not call, does nothing. + __declspec(deprecated) virtual void on_files_deleted_sorted(const pfc::list_base_const_t & p_data) = 0; +public: + //! Retrieves the entire Media Library content. + virtual void get_all_items(pfc::list_base_t & p_out) = 0; + + //! Returns whether Media Library functionality is enabled or not (to be exact: whether there's at least one Media Library folder present in settings), for e.g. notifying the user to change settings when trying to use a Media Library viewer without having configured the Media Library first. + virtual bool is_library_enabled() = 0; + //! Pops up the Media Library preferences page. + virtual void show_preferences() = 0; + + //! OBSOLETE, do not call. + virtual void rescan() = 0; + +protected: + //! OBSOLETE, do not call, does nothing. + __declspec(deprecated) virtual void check_dead_entries(const pfc::list_base_t & p_list) = 0; +public: + + +}; + +//! \since 0.9.3 +class NOVTABLE library_manager_v2 : public library_manager { + FB2K_MAKE_SERVICE_COREAPI_EXTENSION(library_manager_v2,library_manager); +protected: + //! OBSOLETE, do not call, does nothing. + __declspec(deprecated) virtual bool is_rescan_running() = 0; + + //! OBSOLETE, do not call, does nothing. + __declspec(deprecated) virtual void rescan_async(HWND p_parent,completion_notify_ptr p_notify) = 0; + + //! OBSOLETE, do not call, does nothing. + __declspec(deprecated) virtual void check_dead_entries_async(const pfc::list_base_const_t & p_list,HWND p_parent,completion_notify_ptr p_notify) = 0; + + +}; + + +class NOVTABLE library_callback_dynamic { +public: + //! Called when new items are added to the Media Library. + virtual void on_items_added(const pfc::list_base_const_t & p_data) = 0; + //! Called when some items have been removed from the Media Library. + virtual void on_items_removed(const pfc::list_base_const_t & p_data) = 0; + //! Called when some items in the Media Library have been modified. + virtual void on_items_modified(const pfc::list_base_const_t & p_data) = 0; +}; + +//! \since 0.9.5 +class NOVTABLE library_manager_v3 : public library_manager_v2 { +public: + //! Retrieves directory path and subdirectory/filename formatting scheme for newly encoded/copied/moved tracks. + //! @returns True on success, false when the feature has not been configured. + virtual bool get_new_file_pattern_tracks(pfc::string_base & p_directory,pfc::string_base & p_format) = 0; + //! Retrieves directory path and subdirectory/filename formatting scheme for newly encoded/copied/moved full album images. + //! @returns True on success, false when the feature has not been configured. + virtual bool get_new_file_pattern_images(pfc::string_base & p_directory,pfc::string_base & p_format) = 0; + + virtual void register_callback(library_callback_dynamic * p_callback) = 0; + virtual void unregister_callback(library_callback_dynamic * p_callback) = 0; + + FB2K_MAKE_SERVICE_COREAPI_EXTENSION(library_manager_v3,library_manager_v2); +}; + +class library_callback_dynamic_impl_base : public library_callback_dynamic { +public: + library_callback_dynamic_impl_base() {library_manager_v3::get()->register_callback(this);} + ~library_callback_dynamic_impl_base() {library_manager_v3::get()->unregister_callback(this);} + + //stub implementations - avoid pure virtual function call issues + void on_items_added(metadb_handle_list_cref p_data) {} + void on_items_removed(metadb_handle_list_cref p_data) {} + void on_items_modified(metadb_handle_list_cref p_data) {} + + PFC_CLASS_NOT_COPYABLE_EX(library_callback_dynamic_impl_base); +}; + +//! Callback service receiving notifications about Media Library content changes. Methods called only from main thread.\n +//! Use library_callback_factory_t template to register. +class NOVTABLE library_callback : public service_base { +public: + //! Called when new items are added to the Media Library. + virtual void on_items_added(const pfc::list_base_const_t & p_data) = 0; + //! Called when some items have been removed from the Media Library. + virtual void on_items_removed(const pfc::list_base_const_t & p_data) = 0; + //! Called when some items in the Media Library have been modified. + virtual void on_items_modified(const pfc::list_base_const_t & p_data) = 0; + + FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(library_callback); +}; + +template +class library_callback_factory_t : public service_factory_single_t {}; + +//! Implement this service to appear on "library viewers" list in Media Library preferences page.\n +//! Use library_viewer_factory_t to register. +class NOVTABLE library_viewer : public service_base { +public: + //! Retrieves GUID of your preferences page (pfc::guid_null if you don't have one). + virtual GUID get_preferences_page() = 0; + //! Queries whether "activate" action is supported (relevant button will be disabled if it's not). + virtual bool have_activate() = 0; + //! Activates your Media Library viewer component (e.g. shows its window). + virtual void activate() = 0; + //! Retrieves GUID of your library_viewer implementation, for internal identification. Note that this not the same as preferences page GUID. + virtual GUID get_guid() = 0; + //! Retrieves name of your Media Library viewer, a null-terminated UTF-8 encoded string. + virtual const char * get_name() = 0; + + FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(library_viewer); +}; + +template +class library_viewer_factory_t : public service_factory_single_t {}; + + + + +//! \since 0.9.5.4 +//! Allows you to spawn a popup Media Library Search window with any query string that you specify. \n +//! Usage: library_search_ui::get()->show("querygoeshere"); +class NOVTABLE library_search_ui : public service_base { +public: + virtual void show(const char * query) = 0; + + FB2K_MAKE_SERVICE_COREAPI(library_search_ui) +}; + +//! \since 0.9.6 +class NOVTABLE library_file_move_scope : public service_base { + FB2K_MAKE_SERVICE_INTERFACE(library_file_move_scope, service_base) +public: +}; + +//! \since 0.9.6 +class NOVTABLE library_file_move_manager : public service_base { + FB2K_MAKE_SERVICE_COREAPI(library_file_move_manager) +public: + virtual library_file_move_scope::ptr acquire_scope() = 0; + virtual bool is_move_in_progress() = 0; +}; + +//! \since 0.9.6 +class NOVTABLE library_file_move_notify_ { +public: + virtual void on_state_change(bool isMoving) = 0; +}; + +//! \since 0.9.6 +class NOVTABLE library_file_move_notify : public service_base, public library_file_move_notify_ { + FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(library_file_move_notify) +public: +}; + + +//! \since 0.9.6.1 +class NOVTABLE library_meta_autocomplete : public service_base { + FB2K_MAKE_SERVICE_COREAPI(library_meta_autocomplete) +public: + virtual bool get_value_list(const char * metaName, pfc::com_ptr_t & out) = 0; +}; + +//! \since 1.6.1 +//! Caching & asynchronous version. \n +//! Keep a reference to your library_meta_autocomplete_v2 object in your dialog class to cache the looked up values & speed up the operation. +class NOVTABLE library_meta_autocomplete_v2 : public service_base { + FB2K_MAKE_SERVICE_COREAPI(library_meta_autocomplete_v2) +public: + virtual bool get_value_list_async(const char* metaName, pfc::com_ptr_t& out) = 0; +}; diff --git a/foobar2000/SDK/link_resolver.cpp b/foobar2000/SDK/link_resolver.cpp new file mode 100644 index 0000000..321e28a --- /dev/null +++ b/foobar2000/SDK/link_resolver.cpp @@ -0,0 +1,17 @@ +#include "foobar2000.h" + +bool link_resolver::g_find(service_ptr_t & p_out,const char * p_path) +{ + service_enum_t e; + service_ptr_t ptr; + pfc::string_extension ext(p_path); + while(e.next(ptr)) + { + if (ptr->is_our_path(p_path,ext)) + { + p_out = ptr; + return true; + } + } + return false; +} diff --git a/foobar2000/SDK/link_resolver.h b/foobar2000/SDK/link_resolver.h new file mode 100644 index 0000000..0c4ca85 --- /dev/null +++ b/foobar2000/SDK/link_resolver.h @@ -0,0 +1,33 @@ +#ifndef _foobar2000_sdk_link_resolver_h_ +#define _foobar2000_sdk_link_resolver_h_ + +//! Interface for resolving different sorts of link files. +//! Utilized by mechanisms that convert filesystem path into list of playable locations. +//! For security reasons, link may only point to playable object path, not to a playlist or another link. + +class NOVTABLE link_resolver : public service_base +{ +public: + + //! Tests whether specified file is supported by this link_resolver service. + //! @param p_path Path of file being queried. + //! @param p_extension Extension of file being queried. This is provided for performance reasons, path already includes it. + virtual bool is_our_path(const char * p_path,const char * p_extension) = 0; + + //! Resolves a link file. Before this is called, path must be accepted by is_our_path(). + //! @param p_filehint Optional file interface to use. If null/empty, implementation should open file by itself. + //! @param p_path Path of link file to resolve. + //! @param p_out Receives path the link is pointing to. + //! @param p_abort abort_callback object signaling user aborting the operation. + virtual void resolve(service_ptr_t p_filehint,const char * p_path,pfc::string_base & p_out,abort_callback & p_abort) = 0; + + //! Helper function; finds link_resolver interface that supports specified link file. + //! @param p_out Receives link_resolver interface on success. + //! @param p_path Path of file to query. + //! @returns True on success, false on failure (no interface that supports specified path could be found). + static bool g_find(service_ptr_t & p_out,const char * p_path); + + FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(link_resolver); +}; + +#endif //_foobar2000_sdk_link_resolver_h_ diff --git a/foobar2000/SDK/main_thread_callback.cpp b/foobar2000/SDK/main_thread_callback.cpp new file mode 100644 index 0000000..a27393f --- /dev/null +++ b/foobar2000/SDK/main_thread_callback.cpp @@ -0,0 +1,37 @@ +#include "foobar2000.h" + + +void main_thread_callback::callback_enqueue() { + main_thread_callback_manager::get()->add_callback(this); +} + +void main_thread_callback_add(main_thread_callback::ptr ptr) { + main_thread_callback_manager::get()->add_callback(ptr); +} + +namespace { + typedef std::function func_t; + class mtcallback_func : public main_thread_callback { + public: + mtcallback_func(func_t const & f) : m_f(f) {} + + void callback_run() { + m_f(); + } + + private: + func_t m_f; + }; +} + +void fb2k::inMainThread( std::function f ) { + main_thread_callback_add( new service_impl_t(f)); +} + +void fb2k::inMainThread2( std::function f ) { + if ( core_api::is_main_thread() ) { + f(); + } else { + inMainThread(f); + } +} diff --git a/foobar2000/SDK/main_thread_callback.h b/foobar2000/SDK/main_thread_callback.h new file mode 100644 index 0000000..b1158cc --- /dev/null +++ b/foobar2000/SDK/main_thread_callback.h @@ -0,0 +1,187 @@ +#pragma once +#include + +// ====================================================================================================== +// Most of main_thread_callback.h declares API internals and obsolete helpers. +// In modern code, simply use fb2k::inMainThread() declared below and disregard the rest. +// ====================================================================================================== +namespace fb2k { + //! Queue a call in main thread. Returns immediately. \n + //! You can call this from any thread, including main thread - to execute some code outside the current call stack / global fb2k callbacks / etc. + void inMainThread(std::function f); + //! Call f synchronously if called from main thread, queue call if called from another. + void inMainThread2(std::function f); +} + + + + +// ====================================================================================================== +// API declarations +// ====================================================================================================== + +//! Callback object class for main_thread_callback_manager service. \n +//! You do not need to implement this directly - simply use fb2k::inMainThread() with a lambda. +class NOVTABLE main_thread_callback : public service_base { +public: + //! Gets called from main app thread. See main_thread_callback_manager description for more info. + virtual void callback_run() = 0; + + void callback_enqueue(); // helper + + FB2K_MAKE_SERVICE_INTERFACE(main_thread_callback,service_base); +}; + +/*! +Allows you to queue a callback object to be called from main app thread. This is commonly used to trigger main-thread-only API calls from worker threads.\n +This can be also used from main app thread, to avoid race conditions when trying to use APIs that dispatch global callbacks from inside some other global callback, or using message loops / modal dialogs inside global callbacks. \n +There is no need to use this API directly - use fb2k::inMainThread() with a lambda. +*/ +class NOVTABLE main_thread_callback_manager : public service_base { +public: + //! Queues a callback object. This can be called from any thread, implementation ensures multithread safety. Implementation will call p_callback->callback_run() once later. To get it called repeatedly, you would need to add your callback again. + virtual void add_callback(service_ptr_t p_callback) = 0; + + FB2K_MAKE_SERVICE_COREAPI(main_thread_callback_manager); +}; + + +// ====================================================================================================== +// Obsolete helpers - they still work, but it's easier to just use fb2k::inMainThread(). +// ====================================================================================================== + +//! Helper, equivalent to main_thread_callback_manager::get()->add_callback(ptr) +void main_thread_callback_add(main_thread_callback::ptr ptr); + +template static void main_thread_callback_spawn() { + main_thread_callback_add(new service_impl_t); +} +template static void main_thread_callback_spawn(const t_param1 & p1) { + main_thread_callback_add(new service_impl_t(p1)); +} +template static void main_thread_callback_spawn(const t_param1 & p1, const t_param2 & p2) { + main_thread_callback_add(new service_impl_t(p1, p2)); +} + +// Proxy class - friend this to allow callInMainThread to access your private methods +class callInMainThread { +public: + template + static void callThis(host_t * host, param_t & param) { + host->inMainThread(param); + } + template + static void callThis( host_t * host ) { + host->inMainThread(); + } +}; + +// Internal class, do not use. +template +class _callInMainThreadSvc_t : public main_thread_callback { +public: + _callInMainThreadSvc_t(service_t * host, param_t const & param) : m_host(host), m_param(param) {} + void callback_run() { + callInMainThread::callThis(m_host.get_ptr(), m_param); + } +private: + service_ptr_t m_host; + param_t m_param; +}; + + +// Main thread callback helper. You can use this to easily invoke inMainThread(someparam) on your class without writing any wrapper code. +// Requires myservice_t to be a fb2k service class with reference counting. +template +static void callInMainThreadSvc(myservice_t * host, param_t const & param) { + typedef _callInMainThreadSvc_t impl_t; + service_ptr_t obj = new service_impl_t(host, param); + main_thread_callback_manager::get()->add_callback( obj ); +} + + + + +//! Helper class to call methods of your class (host class) in main thread with convenience. \n +//! Deals with the otherwise ugly scenario of your class becoming invalid while a method is queued. \n +//! Have this as a member of your class, then use m_mthelper.add( this, somearg ) ; to defer a call to this->inMainThread(somearg). \n +//! If your class becomes invalid before inMainThread is executed, the pending callback is discarded. \n +//! You can optionally call shutdown() to invalidate all pending callbacks early (in a destructor of your class - without waiting for callInMainThreadHelper destructor to do the job. \n +//! In order to let callInMainThreadHelper access your private methods, declare friend class callInMainThread. \n +//! Note that callInMainThreadHelper is expected to be created and destroyed in main thread. +class callInMainThreadHelper { +public: + + typedef std::function< void () > func_t; + + typedef pfc::rcptr_t< bool > killswitch_t; + + class entryFunc : public main_thread_callback { + public: + entryFunc( func_t const & func, killswitch_t ks ) : m_ks(ks), m_func(func) {} + + void callback_run() { + if (!*m_ks) m_func(); + } + + private: + killswitch_t m_ks; + func_t m_func; + }; + + template + class entry : public main_thread_callback { + public: + entry( host_t * host, arg_t const & arg, killswitch_t ks ) : m_ks(ks), m_host(host), m_arg(arg) {} + void callback_run() { + if (!*m_ks) callInMainThread::callThis( m_host, m_arg ); + } + private: + killswitch_t m_ks; + host_t * m_host; + arg_t m_arg; + }; + template + class entryVoid : public main_thread_callback { + public: + entryVoid( host_t * host, killswitch_t ks ) : m_ks(ks), m_host(host) {} + void callback_run() { + if (!*m_ks) callInMainThread::callThis( m_host ); + } + private: + killswitch_t m_ks; + host_t * m_host; + }; + + void add(func_t f) { + add_( new service_impl_t< entryFunc > ( f, m_ks ) ); + } + + template + void add( host_t * host, arg_t const & arg) { + add_( new service_impl_t< entry >( host, arg, m_ks ) ); + } + template + void add( host_t * host ) { + add_( new service_impl_t< entryVoid >( host, m_ks ) ); + } + void add_( main_thread_callback::ptr cb ) { + main_thread_callback_add( cb ); + } + + callInMainThreadHelper() { + m_ks.new_t(); + * m_ks = false; + } + void shutdown() { + PFC_ASSERT( core_api::is_main_thread() ); + * m_ks = true; + } + ~callInMainThreadHelper() { + shutdown(); + } + +private: + killswitch_t m_ks; + +}; diff --git a/foobar2000/SDK/mainmenu.cpp b/foobar2000/SDK/mainmenu.cpp new file mode 100644 index 0000000..79eb5d2 --- /dev/null +++ b/foobar2000/SDK/mainmenu.cpp @@ -0,0 +1,57 @@ +#include "foobar2000.h" + +bool mainmenu_commands::g_execute_dynamic(const GUID & p_guid, const GUID & p_subGuid,service_ptr_t p_callback) { + mainmenu_commands::ptr ptr; t_uint32 index; + if (!menu_item_resolver::g_resolve_main_command(p_guid, ptr, index)) return false; + mainmenu_commands_v2::ptr v2; + if (!ptr->service_query_t(v2)) return false; + if (!v2->is_command_dynamic(index)) return false; + return v2->dynamic_execute(index, p_subGuid, p_callback); +} +bool mainmenu_commands::g_execute(const GUID & p_guid,service_ptr_t p_callback) { + mainmenu_commands::ptr ptr; t_uint32 index; + if (!menu_item_resolver::g_resolve_main_command(p_guid, ptr, index)) return false; + ptr->execute(index, p_callback); + return true; +} + +bool mainmenu_commands::g_find_by_name(const char * p_name,GUID & p_guid) { + service_enum_t e; + service_ptr_t ptr; + pfc::string8_fastalloc temp; + while(e.next(ptr)) { + const t_uint32 count = ptr->get_command_count(); + for(t_uint32 n=0;nget_name(n,temp); + if (stricmp_utf8(temp,p_name) == 0) { + p_guid = ptr->get_command(n); + return true; + } + } + } + return false; + +} + + +static bool dynamic_execute_recur(mainmenu_node::ptr node, const GUID & subID, service_ptr_t callback) { + switch(node->get_type()) { + case mainmenu_node::type_command: + if (subID == node->get_guid()) { + node->execute(callback); return true; + } + break; + case mainmenu_node::type_group: + { + const t_size total = node->get_children_count(); + for(t_size walk = 0; walk < total; ++walk) { + if (dynamic_execute_recur(node->get_child(walk), subID, callback)) return true; + } + } + break; + } + return false; +} +bool mainmenu_commands_v2::dynamic_execute(t_uint32 index, const GUID & subID, service_ptr_t callback) { + return dynamic_execute_recur(dynamic_instantiate(index), subID, callback); +} diff --git a/foobar2000/SDK/mem_block_container.cpp b/foobar2000/SDK/mem_block_container.cpp new file mode 100644 index 0000000..d347a87 --- /dev/null +++ b/foobar2000/SDK/mem_block_container.cpp @@ -0,0 +1,12 @@ +#include "foobar2000.h" + +void mem_block_container::from_stream(stream_reader * p_stream,t_size p_bytes,abort_callback & p_abort) { + if (p_bytes == 0) {set_size(0);} + set_size(p_bytes); + p_stream->read_object(get_ptr(),p_bytes,p_abort); +} + +void mem_block_container::set(const void * p_buffer,t_size p_size) { + set_size(p_size); + memcpy(get_ptr(),p_buffer,p_size); +} diff --git a/foobar2000/SDK/mem_block_container.h b/foobar2000/SDK/mem_block_container.h new file mode 100644 index 0000000..e6d7efc --- /dev/null +++ b/foobar2000/SDK/mem_block_container.h @@ -0,0 +1,96 @@ +#pragma once +//! Generic interface for a memory block; used by various other interfaces to return memory blocks while allowing caller to allocate. +class NOVTABLE mem_block_container { +public: + virtual const void * get_ptr() const = 0; + virtual void * get_ptr() = 0; + virtual t_size get_size() const = 0; + virtual void set_size(t_size p_size) = 0; + + void from_stream(stream_reader * p_stream,t_size p_bytes,abort_callback & p_abort); + + void set(const void * p_buffer,t_size p_size); + void set(const mem_block_container & source) {copy(source);} + template void set(const t_source & source) { + PFC_STATIC_ASSERT( sizeof(source[0]) == 1 ); + set(source.get_ptr(), source.get_size()); + } + + inline void copy(const mem_block_container & p_source) {set(p_source.get_ptr(),p_source.get_size());} + inline void reset() {set_size(0);} + + const mem_block_container & operator=(const mem_block_container & p_source) {copy(p_source);return *this;} + +protected: + mem_block_container() {} + ~mem_block_container() {} +}; + +//! mem_block_container implementation. +template class t_alloc = pfc::alloc_standard> +class mem_block_container_impl_t : public mem_block_container { +public: + const void * get_ptr() const {return m_data.get_ptr();} + void * get_ptr() {return m_data.get_ptr();} + t_size get_size() const {return m_data.get_size();} + void set_size(t_size p_size) { + m_data.set_size(p_size); + } +private: + pfc::array_t m_data; +}; + +typedef mem_block_container_impl_t<> mem_block_container_impl; + +template class mem_block_container_aligned_impl : public mem_block_container { +public: + const void * get_ptr() const {return m_data.get_ptr();} + void * get_ptr() {return m_data.get_ptr();} + t_size get_size() const {return m_data.get_size();} + void set_size(t_size p_size) {m_data.set_size(p_size);} +private: + pfc::mem_block_aligned<16> m_data; +}; + +template class mem_block_container_aligned_incremental_impl : public mem_block_container { +public: + mem_block_container_aligned_incremental_impl() : m_size() {} + const void * get_ptr() const {return m_data.get_ptr();} + void * get_ptr() {return m_data.get_ptr();} + t_size get_size() const {return m_size;} + void set_size(t_size p_size) { + if (m_data.size() < p_size) { + m_data.resize( pfc::multiply_guarded(p_size, 3) / 2 ); + } + m_size = p_size; + } +private: + pfc::mem_block_aligned<16> m_data; + size_t m_size; +}; + +class mem_block_container_temp_impl : public mem_block_container { +public: + mem_block_container_temp_impl(void * p_buffer,t_size p_size) : m_buffer(p_buffer), m_buffer_size(p_size), m_size(0) {} + const void * get_ptr() const {return m_buffer;} + void * get_ptr() {return m_buffer;} + t_size get_size() const {return m_size;} + void set_size(t_size p_size) {if (p_size > m_buffer_size) throw pfc::exception_overflow(); m_size = p_size;} +private: + t_size m_size,m_buffer_size; + void * m_buffer; +}; + +template +class mem_block_container_ref_impl : public mem_block_container { +public: + mem_block_container_ref_impl(t_ref & ref) : m_ref(ref) { + PFC_STATIC_ASSERT( sizeof(ref[0]) == 1 ); + } + const void * get_ptr() const {return m_ref.get_ptr();} + void * get_ptr() {return m_ref.get_ptr();} + t_size get_size() const {return m_ref.get_size();} + void set_size(t_size p_size) {m_ref.set_size(p_size);} +private: + t_ref & m_ref; +}; \ No newline at end of file diff --git a/foobar2000/SDK/menu.h b/foobar2000/SDK/menu.h new file mode 100644 index 0000000..34d7b1e --- /dev/null +++ b/foobar2000/SDK/menu.h @@ -0,0 +1,225 @@ +#pragma once + +class NOVTABLE mainmenu_group : public service_base { + FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(mainmenu_group); +public: + virtual GUID get_guid() = 0; + virtual GUID get_parent() = 0; + virtual t_uint32 get_sort_priority() = 0; +}; + +class NOVTABLE mainmenu_group_popup : public mainmenu_group { + FB2K_MAKE_SERVICE_INTERFACE(mainmenu_group_popup, mainmenu_group); +public: + virtual void get_display_string(pfc::string_base & p_out) = 0; + void get_name(pfc::string_base & out) {get_display_string(out);} +}; + +//! \since 1.4 +//! Allows you to control whether to render the group as a popup or inline. +class NOVTABLE mainmenu_group_popup_v2 : public mainmenu_group_popup { + FB2K_MAKE_SERVICE_INTERFACE(mainmenu_group_popup_v2, mainmenu_group_popup); +public: + virtual bool popup_condition() = 0; +}; + +class NOVTABLE mainmenu_commands : public service_base { +public: + enum { + flag_disabled = 1<<0, + flag_checked = 1<<1, + flag_radiochecked = 1<<2, + //! \since 1.0 + //! Replaces the old return-false-from-get_display() behavior - use this to make your command hidden by default but accessible when holding shift. + flag_defaulthidden = 1<<3, + sort_priority_base = 0x10000, + sort_priority_dontcare = 0x80000000u, + sort_priority_last = ~0, + }; + + //! Retrieves number of implemented commands. Index parameter of other methods must be in 0....command_count-1 range. + virtual t_uint32 get_command_count() = 0; + //! Retrieves GUID of specified command. + virtual GUID get_command(t_uint32 p_index) = 0; + //! Retrieves name of item, for list of commands to assign keyboard shortcuts to etc. + virtual void get_name(t_uint32 p_index,pfc::string_base & p_out) = 0; + //! Retrieves item's description for statusbar etc. + virtual bool get_description(t_uint32 p_index,pfc::string_base & p_out) = 0; + //! Retrieves GUID of owning menu group. + virtual GUID get_parent() = 0; + //! Retrieves sorting priority of the command; the lower the number, the upper in the menu your commands will appear. Third party components should use sorting_priority_base and up (values below are reserved for internal use). In case of equal priority, order is undefined. + virtual t_uint32 get_sort_priority() {return sort_priority_dontcare;} + //! Retrieves display string and display flags to use when menu is about to be displayed. If returns false, menu item won't be displayed. You can create keyboard-shortcut-only commands by always returning false from get_display(). + virtual bool get_display(t_uint32 p_index,pfc::string_base & p_text,t_uint32 & p_flags) {p_flags = 0;get_name(p_index,p_text);return true;} + //! Executes the command. p_callback parameter is reserved for future use and should be ignored / set to null pointer. + virtual void execute(t_uint32 p_index,service_ptr_t p_callback) = 0; + + static bool g_execute(const GUID & p_guid,service_ptr_t p_callback = NULL); + static bool g_execute_dynamic(const GUID & p_guid, const GUID & p_subGuid,service_ptr_t p_callback = NULL); + static bool g_find_by_name(const char * p_name,GUID & p_guid); + + FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(mainmenu_commands); +}; + +class NOVTABLE mainmenu_manager : public service_base { +public: + enum { + flag_show_shortcuts = 1 << 0, + flag_show_shortcuts_global = 1 << 1, + //! \since 1.0 + //! To control which commands are shown, you should specify either flag_view_reduced or flag_view_full. If neither is specified, the implementation will decide automatically based on shift key being pressed, for backwards compatibility. + flag_view_reduced = 1 << 2, + //! \since 1.0 + //! To control which commands are shown, you should specify either flag_view_reduced or flag_view_full. If neither is specified, the implementation will decide automatically based on shift key being pressed, for backwards compatibility. + flag_view_full = 1 << 3, + }; + + virtual void instantiate(const GUID & p_root) = 0; + +#ifdef _WIN32 + virtual void generate_menu_win32(HMENU p_menu,t_uint32 p_id_base,t_uint32 p_id_count,t_uint32 p_flags) = 0; +#else +#error portme +#endif + //@param p_id Identifier of command to execute, relative to p_id_base of generate_menu_*() + //@returns true if command was executed successfully, false if not (e.g. command with given identifier not found). + virtual bool execute_command(t_uint32 p_id,service_ptr_t p_callback = service_ptr_t()) = 0; + + virtual bool get_description(t_uint32 p_id,pfc::string_base & p_out) = 0; + + //! Safely prevent destruction from worker threads (some components attempt that). + static bool serviceRequiresMainThreadDestructor() { return true; } + + FB2K_MAKE_SERVICE_COREAPI(mainmenu_manager); +}; + +class mainmenu_groups { +public: + static const GUID file,view,edit,playback,library,help; + static const GUID file_open,file_add,file_playlist,file_etc; + static const GUID playback_controls,playback_etc; + static const GUID view_visualisations, view_alwaysontop, view_dsp; + static const GUID edit_part1,edit_part2,edit_part3; + static const GUID edit_part2_selection,edit_part2_sort,edit_part2_selection_sort; + static const GUID file_etc_preferences, file_etc_exit; + static const GUID help_about; + static const GUID library_refresh; + + enum {priority_edit_part1,priority_edit_part2,priority_edit_part3}; + enum {priority_edit_part2_commands,priority_edit_part2_selection,priority_edit_part2_sort}; + enum {priority_edit_part2_selection_commands,priority_edit_part2_selection_sort}; + enum {priority_file_open,priority_file_add,priority_file_playlist,priority_file_etc = mainmenu_commands::sort_priority_last}; +}; + + +class mainmenu_group_impl : public mainmenu_group { +public: + mainmenu_group_impl(const GUID & p_guid,const GUID & p_parent,t_uint32 p_priority) : m_guid(p_guid), m_parent(p_parent), m_priority(p_priority) {} + GUID get_guid() {return m_guid;} + GUID get_parent() {return m_parent;} + t_uint32 get_sort_priority() {return m_priority;} +private: + GUID m_guid,m_parent; t_uint32 m_priority; +}; + +class mainmenu_group_popup_impl : public mainmenu_group_popup { +public: + mainmenu_group_popup_impl(const GUID & p_guid,const GUID & p_parent,t_uint32 p_priority,const char * p_name) : m_guid(p_guid), m_parent(p_parent), m_priority(p_priority), m_name(p_name) {} + GUID get_guid() {return m_guid;} + GUID get_parent() {return m_parent;} + t_uint32 get_sort_priority() {return m_priority;} + void get_display_string(pfc::string_base & p_out) {p_out = m_name;} +private: + GUID m_guid,m_parent; t_uint32 m_priority; pfc::string8 m_name; +}; + +typedef service_factory_single_t __mainmenu_group_factory; +typedef service_factory_single_t __mainmenu_group_popup_factory; + +class mainmenu_group_factory : public __mainmenu_group_factory { +public: + mainmenu_group_factory(const GUID & p_guid,const GUID & p_parent,t_uint32 p_priority) : __mainmenu_group_factory(p_guid,p_parent,p_priority) {} +}; + +class mainmenu_group_popup_factory : public __mainmenu_group_popup_factory { +public: + mainmenu_group_popup_factory(const GUID & p_guid,const GUID & p_parent,t_uint32 p_priority,const char * p_name) : __mainmenu_group_popup_factory(p_guid,p_parent,p_priority,p_name) {} +}; + +template +class mainmenu_commands_factory_t : public service_factory_single_t {}; + + + + + + +// \since 1.0 +class NOVTABLE mainmenu_node : public service_base { + FB2K_MAKE_SERVICE_INTERFACE(mainmenu_node, service_base) +public: + enum { //same as contextmenu_item_node::t_type + type_group,type_command,type_separator + }; + + virtual t_uint32 get_type() = 0; + virtual void get_display(pfc::string_base & text, t_uint32 & flags) = 0; + //! Valid only if type is type_group. + virtual t_size get_children_count() = 0; + //! Valid only if type is type_group. + virtual ptr get_child(t_size index) = 0; + //! Valid only if type is type_command. + virtual void execute(service_ptr_t callback) = 0; + //! Valid only if type is type_command. + virtual GUID get_guid() = 0; + //! Valid only if type is type_command. + virtual bool get_description(pfc::string_base & out) {return false;} + +}; + +class mainmenu_node_separator : public mainmenu_node { +public: + t_uint32 get_type() {return type_separator;} + void get_display(pfc::string_base & text, t_uint32 & flags) {text = ""; flags = 0;} + t_size get_children_count() {return 0;} + ptr get_child(t_size index) {throw pfc::exception_invalid_params();} + void execute(service_ptr_t) {} + GUID get_guid() {return pfc::guid_null;} +}; + +class mainmenu_node_command : public mainmenu_node { +public: + t_uint32 get_type() {return type_command;} + t_size get_children_count() {return 0;} + ptr get_child(t_size index) {throw pfc::exception_invalid_params();} +/* + void get_display(pfc::string_base & text, t_uint32 & flags); + void execute(service_ptr_t callback); + GUID get_guid(); + bool get_description(pfc::string_base & out) {return false;} +*/ +}; + +class mainmenu_node_group : public mainmenu_node { +public: + t_uint32 get_type() {return type_group;} + void execute(service_ptr_t callback) {} + GUID get_guid() {return pfc::guid_null;} +/* + void get_display(pfc::string_base & text, t_uint32 & flags); + t_size get_children_count(); + ptr get_child(t_size index); +*/ +}; + + +// \since 1.0 +class NOVTABLE mainmenu_commands_v2 : public mainmenu_commands { + FB2K_MAKE_SERVICE_INTERFACE(mainmenu_commands_v2, mainmenu_commands) +public: + virtual bool is_command_dynamic(t_uint32 index) = 0; + //! Valid only when is_command_dynamic() returns true. Behavior undefined otherwise. + virtual mainmenu_node::ptr dynamic_instantiate(t_uint32 index) = 0; + //! Default fallback implementation provided. + virtual bool dynamic_execute(t_uint32 index, const GUID & subID, service_ptr_t callback); +}; diff --git a/foobar2000/SDK/menu_helpers.cpp b/foobar2000/SDK/menu_helpers.cpp new file mode 100644 index 0000000..79ac50b --- /dev/null +++ b/foobar2000/SDK/menu_helpers.cpp @@ -0,0 +1,295 @@ +#include "foobar2000.h" + + +bool menu_helpers::context_get_description(const GUID& p_guid,pfc::string_base & out) { + service_ptr_t ptr; t_uint32 index; + if (!menu_item_resolver::g_resolve_context_command(p_guid, ptr, index)) return false; + bool rv = ptr->get_item_description(index, out); + if (!rv) out.reset(); + return rv; +} + +static bool run_context_command_internal(const GUID & p_command,const GUID & p_subcommand,const pfc::list_base_const_t & data,const GUID & caller) { + if (data.get_count() == 0) return false; + service_ptr_t ptr; t_uint32 index; + if (!menu_item_resolver::g_resolve_context_command(p_command, ptr, index)) return false; + + { + TRACK_CALL_TEXT("menu_helpers::run_command(), by GUID"); + ptr->item_execute_simple(index, p_subcommand, data, caller); + } + + return true; +} + +bool menu_helpers::run_command_context(const GUID & p_command,const GUID & p_subcommand,const pfc::list_base_const_t & data) +{ + return run_context_command_internal(p_command,p_subcommand,data,contextmenu_item::caller_undefined); +} + +bool menu_helpers::run_command_context_ex(const GUID & p_command,const GUID & p_subcommand,const pfc::list_base_const_t & data,const GUID & caller) +{ + return run_context_command_internal(p_command,p_subcommand,data,caller); +} + +bool menu_helpers::test_command_context(const GUID & p_guid) +{ + service_ptr_t ptr; t_uint32 index; + return menu_item_resolver::g_resolve_context_command(p_guid, ptr, index); +} + +static bool g_is_checked(const GUID & p_command,const GUID & p_subcommand,const pfc::list_base_const_t & data,const GUID & caller) +{ + service_ptr_t ptr; t_uint32 index; + if (!menu_item_resolver::g_resolve_context_command(p_command, ptr, index)) return false; + + unsigned displayflags = 0; + pfc::string_formatter dummystring; + if (!ptr->item_get_display_data(dummystring,displayflags,index,p_subcommand,data,caller)) return false; + return (displayflags & contextmenu_item_node::FLAG_CHECKED) != 0; + +} + +bool menu_helpers::is_command_checked_context(const GUID & p_command,const GUID & p_subcommand,const pfc::list_base_const_t & data) +{ + return g_is_checked(p_command,p_subcommand,data,contextmenu_item::caller_undefined); +} + +bool menu_helpers::is_command_checked_context_playlist(const GUID & p_command,const GUID & p_subcommand) +{ + metadb_handle_list temp; + playlist_manager::get()->activeplaylist_get_selected_items(temp); + return g_is_checked(p_command,p_subcommand,temp,contextmenu_item::caller_playlist); +} + + + + + + + +bool menu_helpers::run_command_context_playlist(const GUID & p_command,const GUID & p_subcommand) +{ + metadb_handle_list temp; + playlist_manager::get()->activeplaylist_get_selected_items(temp); + return run_command_context_ex(p_command,p_subcommand,temp,contextmenu_item::caller_playlist); +} + +bool menu_helpers::run_command_context_now_playing(const GUID & p_command,const GUID & p_subcommand) +{ + metadb_handle_ptr item; + if (!playback_control::get()->get_now_playing(item)) return false;//not playing + return run_command_context_ex(p_command,p_subcommand,pfc::list_single_ref_t(item),contextmenu_item::caller_now_playing); +} + + +bool menu_helpers::guid_from_name(const char * p_name,unsigned p_name_len,GUID & p_out) +{ + service_enum_t e; + service_ptr_t ptr; + pfc::string8_fastalloc nametemp; + while(e.next(ptr)) + { + unsigned n, m = ptr->get_num_items(); + for(n=0;nget_item_name(n,nametemp); + if (!strcmp_ex(nametemp,~0,p_name,p_name_len)) + { + p_out = ptr->get_item_guid(n); + return true; + } + } + } + return false; +} + +bool menu_helpers::name_from_guid(const GUID & p_guid,pfc::string_base & p_out) { + service_ptr_t ptr; t_uint32 index; + if (!menu_item_resolver::g_resolve_context_command(p_guid, ptr, index)) return false; + ptr->get_item_name(index, p_out); + return true; +} + + +static unsigned calc_total_action_count() +{ + service_enum_t e; + service_ptr_t ptr; + unsigned ret = 0; + while(e.next(ptr)) + ret += ptr->get_num_items(); + return ret; +} + + +const char * menu_helpers::guid_to_name_table::search(const GUID & p_guid) +{ + if (!m_inited) + { + m_data.set_size(calc_total_action_count()); + t_size dataptr = 0; + pfc::string8_fastalloc nametemp; + + service_enum_t e; + service_ptr_t ptr; + while(e.next(ptr)) + { + unsigned n, m = ptr->get_num_items(); + for(n=0;nget_item_name(n,nametemp); + m_data[dataptr].m_name = _strdup(nametemp); + m_data[dataptr].m_guid = ptr->get_item_guid(n); + dataptr++; + } + } + assert(dataptr == m_data.get_size()); + + pfc::sort_t(m_data,entry_compare,m_data.get_size()); + m_inited = true; + } + t_size index; + if (pfc::bsearch_t(m_data.get_size(),m_data,entry_compare_search,p_guid,index)) + return m_data[index].m_name; + else + return 0; +} + +int menu_helpers::guid_to_name_table::entry_compare_search(const entry & entry1,const GUID & entry2) +{ + return pfc::guid_compare(entry1.m_guid,entry2); +} + +int menu_helpers::guid_to_name_table::entry_compare(const entry & entry1,const entry & entry2) +{ + return pfc::guid_compare(entry1.m_guid,entry2.m_guid); +} + +menu_helpers::guid_to_name_table::guid_to_name_table() +{ + m_inited = false; +} + +menu_helpers::guid_to_name_table::~guid_to_name_table() +{ + t_size n, m = m_data.get_size(); + for(n=0;n e; + service_ptr_t ptr; + while(e.next(ptr)) + { + unsigned n, m = ptr->get_num_items(); + for(n=0;nget_item_name(n,nametemp); + m_data[dataptr].m_name = _strdup(nametemp); + m_data[dataptr].m_guid = ptr->get_item_guid(n); + dataptr++; + } + } + assert(dataptr == m_data.get_size()); + + pfc::sort_t(m_data,entry_compare,m_data.get_size()); + m_inited = true; + } + t_size index; + search_entry temp = {p_name,p_name_len}; + if (pfc::bsearch_t(m_data.get_size(),m_data,entry_compare_search,temp,index)) + { + p_out = m_data[index].m_guid; + return true; + } + else + return false; +} + +menu_helpers::name_to_guid_table::name_to_guid_table() +{ + m_inited = false; +} + +menu_helpers::name_to_guid_table::~name_to_guid_table() +{ + t_size n, m = m_data.get_size(); + for(n=0;n & p_item,unsigned & p_index) +{ + pfc::string8_fastalloc path,name; + service_enum_t e; + service_ptr_t ptr; + if (e.first(ptr)) do { +// if (ptr->get_type()==type) + { + unsigned action,num_actions = ptr->get_num_items(); + for(action=0;actionget_item_default_path(action,path); ptr->get_item_name(action,name); + if (!path.is_empty()) path += "/"; + path += name; + if (!stricmp_utf8(p_name,path)) + { + p_item = ptr; + p_index = action; + return true; + } + } + } + } while(e.next(ptr)); + return false; + +} + +bool menu_helpers::find_command_by_name(const char * p_name,GUID & p_command) +{ + service_ptr_t item; + unsigned index; + bool ret = find_command_by_name(p_name,item,index); + if (ret) p_command = item->get_item_guid(index); + return ret; +} + + +bool standard_commands::run_main(const GUID & p_guid) { + t_uint32 index; + mainmenu_commands::ptr ptr; + if (!menu_item_resolver::g_resolve_main_command(p_guid, ptr, index)) return false; + ptr->execute(index,service_ptr_t()); + return true; +} + +bool menu_item_resolver::g_resolve_context_command(const GUID & id, contextmenu_item::ptr & out, t_uint32 & out_index) { + return menu_item_resolver::get()->resolve_context_command(id, out, out_index); +} +bool menu_item_resolver::g_resolve_main_command(const GUID & id, mainmenu_commands::ptr & out, t_uint32 & out_index) { + return menu_item_resolver::get()->resolve_main_command(id, out, out_index); +} diff --git a/foobar2000/SDK/menu_helpers.h b/foobar2000/SDK/menu_helpers.h new file mode 100644 index 0000000..08bcb13 --- /dev/null +++ b/foobar2000/SDK/menu_helpers.h @@ -0,0 +1,183 @@ +namespace menu_helpers { +#ifdef _WIN32 + void win32_auto_mnemonics(HMENU menu); +#endif + + bool run_command_context(const GUID & p_command,const GUID & p_subcommand,const pfc::list_base_const_t & data); + bool run_command_context_ex(const GUID & p_command,const GUID & p_subcommand,const pfc::list_base_const_t & data,const GUID & caller); + bool run_command_context_playlist(const GUID & p_command,const GUID & p_subcommand); + bool run_command_context_now_playing(const GUID & p_command,const GUID & p_subcommand); + + bool test_command_context(const GUID & p_guid); + + bool is_command_checked_context(const GUID & p_command,const GUID & p_subcommand,const pfc::list_base_const_t & data); + bool is_command_checked_context_playlist(const GUID & p_command,const GUID & p_subcommand); + + bool find_command_by_name(const char * p_name,service_ptr_t & p_item,unsigned & p_index); + bool find_command_by_name(const char * p_name,GUID & p_command); + + bool context_get_description(const GUID& p_guid,pfc::string_base & out); + + bool guid_from_name(const char * p_name,unsigned p_name_len,GUID & p_out); + bool name_from_guid(const GUID & p_guid,pfc::string_base & p_out); + + class guid_to_name_table + { + public: + guid_to_name_table(); + ~guid_to_name_table(); + const char * search(const GUID & p_guid); + private: + struct entry { + char* m_name; + GUID m_guid; + }; + pfc::array_t m_data; + bool m_inited; + + static int entry_compare_search(const entry & entry1,const GUID & entry2); + static int entry_compare(const entry & entry1,const entry & entry2); + }; + + class name_to_guid_table + { + public: + name_to_guid_table(); + ~name_to_guid_table(); + bool search(const char * p_name,unsigned p_name_len,GUID & p_out); + private: + struct entry { + char* m_name; + GUID m_guid; + }; + pfc::array_t m_data; + bool m_inited; + struct search_entry { + const char * m_name; unsigned m_name_len; + }; + + static int entry_compare_search(const entry & entry1,const search_entry & entry2); + static int entry_compare(const entry & entry1,const entry & entry2); + }; + +}; + + + +class standard_commands +{ +public: + static const GUID + guid_context_file_properties, guid_context_file_open_directory, guid_context_copy_names, + guid_context_send_to_playlist, guid_context_reload_info, guid_context_reload_info_if_changed, + guid_context_rewrite_info, guid_context_remove_tags, + guid_context_convert_run, guid_context_convert_run_singlefile,guid_context_convert_run_withcue, + guid_context_write_cd, + guid_context_rg_scan_track, guid_context_rg_scan_album, guid_context_rg_scan_album_multi, + guid_context_rg_remove, guid_context_save_playlist, guid_context_masstag_edit, + guid_context_masstag_rename, + guid_main_always_on_top, guid_main_preferences, guid_main_about, + guid_main_exit, guid_main_restart, guid_main_activate, + guid_main_hide, guid_main_activate_or_hide, guid_main_titleformat_help, + guid_main_next, guid_main_previous, + guid_main_next_or_random, guid_main_random, guid_main_pause, + guid_main_play, guid_main_play_or_pause, guid_main_rg_set_album, + guid_main_rg_set_track, guid_main_rg_disable, guid_main_rg_byorder, + guid_main_stop, + guid_main_stop_after_current, guid_main_volume_down, guid_main_volume_up, + guid_main_volume_mute, guid_main_add_directory, guid_main_add_files, + guid_main_add_location, guid_main_add_playlist, guid_main_clear_playlist, + guid_main_create_playlist, guid_main_highlight_playing, guid_main_load_playlist, + guid_main_next_playlist, guid_main_previous_playlist, guid_main_open, + guid_main_remove_playlist, guid_main_remove_dead_entries, guid_main_remove_duplicates, + guid_main_rename_playlist, guid_main_save_all_playlists, guid_main_save_playlist, + guid_main_playlist_search, guid_main_playlist_sel_crop, guid_main_playlist_sel_remove, + guid_main_playlist_sel_invert, guid_main_playlist_undo, guid_main_show_console, + guid_main_play_cd, guid_main_restart_resetconfig, guid_main_record, + guid_main_playlist_moveback, guid_main_playlist_moveforward, guid_main_playlist_redo, + guid_main_playback_follows_cursor, guid_main_cursor_follows_playback, guid_main_saveconfig, + guid_main_playlist_select_all, guid_main_show_now_playing, + + guid_seek_ahead_1s, guid_seek_ahead_5s, guid_seek_ahead_10s, guid_seek_ahead_30s, + guid_seek_ahead_1min, guid_seek_ahead_2min, guid_seek_ahead_5min, guid_seek_ahead_10min, + + guid_seek_back_1s, guid_seek_back_5s, guid_seek_back_10s, guid_seek_back_30s, + guid_seek_back_1min, guid_seek_back_2min, guid_seek_back_5min, guid_seek_back_10min + ; + + static bool run_main(const GUID & guid); + static inline bool run_context(const GUID & guid,const pfc::list_base_const_t &data) {return menu_helpers::run_command_context(guid,pfc::guid_null,data);} + static inline bool run_context(const GUID & guid,const pfc::list_base_const_t &data,const GUID& caller) {return menu_helpers::run_command_context_ex(guid,pfc::guid_null,data,caller);} + + static inline bool context_file_properties(const pfc::list_base_const_t &data,const GUID& caller = contextmenu_item::caller_undefined) {return run_context(guid_context_file_properties,data,caller);} + static inline bool context_file_open_directory(const pfc::list_base_const_t &data,const GUID& caller = contextmenu_item::caller_undefined) {return run_context(guid_context_file_open_directory,data,caller);} + static inline bool context_copy_names(const pfc::list_base_const_t &data,const GUID& caller = contextmenu_item::caller_undefined) {return run_context(guid_context_copy_names,data,caller);} + static inline bool context_send_to_playlist(const pfc::list_base_const_t &data,const GUID& caller = contextmenu_item::caller_undefined) {return run_context(guid_context_send_to_playlist,data,caller);} + static inline bool context_reload_info(const pfc::list_base_const_t &data,const GUID& caller = contextmenu_item::caller_undefined) {return run_context(guid_context_reload_info,data,caller);} + static inline bool context_reload_info_if_changed(const pfc::list_base_const_t &data,const GUID& caller = contextmenu_item::caller_undefined) {return run_context(guid_context_reload_info_if_changed,data,caller);} + static inline bool context_rewrite_info(const pfc::list_base_const_t &data,const GUID& caller = contextmenu_item::caller_undefined) {return run_context(guid_context_rewrite_info,data,caller);} + static inline bool context_remove_tags(const pfc::list_base_const_t &data,const GUID& caller = contextmenu_item::caller_undefined) {return run_context(guid_context_remove_tags,data,caller);} + static inline bool context_convert_run(const pfc::list_base_const_t &data,const GUID& caller = contextmenu_item::caller_undefined) {return run_context(guid_context_convert_run,data,caller);} + static inline bool context_convert_run_singlefile(const pfc::list_base_const_t &data,const GUID& caller = contextmenu_item::caller_undefined) {return run_context(guid_context_convert_run_singlefile,data,caller);} + static inline bool context_write_cd(const pfc::list_base_const_t &data,const GUID& caller = contextmenu_item::caller_undefined) {return run_context(guid_context_write_cd,data,caller);} + static inline bool context_rg_scan_track(const pfc::list_base_const_t &data,const GUID& caller = contextmenu_item::caller_undefined) {return run_context(guid_context_rg_scan_track,data,caller);} + static inline bool context_rg_scan_album(const pfc::list_base_const_t &data,const GUID& caller = contextmenu_item::caller_undefined) {return run_context(guid_context_rg_scan_album,data,caller);} + static inline bool context_rg_scan_album_multi(const pfc::list_base_const_t &data,const GUID& caller = contextmenu_item::caller_undefined) {return run_context(guid_context_rg_scan_album_multi,data,caller);} + static inline bool context_rg_remove(const pfc::list_base_const_t &data,const GUID& caller = contextmenu_item::caller_undefined) {return run_context(guid_context_rg_remove,data,caller);} + static inline bool context_save_playlist(const pfc::list_base_const_t &data,const GUID& caller = contextmenu_item::caller_undefined) {return run_context(guid_context_save_playlist,data,caller);} + static inline bool context_masstag_edit(const pfc::list_base_const_t &data,const GUID& caller = contextmenu_item::caller_undefined) {return run_context(guid_context_masstag_edit,data,caller);} + static inline bool context_masstag_rename(const pfc::list_base_const_t &data,const GUID& caller = contextmenu_item::caller_undefined) {return run_context(guid_context_masstag_rename,data,caller);} + static inline bool main_always_on_top() {return run_main(guid_main_always_on_top);} + static inline bool main_preferences() {return run_main(guid_main_preferences);} + static inline bool main_about() {return run_main(guid_main_about);} + static inline bool main_exit() {return run_main(guid_main_exit);} + static inline bool main_restart() {return run_main(guid_main_restart);} + static inline bool main_activate() {return run_main(guid_main_activate);} + static inline bool main_hide() {return run_main(guid_main_hide);} + static inline bool main_activate_or_hide() {return run_main(guid_main_activate_or_hide);} + static inline bool main_titleformat_help() {return run_main(guid_main_titleformat_help);} + static inline bool main_playback_follows_cursor() {return run_main(guid_main_playback_follows_cursor);} + static inline bool main_next() {return run_main(guid_main_next);} + static inline bool main_previous() {return run_main(guid_main_previous);} + static inline bool main_next_or_random() {return run_main(guid_main_next_or_random);} + static inline bool main_random() {return run_main(guid_main_random);} + static inline bool main_pause() {return run_main(guid_main_pause);} + static inline bool main_play() {return run_main(guid_main_play);} + static inline bool main_play_or_pause() {return run_main(guid_main_play_or_pause);} + static inline bool main_rg_set_album() {return run_main(guid_main_rg_set_album);} + static inline bool main_rg_set_track() {return run_main(guid_main_rg_set_track);} + static inline bool main_rg_disable() {return run_main(guid_main_rg_disable);} + static inline bool main_stop() {return run_main(guid_main_stop);} + static inline bool main_stop_after_current() {return run_main(guid_main_stop_after_current);} + static inline bool main_volume_down() {return run_main(guid_main_volume_down);} + static inline bool main_volume_up() {return run_main(guid_main_volume_up);} + static inline bool main_volume_mute() {return run_main(guid_main_volume_mute);} + static inline bool main_add_directory() {return run_main(guid_main_add_directory);} + static inline bool main_add_files() {return run_main(guid_main_add_files);} + static inline bool main_add_location() {return run_main(guid_main_add_location);} + static inline bool main_add_playlist() {return run_main(guid_main_add_playlist);} + static inline bool main_clear_playlist() {return run_main(guid_main_clear_playlist);} + static inline bool main_create_playlist() {return run_main(guid_main_create_playlist);} + static inline bool main_highlight_playing() {return run_main(guid_main_highlight_playing);} + static inline bool main_load_playlist() {return run_main(guid_main_load_playlist);} + static inline bool main_next_playlist() {return run_main(guid_main_next_playlist);} + static inline bool main_previous_playlist() {return run_main(guid_main_previous_playlist);} + static inline bool main_open() {return run_main(guid_main_open);} + static inline bool main_remove_playlist() {return run_main(guid_main_remove_playlist);} + static inline bool main_remove_dead_entries() {return run_main(guid_main_remove_dead_entries);} + static inline bool main_remove_duplicates() {return run_main(guid_main_remove_duplicates);} + static inline bool main_rename_playlist() {return run_main(guid_main_rename_playlist);} + static inline bool main_save_all_playlists() {return run_main(guid_main_save_all_playlists);} + static inline bool main_save_playlist() {return run_main(guid_main_save_playlist);} + static inline bool main_playlist_search() {return run_main(guid_main_playlist_search);} + static inline bool main_playlist_sel_crop() {return run_main(guid_main_playlist_sel_crop);} + static inline bool main_playlist_sel_remove() {return run_main(guid_main_playlist_sel_remove);} + static inline bool main_playlist_sel_invert() {return run_main(guid_main_playlist_sel_invert);} + static inline bool main_playlist_undo() {return run_main(guid_main_playlist_undo);} + static inline bool main_show_console() {return run_main(guid_main_show_console);} + static inline bool main_play_cd() {return run_main(guid_main_play_cd);} + static inline bool main_restart_resetconfig() {return run_main(guid_main_restart_resetconfig);} + static inline bool main_playlist_moveback() {return run_main(guid_main_playlist_moveback);} + static inline bool main_playlist_moveforward() {return run_main(guid_main_playlist_moveforward);} + static inline bool main_saveconfig() {return run_main(guid_main_saveconfig);} +}; diff --git a/foobar2000/SDK/menu_item.cpp b/foobar2000/SDK/menu_item.cpp new file mode 100644 index 0000000..da9485d --- /dev/null +++ b/foobar2000/SDK/menu_item.cpp @@ -0,0 +1,61 @@ +#include "foobar2000.h" + + + +bool contextmenu_item::item_get_display_data_root(pfc::string_base & p_out,unsigned & p_displayflags,unsigned p_index,const pfc::list_base_const_t & p_data,const GUID & p_caller) +{ + bool status = false; + pfc::ptrholder_t root ( instantiate_item(p_index,p_data,p_caller) ); + if (root.is_valid()) status = root->get_display_data(p_out,p_displayflags,p_data,p_caller); + return status; +} + + +static contextmenu_item_node * g_find_node(const GUID & p_guid,contextmenu_item_node * p_parent) +{ + if (p_parent->get_guid() == p_guid) return p_parent; + else if (p_parent->get_type() == contextmenu_item_node::TYPE_POPUP) + { + t_size n, m = p_parent->get_children_count(); + for(n=0;nget_child(n)); + if (temp) return temp; + } + return 0; + } + else return 0; +} + +bool contextmenu_item::item_get_display_data(pfc::string_base & p_out,unsigned & p_displayflags,unsigned p_index,const GUID & p_node,const pfc::list_base_const_t & p_data,const GUID & p_caller) +{ + bool status = false; + pfc::ptrholder_t root ( instantiate_item(p_index,p_data,p_caller) ); + if (root.is_valid()) + { + contextmenu_item_node * node = g_find_node(p_node,root.get_ptr()); + if (node) status = node->get_display_data(p_out,p_displayflags,p_data,p_caller); + } + return status; +} + + +GUID contextmenu_item::get_parent_fallback() { + unsigned total = get_num_items(); + if (total < 1) return pfc::guid_null; // hide the item + pfc::string_formatter path, base; this->get_item_default_path(0, base); + for(unsigned walk = 1; walk < total; ++walk) { + this->get_item_default_path(walk, path); + if (strcmp(path, base) != 0) return contextmenu_groups::legacy; + } + return contextmenu_group_manager::get()->path_to_group(base); +} + +GUID contextmenu_item::get_parent_() { + contextmenu_item_v2::ptr v2; + if (service_query_t(v2)) { + return v2->get_parent(); + } else { + return get_parent_fallback(); + } +} diff --git a/foobar2000/SDK/menu_manager.cpp b/foobar2000/SDK/menu_manager.cpp new file mode 100644 index 0000000..2e1b097 --- /dev/null +++ b/foobar2000/SDK/menu_manager.cpp @@ -0,0 +1,416 @@ +#include "foobar2000.h" + +#ifdef WIN32 + +#include "ui_element_typable_window_manager.h" + +static void fix_ampersand(const char * src,pfc::string_base & out) +{ + out.reset(); + unsigned ptr = 0; + while(src[ptr]) + { + if (src[ptr]=='&') + { + out.add_string("&&"); + ptr++; + while(src[ptr]=='&') + { + out.add_string("&&"); + ptr++; + } + } + else out.add_byte(src[ptr++]); + } +} + +static unsigned flags_to_win32(unsigned flags) +{ + unsigned ret = 0; + if (flags & contextmenu_item_node::FLAG_RADIOCHECKED) {/* dealt with elsewhere */} + else if (flags & contextmenu_item_node::FLAG_CHECKED) ret |= MF_CHECKED; + if (flags & contextmenu_item_node::FLAG_DISABLED) ret |= MF_DISABLED; + if (flags & contextmenu_item_node::FLAG_GRAYED) ret |= MF_GRAYED; + return ret; +} + +void contextmenu_manager::win32_build_menu(HMENU menu,contextmenu_node * parent,int base_id,int max_id)//menu item identifiers are base_id<=Nget_type()==contextmenu_item_node::TYPE_POPUP) + { + pfc::string8_fastalloc temp; + t_size child_idx,child_num = parent->get_num_children(); + for(child_idx=0;child_idxget_child(child_idx); + if (child) + { + const char * name = child->get_name(); + if (strchr(name,'&')) {fix_ampersand(name,temp);name=temp;} + contextmenu_item_node::t_type type = child->get_type(); + if (type==contextmenu_item_node::TYPE_POPUP) + { + HMENU new_menu = CreatePopupMenu(); + uAppendMenu(menu,MF_STRING|MF_POPUP | flags_to_win32(child->get_display_flags()),(UINT_PTR)new_menu,name); + win32_build_menu(new_menu,child,base_id,max_id); + } + else if (type==contextmenu_item_node::TYPE_SEPARATOR) + { + uAppendMenu(menu,MF_SEPARATOR,0,0); + } + else if (type==contextmenu_item_node::TYPE_COMMAND) + { + int id = child->get_id(); + if (id>=0 && (max_id<0 || idget_display_flags(); + const UINT ID = base_id+id; + uAppendMenu(menu,MF_STRING | flags_to_win32(flags),ID,name); + if (flags & contextmenu_item_node::FLAG_RADIOCHECKED) CheckMenuRadioItem(menu,ID,ID,ID,MF_BYCOMMAND); + } + } + } + } + } +} + +#endif + +bool contextmenu_manager::get_description_by_id(unsigned id,pfc::string_base & out) { + contextmenu_node * ptr = find_by_id(id); + if (ptr == NULL) return false; + return ptr->get_description(out); +} +bool contextmenu_manager::execute_by_id(unsigned id) { + contextmenu_node * ptr = find_by_id(id); + if (ptr == NULL) return false; + ptr->execute(); + return true; +} + +#ifdef WIN32 + +void contextmenu_manager::win32_run_menu_popup(HWND parent,const POINT * pt) +{ + enum {ID_CUSTOM_BASE = 1}; + + int cmd; + + { + POINT p; + if (pt) p = *pt; + else GetCursorPos(&p); + + HMENU hmenu = CreatePopupMenu(); + try { + + win32_build_menu(hmenu,ID_CUSTOM_BASE,-1); + menu_helpers::win32_auto_mnemonics(hmenu); + + cmd = TrackPopupMenu(hmenu,TPM_RIGHTBUTTON|TPM_NONOTIFY|TPM_RETURNCMD,p.x,p.y,0,parent,0); + } catch(...) {DestroyMenu(hmenu); throw;} + + DestroyMenu(hmenu); + } + + if (cmd>0) + { + if (cmd>=ID_CUSTOM_BASE) + { + execute_by_id(cmd - ID_CUSTOM_BASE); + } + } +} + +void contextmenu_manager::win32_run_menu_context(HWND parent,const pfc::list_base_const_t & data,const POINT * pt,unsigned flags) +{ + service_ptr_t manager; + contextmenu_manager::g_create(manager); + manager->init_context(data,flags); + manager->win32_run_menu_popup(parent,pt); +} + +void contextmenu_manager::win32_run_menu_context_playlist(HWND parent,const POINT * pt,unsigned flags) +{ + service_ptr_t manager; + contextmenu_manager::g_create(manager); + manager->init_context_playlist(flags); + manager->win32_run_menu_popup(parent,pt); +} + + +namespace { + class mnemonic_manager + { + pfc::string8_fastalloc used; + bool is_used(unsigned c) + { + char temp[8]; + temp[pfc::utf8_encode_char(uCharLower(c),temp)]=0; + return !!strstr(used,temp); + } + + static bool is_alphanumeric(char c) + { + return (c>='a' && c<='z') || (c>='A' && c<='Z') || (c>='0' && c<='9'); + } + + + + + void insert(const char * src,unsigned idx,pfc::string_base & out) + { + out.reset(); + out.add_string(src,idx); + out.add_string("&"); + out.add_string(src+idx); + used.add_char(uCharLower(src[idx])); + } + public: + bool check_string(const char * src) + {//check for existing mnemonics + const char * ptr = src; + while(ptr = strchr(ptr,'&')) + { + if (ptr[1]=='&') ptr+=2; + else + { + unsigned c = 0; + if (pfc::utf8_decode_char(ptr+1,c)>0) + { + if (!is_used(c)) used.add_char(uCharLower(c)); + } + return true; + } + } + return false; + } + bool process_string(const char * src,pfc::string_base & out)//returns if changed + { + if (check_string(src)) {out=src;return false;} + unsigned idx=0; + while(src[idx]==' ') idx++; + while(src[idx]) + { + if (is_alphanumeric(src[idx]) && !is_used(src[idx])) + { + insert(src,idx,out); + return true; + } + + while(src[idx] && src[idx]!=' ' && src[idx]!='\t') idx++; + if (src[idx]=='\t') break; + while(src[idx]==' ') idx++; + } + + //no success picking first letter of one of words + idx=0; + while(src[idx]) + { + if (src[idx]=='\t') break; + if (is_alphanumeric(src[idx]) && !is_used(src[idx])) + { + insert(src,idx,out); + return true; + } + idx++; + } + + //giving up + out = src; + return false; + } + }; +} + +void menu_helpers::win32_auto_mnemonics(HMENU menu) +{ + mnemonic_manager mgr; + unsigned n, m = GetMenuItemCount(menu); + pfc::string8_fastalloc temp,temp2; + for(n=0;n & data,WPARAM wp,const GUID & caller) +{ + if (data.get_count()==0) return false; + return process_keydown_ex(TYPE_CONTEXT,data,get_key_code(wp),caller); +} + +bool keyboard_shortcut_manager::on_keydown_auto(WPARAM wp) +{ + if (on_keydown(TYPE_MAIN,wp)) return true; + if (on_keydown(TYPE_CONTEXT_PLAYLIST,wp)) return true; + if (on_keydown(TYPE_CONTEXT_NOW_PLAYING,wp)) return true; + return false; +} + +bool keyboard_shortcut_manager::on_keydown_auto_playlist(WPARAM wp) +{ + metadb_handle_list data; + playlist_manager::get()->activeplaylist_get_selected_items(data); + return on_keydown_auto_context(data,wp,contextmenu_item::caller_playlist); +} + +bool keyboard_shortcut_manager::on_keydown_auto_context(const pfc::list_base_const_t & data,WPARAM wp,const GUID & caller) +{ + if (on_keydown_context(data,wp,caller)) return true; + else return on_keydown_auto(wp); +} + +static bool should_relay_key_restricted(UINT p_key) { + switch(p_key) { + case VK_LEFT: + case VK_RIGHT: + case VK_UP: + case VK_DOWN: + return false; + default: + return (p_key >= VK_F1 && p_key <= VK_F24) || IsKeyPressed(VK_CONTROL) || IsKeyPressed(VK_LWIN) || IsKeyPressed(VK_RWIN); + } +} + +bool keyboard_shortcut_manager::on_keydown_restricted_auto(WPARAM wp) { + if (!should_relay_key_restricted(wp)) return false; + return on_keydown_auto(wp); +} +bool keyboard_shortcut_manager::on_keydown_restricted_auto_playlist(WPARAM wp) { + if (!should_relay_key_restricted(wp)) return false; + return on_keydown_auto_playlist(wp); +} +bool keyboard_shortcut_manager::on_keydown_restricted_auto_context(const pfc::list_base_const_t & data,WPARAM wp,const GUID & caller) { + if (!should_relay_key_restricted(wp)) return false; + return on_keydown_auto_context(data,wp,caller); +} + +static bool filterTypableWindowMessage(const MSG * msg, t_uint32 modifiers) { + if (keyboard_shortcut_manager::is_typing_key_combo((t_uint32)msg->wParam, modifiers)) { + const DWORD mask = DLGC_HASSETSEL | DLGC_WANTCHARS | DLGC_WANTARROWS; + auto status = ::SendMessage(msg->hwnd, WM_GETDLGCODE,0, 0); + if ( (status & mask) == mask ) return false; + + ui_element_typable_window_manager::ptr api; + if (ui_element_typable_window_manager::tryGet(api)) { + if (api->is_registered(msg->hwnd)) return false; + } + } + return true; +} + +bool keyboard_shortcut_manager_v2::pretranslate_message(const MSG * msg, HWND thisPopupWnd) { + switch(msg->message) { + case WM_KEYDOWN: + case WM_SYSKEYDOWN: + if (thisPopupWnd != NULL && FindOwningPopup(msg->hwnd) == thisPopupWnd) { + const t_uint32 modifiers = GetHotkeyModifierFlags(); + if (filterTypableWindowMessage(msg, modifiers)) { + if (process_keydown_simple(get_key_code(msg->wParam,modifiers))) return true; + } + } + return false; + default: + return false; + } +} + +bool keyboard_shortcut_manager::is_text_key(t_uint32 vkCode) { + return vkCode == VK_SPACE + || (vkCode >= '0' && vkCode < 0x40) + || (vkCode > 0x40 && vkCode < VK_LWIN) + || (vkCode >= VK_NUMPAD0 && vkCode <= VK_DIVIDE) + || (vkCode >= VK_OEM_1 && vkCode <= VK_OEM_3) + || (vkCode >= VK_OEM_4 && vkCode <= VK_OEM_8) + ; +} + +bool keyboard_shortcut_manager::is_typing_key(t_uint32 vkCode) { + return is_text_key(vkCode) + || vkCode == VK_BACK + || vkCode == VK_RETURN + || vkCode == VK_INSERT + || (vkCode > VK_SPACE && vkCode < '0'); +} + +bool keyboard_shortcut_manager::is_typing_key_combo(t_uint32 vkCode, t_uint32 modifiers) { + if (!is_typing_modifier(modifiers)) return false; + return is_typing_key(vkCode); +} + +bool keyboard_shortcut_manager::is_typing_modifier(t_uint32 flags) { + flags &= ~MOD_SHIFT; + return flags == 0 || flags == (MOD_ALT | MOD_CONTROL); +} + +bool keyboard_shortcut_manager::is_typing_message(HWND editbox, const MSG * msg) { + if (msg->hwnd != editbox) return false; + return is_typing_message(msg); +} +bool keyboard_shortcut_manager::is_typing_message(const MSG * msg) { + if (msg->message != WM_KEYDOWN && msg->message != WM_SYSKEYDOWN) return false; + return is_typing_key_combo(msg->wParam, GetHotkeyModifierFlags()); +} diff --git a/foobar2000/SDK/message_loop.h b/foobar2000/SDK/message_loop.h new file mode 100644 index 0000000..9d21ddd --- /dev/null +++ b/foobar2000/SDK/message_loop.h @@ -0,0 +1,91 @@ +class NOVTABLE message_filter +{ +public: + virtual bool pretranslate_message(MSG * p_msg) = 0; +}; + +class NOVTABLE idle_handler { +public: + virtual bool on_idle() = 0; +}; + +class NOVTABLE message_loop : public service_base +{ +public: + virtual void add_message_filter(message_filter * ptr) = 0; + virtual void remove_message_filter(message_filter * ptr) = 0; + + virtual void add_idle_handler(idle_handler * ptr) = 0; + virtual void remove_idle_handler(idle_handler * ptr) = 0; + + FB2K_MAKE_SERVICE_COREAPI(message_loop); +}; + +class NOVTABLE message_loop_v2 : public message_loop { +public: + virtual void add_message_filter_ex(message_filter * ptr, t_uint32 lowest, t_uint32 highest) = 0; + + FB2K_MAKE_SERVICE_COREAPI_EXTENSION(message_loop_v2, message_loop); +}; + +class message_filter_impl_base : public message_filter { +public: + message_filter_impl_base() {message_loop::get()->add_message_filter(this);} + message_filter_impl_base(t_uint32 lowest, t_uint32 highest) { + message_loop_v2::get()->add_message_filter_ex(this, lowest, highest); + } + ~message_filter_impl_base() {message_loop::get()->remove_message_filter(this);} + bool pretranslate_message(MSG * p_msg) {return false;} + + PFC_CLASS_NOT_COPYABLE(message_filter_impl_base,message_filter_impl_base); +}; + +class message_filter_impl_accel : public message_filter_impl_base { +protected: + bool pretranslate_message(MSG * p_msg) { + if (m_wnd != NULL) { + if (GetActiveWindow() == m_wnd) { + if (TranslateAccelerator(m_wnd,m_accel.get(),p_msg) != 0) { + return true; + } + } + } + return false; + } +public: + message_filter_impl_accel(HINSTANCE p_instance,const TCHAR * p_accel) : m_wnd(NULL) { + m_accel.load(p_instance,p_accel); + } + void set_wnd(HWND p_wnd) {m_wnd = p_wnd;} +private: + HWND m_wnd; + win32_accelerator m_accel; + + PFC_CLASS_NOT_COPYABLE(message_filter_impl_accel,message_filter_impl_accel); +}; + +class message_filter_remap_f1 : public message_filter_impl_base { +public: + bool pretranslate_message(MSG * p_msg) { + if (IsOurMsg(p_msg) && m_wnd != NULL && GetActiveWindow() == m_wnd) { + ::PostMessage(m_wnd, WM_SYSCOMMAND, SC_CONTEXTHELP, -1); + return true; + } + return false; + } + void set_wnd(HWND wnd) {m_wnd = wnd;} +private: + static bool IsOurMsg(const MSG * msg) { + return msg->message == WM_KEYDOWN && msg->wParam == VK_F1; + } + HWND m_wnd; +}; + +class idle_handler_impl_base : public idle_handler { +public: + idle_handler_impl_base() {message_loop::get()->add_idle_handler(this);} + ~idle_handler_impl_base() { message_loop::get()->remove_idle_handler(this);} + bool on_idle() {return true;} + + PFC_CLASS_NOT_COPYABLE_EX(idle_handler_impl_base) +}; diff --git a/foobar2000/SDK/metadb.cpp b/foobar2000/SDK/metadb.cpp new file mode 100644 index 0000000..99cea1e --- /dev/null +++ b/foobar2000/SDK/metadb.cpp @@ -0,0 +1,112 @@ +#include "foobar2000.h" +#include "file_info_filter_impl.h" + +void metadb::handle_create_replace_path_canonical(metadb_handle_ptr & p_out,const metadb_handle_ptr & p_source,const char * p_new_path) { + handle_create(p_out,make_playable_location(p_new_path,p_source->get_subsong_index())); +} + +void metadb::handle_create_replace_path(metadb_handle_ptr & p_out,const metadb_handle_ptr & p_source,const char * p_new_path) { + pfc::string8 path; + filesystem::g_get_canonical_path(p_new_path,path); + handle_create_replace_path_canonical(p_out,p_source,path); +} + +void metadb::handle_replace_path_canonical(metadb_handle_ptr & p_out,const char * p_new_path) { + metadb_handle_ptr temp; + handle_create_replace_path_canonical(temp,p_out,p_new_path); + p_out = temp; +} + + +metadb_io::t_load_info_state metadb_io::load_info(metadb_handle_ptr p_item,t_load_info_type p_type,HWND p_parent_window,bool p_show_errors) { + return load_info_multi(pfc::list_single_ref_t(p_item),p_type,p_parent_window,p_show_errors); +} + +metadb_io::t_update_info_state metadb_io::update_info(metadb_handle_ptr p_item,file_info & p_info,HWND p_parent_window,bool p_show_errors) +{ + file_info * blah = &p_info; + return update_info_multi(pfc::list_single_ref_t(p_item),pfc::list_single_ref_t(blah),p_parent_window,p_show_errors); +} + + +void metadb_io::hint_async(metadb_handle_ptr p_item,const file_info & p_info,const t_filestats & p_stats,bool p_fresh) +{ + const file_info * blargh = &p_info; + hint_multi_async(pfc::list_single_ref_t(p_item),pfc::list_single_ref_t(blargh),pfc::list_single_ref_t(p_stats), pfc::bit_array_val(p_fresh)); +} + + +bool metadb::g_get_random_handle(metadb_handle_ptr & p_out) { + if (playback_control::get()->get_now_playing(p_out)) return true; + + { + auto api = playlist_manager::get(); + + t_size playlist_count = api->get_playlist_count(); + t_size active_playlist = api->get_active_playlist(); + if (active_playlist != ~0) { + if (api->playlist_get_focus_item_handle(p_out,active_playlist)) return true; + } + + for(t_size n = 0; n < playlist_count; n++) { + if (api->playlist_get_focus_item_handle(p_out,n)) return true; + } + + if (active_playlist != ~0) { + t_size item_count = api->playlist_get_item_count(active_playlist); + if (item_count > 0) { + if (api->playlist_get_item_handle(p_out,active_playlist,0)) return true; + } + } + + for(t_size n = 0; n < playlist_count; n++) { + t_size item_count = api->playlist_get_item_count(n); + if (item_count > 0) { + if (api->playlist_get_item_handle(p_out,n,0)) return true; + } + } + } + + return false; +} + + +void metadb_io_v2::update_info_async_simple(const pfc::list_base_const_t & p_list,const pfc::list_base_const_t & p_new_info, HWND p_parent_window,t_uint32 p_op_flags,completion_notify_ptr p_notify) { + update_info_async(p_list,new service_impl_t(p_list,p_new_info),p_parent_window,p_op_flags,p_notify); +} + +void metadb_io_v2::on_file_rechaptered( const char * path, metadb_handle_list_cref newItems ) { + metadb_handle_list handles( newItems ); + pfc::string8 pathLocal( path ); + auto notify = fb2k::makeCompletionNotify( [handles, pathLocal] (unsigned) { + playlist_manager::get()->on_file_rechaptered( pathLocal, handles ); + } ); + + load_info_async( handles, metadb_io::load_info_force, core_api::get_main_window(), metadb_io_v3::op_flag_delay_ui, notify ); +} + +void metadb_io_v2::on_files_rechaptered( metadb_handle_list_cref newHandles ) { + metadb_handle_list local ( newHandles ); + auto notify = fb2k::makeCompletionNotify( [local] (unsigned) { + playlist_manager::get()->on_files_rechaptered(local); + } ); + + load_info_async( newHandles, metadb_io::load_info_force, core_api::get_main_window(), metadb_io_v3::op_flag_delay_ui, notify ); +} + + +metadb_hint_list::ptr metadb_hint_list::create() { + return metadb_io_v2::get()->create_hint_list(); +} + +metadb_hint_list_v2::ptr metadb_hint_list_v2::create() { + metadb_hint_list_v2::ptr ret; + ret ^= metadb_hint_list::create(); + return ret; +} + +metadb_hint_list_v3::ptr metadb_hint_list_v3::create() { + metadb_hint_list_v3::ptr ret; + ret ^= metadb_hint_list::create(); + return ret; +} diff --git a/foobar2000/SDK/metadb.h b/foobar2000/SDK/metadb.h new file mode 100644 index 0000000..47c0d59 --- /dev/null +++ b/foobar2000/SDK/metadb.h @@ -0,0 +1,447 @@ +#pragma once + + +class file_info_filter; // forward decl; file_info_filter moved to file_info_filter.h + + +//! API for tag read/write operations. Legal to call from main thread only, except for hint_multi_async() / hint_async() / hint_reader().\n +//! Implemented only by core, do not reimplement.\n +//! Use static_api_ptr_t template to access metadb_io methods.\n +//! WARNING: Methods that perform file access (tag reads/writes) run a modal message loop. They SHOULD NOT be called from global callbacks and such. +class NOVTABLE metadb_io : public service_base +{ +public: + enum t_load_info_type { + load_info_default, + load_info_force, + load_info_check_if_changed + }; + + enum t_update_info_state { + update_info_success, + update_info_aborted, + update_info_errors, + }; + + enum t_load_info_state { + load_info_success, + load_info_aborted, + load_info_errors, + }; + + //! No longer used - returns false always. + __declspec(deprecated) virtual bool is_busy() = 0; + //! No longer used - returns false always. + __declspec(deprecated) virtual bool is_updating_disabled() = 0; + //! No longer used - returns false always. + __declspec(deprecated) virtual bool is_file_updating_blocked() = 0; + //! No longer used. + __declspec(deprecated) virtual void highlight_running_process() = 0; + //! Loads tags from multiple items. Use the async version in metadb_io_v2 instead if possible. + __declspec(deprecated) virtual t_load_info_state load_info_multi(metadb_handle_list_cref p_list,t_load_info_type p_type,HWND p_parent_window,bool p_show_errors) = 0; + //! Updates tags on multiple items. Use the async version in metadb_io_v2 instead if possible. + __declspec(deprecated) virtual t_update_info_state update_info_multi(metadb_handle_list_cref p_list,const pfc::list_base_const_t & p_new_info,HWND p_parent_window,bool p_show_errors) = 0; + //! Rewrites tags on multiple items. Use the async version in metadb_io_v2 instead if possible. + __declspec(deprecated) virtual t_update_info_state rewrite_info_multi(metadb_handle_list_cref p_list,HWND p_parent_window,bool p_show_errors) = 0; + //! Removes tags from multiple items. Use the async version in metadb_io_v2 instead if possible. + __declspec(deprecated) virtual t_update_info_state remove_info_multi(metadb_handle_list_cref p_list,HWND p_parent_window,bool p_show_errors) = 0; + + virtual void hint_multi(metadb_handle_list_cref p_list,const pfc::list_base_const_t & p_infos,const pfc::list_base_const_t & p_stats,const bit_array & p_fresh_mask) = 0; + + virtual void hint_multi_async(metadb_handle_list_cref p_list,const pfc::list_base_const_t & p_infos,const pfc::list_base_const_t & p_stats,const bit_array & p_fresh_mask) = 0; + + virtual void hint_reader(service_ptr_t p_reader,const char * p_path,abort_callback & p_abort) = 0; + + //! For internal use only. + virtual void path_to_handles_simple(const char * p_path, metadb_handle_list_ref p_out) = 0; + + //! Dispatches metadb_io_callback calls with specified items. To be used with metadb_display_field_provider when your component needs specified items refreshed. + virtual void dispatch_refresh(metadb_handle_list_cref p_list) = 0; + + void dispatch_refresh(metadb_handle_ptr const & handle) {dispatch_refresh(pfc::list_single_ref_t(handle));} + + void hint_async(metadb_handle_ptr p_item,const file_info & p_info,const t_filestats & p_stats,bool p_fresh); + + __declspec(deprecated) t_load_info_state load_info(metadb_handle_ptr p_item,t_load_info_type p_type,HWND p_parent_window,bool p_show_errors); + __declspec(deprecated) t_update_info_state update_info(metadb_handle_ptr p_item,file_info & p_info,HWND p_parent_window,bool p_show_errors); + + FB2K_MAKE_SERVICE_COREAPI(metadb_io); +}; + +//! Advanced interface for passing infos read from files to metadb backend. Use metadb_io_v2::create_hint_list() to instantiate. \n +//! Thread safety: all methods other than on_done() are intended for worker threads. Instantiate and use the object in a worker thread, call on_done() in main thread to finalize. \n +//! Typical usage pattern: create a hint list (in any thread), hand infos to it from files that you work with (in a worker thread), call on_done() in main thread. \n +class NOVTABLE metadb_hint_list : public service_base { + FB2K_MAKE_SERVICE_INTERFACE(metadb_hint_list,service_base); +public: + //! Helper. + static metadb_hint_list::ptr create(); + //! Adds a hint to the list. + //! @param p_location Location of the item the hint applies to. + //! @param p_info file_info object describing the item. + //! @param p_stats Information about the file containing item the hint applies to. + //! @param p_freshflag Set to true if the info has been directly read from the file, false if it comes from another source such as a playlist file. + virtual void add_hint(metadb_handle_ptr const & p_location,const file_info & p_info,const t_filestats & p_stats,bool p_freshflag) = 0; + //! Reads info from specified info reader instance and adds hints. May throw an exception in case info read has failed. \n + //! If the file has multiple subsongs, info from all the subsongs will be read and pssed to add_hint(). \n + //! Note that an input_info_writer is a subclass of input_info_reader - so any input_info_reader OR input_info_writer is a valid argument for add_hint_reader(). \n + //! This method is often called with your input_info_writer instance after committing tag updates, to notify metadb about altered tags. + virtual void add_hint_reader(const char * p_path,service_ptr_t const & p_reader,abort_callback & p_abort) = 0; + //! Call this when you're done working with this metadb_hint_list instance, to apply hints and dispatch callbacks. \n + //! If you don't call this, all added hints will be ignored. \n + //! As a general rule, you should add as many infos as possible - such as all the tracks involved in some operation that you perform - then call on_done() once. \n + //! on_done() is expensive because it not only updates the metadb, but tells all components about the changes made - refreshes playlists/autoplaylists, library viewers, etc. \n + //! Calling on_done() repeatedly is inefficient and should be avoided. + virtual void on_done() = 0; +}; + +//! \since 1.0 +//! To obtain metadb_hint_list_v2, use service_query on a metadb_hint_list object. \n +//! Simplified: metadb_hint_list_v2::ptr v2; v2 ^= myHintList; ( causes bugcheck if old fb2k / no interface ). +class NOVTABLE metadb_hint_list_v2 : public metadb_hint_list { + FB2K_MAKE_SERVICE_INTERFACE(metadb_hint_list_v2, metadb_hint_list); +public: + //! Helper. + static metadb_hint_list_v2::ptr create(); + //! Hint with browse info. \n + //! See: metadb_handle::get_browse_info() for browse info rationale. + //! @param p_location Location for which we're providing browse info. + //! @param p_info Browse info for this location. + //! @param browseTS timestamp of the browse info - such as last-modified time of the playlist file providing browse info. + virtual void add_hint_browse(metadb_handle_ptr const & p_location,const file_info & p_info, t_filetimestamp browseTS) = 0; +}; + +//! \since 1.3 +//! To obtain metadb_hint_list_v3, use service_query on a metadb_hint_list object. \n +//! Simplified: metadb_hint_list_v3::ptr v3; v3 ^= myHintList; ( causes bugcheck if old fb2k / no interface ). +class NOVTABLE metadb_hint_list_v3 : public metadb_hint_list_v2 { + FB2K_MAKE_SERVICE_INTERFACE(metadb_hint_list_v3, metadb_hint_list_v2); +public: + //! Helper. + static metadb_hint_list_v3::ptr create(); + //! Hint primary info with a metadb_info_container. + virtual void add_hint_v3(metadb_handle_ptr const & p_location, metadb_info_container::ptr info,bool p_freshflag) = 0; + //! Hint browse info with a metadb_info_container. + virtual void add_hint_browse_v3(metadb_handle_ptr const & p_location,metadb_info_container::ptr info) = 0; + + //! Add a forced hint.\n + //! A normal hint may or may not cause metadb update - metadb is not updated if the file has not changed according to last modified time. \n + //! A forced hint always updates metadb regardless of timestamps. + virtual void add_hint_forced(metadb_handle_ptr const & p_location, const file_info & p_info,const t_filestats & p_stats,bool p_freshflag) = 0; + //! Add a forced hint, with metadb_info_container. \n + //! Forced hint rationale - see add_hint_forced(). + virtual void add_hint_forced_v3(metadb_handle_ptr const & p_location, metadb_info_container::ptr info,bool p_freshflag) = 0; + //! Adds a forced hint, with an input_info_reader. \n + //! Forced hint rationale - see add_hint_forced(). \n + //! Info reader use rationale - see add_hint_reader(). + virtual void add_hint_forced_reader(const char * p_path,service_ptr_t const & p_reader,abort_callback & p_abort) = 0; +}; + + +//! New in 0.9.3. Extends metadb_io functionality with nonblocking versions of tag read/write functions, and some other utility features. +class NOVTABLE metadb_io_v2 : public metadb_io { +public: + enum { + //! By default, when some part of requested operation could not be performed for reasons other than user abort, a popup dialog with description of the problem is spawned.\n + //! Set this flag to disable error notification. + op_flag_no_errors = 1 << 0, + //! Set this flag to make the progress dialog not steal focus on creation. + op_flag_background = 1 << 1, + //! Set this flag to delay the progress dialog becoming visible, so it does not appear at all during short operations. Also implies op_flag_background effect. + op_flag_delay_ui = 1 << 2, + + //! \since 1.3 + //! Indicates that the caller is aware of the metadb partial info feature introduced at v1.3. + //! When not specified, affected info will be quietly preserved when updating tags. + op_flag_partial_info_aware = 1 << 3, + }; + + //! Preloads information from the specified tracks. + //! @param p_list List of items to process. + //! @param p_op_flags Can be null, or one or more of op_flag_* enum values combined, altering behaviors of the operation. + //! @param p_notify Called when the task is completed. Status code is one of t_load_info_state values. Can be null if caller doesn't care. + virtual void load_info_async(metadb_handle_list_cref p_list,t_load_info_type p_type,HWND p_parent_window,t_uint32 p_op_flags,completion_notify_ptr p_notify) = 0; + //! Updates tags of the specified tracks. + //! @param p_list List of items to process. + //! @param p_op_flags Can be null, or one or more of op_flag_* enum values combined, altering behaviors of the operation. + //! @param p_notify Called when the task is completed. Status code is one of t_update_info values. Can be null if caller doesn't care. + //! @param p_filter Callback handling actual file_info alterations. Typically used to replace entire meta part of file_info, or to alter something else such as ReplayGain while leaving meta intact. + virtual void update_info_async(metadb_handle_list_cref p_list,service_ptr_t p_filter,HWND p_parent_window,t_uint32 p_op_flags,completion_notify_ptr p_notify) = 0; + //! Rewrites tags of the specified tracks; similar to update_info_async() but using last known/cached file_info values rather than values passed by caller. + //! @param p_list List of items to process. + //! @param p_op_flags Can be null, or one or more of op_flag_* enum values combined, altering behaviors of the operation. + //! @param p_notify Called when the task is completed. Status code is one of t_update_info values. Can be null if caller doesn't care. + virtual void rewrite_info_async(metadb_handle_list_cref p_list,HWND p_parent_window,t_uint32 p_op_flags,completion_notify_ptr p_notify) = 0; + //! Strips all tags / metadata fields from the specified tracks. + //! @param p_list List of items to process. + //! @param p_op_flags Can be null, or one or more of op_flag_* enum values combined, altering behaviors of the operation. + //! @param p_notify Called when the task is completed. Status code is one of t_update_info values. Can be null if caller doesn't care. + virtual void remove_info_async(metadb_handle_list_cref p_list,HWND p_parent_window,t_uint32 p_op_flags,completion_notify_ptr p_notify) = 0; + + //! Creates a metadb_hint_list object. \n + //! Contrary to other metadb_io methods, this can be safely called in a worker thread. You only need to call the hint list's on_done() method in main thread to finalize. + virtual metadb_hint_list::ptr create_hint_list() = 0; + + //! Updates tags of the specified tracks. Helper; uses update_info_async internally. + //! @param p_list List of items to process. + //! @param p_op_flags Can be null, or one or more of op_flag_* enum values combined, altering behaviors of the operation. + //! @param p_notify Called when the task is completed. Status code is one of t_update_info values. Can be null if caller doesn't care. + //! @param p_new_info New infos to write to specified items. + void update_info_async_simple(metadb_handle_list_cref p_list,const pfc::list_base_const_t & p_new_info, HWND p_parent_window,t_uint32 p_op_flags,completion_notify_ptr p_notify); + + //! Helper to be called after a file has been rechaptered. \n + //! Forcibly reloads info then tells playlist_manager to update all affected playlists. + void on_file_rechaptered( const char * path, metadb_handle_list_cref newItems ); + //! Helper to be called after a file has been rechaptered. \n + //! Forcibly reloads info then tells playlist_manager to update all affected playlists. + void on_files_rechaptered( metadb_handle_list_cref newHandles ); + + FB2K_MAKE_SERVICE_COREAPI_EXTENSION(metadb_io_v2,metadb_io); +}; + + +//! Dynamically-registered version of metadb_io_callback. See metadb_io_callback for documentation, register instances using metadb_io_v3::register_callback(). It's recommended that you use the metadb_io_callback_dynamic_impl_base helper class to manage registration/unregistration. +class NOVTABLE metadb_io_callback_dynamic { +public: + virtual void on_changed_sorted(metadb_handle_list_cref p_items_sorted, bool p_fromhook) = 0; +}; + +//! \since 0.9.5 +class NOVTABLE metadb_io_v3 : public metadb_io_v2 { +public: + virtual void register_callback(metadb_io_callback_dynamic * p_callback) = 0; + virtual void unregister_callback(metadb_io_callback_dynamic * p_callback) = 0; + + FB2K_MAKE_SERVICE_COREAPI_EXTENSION(metadb_io_v3,metadb_io_v2); +}; + + +class threaded_process_callback; + +#if FOOBAR2000_TARGET_VERSION >= 80 +//! \since 1.5 +class NOVTABLE metadb_io_v4 : public metadb_io_v3 { + FB2K_MAKE_SERVICE_COREAPI_EXTENSION(metadb_io_v4, metadb_io_v3); +public: + //! Creates an update-info task, that can be either fed to threaded_process API, or invoked by yourself respecting threaded_process semantics. \n + //! May return null pointer if the operation has been refused (by user settings or such). \n + //! Useful for performing the operation with your own in-dialog progress display instead of the generic progress popup. + virtual service_ptr_t spawn_update_info( metadb_handle_list_cref items, service_ptr_t p_filter, uint32_t opFlags, completion_notify_ptr reply ) = 0; + //! Creates an remove-info task, that can be either fed to threaded_process API, or invoked by yourself respecting threaded_process semantics. \n + //! May return null pointer if the operation has been refused (by user settings or such). \n + //! Useful for performing the operation with your own in-dialog progress display instead of the generic progress popup. + virtual service_ptr_t spawn_remove_info( metadb_handle_list_cref items, uint32_t opFlags, completion_notify_ptr reply) = 0; + //! Creates an load-info task, that can be either fed to threaded_process API, or invoked by yourself respecting threaded_process semantics. \n + //! May return null pointer if the operation has been refused (for an example no loading is needed for these items). \n + //! Useful for performing the operation with your own in-dialog progress display instead of the generic progress popup. + virtual service_ptr_t spawn_load_info( metadb_handle_list_cref items, t_load_info_type opType, uint32_t opFlags, completion_notify_ptr reply) = 0; +}; +#endif + +//! metadb_io_callback_dynamic implementation helper. +class metadb_io_callback_dynamic_impl_base : public metadb_io_callback_dynamic { +public: + void on_changed_sorted(metadb_handle_list_cref p_items_sorted, bool p_fromhook) {} + + metadb_io_callback_dynamic_impl_base() {static_api_ptr_t()->register_callback(this);} + ~metadb_io_callback_dynamic_impl_base() {static_api_ptr_t()->unregister_callback(this);} + + PFC_CLASS_NOT_COPYABLE_EX(metadb_io_callback_dynamic_impl_base) +}; +//! Callback service receiving notifications about metadb contents changes. +class NOVTABLE metadb_io_callback : public service_base { +public: + //! Called when metadb contents change. (Or, one of display hook component requests display update). + //! @param p_items_sorted List of items that have been updated. The list is always sorted by pointer value, to allow fast bsearch to test whether specific item has changed. + //! @param p_fromhook Set to true when actual file contents haven't changed but one of metadb_display_field_provider implementations requested an update so output of metadb_handle::format_title() etc has changed. + virtual void on_changed_sorted(metadb_handle_list_cref p_items_sorted, bool p_fromhook) = 0; + + FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(metadb_io_callback); +}; + +//! \since 1.1 +//! Callback service receiving notifications about user-triggered tag edits. \n +//! You want to use metadb_io_callback instead most of the time, unless you specifically want to track tag edits for purposes other than updating user interface. +class NOVTABLE metadb_io_edit_callback : public service_base { + FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(metadb_io_edit_callback) +public: + //! Called after the user has edited tags on a set of files. + typedef const pfc::list_base_const_t & t_infosref; + virtual void on_edited(metadb_handle_list_cref items, t_infosref before, t_infosref after) = 0; +}; + +//! Entrypoint service for metadb_handle related operations.\n +//! Implemented only by core, do not reimplement.\n +//! Use metadb::get() to obtain an instance. +class NOVTABLE metadb : public service_base +{ +protected: + //! OBSOLETE, DO NOT CALL + virtual void database_lock()=0; + //! OBSOLETE, DO NOT CALL + virtual void database_unlock()=0; +public: + + //! Returns a metadb_handle object referencing the specified location. If one doesn't exist yet a new one is created. There can be only one metadb_handle object referencing specific location. \n + //! This function should never fail unless there's something critically wrong (can't allocate memory for the new object, etc). \n + //! Speed: O(log(n)) to total number of metadb_handles present. It's recommended to pass metadb_handles around whenever possible rather than pass playable_locations then retrieve metadb_handles on demand when needed. + //! @param p_out Receives the metadb_handle pointer. + //! @param p_location Location to create a metadb_handle for. + virtual void handle_create(metadb_handle_ptr & p_out,const playable_location & p_location)=0; + + void handle_create_replace_path_canonical(metadb_handle_ptr & p_out,const metadb_handle_ptr & p_source,const char * p_new_path); + void handle_replace_path_canonical(metadb_handle_ptr & p_out,const char * p_new_path); + void handle_create_replace_path(metadb_handle_ptr & p_out,const metadb_handle_ptr & p_source,const char * p_new_path); + + //! Helper function; attempts to retrieve a handle to any known playable location to be used for e.g. titleformatting script preview.\n + //! @returns True on success; false on failure (no known playable locations). + static bool g_get_random_handle(metadb_handle_ptr & p_out); + + enum {case_sensitive = playable_location::case_sensitive}; + typedef playable_location::path_comparator path_comparator; + + inline static int path_compare_ex(const char * p1,t_size len1,const char * p2,t_size len2) {return case_sensitive ? pfc::strcmp_ex(p1,len1,p2,len2) : stricmp_utf8_ex(p1,len1,p2,len2);} + inline static int path_compare_nc(const char * p1, size_t len1, const char * p2, size_t len2) {return case_sensitive ? pfc::strcmp_nc(p1,len1,p2,len2) : stricmp_utf8_ex(p1,len1,p2,len2);} + inline static int path_compare(const char * p1,const char * p2) {return case_sensitive ? strcmp(p1,p2) : stricmp_utf8(p1,p2);} + inline static int path_compare_metadb_handle(const metadb_handle_ptr & p1,const metadb_handle_ptr & p2) {return path_compare(p1->get_path(),p2->get_path());} + + metadb_handle_ptr handle_create(playable_location const & l) {metadb_handle_ptr temp; handle_create(temp, l); return temp;} + metadb_handle_ptr handle_create(const char * path, uint32_t subsong) {return handle_create(make_playable_location(path, subsong));} + + FB2K_MAKE_SERVICE_COREAPI(metadb); +}; + +class titleformat_text_out; +class titleformat_hook_function_params; + + +/*! + Implementing this service lets you provide your own title-formatting fields that are parsed globally with each call to metadb_handle::format_title methods. \n + Note that this API is meant to allow you to add your own component-specific fields - not to overlay your data over standard fields or over fields provided by other components. Any attempts to interfere with standard fields will have severe ill effects. \n + This should be implemented only where absolutely necessary, for safety and performance reasons. Any expensive operations inside the process_field() method may severely damage performance of affected title-formatting calls. \n + You must NEVER make any other foobar2000 API calls from inside process_field, other than possibly querying information from the passed metadb_handle pointer; you should read your own implementation-specific private data and return as soon as possible. You must not make any assumptions about calling context (threading etc). \n + It is guaranteed that process_field() is called only inside a metadb lock scope so you can safely call "locked" metadb_handle methods on the metadb_handle pointer you get. You must not lock metadb by yourself inside process_field() - while it is always called from inside a metadb lock scope, it may be called from another thread than the one maintaining the lock because of multi-CPU optimizations active. \n + If there are multiple metadb_display_field_provider services registered providing fields of the same name, the behavior is undefined. You must pick unique names for provided fields to ensure safe coexistence with other people's components. \n + IMPORTANT: Any components implementing metadb_display_field_provider MUST call metadb_io::dispatch_refresh() with affected metadb_handles whenever info that they present changes. Otherwise, anything rendering title-formatting strings that reference your data will not update properly, resulting in unreliable/broken output, repaint glitches, etc. \n + Do not expect a process_field() call each time somebody uses title formatting, calling code might perform its own caching of strings that you return, getting new ones only after metadb_io::dispatch_refresh() with relevant items. \n + If you can't reliably notify other components about changes of content of fields that you provide (such as when your fields provide some kind of global information and not information specific to item identified by passed metadb_handle), you should not be providing those fields in first place. You must not change returned values of your fields without dispatching appropriate notifications. \n + Use static service_factory_single_t to register your metadb_display_field_provider implementations. Do not call other people's metadb_display_field_provider services directly, they're meant to be called by backend only. \n + List of fields that you provide is expected to be fixed at run-time. The backend will enumerate your fields only once and refer to them by indexes later. \n +*/ + +class NOVTABLE metadb_display_field_provider : public service_base { +public: + //! Returns number of fields provided by this metadb_display_field_provider implementation. + virtual t_uint32 get_field_count() = 0; + //! Returns name of specified field provided by this metadb_display_field_provider implementation. Names are not case sensitive. It's strongly recommended that you keep your field names plain English / ASCII only. + virtual void get_field_name(t_uint32 index, pfc::string_base & out) = 0; + //! Evaluates the specified field. + //! @param index Index of field being processed : 0 <= index < get_field_count(). + //! @param handle Handle to item being processed. You can safely call "locked" methods on this handle to retrieve track information and such. + //! @param out Interface receiving your text output. + //! @returns Return true to indicate that the field is present so if it's enclosed in square brackets, contents of those brackets should not be skipped, false otherwise. + virtual bool process_field(t_uint32 index, metadb_handle * handle, titleformat_text_out * out) = 0; + + FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(metadb_display_field_provider); +}; + + + + + +//! \since 1.1 +//! metadb_index_manager hash, currently a 64bit int, typically made from halving MD5 hash. +typedef t_uint64 metadb_index_hash; + + +//! \since 1.1 +//! A class that transforms track information (location+metadata) to a hash for metadb_index_manager. \n +//! You need to implement your own when using metadb_index_manager to pin your data to user's tracks. \n +//! Possible routes to take when implementing: \n +//! Rely on location only - pinning lost when user moves, but survives editing tags\n +//! Rely on metadata - pinning survives moving files, but lost when editing tags\n +//! If you do the latter, you can implement metadb_io_edit_callback to respond to tag edits and avoid data loss. +class NOVTABLE metadb_index_client : public service_base { + FB2K_MAKE_SERVICE_INTERFACE(metadb_index_client, service_base) +public: + virtual metadb_index_hash transform(const file_info & info, const playable_location & location) = 0; + + bool hashHandle(metadb_handle_ptr const & h, metadb_index_hash & out) { + metadb_info_container::ptr info; + if (!h->get_info_ref(info)) return false; + out = transform(info->info(), h->get_location()); + return true; + } + + static metadb_index_hash from_md5(hasher_md5_result const & in) {return in.xorHalve();} +}; + +//! \since 1.1 +//! This service lets you pin your data to user's music library items, typically to be presented as title formatting %fields% via metadb_display_field_provider. \n +//! Implement metadb_index_client to define how your data gets pinned to the songs. +class NOVTABLE metadb_index_manager : public service_base { + FB2K_MAKE_SERVICE_COREAPI(metadb_index_manager) +public: + //! Install a metadb_index_client. \n + //! This is best done from init_stage_callback::on_init_stage(init_stages::before_config_read) to avoid hammering already loaded UI & playlists with refresh requests. \n + //! If you provide your own title formatting fields, call dispatch_global_refresh() after a successful add() to signal all components to refresh all tracks \n + //! - which is expensive, hence it should be done in early app init phase for minimal performance penalty. \n + //! Always put a try/catch around add() as it may fail with an exception in an unlikely scenario of corrupted files holding your previously saved data. \n + //! @param client your metadb_index_client object. + //! @param index_id Your GUID that you will pass to other methods when referring to your index. + //! @param userDataRetentionPeriod Time for which the data should be retained if no matching tracks are present. \n + //! If this was set to zero, the data could be lost immediately if a library folder disappers for some reason. \n + //! Hint: use system_time_periods::* constants, for an example, system_time_periods::week. + virtual void add(metadb_index_client::ptr client, const GUID & index_id, t_filetimestamp userDataRetentionPeriod) = 0; + //! Uninstalls a previously installed index. + virtual void remove(const GUID & index_id) = 0; + //! Sets your data for the specified index+hash. + virtual void set_user_data(const GUID & index_id, const metadb_index_hash & hash, const void * data, t_size dataSize) = 0; + //! Gets your data for the specified index+hash. + virtual void get_user_data(const GUID & index_id, const metadb_index_hash & hash, mem_block_container & out) = 0; + + + //! Helper + template void get_user_data_t(const GUID & index_id, const metadb_index_hash & hash, t_array & out) { + mem_block_container_ref_impl ref(out); + get_user_data(index_id, hash, ref); + } + + //! Helper + t_size get_user_data_here(const GUID & index_id, const metadb_index_hash & hash, void * out, t_size outSize) { + mem_block_container_temp_impl ref(out, outSize); + get_user_data(index_id, hash, ref); + return ref.get_size(); + } + + //! Signals all components that your data for the tracks matching the specified hashes has been altered; this will redraw the affected tracks in playlists and such. + virtual void dispatch_refresh(const GUID & index_id, const pfc::list_base_const_t & hashes) = 0; + + //! Helper + void dispatch_refresh(const GUID & index_id, const metadb_index_hash & hash) { + pfc::list_single_ref_t l(hash); + dispatch_refresh(index_id, l); + } + + //! Dispatches a global refresh, asks all components to refresh all tracks. To be calling after adding/removing indexes. Expensive! + virtual void dispatch_global_refresh() = 0; + + //! Efficiently retrieves metadb_handles of items present in the Media Library matching the specified index value. \n + //! This can be called from the app main thread only (interfaces with the library_manager API). + virtual void get_ML_handles(const GUID & index_id, const metadb_index_hash & hash, metadb_handle_list_ref out) = 0; + + //! Retrieves all known hash values for this index. + virtual void get_all_hashes(const GUID & index_id, pfc::list_base_t & out) = 0; + + //! Determines whether a no longer needed user data file for this index exists. \n + //! For use with index IDs that are not currently registered only. + virtual bool have_orphaned_data(const GUID & index_id) = 0; + + //! Deletes no longer needed index user data files. \n + //! For use with index IDs that are not currently registered only. + virtual void erase_orphaned_data(const GUID & index_id) = 0; + + //! Saves index user data file now. You normally don't need to call this; it's done automatically when saving foobar2000 configuration. \n + //! This will throw exceptions in case of a failure (out of disk space etc). + virtual void save_index_data(const GUID & index_id) = 0; +}; diff --git a/foobar2000/SDK/metadb_handle.cpp b/foobar2000/SDK/metadb_handle.cpp new file mode 100644 index 0000000..967423d --- /dev/null +++ b/foobar2000/SDK/metadb_handle.cpp @@ -0,0 +1,128 @@ +#include "foobar2000.h" + + +double metadb_handle::get_length() +{ + return this->get_info_ref()->info().get_length(); +} + +t_filetimestamp metadb_handle::get_filetimestamp() +{ + return get_filestats().m_timestamp; +} + +t_filesize metadb_handle::get_filesize() +{ + return get_filestats().m_size; +} + +bool metadb_handle::format_title_legacy(titleformat_hook * p_hook,pfc::string_base & p_out,const char * p_spec,titleformat_text_filter * p_filter) +{ + service_ptr_t script; + if (titleformat_compiler::get()->compile(script,p_spec)) { + return format_title(p_hook,p_out,script,p_filter); + } else { + p_out.reset(); + return false; + } +} + + + +bool metadb_handle::g_should_reload(const t_filestats & p_old_stats,const t_filestats & p_new_stats,bool p_fresh) +{ + if (p_new_stats.m_timestamp == filetimestamp_invalid) return p_fresh; + else if (p_fresh) return p_old_stats!= p_new_stats; + else return p_old_stats.m_timestamp < p_new_stats.m_timestamp; +} + +bool metadb_handle::should_reload(const t_filestats & p_new_stats, bool p_fresh) const +{ + if (!is_info_loaded_async()) return true; + else return g_should_reload(get_filestats(),p_new_stats,p_fresh); +} + + +bool metadb_handle::get_browse_info_merged(file_info & infoMerged) const { + bool rv = true; + metadb_info_container::ptr info, browse; + this->get_browse_info_ref(info, browse); + if (info.is_valid() && browse.is_valid()) { + infoMerged = info->info(); + infoMerged.merge_fallback( browse->info() ); + } else if (info.is_valid()) { + infoMerged = info->info(); + } else if (browse.is_valid()) { + infoMerged = browse->info(); + } else { + infoMerged.reset(); + rv = false; + } + return rv; +} + +namespace { + class metadb_info_container_impl : public metadb_info_container { + public: + metadb_info_container_impl() : m_stats( filestats_invalid ), m_partial() {} + file_info const & info() { + return m_info; + } + t_filestats const & stats() { + return m_stats; + } + bool isInfoPartial() { + return m_partial; + } + + file_info_impl m_info; + t_filestats m_stats; + bool m_partial; + + }; +} + +metadb_info_container::ptr metadb_handle::get_full_info_ref( abort_callback & aborter ) const { + { + metadb_info_container::ptr info; + if (this->get_info_ref( info ) ) { + if (!info->isInfoPartial()) return info; + } + } + + + input_info_reader::ptr reader; + input_entry::g_open_for_info_read( reader, NULL, this->get_path(), aborter ); + + service_ptr_t< metadb_info_container_impl > obj = new service_impl_t(); + obj->m_stats = reader->get_file_stats( aborter ); + reader->get_info( this->get_subsong_index(), obj->m_info, aborter ); + return obj; +} + +namespace fb2k { + pfc::string_formatter formatTrackList( metadb_handle_list_cref lst ) { + pfc::string_formatter ret; + auto cnt = lst.get_count(); + if ( cnt == 0 ) ret << "[Empty track list]\n"; + else { + if (cnt == 1) ret << "[Track list: 1 track]\n"; + else ret << "[Track list: " << cnt << " tracks]\n"; + for( size_t walk = 0; walk < cnt; ++ walk ) { + ret << " " << lst[walk]->get_location() << "\n"; + } + ret << "[Track list end]"; + } + return ret; + } + pfc::string_formatter formatTrackTitle(metadb_handle_ptr item, const char * script ) { + pfc::string_formatter ret; + item->format_title_legacy(NULL,ret,script,NULL); + return ret; + } + pfc::string_formatter formatTrackTitle(metadb_handle_ptr item,service_ptr_t script) { + pfc::string_formatter ret; + item->format_title(NULL,ret,script,NULL); + return ret; + } +} diff --git a/foobar2000/SDK/metadb_handle.h b/foobar2000/SDK/metadb_handle.h new file mode 100644 index 0000000..25eadb7 --- /dev/null +++ b/foobar2000/SDK/metadb_handle.h @@ -0,0 +1,262 @@ +#pragma once + +class titleformat_hook; +class titleformat_text_filter; +class titleformat_object; + + +class metadb_info_container : public service_base { + FB2K_MAKE_SERVICE_INTERFACE(metadb_info_container, service_base); +public: + virtual file_info const & info() = 0; + virtual t_filestats const & stats() = 0; + virtual bool isInfoPartial() = 0; +}; + +//! A metadb_handle object represents interface to reference-counted file_info cache entry for the specified location.\n +//! To obtain a metadb_handle to specific location, use metadb::handle_create(). To obtain a list of metadb_handle objects corresponding to specific path (directory, playlist, multitrack file, etc), use relevant playlist_incoming_item_filter methods (recommended), or call playlist_loader methods directly.\n +//! A metadb_handle is also the most efficient way of passing playable object locations around because it provides fast access to both location and infos, and is reference counted so duplicating it is as fast as possible.\n +//! To retrieve a path of a file from a metadb_handle, use metadb_handle::get_path() function. Note that metadb_handle is NOT just file path, some formats support multiple subsongs per physical file, which are signaled using subsong indexes. +class NOVTABLE metadb_handle : public service_base +{ +public: + //! Retrieves location represented by this metadb_handle object. Returned reference is valid until calling context releases metadb_handle that returned it (metadb_handle_ptr is deallocated etc). + virtual const playable_location & get_location() const = 0;//never fails, returned pointer valid till the object is released + + + //! Renders information about item referenced by this metadb_handle object. + //! @param p_hook Optional callback object overriding fields and functions; set to NULL if not used. + //! @param p_out String receiving the output on success. + //! @param p_script Titleformat script to use. Use titleformat_compiler service to create one. + //! @param p_filter Optional callback object allowing input to be filtered according to context (i.e. removal of linebreak characters present in tags when rendering playlist lines). Set to NULL when not used. + //! @returns true on success, false when dummy file_info instance was used because actual info is was not (yet) known. + virtual bool format_title(titleformat_hook * p_hook,pfc::string_base & p_out,const service_ptr_t & p_script,titleformat_text_filter * p_filter) = 0; + + //! OBSOLETE, DO NOT CALL + __declspec(deprecated) virtual void metadb_lock() = 0; + //! OBSOLETE, DO NOT CALL + __declspec(deprecated) virtual void metadb_unlock() = 0; + + //! Returns last seen file stats, filestats_invalid if unknown. + virtual t_filestats get_filestats() const = 0; + + //! Obsolete, use get_info_ref() family of methods instead. \n + //! Queries whether cached info about item referenced by this metadb_handle object is already available. Note that this function causes the metadb to be temporarily locked; you can not use it in context that where locking is forbidden.\n + //! Note that state of cached info changes only inside main thread, so you can safely assume that it doesn't change while some block of your code inside main thread is being executed. + virtual bool is_info_loaded() const = 0; + //! Obsolete, use get_info_ref() instead. \n + //! Queries cached info about item referenced by this metadb_handle object. Returns true on success, false when info is not yet known. Note that this function causes the metadb to be temporarily locked; you can not use it in context that where locking is forbidden. \n + //! Note that state of cached info changes only inside main thread, so you can safely assume that it doesn't change while some block of your code inside main thread is being executed. + virtual bool get_info(file_info & p_info) const = 0; + + //! OBSOLETE, DO NOT CALL + __declspec(deprecated) virtual bool get_info_locked(const file_info * & p_info) const = 0; + + //! Obsolete, use get_info_ref() family of methods instead. \n + //! Queries whether cached info about item referenced by this metadb_handle object is already available.\n + //! This is intended for use in special cases when you need to immediately retrieve info sent by metadb_io hint from another thread; state of returned data can be altered by any thread, as opposed to non-async methods. + virtual bool is_info_loaded_async() const = 0; + //! Obsolete, use get_info_ref() family of methods instead. \n + //! Queries cached info about item referenced by this metadb_handle object. Returns true on success, false when info is not yet known. Note that this function causes the metadb to be temporarily locked; you can not use it in context that where locking is forbidden.\n + //! This is intended for use in special cases when you need to immediately retrieve info sent by metadb_io hint from another thread; state of returned data can be altered by any thread, as opposed to non-async methods. + virtual bool get_info_async(file_info & p_info) const = 0; + + //! OBSOLETE, DO NOT CALL + __declspec(deprecated) virtual bool get_info_async_locked(const file_info * & p_info) const = 0; + + //! Renders information about item referenced by this metadb_handle object, using external file_info data. + virtual void format_title_from_external_info(const file_info & p_info,titleformat_hook * p_hook,pfc::string_base & p_out,const service_ptr_t & p_script,titleformat_text_filter * p_filter) = 0; + + //! OBSOLETE, DO NOT CALL + __declspec(deprecated) virtual bool format_title_nonlocking(titleformat_hook * p_hook,pfc::string_base & p_out,const service_ptr_t & p_script,titleformat_text_filter * p_filter) = 0; + //! OBSOLETE, DO NOT CALL + __declspec(deprecated) virtual void format_title_from_external_info_nonlocking(const file_info & p_info,titleformat_hook * p_hook,pfc::string_base & p_out,const service_ptr_t & p_script,titleformat_text_filter * p_filter) = 0; + +#if FOOBAR2000_TARGET_VERSION >= 76 + //! \since 1.0 + //! Returns browse info for this track. \n + //! Browse info comes from an external source - such as internet playlist metadata - not from the media file itself, and is maintained separately. \n + //! When title formatting calls are invoked on for a track having browse info present, data for title formatting is sourced from both primary and browse info. \n + //! Example: internet radio stream provides no metadata but its playlist XML has title (radio station name), %title% resolves to the radio station name from the playlist. + virtual bool get_browse_info(file_info & info, t_filetimestamp & ts) const = 0; + + //! \since 1.0 + //! OBSOLETE, DO NOT CALL + __declspec(deprecated) virtual bool get_browse_info_locked(const file_info * & p_info, t_filetimestamp & ts) const = 0; +#endif +#if FOOBAR2000_TARGET_VERSION >= 78 + //! \since 1.3 + //! Retrieve a reference to the primary info. \n + //! You can hold the reference to the info as long as you like, call the method in any context you like with no lock semantics involved. The info held by the returned reference will remain constant even if the metadb content changes. \n + //! Returns true and sets outInfo to a reference to this item's primary info on success, returns false on failure (no info known at this time). + virtual bool get_info_ref(metadb_info_container::ptr & outInfo) const = 0; + + //! \since 1.3 + //! Retrieve a reference to the async info (pending info update). If async info isn't set, a reference to the primary info is returned.\n + //! You can hold the reference to the info as long as you like, call the method in any context you like with no lock semantics involved. The info held by the returned reference will remain constant even if the metadb content changes. \n + //! Returns true and sets outInfo to a reference to this item's async or primary info on success, returns false on failure (no info known at this time). + virtual bool get_async_info_ref(metadb_info_container::ptr & outInfo) const = 0; + + //! \since 1.3 + //! Retrieve references to the item's primary and browse infos. If no info is set, NULL pointers are returned. For most local files, browse info is not available and you get a NULL for it.\n + //! Since browse info is usually used along with the primary info (as a fallback for missing metas), you can get the two with one call for better performance. \n + //! You can hold the reference to the info as long as you like, call the method in any context you like with no lock semantics involved. The info held by the returned reference will remain constant even if the metadb content changes. \n + //! See also: get_browse_info(), for browse info rationale. + virtual void get_browse_info_ref(metadb_info_container::ptr & outInfo, metadb_info_container::ptr & outBrowse) const = 0; + + //! Simplified method, always returns non-null, dummy info if nothing to return. + virtual metadb_info_container::ptr get_info_ref() const = 0; + //! Simplified method, always returns non-null, dummy info if nothing to return. + virtual metadb_info_container::ptr get_async_info_ref() const = 0; + + //! \since 1.3 + //! Retrieve full info using available means - read actual file if not cached. \n + //! Throws exceptions on failure. + metadb_info_container::ptr get_full_info_ref( abort_callback & aborter ) const; +#endif + + //! \since 1.3 + //! Helper using get_browse_info_ref(). \n + //! Retrieves primary info + browse info merged together. \n + //! Returns true on success, false if neither info is available. \n + //! If neither info is avaialble, output data structure is emptied. \n + //! See also: get_browse_info() for browse info rationale. + bool get_browse_info_merged(file_info & infoMerged) const; + + + static bool g_should_reload(const t_filestats & p_old_stats,const t_filestats & p_new_stats,bool p_fresh); + bool should_reload(const t_filestats & p_new_stats,bool p_fresh) const; + + + //! Helper provided for backwards compatibility; takes formatting script as text string and calls relevant titleformat_compiler methods; returns false when the script could not be compiled.\n + //! See format_title() for descriptions of parameters.\n + //! Bottleneck warning: you should consider using precompiled titleformat script object and calling regular format_title() instead when processing large numbers of items. + bool format_title_legacy(titleformat_hook * p_hook,pfc::string_base & out,const char * p_spec,titleformat_text_filter * p_filter); + + //! Retrieves path of item described by this metadb_handle instance. Returned string is valid until calling context releases metadb_handle that returned it (metadb_handle_ptr is deallocated etc). + inline const char * get_path() const {return get_location().get_path();} + //! Retrieves subsong index of item described by this metadb_handle instance (used for multiple playable tracks within single physical file). + inline t_uint32 get_subsong_index() const {return get_location().get_subsong_index();} + + double get_length();//helper + + t_filetimestamp get_filetimestamp(); + t_filesize get_filesize(); + + //! Internal method, do not use + inline const char * _get_path() const { return get_path(); } + + FB2K_MAKE_SERVICE_INTERFACE(metadb_handle,service_base); +}; + +typedef service_ptr_t metadb_handle_ptr; + +typedef pfc::list_base_t & metadb_handle_list_ref; +typedef pfc::list_base_const_t const & metadb_handle_list_cref; + +namespace metadb_handle_list_helper { + void sort_by_format(metadb_handle_list_ref p_list,const char * spec,titleformat_hook * p_hook); + void sort_by_format_get_order(metadb_handle_list_cref p_list,t_size* order,const char * spec,titleformat_hook * p_hook); + void sort_by_format(metadb_handle_list_ref p_list,const service_ptr_t & p_script,titleformat_hook * p_hook, int direction = 1); + void sort_by_format_get_order(metadb_handle_list_cref p_list,t_size* order,const service_ptr_t & p_script,titleformat_hook * p_hook,int p_direction = 1); + + void sort_by_relative_path(metadb_handle_list_ref p_list); + void sort_by_relative_path_get_order(metadb_handle_list_cref p_list,t_size* order); + + void remove_duplicates(pfc::list_base_t & p_list); + void sort_by_pointer_remove_duplicates(pfc::list_base_t & p_list); + void sort_by_path_quick(pfc::list_base_t & p_list); + + void sort_by_pointer(pfc::list_base_t & p_list); + t_size bsearch_by_pointer(const pfc::list_base_const_t & p_list,const metadb_handle_ptr & val); + + double calc_total_duration(const pfc::list_base_const_t & p_list); + + void sort_by_path(pfc::list_base_t & p_list); + + t_filesize calc_total_size(metadb_handle_list_cref list, bool skipUnknown = false); + t_filesize calc_total_size_ex(metadb_handle_list_cref list, bool & foundUnknown); + + bool extract_single_path(metadb_handle_list_cref list, const char * &path); + bool extract_folder_path(metadb_handle_list_cref list, pfc::string_base & path); + + void sort_by_format_get_order_v2( metadb_handle_list_cref p_list, size_t * order, const service_ptr_t & script, titleformat_hook * hook, int direction, abort_callback & aborter ); + void sort_by_format_v2(metadb_handle_list_ref p_list, const service_ptr_t & script, titleformat_hook * hook, int direction, abort_callback & aborter); + +}; + +template class t_alloc = pfc::alloc_fast > +class metadb_handle_list_t : public service_list_t { +private: + typedef metadb_handle_list_t t_self; + typedef pfc::list_base_const_t t_interface; +public: + inline void sort_by_format(const char * spec,titleformat_hook * p_hook) { + return metadb_handle_list_helper::sort_by_format(*this, spec, p_hook); + } + inline void sort_by_format_get_order(t_size* order,const char * spec,titleformat_hook * p_hook) const { + metadb_handle_list_helper::sort_by_format_get_order(*this, order, spec, p_hook); + } + + inline void sort_by_format(const service_ptr_t & p_script,titleformat_hook * p_hook, int direction = 1) { + metadb_handle_list_helper::sort_by_format(*this, p_script, p_hook, direction); + } + inline void sort_by_format_get_order(t_size* order,const service_ptr_t & p_script,titleformat_hook * p_hook) const { + metadb_handle_list_helper::sort_by_format_get_order(*this, order, p_script, p_hook); + } + + inline void sort_by_relative_path() { + metadb_handle_list_helper::sort_by_relative_path(*this); + } + inline void sort_by_relative_path_get_order(t_size* order) const { + metadb_handle_list_helper::sort_by_relative_path_get_order(*this,order); + } + + inline void remove_duplicates() {metadb_handle_list_helper::remove_duplicates(*this);} + inline void sort_by_pointer_remove_duplicates() {metadb_handle_list_helper::sort_by_pointer_remove_duplicates(*this);} + inline void sort_by_path_quick() {metadb_handle_list_helper::sort_by_path_quick(*this);} + + inline void sort_by_pointer() {metadb_handle_list_helper::sort_by_pointer(*this);} + inline t_size bsearch_by_pointer(const metadb_handle_ptr & val) const {return metadb_handle_list_helper::bsearch_by_pointer(*this,val);} + + inline double calc_total_duration() const {return metadb_handle_list_helper::calc_total_duration(*this);} + + inline void sort_by_path() {metadb_handle_list_helper::sort_by_path(*this);} + + const t_self & operator=(const t_self & p_source) { this->remove_all(); this->add_items(p_source);return *this;} + const t_self & operator=(const t_interface & p_source) {this->remove_all(); this->add_items(p_source);return *this;} + const t_self & operator=(t_self && p_source) {this->move_from(p_source); return *this; } + metadb_handle_list_t(const t_self & p_source) { this->add_items(p_source);} + metadb_handle_list_t(const t_interface & p_source) { this->add_items(p_source);} + metadb_handle_list_t() {} + + metadb_handle_list_t(t_self && p_source) { this->move_from(p_source);} + + t_self & operator+=(const t_interface & source) { this->add_items(source); return *this;} + t_self & operator+=(const metadb_handle_ptr & source) { this->add_item(source); return *this;} + + bool extract_single_path(const char * &path) const {return metadb_handle_list_helper::extract_single_path(*this, path);} +}; + +typedef metadb_handle_list_t<> metadb_handle_list; + +namespace metadb_handle_list_helper { + void sorted_by_pointer_extract_difference(metadb_handle_list const & p_list_1,metadb_handle_list const & p_list_2,metadb_handle_list & p_list_1_specific,metadb_handle_list & p_list_2_specific); +}; + + +inline pfc::string_base & operator<<(pfc::string_base & p_fmt,const metadb_handle_ptr & p_location) { + if (p_location.is_valid()) + return p_fmt << p_location->get_location(); + else + return p_fmt << "[invalid location]"; +} + + + +namespace fb2k { + pfc::string_formatter formatTrackList( metadb_handle_list_cref ); + pfc::string_formatter formatTrackTitle(metadb_handle_ptr item, const char * script = "%title%" ); + pfc::string_formatter formatTrackTitle(metadb_handle_ptr item,service_ptr_t script); +} + diff --git a/foobar2000/SDK/metadb_handle_list.cpp b/foobar2000/SDK/metadb_handle_list.cpp new file mode 100644 index 0000000..2c03b6e --- /dev/null +++ b/foobar2000/SDK/metadb_handle_list.cpp @@ -0,0 +1,419 @@ +#include "foobar2000.h" +#include +#include "foosort.h" + +namespace { + + wchar_t * makeSortString(const char * in) { + wchar_t * out = new wchar_t[pfc::stringcvt::estimate_utf8_to_wide(in) + 1]; + out[0] = ' ';//StrCmpLogicalW bug workaround. + pfc::stringcvt::convert_utf8_to_wide_unchecked(out + 1, in); + return out; + } + + struct custom_sort_data { + wchar_t * text; + t_size index; + }; +} + +template +static int custom_sort_compare(const custom_sort_data & elem1, const custom_sort_data & elem2 ) { + int ret = direction * StrCmpLogicalW(elem1.text,elem2.text); + if (ret == 0) ret = pfc::sgn_t((t_ssize)elem1.index - (t_ssize)elem2.index); + return ret; +} + + +template +static int _cdecl _custom_sort_compare(const void * v1, const void * v2) { + return custom_sort_compare(*reinterpret_cast(v1),*reinterpret_cast(v2)); +} +void metadb_handle_list_helper::sort_by_format(metadb_handle_list_ref p_list,const char * spec,titleformat_hook * p_hook) +{ + service_ptr_t script; + if (titleformat_compiler::get()->compile(script,spec)) + sort_by_format(p_list,script,p_hook); +} + +void metadb_handle_list_helper::sort_by_format_get_order(metadb_handle_list_cref p_list,t_size* order,const char * spec,titleformat_hook * p_hook) +{ + service_ptr_t script; + if (titleformat_compiler::get()->compile(script,spec)) + sort_by_format_get_order(p_list,order,script,p_hook); +} + +void metadb_handle_list_helper::sort_by_format(metadb_handle_list_ref p_list,const service_ptr_t & p_script,titleformat_hook * p_hook, int direction) +{ + const t_size count = p_list.get_count(); + pfc::array_t order; order.set_size(count); + sort_by_format_get_order(p_list,order.get_ptr(),p_script,p_hook,direction); + p_list.reorder(order.get_ptr()); +} + +namespace { + + class tfhook_sort : public titleformat_hook { + public: + tfhook_sort() { + m_API->seed((unsigned)__rdtsc()); + } + bool process_field(titleformat_text_out * p_out,const char * p_name,t_size p_name_length,bool & p_found_flag) { + return false; + } + bool process_function(titleformat_text_out * p_out,const char * p_name,t_size p_name_length,titleformat_hook_function_params * p_params,bool & p_found_flag) { + if (stricmp_utf8_ex(p_name, p_name_length, "rand", ~0) == 0) { + t_size param_count = p_params->get_param_count(); + t_uint32 val; + if (param_count == 1) { + t_uint32 mod = (t_uint32)p_params->get_param_uint(0); + if (mod > 0) { + val = m_API->genrand(mod); + } else { + val = 0; + } + } else { + val = m_API->genrand(0xFFFFFFFF); + } + p_out->write_int(titleformat_inputtypes::unknown, val); + p_found_flag = true; + return true; + } else { + return false; + } + } + private: + genrand_service::ptr m_API = genrand_service::get(); + }; +} + +void metadb_handle_list_helper::sort_by_format_get_order(metadb_handle_list_cref p_list,t_size* order,const service_ptr_t & p_script,titleformat_hook * p_hook,int p_direction) +{ + sort_by_format_get_order_v2(p_list, order, p_script, p_hook, p_direction, fb2k::noAbort ); +} + +void metadb_handle_list_helper::sort_by_relative_path(metadb_handle_list_ref p_list) +{ + const t_size count = p_list.get_count(); + pfc::array_t order; order.set_size(count); + sort_by_relative_path_get_order(p_list,order.get_ptr()); + p_list.reorder(order.get_ptr()); +} + +void metadb_handle_list_helper::sort_by_relative_path_get_order(metadb_handle_list_cref p_list,t_size* order) +{ + const t_size count = p_list.get_count(); + t_size n; + pfc::array_t data; + data.set_size(count); + auto api = library_manager::get(); + + pfc::string8_fastalloc temp; + temp.prealloc(512); + for(n=0;nget_relative_path(item,temp)) temp = ""; + data[n].index = n; + data[n].text = makeSortString(temp); + //data[n].subsong = item->get_subsong_index(); + } + + pfc::sort_t(data,custom_sort_compare<1>,count); + //qsort(data.get_ptr(),count,sizeof(custom_sort_data),(int (__cdecl *)(const void *elem1, const void *elem2 ))custom_sort_compare); + + for(n=0;n0) + { + pfc::bit_array_bittable mask(count); + pfc::array_t order; order.set_size(count); + order_helper::g_fill(order); + + p_list.sort_get_permutation_t(pfc::compare_t,order.get_ptr()); + + t_size n; + bool found = false; + for(n=0;n0) + { + sort_by_pointer(p_list); + bool b_found = false; + t_size n; + for(n=0;n); + p_list.sort(); +} + +t_size metadb_handle_list_helper::bsearch_by_pointer(metadb_handle_list_cref p_list,const metadb_handle_ptr & val) +{ + t_size blah; + if (p_list.bsearch_t(pfc::compare_t,val,blah)) return blah; + else return ~0; +} + + +void metadb_handle_list_helper::sorted_by_pointer_extract_difference(metadb_handle_list const & p_list_1,metadb_handle_list const & p_list_2,metadb_handle_list & p_list_1_specific,metadb_handle_list & p_list_2_specific) +{ + t_size found_1, found_2; + const t_size count_1 = p_list_1.get_count(), count_2 = p_list_2.get_count(); + t_size ptr_1, ptr_2; + + found_1 = found_2 = 0; + ptr_1 = ptr_2 = 0; + while(ptr_1 < count_1 || ptr_2 < count_2) + { + while(ptr_1 < count_1 && (ptr_2 == count_2 || p_list_1[ptr_1] < p_list_2[ptr_2])) + { + found_1++; + t_size ptr_1_new = ptr_1 + 1; + while(ptr_1_new < count_1 && p_list_1[ptr_1_new] == p_list_1[ptr_1]) ptr_1_new++; + ptr_1 = ptr_1_new; + } + while(ptr_2 < count_2 && (ptr_1 == count_1 || p_list_2[ptr_2] < p_list_1[ptr_1])) + { + found_2++; + t_size ptr_2_new = ptr_2 + 1; + while(ptr_2_new < count_2 && p_list_2[ptr_2_new] == p_list_2[ptr_2]) ptr_2_new++; + ptr_2 = ptr_2_new; + } + while(ptr_1 < count_1 && ptr_2 < count_2 && p_list_1[ptr_1] == p_list_2[ptr_2]) {ptr_1++; ptr_2++;} + } + + + + p_list_1_specific.set_count(found_1); + p_list_2_specific.set_count(found_2); + if (found_1 > 0 || found_2 > 0) + { + found_1 = found_2 = 0; + ptr_1 = ptr_2 = 0; + + while(ptr_1 < count_1 || ptr_2 < count_2) + { + while(ptr_1 < count_1 && (ptr_2 == count_2 || p_list_1[ptr_1] < p_list_2[ptr_2])) + { + p_list_1_specific[found_1++] = p_list_1[ptr_1]; + t_size ptr_1_new = ptr_1 + 1; + while(ptr_1_new < count_1 && p_list_1[ptr_1_new] == p_list_1[ptr_1]) ptr_1_new++; + ptr_1 = ptr_1_new; + } + while(ptr_2 < count_2 && (ptr_1 == count_1 || p_list_2[ptr_2] < p_list_1[ptr_1])) + { + p_list_2_specific[found_2++] = p_list_2[ptr_2]; + t_size ptr_2_new = ptr_2 + 1; + while(ptr_2_new < count_2 && p_list_2[ptr_2_new] == p_list_2[ptr_2]) ptr_2_new++; + ptr_2 = ptr_2_new; + } + while(ptr_1 < count_1 && ptr_2 < count_2 && p_list_1[ptr_1] == p_list_2[ptr_2]) {ptr_1++; ptr_2++;} + } + + } +} + +double metadb_handle_list_helper::calc_total_duration(metadb_handle_list_cref p_list) +{ + double ret = 0; + t_size n, m = p_list.get_count(); + for(n=0;nget_length(); + if (temp > 0) ret += temp; + } + return ret; +} + +void metadb_handle_list_helper::sort_by_path(metadb_handle_list_ref p_list) +{ + sort_by_format(p_list,"%path_sort%",NULL); +} + +void metadb_handle_list_helper::sort_by_format_v2(metadb_handle_list_ref p_list, const service_ptr_t & script, titleformat_hook * hook, int direction, abort_callback & aborter) { + pfc::array_t order; order.set_size( p_list.get_count() ); + sort_by_format_get_order_v2( p_list, order.get_ptr(), script, hook, direction, aborter ); + p_list.reorder( order.get_ptr() ); +} + +void metadb_handle_list_helper::sort_by_format_get_order_v2(metadb_handle_list_cref p_list, size_t * order, const service_ptr_t & p_script, titleformat_hook * p_hook, int p_direction, abort_callback & aborter) { + // pfc::hires_timer timer; timer.start(); + + const t_size count = p_list.get_count(); + pfc::array_t data; data.set_size(count); + + { + pfc::counter counter(0); + + auto work = [&] { + tfhook_sort myHook; + titleformat_hook_impl_splitter hookSplitter(&myHook, p_hook); + titleformat_hook * const hookPtr = p_hook ? pfc::implicit_cast(&hookSplitter) : &myHook; + + pfc::string8_fastalloc temp; temp.prealloc(512); + const t_size total = p_list.get_size(); + while( ! aborter.is_aborting() ) { + const t_size index = (counter)++; + if (index >= total) break; + data[index].index = index; + p_list[index]->format_title(hookPtr, temp, p_script, 0); + data[index].text = makeSortString(temp); + } + }; + + + pfc::array_staticsize_t< pfc::thread2 > threads; threads.set_size_discard(pfc::getOptimalWorkerThreadCountEx(count / 128) - 1); + for (size_t w = 0; w < threads.get_size(); ++w) { threads[w].startHere(work); } + work(); + for (t_size walk = 0; walk < threads.get_size(); ++walk) threads[walk].waitTillDone(); + } + aborter.check(); + // console::formatter() << "metadb_handle sort: prepared in " << pfc::format_time_ex(timer.query(),6); + + + { + typedef decltype(data) container_t; + auto compare = p_direction > 0 ? custom_sort_compare<1> : custom_sort_compare<-1>; + typedef decltype(compare) compare_t; + pfc::sort_callback_impl_simple_wrap_t cb(data, compare); + + //pfc::sort_t(data, p_direction > 0 ? custom_sort_compare<1> : custom_sort_compare<-1>, count); + + size_t concurrency = pfc::getOptimalWorkerThreadCountEx( count / 4096 ); + fb2k::sort( cb, count, concurrency, aborter ); + } + + //qsort(data.get_ptr(),count,sizeof(custom_sort_data),p_direction > 0 ? _custom_sort_compare<1> : _custom_sort_compare<-1>); + + + // console::formatter() << "metadb_handle sort: sorted in " << pfc::format_time_ex(timer.query(),6); + + for (t_size n = 0; n beenHere; +// metadb_handle_list list(p_list); +// list.sort_t(metadb::path_compare_metadb_handle); + + t_filesize ret = 0; + t_size n, m = p_list.get_count(); + for(n=0;nget_path(), isNew); + if (isNew) { + t_filesize t = h->get_filesize(); + if (t == filesize_invalid) { + if (!skipUnknown) return filesize_invalid; + } else { + ret += t; + } + } + } + return ret; +} + +t_filesize metadb_handle_list_helper::calc_total_size_ex(metadb_handle_list_cref p_list, bool & foundUnknown) { + foundUnknown = false; + metadb_handle_list list(p_list); + list.sort_t(metadb::path_compare_metadb_handle); + + t_filesize ret = 0; + t_size n, m = list.get_count(); + for(n=0;nget_path(),list[n]->get_path())) { + t_filesize t = list[n]->get_filesize(); + if (t == filesize_invalid) { + foundUnknown = true; + } else { + ret += t; + } + } + } + return ret; +} + +bool metadb_handle_list_helper::extract_folder_path(metadb_handle_list_cref list, pfc::string_base & folderOut) { + const t_size total = list.get_count(); + if (total == 0) return false; + pfc::string_formatter temp, folder; + folder = list[0]->get_path(); + folder.truncate_to_parent_path(); + for(size_t walk = 1; walk < total; ++walk) { + temp = list[walk]->get_path(); + temp.truncate_to_parent_path(); + if (metadb::path_compare(folder, temp) != 0) return false; + } + folderOut = folder; + return true; +} +bool metadb_handle_list_helper::extract_single_path(metadb_handle_list_cref list, const char * &pathOut) { + const t_size total = list.get_count(); + if (total == 0) return false; + const char * path = list[0]->get_path(); + for(t_size walk = 1; walk < total; ++walk) { + if (metadb::path_compare(path, list[walk]->get_path()) != 0) return false; + } + pathOut = path; + return true; +} \ No newline at end of file diff --git a/foobar2000/SDK/modeless_dialog.h b/foobar2000/SDK/modeless_dialog.h new file mode 100644 index 0000000..ba9d67e --- /dev/null +++ b/foobar2000/SDK/modeless_dialog.h @@ -0,0 +1,17 @@ +//! Service for plugging your nonmodal dialog windows into main app loop to receive IsDialogMessage()-translated messages.\n +//! Note that all methods are valid from main app thread only.\n +//! Usage: call the static methods - modeless_dialog_manager::g_add / modeless_dialog_manager::g_remove. +class NOVTABLE modeless_dialog_manager : public service_base { + FB2K_MAKE_SERVICE_COREAPI(modeless_dialog_manager); +public: + //! Adds specified window to global list of windows to receive IsDialogMessage(). + virtual void add(HWND p_wnd) = 0; + //! Removes specified window from global list of windows to receive IsDialogMessage(). + virtual void remove(HWND p_wnd) = 0; + + //! Static helper; see add(). + static void g_add(HWND p_wnd) {modeless_dialog_manager::get()->add(p_wnd);} + //! Static helper; see remove(). + static void g_remove(HWND p_wnd) {modeless_dialog_manager::get()->remove(p_wnd);} + +}; diff --git a/foobar2000/SDK/ole_interaction.h b/foobar2000/SDK/ole_interaction.h new file mode 100644 index 0000000..3e81232 --- /dev/null +++ b/foobar2000/SDK/ole_interaction.h @@ -0,0 +1,148 @@ +class NOVTABLE playlist_dataobject_desc { +public: + virtual t_size get_entry_count() const = 0; + virtual void get_entry_name(t_size which, pfc::string_base & out) const = 0; + virtual void get_entry_content(t_size which, metadb_handle_list_ref out) const = 0; + + virtual void set_entry_count(t_size count) = 0; + virtual void set_entry_name(t_size which, const char * name) = 0; + virtual void set_entry_content(t_size which, metadb_handle_list_cref content) = 0; + + void copy(playlist_dataobject_desc const & source) { + const t_size count = source.get_entry_count(); set_entry_count(count); + metadb_handle_list content; pfc::string8 name; + for(t_size walk = 0; walk < count; ++walk) { + source.get_entry_name(walk,name); source.get_entry_content(walk,content); + set_entry_name(walk,name); set_entry_content(walk,content); + } + } +protected: + ~playlist_dataobject_desc() {} +private: + const playlist_dataobject_desc & operator=(const playlist_dataobject_desc &) {return *this;} +}; + +class NOVTABLE playlist_dataobject_desc_v2 : public playlist_dataobject_desc { +public: + virtual void get_side_data(t_size which, mem_block_container & out) const = 0; + virtual void set_side_data(t_size which, const void * data, t_size size) = 0; + + void copy(playlist_dataobject_desc_v2 const & source) { + const t_size count = source.get_entry_count(); set_entry_count(count); + metadb_handle_list content; pfc::string8 name; + mem_block_container_impl_t sideData; + for(t_size walk = 0; walk < count; ++walk) { + source.get_entry_name(walk,name); source.get_entry_content(walk,content); source.get_side_data(walk, sideData); + set_entry_name(walk,name); set_entry_content(walk,content); set_side_data(walk, sideData.get_ptr(), sideData.get_size()); + } + } + + void set_from_playlist_manager(bit_array const & mask) { + auto api = playlist_manager_v4::get(); + const t_size pltotal = api->get_playlist_count(); + const t_size total = mask.calc_count(true,0,pltotal); + set_entry_count(total); + t_size done = 0; + pfc::string8 name; metadb_handle_list content; + for(t_size walk = 0; walk < pltotal; ++walk) if (mask[walk]) { + pfc::dynamic_assert( done < total ); + api->playlist_get_name(walk,name); api->playlist_get_all_items(walk,content); + set_entry_name(done,name); set_entry_content(done,content); + stream_writer_buffer_simple sideData; api->playlist_get_sideinfo(walk, &sideData, fb2k::noAbort); + set_side_data(done,sideData.m_buffer.get_ptr(), sideData.m_buffer.get_size()); + ++done; + } + pfc::dynamic_assert( done == total ); + } + + const playlist_dataobject_desc_v2 & operator=(const playlist_dataobject_desc_v2& source) {copy(source); return *this;} +protected: + ~playlist_dataobject_desc_v2() {} +}; + +class playlist_dataobject_desc_impl : public playlist_dataobject_desc_v2 { +public: + playlist_dataobject_desc_impl() {} + playlist_dataobject_desc_impl(const playlist_dataobject_desc_v2 & source) {copy(source);} + + t_size get_entry_count() const {return m_entries.get_size();} + void get_entry_name(t_size which, pfc::string_base & out) const { + if (which < m_entries.get_size()) out = m_entries[which].m_name; + else throw pfc::exception_invalid_params(); + } + void get_entry_content(t_size which, metadb_handle_list_ref out) const { + if (which < m_entries.get_size()) out = m_entries[which].m_content; + else throw pfc::exception_invalid_params(); + } + void set_entry_count(t_size count) { + m_entries.set_size(count); + } + void set_entry_name(t_size which, const char * name) { + if (which < m_entries.get_size()) m_entries[which].m_name = name; + else throw pfc::exception_invalid_params(); + } + void set_entry_content(t_size which, metadb_handle_list_cref content) { + if (which < m_entries.get_size()) m_entries[which].m_content = content; + else throw pfc::exception_invalid_params(); + } + void get_side_data(t_size which, mem_block_container & out) const { + if (which < m_entries.get_size()) out.set(m_entries[which].m_sideData); + else throw pfc::exception_invalid_params(); + } + void set_side_data(t_size which, const void * data, t_size size) { + if (which < m_entries.get_size()) m_entries[which].m_sideData.set_data_fromptr(reinterpret_cast(data), size); + else throw pfc::exception_invalid_params(); + } +private: + struct entry { metadb_handle_list m_content; pfc::string8 m_name; pfc::array_t m_sideData; }; + pfc::array_t m_entries; +}; + +//! \since 0.9.5 +//! Provides various methods for interaction between foobar2000 and OLE IDataObjects, Windows Clipboard, drag&drop and such. \n +//! To instantiate, use ole_interaction::get(). +class NOVTABLE ole_interaction : public service_base { + FB2K_MAKE_SERVICE_COREAPI(ole_interaction) +public: + enum { + KClipboardFormatSimpleLocations, + KClipboardFormatFPL, + KClipboardFormatMultiFPL, + KClipboardFormatTotal + }; + //! Retrieves clipboard format ID for one of foobar2000's internal data formats. + //! @param which One of KClipboardFormat* constants. + virtual t_uint32 get_clipboard_format(t_uint32 which) = 0; + + //! Creates an IDataObject from a group of tracks. + virtual pfc::com_ptr_t create_dataobject(metadb_handle_list_cref source) = 0; + + //! Creates an IDataObject from one or more playlists, including playlist name info for re-creating those playlists later. + virtual pfc::com_ptr_t create_dataobject(const playlist_dataobject_desc & source) = 0; + + //! Attempts to parse an IDataObject as playlists. + virtual HRESULT parse_dataobject_playlists(pfc::com_ptr_t obj, playlist_dataobject_desc & out) = 0; + + //! For internal use only. Will succeed only if the metadb_handle list can be generated immediately, without performing potentially timeconsuming tasks such as parsing media files (for an example when the specified IDataObject contains data in one of our internal formats). + virtual HRESULT parse_dataobject_immediate(pfc::com_ptr_t obj, metadb_handle_list_ref out) = 0; + + //! Attempts to parse an IDataObject into a dropped_files_data object (list of metadb_handles if immediately available, list of file paths otherwise). + virtual HRESULT parse_dataobject(pfc::com_ptr_t obj, dropped_files_data & out) = 0; + + //! Checks whether the specified IDataObject appears to be parsable by our parse_dataobject methods. + virtual HRESULT check_dataobject(pfc::com_ptr_t obj, DWORD & dropEffect, bool & isNative) = 0; + + //! Checks whether the specified IDataObject appears to be parsable as playlists (parse_dataobject_playlists method). + virtual HRESULT check_dataobject_playlists(pfc::com_ptr_t obj) = 0; +}; + +//! \since 0.9.5.4 +class NOVTABLE ole_interaction_v2 : public ole_interaction { + FB2K_MAKE_SERVICE_COREAPI_EXTENSION(ole_interaction_v2, ole_interaction) +public: + //! Creates an IDataObject from one or more playlists, including playlist name info for re-creating those playlists later. + virtual pfc::com_ptr_t create_dataobject(const playlist_dataobject_desc_v2 & source) = 0; + + //! Attempts to parse an IDataObject as playlists. + virtual HRESULT parse_dataobject_playlists(pfc::com_ptr_t obj, playlist_dataobject_desc_v2 & out) = 0; +}; diff --git a/foobar2000/SDK/output.cpp b/foobar2000/SDK/output.cpp new file mode 100644 index 0000000..817934e --- /dev/null +++ b/foobar2000/SDK/output.cpp @@ -0,0 +1,231 @@ +#include "foobar2000.h" +#include "output.h" +#include "audio_chunk_impl.h" +#include "dsp.h" +#include "resampler.h" + +pfc::string8 output_entry::get_device_name( const GUID & deviceID ) { + pfc::string8 temp; + if (!get_device_name(deviceID, temp)) temp = "[unknown device]"; + return temp; +} + +namespace { + class output_device_enum_callback_getname : public output_device_enum_callback { + public: + output_device_enum_callback_getname( const GUID & wantID, pfc::string_base & strOut ) : m_wantID(wantID), m_got(), m_strOut(strOut) {} + void on_device(const GUID & p_guid,const char * p_name,unsigned p_name_length) { + if (!m_got && p_guid == m_wantID) { + m_strOut.set_string(p_name, p_name_length); + m_got = true; + } + } + bool m_got; + pfc::string_base & m_strOut; + const GUID m_wantID; + }; + +} + +bool output_entry::get_device_name( const GUID & deviceID, pfc::string_base & out ) { + output_device_enum_callback_getname cb(deviceID, out); + this->enum_devices(cb); + return cb.m_got; +} + +bool output_entry::g_find( const GUID & outputID, output_entry::ptr & outObj ) { + service_enum_t e; output_entry::ptr obj; + while(e.next(obj)) { + if (obj->get_guid() == outputID) { + outObj = obj; return true; + } + } + return false; +} + +output_entry::ptr output_entry::g_find( const GUID & outputID ) { + output_entry::ptr ret; + if (!g_find( outputID, ret ) ) throw exception_output_module_not_found(); + return ret; +} + + +bool output::is_progressing_() { + output_v4::ptr v4; + if ( v4 &= this ) return v4->is_progressing(); + return true; +} + +size_t output::update_v2_() { + output_v4::ptr v4; + if ( v4 &= this ) return v4->update_v2(); + bool bReady = false; + this->update(bReady); + return bReady ? SIZE_MAX : 0; +} + +pfc::eventHandle_t output::get_trigger_event_() { + output_v4::ptr v4; + if ( v4 &= this ) return v4->get_trigger_event(); + return pfc::eventInvalid; +} + +void output_impl::flush() { + m_incoming_ptr = 0; + m_incoming.set_size(0); + on_flush(); +} +void output_impl::flush_changing_track() { + m_incoming_ptr = 0; + m_incoming.set_size(0); + on_flush_changing_track(); +} + +void output_impl::update(bool & p_ready) { + p_ready = update_v2() > 0; +} +size_t output_impl::update_v2() { + on_update(); + if (m_incoming_spec != m_active_spec && m_incoming_ptr < m_incoming.get_size()) { + if (get_latency_samples() == 0) { + open(m_incoming_spec); + m_active_spec = m_incoming_spec; + } else { + force_play(); + } + } + size_t retCanWriteSamples = 0; + if (m_incoming_spec == m_active_spec && m_incoming_ptr < m_incoming.get_size()) { + t_size cw = can_write_samples() * m_incoming_spec.m_channels; + t_size delta = pfc::min_t(m_incoming.get_size() - m_incoming_ptr,cw); + if (delta > 0) { + write(audio_chunk_temp_impl(m_incoming.get_ptr()+m_incoming_ptr,delta / m_incoming_spec.m_channels,m_incoming_spec.m_sample_rate,m_incoming_spec.m_channels,m_incoming_spec.m_channel_config)); + m_incoming_ptr += delta; + } + retCanWriteSamples = (cw - delta) / m_incoming_spec.m_channels; + } else if ( m_incoming_ptr == m_incoming.get_size() ) { + retCanWriteSamples = SIZE_MAX; + } + return retCanWriteSamples; +} + +double output_impl::get_latency() { + double ret = 0; + if (m_incoming_spec.is_valid()) { + ret += audio_math::samples_to_time( (m_incoming.get_size() - m_incoming_ptr) / m_incoming_spec.m_channels, m_incoming_spec.m_sample_rate ); + } + if (m_active_spec.is_valid()) { + ret += audio_math::samples_to_time( get_latency_samples() , m_active_spec.m_sample_rate ); + } + return ret; +} +void output_impl::process_samples(const audio_chunk & p_chunk) { + pfc::dynamic_assert(m_incoming_ptr == m_incoming.get_size()); + t_samplespec spec; + spec.fromchunk(p_chunk); + if (!spec.is_valid()) pfc::throw_exception_with_message< exception_io_data >("Invalid audio stream specifications"); + m_incoming_spec = spec; + t_size length = p_chunk.get_used_size(); + m_incoming.set_data_fromptr(p_chunk.get_data(),length); + m_incoming_ptr = 0; +} + +void output_v3::get_injected_dsps( dsp_chain_config & dsps ) { + dsps.remove_all(); +#if 0 // DEPRECATED + unsigned rate = this->get_forced_sample_rate(); + if (rate != 0) { +#if PFC_DEBUG + FB2K_console_formatter() << "output_v3::get_injected_dsps() : requesting resampling to " << rate << " Hz"; +#endif + dsp_preset_impl temp; + if (resampler_entry::g_create_preset( temp, 0, rate, 0 )) { + dsps.insert_item( temp, dsps.get_count() ); + } else { +#if PFC_DEBUG + FB2K_console_formatter() << "output_v3::get_injected_dsps() : resampler could not be created"; +#endif + } + } +#endif +} + +size_t output_v4::update_v2() { + bool bReady = false; + update(bReady); + return bReady ? SIZE_MAX : 0; +} + +uint32_t output_entry::get_config_flags_compat() { + uint32_t ret = get_config_flags(); + if ((ret & (flag_low_latency | flag_high_latency)) == 0) { + // output predating flag_high_latency + flag_low_latency + // if it's old foo_out_upnp, report high latency, otherwise low latency. + static const GUID guid_foo_out_upnp = { 0x9900b4f6, 0x8431, 0x4b0a, { 0x95, 0x56, 0xa7, 0xfc, 0xb9, 0x5b, 0x74, 0x3 } }; + if (this->get_guid() == guid_foo_out_upnp) ret |= flag_high_latency; + else ret |= flag_low_latency; + } + return ret; +} + +bool output_entry::is_high_latency() { + return (this->get_config_flags_compat() & flag_high_latency) != 0; +} + +bool output_entry::is_low_latency() { + return (this->get_config_flags_compat() & flag_low_latency) != 0; +} + +// {EEEB07DE-C2C8-44c2-985C-C85856D96DA1} +const GUID output_id_null = +{ 0xeeeb07de, 0xc2c8, 0x44c2, { 0x98, 0x5c, 0xc8, 0x58, 0x56, 0xd9, 0x6d, 0xa1 } }; + +// {D41D2423-FBB0-4635-B233-7054F79814AB} +const GUID output_id_default = +{ 0xd41d2423, 0xfbb0, 0x4635, { 0xb2, 0x33, 0x70, 0x54, 0xf7, 0x98, 0x14, 0xab } }; + +outputCoreConfig_t outputCoreConfig_t::defaults() { + outputCoreConfig_t cfg = {}; + cfg.m_bitDepth = 16; + cfg.m_buffer_length = 1.0; + cfg.m_output = output_id_default; + // remaining fields nulled by {} + return cfg; +} +namespace { + class output_device_list_callback_impl : public output_device_list_callback { + public: + void onDevice( const char * fullName, const GUID & output, const GUID & device ) { + f(fullName, output, device); + } + std::function< void ( const char*, const GUID&, const GUID&) > f; + }; + + class output_config_change_callback_impl : public output_config_change_callback { + public: + void outputConfigChanged() { + f(); + } + std::function f; + }; +} +void output_manager_v2::listDevices( std::function< void ( const char*, const GUID&, const GUID&) > f ) { + output_device_list_callback_impl cb; cb.f = f; + this->listDevices( cb ); +} + +service_ptr output_manager_v2::addCallback( std::function f ) { + output_config_change_callback_impl * obj = new output_config_change_callback_impl(); + obj->f = f; + this->addCallback( obj ); + service_ptr_t selfRef ( this ); + return fb2k::callOnRelease( [obj, selfRef] { + selfRef->removeCallback( obj ); delete obj; + } ); +} + +void output_manager_v2::addCallbackPermanent( std::function f ) { + output_config_change_callback_impl * obj = new output_config_change_callback_impl(); + obj->f = f; + addCallback( obj ); +} diff --git a/foobar2000/SDK/output.h b/foobar2000/SDK/output.h new file mode 100644 index 0000000..1957f25 --- /dev/null +++ b/foobar2000/SDK/output.h @@ -0,0 +1,414 @@ +#pragma once + +#include + +PFC_DECLARE_EXCEPTION(exception_output_device_not_found, pfc::exception, "Audio device not found") +PFC_DECLARE_EXCEPTION(exception_output_module_not_found, exception_output_device_not_found, "Output module not found") +PFC_DECLARE_EXCEPTION(exception_output_invalidated, pfc::exception, "Audio device invalidated") +PFC_DECLARE_EXCEPTION(exception_output_device_in_use, pfc::exception, "Audio device in use") +PFC_DECLARE_EXCEPTION(exception_output_unsupported_stream_format, pfc::exception, "Unsupported audio stream format") + + +// ======================================================= +// IDEA BIN +// ======== +// Accurate timing info required! get_latency NOT safe to call from any thread while it should be +// There should be a legitimate way ( as in other than matching get_latency() against the amount of sent data ) to know when the output has finished prebuffering and started actual playback +// Outputs should be able to handle idling : idle(abort_callback&) => while(!update()) aborter.sleep(); or optimized for specific output +// ======================================================= + +//! Structure describing PCM audio data format, with basic helper functions. +struct t_pcmspec +{ + unsigned m_sample_rate = 0; + unsigned m_bits_per_sample = 0; + unsigned m_channels = 0,m_channel_config = 0; + bool m_float = false; + + inline unsigned align() const {return (m_bits_per_sample / 8) * m_channels;} + + uint64_t time_to_bytes(double p_time) const {return audio_math::time_to_samples(p_time,m_sample_rate) * (m_bits_per_sample / 8) * m_channels;} + double bytes_to_time(uint64_t p_bytes) const {return (double) (p_bytes / ((m_bits_per_sample / 8) * m_channels)) / (double) m_sample_rate;} + + inline bool operator==(/*const t_pcmspec & p_spec1,*/const t_pcmspec & p_spec2) const + { + return /*p_spec1.*/m_sample_rate == p_spec2.m_sample_rate + && /*p_spec1.*/m_bits_per_sample == p_spec2.m_bits_per_sample + && /*p_spec1.*/m_channels == p_spec2.m_channels + && /*p_spec1.*/m_channel_config == p_spec2.m_channel_config + && /*p_spec1.*/m_float == p_spec2.m_float; + } + + inline bool operator!=(/*const t_pcmspec & p_spec1,*/const t_pcmspec & p_spec2) const + { + return !(*this == p_spec2); + } + + inline void reset() { *this = t_pcmspec(); } + inline bool is_valid() const + { + return m_sample_rate >= 1000 && m_sample_rate <= 1000000 && + m_channels > 0 && m_channels <= 256 && m_channel_config != 0 && + (m_bits_per_sample == 8 || m_bits_per_sample == 16 || m_bits_per_sample == 24 || m_bits_per_sample == 32); + } +}; + +struct t_samplespec { + t_uint32 m_sample_rate; + t_uint32 m_channels,m_channel_config; + + t_size time_to_samples(double p_time) const {PFC_ASSERT(is_valid());return (t_size)audio_math::time_to_samples(p_time,m_sample_rate);} + double samples_to_time(t_size p_samples) const {PFC_ASSERT(is_valid()); return audio_math::samples_to_time(p_samples,m_sample_rate);} + + inline t_samplespec() {reset();} + inline t_samplespec(audio_chunk const & in) {fromchunk(in);} + + inline void reset() {m_sample_rate = 0; m_channels = 0; m_channel_config = 0;} + + inline bool operator==(const t_samplespec & p_spec2) const { + return m_sample_rate == p_spec2.m_sample_rate && m_channels == p_spec2.m_channels && m_channel_config == p_spec2.m_channel_config; + } + + inline bool operator!=(const t_samplespec & p_spec2) const { + return !(*this == p_spec2); + } + + inline bool is_valid() const { + return m_sample_rate > 0 && m_channels > 0 && audio_chunk::g_count_channels(m_channel_config) == m_channels; + } + + static t_samplespec g_fromchunk(const audio_chunk & p_chunk) { + t_samplespec temp; temp.fromchunk(p_chunk); return temp; + } + + void fromchunk(const audio_chunk & p_chunk) { + m_sample_rate = p_chunk.get_sample_rate(); + m_channels = p_chunk.get_channels(); + m_channel_config = p_chunk.get_channel_config(); + } +}; + +class NOVTABLE output_device_enum_callback +{ +public: + virtual void on_device(const GUID & p_guid,const char * p_name,unsigned p_name_length) = 0; +}; + +class NOVTABLE output : public service_base { + FB2K_MAKE_SERVICE_INTERFACE(output,service_base); +public: + //! Retrieves amount of audio data queued for playback, in seconds. + virtual double get_latency() = 0; + //! Sends new samples to the device. Allowed to be called only when update() indicates that the device is ready. + virtual void process_samples(const audio_chunk & p_chunk) = 0; + //! Updates playback; queries whether the device is ready to receive new data. + //! @param p_ready On success, receives value indicating whether the device is ready for next process_samples() call. + virtual void update(bool & p_ready) = 0; + //! Pauses/unpauses playback. + virtual void pause(bool p_state) = 0; + //! Flushes queued audio data. Called after seeking. + virtual void flush() = 0; + //! Forces playback of queued data. Called when there's no more data to send, to prevent infinite waiting if output implementation starts actually playing after amount of data in internal buffer reaches some level. + virtual void force_play() = 0; + + //! Sets playback volume. + //! @p_val Volume level in dB. Value of 0 indicates full ("100%") volume, negative values indciate different attenuation levels. + virtual void volume_set(double p_val) = 0; + + //! Helper, see output_v4::is_progressing(). + bool is_progressing_(); + //! Helper, see output_v4::update_v2() + size_t update_v2_(); + //! Helper, see output_v4::get_event_trigger() + pfc::eventHandle_t get_trigger_event_(); + + //! Helper for output_entry implementation. + static uint32_t g_extra_flags() { return 0; } + +}; + +class NOVTABLE output_v2 : public output { + FB2K_MAKE_SERVICE_INTERFACE(output_v2, output); +public: + //! Obsolete, do not use. + virtual bool want_track_marks() {return false;} + //! Obsolete, do not use. + virtual void on_track_mark() {} + //! Obsolete, do not use. + virtual void enable_fading(bool state) {} + //! Called when flushing due to manual track change rather than seek-within-track + virtual void flush_changing_track() {flush();} +}; + +class dsp_chain_config; + +//! \since 1.4 +class NOVTABLE output_v3 : public output_v2 { + FB2K_MAKE_SERVICE_INTERFACE(output_v3, output_v2); +public: + //! Does this output require a specific sample rate? If yes, return the value, otherwise return zero. \n + //! Returning a nonzero will cause a resampler DSP to be injected. + virtual unsigned get_forced_sample_rate() { return 0; } + //! Allows the output to inject specific DSPs at the end of the used chain. \n + //! Default implementation queries get_forced_sample_rate() and injects a resampler. + virtual void get_injected_dsps( dsp_chain_config & ); +}; + +//! \since 1.6 +class NOVTABLE output_v4 : public output_v3 { + FB2K_MAKE_SERVICE_INTERFACE(output_v4, output_v3); +public: + //! Returns an event handle that becomes signaled once the output wants an update() call and possibly process_samples(). \n + //! Optional; may return pfc::eventInvalid if not available at this time or not supported. \n + //! If implemented, calling update() should clear the event each time. + virtual pfc::eventHandle_t get_trigger_event() {return pfc::eventInvalid;} + //! Returns whether the audio stream is currently being played or not. \n + //! Typically, for a short period of time, initially send data is not played until a sufficient amount is queued to initiate playback without glitches. \n + //! For old outputs that do not implement this, the value can be assumed to be true. + virtual bool is_progressing() {return true;} + + //! Improved version of update(); returns 0 if the output isn't ready to receive any new data, otherwise an advisory number of samples - at the current stream format - that the output expects to take now. \n + //! If the caller changes the stream format, the value is irrelevant. \n + //! The output may return SIZE_MAX to indicate that it can take data but does not currently have any hints to tell how much. + virtual size_t update_v2(); +}; + +//! \since 1.6 +class output_v5 : public output_v4 { + FB2K_MAKE_SERVICE_INTERFACE(output_v5, output_v4); +public: + virtual unsigned get_forced_channel_mask() { return 0; } +}; + +class NOVTABLE output_entry : public service_base { + FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(output_entry); +public: + //! Instantiates output class. + virtual void instantiate(service_ptr_t & p_out,const GUID & p_device,double p_buffer_length,bool p_dither,t_uint32 p_bitdepth) = 0; + //! Enumerates devices supported by this output_entry implementation. + virtual void enum_devices(output_device_enum_callback & p_callback) = 0; + //! For internal use by backend. Each output_entry implementation must have its own guid. + virtual GUID get_guid() = 0; + //! For internal use by backend. Retrieves human-readable name of this output_entry implementation. + virtual const char * get_name() = 0; + + //! Obsolete, do not use. + virtual void advanced_settings_popup(HWND p_parent,POINT p_menupoint) = 0; + + enum { + flag_needs_bitdepth_config = 1 << 0, + flag_needs_dither_config = 1 << 1, + //! Obsolete, do not use. + flag_needs_advanced_config = 1 << 2, + flag_needs_device_list_prefixes = 1 << 3, + + //! Supports playing multiple simultaneous audio streams thru one device? + flag_supports_multiple_streams = 1 << 4, + + //! High latency operation (such as remote network playback), mutually exclusive with flag_low_latency + flag_high_latency = 1 << 5, + //! Low latency operation (local playback), mutually exclusive with flag_high_latency + flag_low_latency = 1 << 6, + //! When set, the output will be used in special compatibility mode: guaranteed regular update() calls, injected padding (silence) at the end of stream. + flag_needs_shims = 1 << 7, + }; + + virtual t_uint32 get_config_flags() = 0; + + uint32_t get_config_flags_compat(); + + bool is_high_latency(); + bool is_low_latency(); + + pfc::string8 get_device_name( const GUID & deviceID); + bool get_device_name( const GUID & deviceID, pfc::string_base & out ); + + static bool g_find( const GUID & outputID, output_entry::ptr & outObj ); + static output_entry::ptr g_find(const GUID & outputID ); +}; + +//! Helper; implements output_entry for specific output class implementation. output_entry methods are forwarded to static methods of your output class. Use output_factory_t instead of using this class directly. +template +class output_entry_impl_t : public E +{ +public: + void instantiate(service_ptr_t & p_out,const GUID & p_device,double p_buffer_length,bool p_dither,t_uint32 p_bitdepth) { + p_out = new service_impl_t(p_device,p_buffer_length,p_dither,p_bitdepth); + } + void enum_devices(output_device_enum_callback & p_callback) {T::g_enum_devices(p_callback);} + GUID get_guid() {return T::g_get_guid();} + const char * get_name() {return T::g_get_name();} + void advanced_settings_popup(HWND p_parent,POINT p_menupoint) {T::g_advanced_settings_popup(p_parent,p_menupoint);} + + t_uint32 get_config_flags() { + t_uint32 flags = 0; + if (T::g_advanced_settings_query()) flags |= output_entry::flag_needs_advanced_config; + if (T::g_needs_bitdepth_config()) flags |= output_entry::flag_needs_bitdepth_config; + if (T::g_needs_dither_config()) flags |= output_entry::flag_needs_dither_config; + if (T::g_needs_device_list_prefixes()) flags |= output_entry::flag_needs_device_list_prefixes; + if (T::g_supports_multiple_streams()) flags |= output_entry::flag_supports_multiple_streams; + if (T::g_is_high_latency()) flags |= output_entry::flag_high_latency; + else flags |= output_entry::flag_low_latency; + flags |= T::g_extra_flags(); + return flags; + } +}; + + +//! Use this to register your output implementation. +template +class output_factory_t : public service_factory_single_t > {}; + +class output_impl : public output_v5 { +protected: + output_impl() : m_incoming_ptr(0) {} + virtual void on_update() = 0; + //! Will never get more input than as returned by can_write_samples(). + virtual void write(const audio_chunk & p_data) = 0; + virtual t_size can_write_samples() = 0; + virtual t_size get_latency_samples() = 0; + virtual void on_flush() = 0; + virtual void on_flush_changing_track() {on_flush();} + virtual void open(t_samplespec const & p_spec) = 0; + + virtual void pause(bool p_state) = 0; + virtual void force_play() = 0; + virtual void volume_set(double p_val) = 0; +protected: + void on_need_reopen() {m_active_spec = t_samplespec();} +private: + void flush(); + void flush_changing_track(); + void update(bool & p_ready); + size_t update_v2(); + double get_latency(); + void process_samples(const audio_chunk & p_chunk); + + pfc::array_t m_incoming; + t_size m_incoming_ptr; + t_samplespec m_incoming_spec,m_active_spec; +}; + + +class NOVTABLE volume_callback { +public: + virtual void on_volume_scale(float v) = 0; + virtual void on_volume_arbitrary(int v) = 0; +}; + +class NOVTABLE volume_control : public service_base { + FB2K_MAKE_SERVICE_INTERFACE(volume_control, service_base) +public: + virtual void add_callback(volume_callback * ptr) = 0; + virtual void remove_callback(volume_callback * ptr) = 0; + + enum style_t { + styleScale, + styleArbitrary + }; + + virtual style_t getStyle() = 0; + + virtual float scaleGet() = 0; + virtual void scaleSet(float v) = 0; + + virtual void arbitrarySet(int val) = 0; + virtual int arbitraryGet() = 0; + virtual int arbitraryGetMin() = 0; + virtual int arbitraryGetMax() = 0; + virtual bool arbitraryGetMute() = 0; + virtual void arbitrarySetMute(bool val) = 0; +}; + + +class NOVTABLE output_entry_v2 : public output_entry { + FB2K_MAKE_SERVICE_INTERFACE(output_entry_v2, output_entry) +public: + virtual bool get_volume_control(const GUID & id, volume_control::ptr & out) = 0; + virtual bool hasVisualisation() = 0; +}; + +//! \since 1.5 +class NOVTABLE output_devices_notify { +public: + virtual void output_devices_changed() = 0; +protected: + output_devices_notify() {} +private: + output_devices_notify(const output_devices_notify &) = delete; + void operator=(const output_devices_notify &) = delete; +}; + +//! \since 1.5 +class NOVTABLE output_entry_v3 : public output_entry_v2 { + FB2K_MAKE_SERVICE_INTERFACE(output_entry_v3, output_entry_v2) +public: + + //! Main thread only! + virtual void add_notify(output_devices_notify *) = 0; + //! Main thread only! + virtual void remove_notify(output_devices_notify *) = 0; + + //! Main thread only! + virtual void set_pinned_device(const GUID & guid) = 0; +}; + +#pragma pack(push, 1) +//! \since 1.3.5 +struct outputCoreConfig_t { + + static outputCoreConfig_t defaults(); + + GUID m_output; + GUID m_device; + double m_buffer_length; + uint32_t m_flags; + uint32_t m_bitDepth; + enum { flagUseDither = 1 << 0 }; +}; +#pragma pack(pop) + +//! \since 1.3.5 +//! Allows components to access foobar2000 core's output settings. +class NOVTABLE output_manager : public service_base { + FB2K_MAKE_SERVICE_COREAPI(output_manager); +public: + //! Instantiates an output instance with core settings. + //! @param overrideBufferLength Specify non zero to override user-configured buffer length in core settings. + //! @returns The new output instance. Throws exceptions on failure (invalid settings or other). + virtual output::ptr instantiateCoreDefault(double overrideBufferLength = 0) = 0; + virtual void getCoreConfig( void * out, size_t outSize ) = 0; + + void getCoreConfig(outputCoreConfig_t & out ) { getCoreConfig(&out, sizeof(out) ); } +}; + +//! \since 1.3.16 +class NOVTABLE output_device_list_callback { +public: + virtual void onDevice( const char * fullName, const GUID & output, const GUID & device ) = 0; +}; + +//! \since 1.3.16 +class NOVTABLE output_config_change_callback { +public: + virtual void outputConfigChanged() = 0; +}; + +//! \since 1.4 +class NOVTABLE output_manager_v2 : public output_manager { + FB2K_MAKE_SERVICE_COREAPI_EXTENSION(output_manager_v2, output_manager); +public: + virtual void setCoreConfig( const void * in, size_t inSize, bool bSuppressPlaybackRestart = false ) = 0; + void setCoreConfig( const outputCoreConfig_t & in ) { setCoreConfig(&in, sizeof(in) ); } + virtual void setCoreConfigDevice( const GUID & output, const GUID & device ) = 0; + virtual void listDevices( output_device_list_callback & callback ) = 0; + void listDevices( std::function< void ( const char*, const GUID&, const GUID&) > f ); + virtual void addCallback( output_config_change_callback * ) = 0; + virtual void removeCallback( output_config_change_callback * ) = 0; + + service_ptr addCallback( std::function f ); + void addCallbackPermanent( std::function f ); +}; + +extern const GUID output_id_null; +extern const GUID output_id_default; diff --git a/foobar2000/SDK/packet_decoder.cpp b/foobar2000/SDK/packet_decoder.cpp new file mode 100644 index 0000000..982867d --- /dev/null +++ b/foobar2000/SDK/packet_decoder.cpp @@ -0,0 +1,63 @@ +#include "foobar2000.h" +#include + +void packet_decoder::g_open(service_ptr_t & p_out,bool p_decode,const GUID & p_owner,t_size p_param1,const void * p_param2,t_size p_param2size,abort_callback & p_abort) +{ + std::exception_ptr rethrow; + bool havePartial = false, tryingPartial = false; + for ( ;; ) { + service_enum_t e; + service_ptr_t ptr; + + while (e.next(ptr)) { + p_abort.check(); + if (ptr->is_our_setup(p_owner, p_param1, p_param2, p_param2size)) { + if (!tryingPartial && ptr->is_supported_partially_(p_owner, p_param1, p_param2, p_param2size)) { + havePartial = true; + } else { + try { + ptr->open(p_out, p_decode, p_owner, p_param1, p_param2, p_param2size, p_abort); + return; + } catch (exception_io_data) { + rethrow = std::current_exception(); + } + } + } + } + + if (!havePartial || tryingPartial) break; + tryingPartial = true; + } + + if (rethrow) std::rethrow_exception(rethrow); + throw exception_io_data(); +} + +size_t packet_decoder::initPadding() { + size_t v = this->set_stream_property(property_bufferpadding, 0, NULL, 0); + if (v > 0) { + this->set_stream_property(property_bufferpadding, v, NULL, 0); + } + return v; +} + +void packet_decoder::setEventLogger(event_logger::ptr logger) { + this->set_stream_property(property_eventlogger, 0, logger.get_ptr(), 0); +} + +void packet_decoder::setCheckingIntegrity(bool checkingIntegrity) { + this->set_stream_property(property_checkingintegrity, checkingIntegrity ? 1 : 0, NULL, 0); +} + +void packet_decoder::setAllowDelayed( bool bAllow ) { + this->set_stream_property( property_allow_delayed_output, bAllow ? 1 : 0, NULL, 0); +} + +bool packet_decoder_entry::is_supported_partially_(const GUID& p_owner, t_size p_param1, const void* p_param2, t_size p_param2size) { + bool ret = false; + packet_decoder_entry_v2::ptr v2; + if (v2 &= this) { + ret = v2->is_supported_partially(p_owner, p_param1, p_param2, p_param2size); + } + return ret; +} \ No newline at end of file diff --git a/foobar2000/SDK/packet_decoder.h b/foobar2000/SDK/packet_decoder.h new file mode 100644 index 0000000..df6b18d --- /dev/null +++ b/foobar2000/SDK/packet_decoder.h @@ -0,0 +1,159 @@ +#pragma once +//! Provides interface to decode various audio data types to PCM. Use packet_decoder_factory_t template to register. + +class NOVTABLE packet_decoder : public service_base { +protected: + //! Prototype of function that must be implemented by packet_decoder implementation but is not accessible through packet_decoder interface itself. + //! Determines whether specific packet_decoder implementation supports specified decoder setup data. + static bool g_is_our_setup(const GUID & p_owner,t_size p_param1,const void * p_param2,t_size p_param2size) {return false;} + + //! Prototype of function that must be implemented by packet_decoder implementation but is not accessible through packet_decoder interface itself. + //! Initializes packet decoder instance with specified decoder setup data. This is called only once, before any other methods. + //! @param p_decode If set to true, decode() and reset_after_seek() calls can be expected later. If set to false, those methods will not be called on this packet_decoder instance - for an example when caller is only retrieving information about the file rather than preparing to decode it. + void open(const GUID & p_owner,bool p_decode,t_size p_param1,const void * p_param2,t_size p_param2size,abort_callback & p_abort) {throw exception_io_data();} +public: + + //! Prototype of function that must be implemented by packet_decoder implementation but is not accessible through packet_decoder interface itself. + //! Returns true if this is not the preferred decoder for this format, another one should be used if found. + static bool g_is_supported_partially(const GUID& p_owner, t_size p_param1, const void* p_param2, t_size p_param2size) { return false; } + + + //! Forwards additional information about stream being decoded. \n + //! Calling: this must be called immediately after packet_decoder object is created, before any other methods are called.\n + //! Implementation: this is called after open() (which is called by implementation framework immediately after creation), and before any other methods are called. + virtual t_size set_stream_property(const GUID & p_type,t_size p_param1,const void * p_param2,t_size p_param2size) = 0; + + + //! Retrieves additional user-readable tech infos that decoder can provide. + //! @param p_info Interface receiving information about the stream being decoded. Note that it already contains partial info about the file; existing info should not be erased, decoder-provided info should be merged with it. + virtual void get_info(file_info & p_info) = 0; + + //! Returns many frames back to start decoding when seeking. + virtual unsigned get_max_frame_dependency()=0; + //! Returns much time back to start decoding when seeking (for containers where going back by specified number of frames is not trivial). + virtual double get_max_frame_dependency_time()=0; + + //! Flushes decoder after seeking. + virtual void reset_after_seek()=0; + + //! Decodes a block of audio data.\n + //! It may return empty chunk even when successful (caused by encoder+decoder delay for an example), caller must check for it and handle it appropriately. + //! Called with 0 bytes at the end of stream - if the decoder introduces a delay between input/output, any buffered data should be passed back then. + virtual void decode(const void * p_buffer,t_size p_bytes,audio_chunk & p_chunk,abort_callback & p_abort)=0; + + //! Returns whether this packet decoder supports analyze_first_frame() function. + virtual bool analyze_first_frame_supported() = 0; + //! Optional. Some codecs need to analyze first frame of the stream to return additional info about the stream, such as encoding setup. This can be only called immediately after instantiation (and set_stream_property() if present), before any actual decoding or get_info(). Caller can determine whether this method is supported or not by calling analyze_first_frame_supported(), to avoid reading first frame when decoder won't utiilize the extra info for an example. If particular decoder can't utilize first frame info in any way (and analyze_first_frame_supported() returns false), this function should do nothing and succeed. + virtual void analyze_first_frame(const void * p_buffer,t_size p_bytes,abort_callback & p_abort) = 0; + + //! Static helper, creates a packet_decoder instance and initializes it with specific decoder setup data. + static void g_open(service_ptr_t & p_out,bool p_decode,const GUID & p_owner,t_size p_param1,const void * p_param2,t_size p_param2size,abort_callback & p_abort); + + static const GUID owner_MP4,owner_matroska,owner_MP3,owner_MP2,owner_MP1,owner_MP4_ALAC,owner_ADTS,owner_ADIF, owner_Ogg, owner_MP4_AMR, owner_MP4_AMR_WB, owner_MP4_AC3, owner_MP4_EAC3; + + struct matroska_setup + { + const char * codec_id; + uint32_t sample_rate,sample_rate_output; + uint32_t channels; + size_t codec_private_size; + const void * codec_private; + }; + //owner_MP4: param1 - codec ID (MP4 audio type), param2 - MP4 codec initialization data + //owner_MP3: raw MP3/MP2 file, parameters ignored + //owner_matroska: param2 = matroska_setup struct, param2size size must be equal to sizeof(matroska_setup) + + + //these are used to initialize PCM decoder + static const GUID property_samplerate,property_bitspersample,property_channels,property_byteorder,property_signed,property_channelmask, property_bufferpadding, property_eventlogger, property_checkingintegrity, property_samples_per_frame; + //property_samplerate : param1 == sample rate in hz + //property_bitspersample : param1 == bits per sample + //property_channels : param1 == channel count + //property_byteorder : if (param1) little_endian; else big_endian; + //property_signed : if (param1) signed; else unsigned; + //propery_bufferpadding : param1 == padding of each passed buffer in bytes; retval: decoder's preferred padding + //property_eventlogger : param2 = event logger ptr, NULL to disable, param2size 0 always + //property_checkingintegrity : param1 = checking integrity bool flag + //property_samples_per_frame : param1 = samples per frame + + + + //property_ogg_header : p_param1 = unused, p_param2 = ogg_packet structure, retval: 0 when more headers are wanted, 1 when done parsing headers + //property_ogg_query_sample_rate : returns sample rate, no parameters + //property_ogg_packet : p_param1 = unused, p_param2 = ogg_packet strucute + //property_ogg_qury_preskip : returns preskip samples (Opus) + static const GUID property_ogg_header, property_ogg_query_sample_rate, property_ogg_packet, property_ogg_query_preskip; + + //property_mp4_esds : p_param2 = MP4 ESDS chunk content as needed by some decoders + static const GUID property_mp4_esds; + + // DEPRECATED + static const GUID property_allow_delayed_output; + + // property_mp3_delayless : return non-zero if this codec drops MP3 delay by itself + static const GUID property_mp3_delayless; + + // property_query_delay_samples : + // Return non-zero if this codec has a decoder delay that the caller should deal with. + // Param1 signals sample rate used by input - should always match decoder's sample rate - return zero if it does not match. + static const GUID property_query_delay_samples; + + // property_query_mp4_use_elst : + // Return non-zero if MP4 elst should be used with this codec. + static const GUID property_query_mp4_use_elst; + + size_t initPadding(); + void setEventLogger(event_logger::ptr logger); + void setCheckingIntegrity(bool checkingIntegrity); + void setAllowDelayed( bool bAllow = true ); + + FB2K_MAKE_SERVICE_INTERFACE(packet_decoder,service_base); +}; + +class NOVTABLE packet_decoder_streamparse : public packet_decoder +{ +public: + virtual void decode_ex(const void * p_buffer,t_size p_bytes,t_size & p_bytes_processed,audio_chunk & p_chunk,abort_callback & p_abort) = 0; + virtual void analyze_first_frame_ex(const void * p_buffer,t_size p_bytes,t_size & p_bytes_processed,abort_callback & p_abort) = 0; + + FB2K_MAKE_SERVICE_INTERFACE(packet_decoder_streamparse,packet_decoder); +}; + +class NOVTABLE packet_decoder_entry : public service_base { + FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(packet_decoder_entry); +public: + virtual bool is_our_setup(const GUID & p_owner,t_size p_param1,const void * p_param2,t_size p_param2size) = 0; + virtual void open(service_ptr_t & p_out,bool p_decode,const GUID & p_owner,t_size p_param1,const void * p_param2,t_size p_param2size,abort_callback & p_abort) = 0; + + //! Returns true if this is not the preferred decoder for this format, another one should be used if found. + bool is_supported_partially_(const GUID& p_owner, t_size p_param1, const void* p_param2, t_size p_param2size); +}; + +class NOVTABLE packet_decoder_entry_v2 : public packet_decoder_entry { + FB2K_MAKE_SERVICE_INTERFACE(packet_decoder_entry_v2, packet_decoder_entry); +public: + //! Returns true if this is not the preferred decoder for this format, another one should be used if found. + virtual bool is_supported_partially(const GUID& p_owner, t_size p_param1, const void* p_param2, t_size p_param2size) = 0; +}; + + +template +class packet_decoder_entry_impl_t : public packet_decoder_entry_v2 +{ +public: + bool is_our_setup(const GUID & p_owner,t_size p_param1,const void * p_param2,t_size p_param2size) override { + return T::g_is_our_setup(p_owner,p_param1,p_param2,p_param2size); + } + void open(service_ptr_t & p_out,bool p_decode,const GUID & p_owner,t_size p_param1,const void * p_param2,t_size p_param2size,abort_callback & p_abort) override { + PFC_ASSERT(is_our_setup(p_owner,p_param1,p_param2,p_param2size)); + service_ptr_t instance = new service_impl_t(); + instance->open(p_owner,p_decode,p_param1,p_param2,p_param2size,p_abort); + p_out = instance.get_ptr(); + } + bool is_supported_partially(const GUID& p_owner, t_size p_param1, const void* p_param2, t_size p_param2size) override { + return T::g_is_supported_partially(p_owner, p_param1, p_param2, p_param2size); + } +}; + +template +class packet_decoder_factory_t : public service_factory_single_t > {}; diff --git a/foobar2000/SDK/play_callback.h b/foobar2000/SDK/play_callback.h new file mode 100644 index 0000000..8c85d96 --- /dev/null +++ b/foobar2000/SDK/play_callback.h @@ -0,0 +1,152 @@ +/*! +Class receiving notifications about playback events. Note that all methods are called only from app's main thread. +Use play_callback_manager to register your dynamically created instances. Statically registered version is available too - see play_callback_static. +*/ +class NOVTABLE play_callback { +public: + //! Playback process is being initialized. on_playback_new_track() should be called soon after this when first file is successfully opened for decoding. + virtual void on_playback_starting(play_control::t_track_command p_command,bool p_paused) = 0; + //! Playback advanced to new track. + virtual void on_playback_new_track(metadb_handle_ptr p_track) = 0; + //! Playback stopped. + virtual void on_playback_stop(play_control::t_stop_reason p_reason) = 0; + //! User has seeked to specific time. + virtual void on_playback_seek(double p_time) = 0; + //! Called on pause/unpause. + virtual void on_playback_pause(bool p_state) = 0; + //! Called when currently played file gets edited. + virtual void on_playback_edited(metadb_handle_ptr p_track) = 0; + //! Dynamic info (VBR bitrate etc) change. + virtual void on_playback_dynamic_info(const file_info & p_info) = 0; + //! Per-track dynamic info (stream track titles etc) change. Happens less often than on_playback_dynamic_info(). + virtual void on_playback_dynamic_info_track(const file_info & p_info) = 0; + //! Called every second, for time display + virtual void on_playback_time(double p_time) = 0; + //! User changed volume settings. Possibly called when not playing. + //! @param p_new_val new volume level in dB; 0 for full volume. + virtual void on_volume_change(float p_new_val) = 0; + + enum { + flag_on_playback_starting = 1 << 0, + flag_on_playback_new_track = 1 << 1, + flag_on_playback_stop = 1 << 2, + flag_on_playback_seek = 1 << 3, + flag_on_playback_pause = 1 << 4, + flag_on_playback_edited = 1 << 5, + flag_on_playback_dynamic_info = 1 << 6, + flag_on_playback_dynamic_info_track = 1 << 7, + flag_on_playback_time = 1 << 8, + flag_on_volume_change = 1 << 9, + + flag_on_playback_all = flag_on_playback_starting | flag_on_playback_new_track | + flag_on_playback_stop | flag_on_playback_seek | + flag_on_playback_pause | flag_on_playback_edited | + flag_on_playback_dynamic_info | flag_on_playback_dynamic_info_track | flag_on_playback_time, + }; +protected: + play_callback() {} + ~play_callback() {} +}; + +//! Standard API (always present); manages registrations of dynamic play_callbacks. \n +//! Usage: use play_callback_manager::get() to obtain on instance. \n +//! Do not reimplement. +class NOVTABLE play_callback_manager : public service_base { + FB2K_MAKE_SERVICE_COREAPI(play_callback_manager); +public: + //! Registers a play_callback object. + //! @param p_callback Interface to register. + //! @param p_flags Indicates which notifications are requested. + //! @param p_forward_status_on_register Set to true to have the callback immediately receive current playback status as notifications if playback is active (eg. to receive info about playback process that started before our callback was registered). + virtual void register_callback(play_callback * p_callback,unsigned p_flags,bool p_forward_status_on_register) = 0; + //! Unregisters a play_callback object. + //! @p_callback Previously registered interface to unregister. + virtual void unregister_callback(play_callback * p_callback) = 0; +}; + +//! Implementation helper. +class play_callback_impl_base : public play_callback { +public: + play_callback_impl_base(unsigned p_flags = UINT_MAX) { + play_callback_manager::get()->register_callback(this,p_flags,false); + } + ~play_callback_impl_base() { + play_callback_manager::get()->unregister_callback(this); + } + void play_callback_reregister(unsigned flags, bool refresh = false) { + auto api = play_callback_manager::get(); + api->unregister_callback(this); + api->register_callback(this,flags,refresh); + } + void on_playback_starting(play_control::t_track_command p_command,bool p_paused) {} + void on_playback_new_track(metadb_handle_ptr p_track) {} + void on_playback_stop(play_control::t_stop_reason p_reason) {} + void on_playback_seek(double p_time) {} + void on_playback_pause(bool p_state) {} + void on_playback_edited(metadb_handle_ptr p_track) {} + void on_playback_dynamic_info(const file_info & p_info) {} + void on_playback_dynamic_info_track(const file_info & p_info) {} + void on_playback_time(double p_time) {} + void on_volume_change(float p_new_val) {} + + PFC_CLASS_NOT_COPYABLE_EX(play_callback_impl_base) +}; + +//! Static (autoregistered) version of play_callback. Use play_callback_static_factory_t to register. +class play_callback_static : public service_base, public play_callback { + FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(play_callback_static); +public: + //! Controls which methods your callback wants called; returned value should not change in run time, you should expect it to be queried only once (on startup). See play_callback::flag_* constants. + virtual unsigned get_flags() = 0; +}; + +template +class play_callback_static_factory_t : public service_factory_single_t {}; + + +//! Gets notified about tracks being played. Notification occurs when at least 60s of the track has been played, or the track has reached its end after at least 1/3 of it has been played through. +//! Use playback_statistics_collector_factory_t to register. +class NOVTABLE playback_statistics_collector : public service_base { + FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(playback_statistics_collector); +public: + virtual void on_item_played(metadb_handle_ptr p_item) = 0; +}; + +template +class playback_statistics_collector_factory_t : public service_factory_single_t {}; + + + + +//! Helper providing a simplified interface for receiving playback events, in case your code does not care about the kind of playback event that has occurred; useful typically for GUI/rendering code that just refreshes some control whenever a playback state change occurs. +class playback_event_notify : private play_callback_impl_base { +public: + playback_event_notify(playback_control::t_display_level level = playback_control::display_level_all) : play_callback_impl_base(GrabCBFlags(level)) {} + + static t_uint32 GrabCBFlags(playback_control::t_display_level level) { + t_uint32 flags = flag_on_playback_starting | flag_on_playback_new_track | flag_on_playback_stop | flag_on_playback_pause | flag_on_playback_edited | flag_on_volume_change; + if (level >= playback_control::display_level_titles) flags |= flag_on_playback_dynamic_info_track; + if (level >= playback_control::display_level_all) flags |= flag_on_playback_seek | flag_on_playback_dynamic_info | flag_on_playback_time; + return flags; + } +protected: + virtual void on_playback_event() {} +private: + void on_playback_starting(play_control::t_track_command p_command,bool p_paused) {on_playback_event();} + void on_playback_new_track(metadb_handle_ptr p_track) {on_playback_event();} + void on_playback_stop(play_control::t_stop_reason p_reason) {on_playback_event();} + void on_playback_seek(double p_time) {on_playback_event();} + void on_playback_pause(bool p_state) {on_playback_event();} + void on_playback_edited(metadb_handle_ptr p_track) {on_playback_event();} + void on_playback_dynamic_info(const file_info & p_info) {on_playback_event();} + void on_playback_dynamic_info_track(const file_info & p_info) {on_playback_event();} + void on_playback_time(double p_time) {on_playback_event();} + void on_volume_change(float p_new_val) {on_playback_event();} +}; + +class playback_volume_notify : private play_callback_impl_base { +public: + playback_volume_notify() : play_callback_impl_base(flag_on_volume_change) {} + // override me + void on_volume_change(float p_new_val) {} +}; diff --git a/foobar2000/SDK/playable_location.cpp b/foobar2000/SDK/playable_location.cpp new file mode 100644 index 0000000..f62146e --- /dev/null +++ b/foobar2000/SDK/playable_location.cpp @@ -0,0 +1,76 @@ +#include "foobar2000.h" + +int playable_location::g_compare(const playable_location & p_item1,const playable_location & p_item2) { + int ret = metadb::path_compare(p_item1.get_path(),p_item2.get_path()); + if (ret != 0) return ret; + return pfc::compare_t(p_item1.get_subsong(),p_item2.get_subsong()); +} +int playable_location::path_compare( const char * p1, const char * p2 ) { + return metadb::path_compare(p1, p2); +} + +bool playable_location::g_equals( const playable_location & p_item1, const playable_location & p_item2) { + return g_compare(p_item1, p_item2) == 0; +} + +pfc::string_base & operator<<(pfc::string_base & p_fmt,const playable_location & p_location) +{ + p_fmt << "\"" << file_path_display(p_location.get_path()) << "\""; + t_uint32 index = p_location.get_subsong_index(); + if (index != 0) p_fmt << " / index: " << p_location.get_subsong_index(); + return p_fmt; +} + + +bool playable_location::operator==(const playable_location & p_other) const { + return metadb::path_compare(get_path(),p_other.get_path()) == 0 && get_subsong() == p_other.get_subsong(); +} +bool playable_location::operator!=(const playable_location & p_other) const { + return !(*this == p_other); +} + +void playable_location::reset() { + set_path("");set_subsong(0); +} + +bool playable_location::is_empty() const { + return * get_path() == 0; +} + +bool playable_location::is_valid() const { + return !is_empty(); +} + +const char * playable_location_impl::get_path() const { + return m_path; +} + +void playable_location_impl::set_path(const char* p_path) { + m_path=p_path; +} + +t_uint32 playable_location_impl::get_subsong() const { + return m_subsong; +} + +void playable_location_impl::set_subsong(t_uint32 p_subsong) { + m_subsong=p_subsong; +} + +const playable_location_impl & playable_location_impl::operator=(const playable_location & src) { + copy(src);return *this; +} + +playable_location_impl::playable_location_impl() : m_subsong(0) {} +playable_location_impl::playable_location_impl(const char * p_path,t_uint32 p_subsong) : m_path(p_path), m_subsong(p_subsong) {} +playable_location_impl::playable_location_impl(const playable_location & src) {copy(src);} + + + +void make_playable_location::set_path(const char*) {throw pfc::exception_not_implemented();} +void make_playable_location::set_subsong(t_uint32) {throw pfc::exception_not_implemented();} + +const char * make_playable_location::get_path() const {return path;} +t_uint32 make_playable_location::get_subsong() const {return num;} + +make_playable_location::make_playable_location(const char * p_path,t_uint32 p_num) : path(p_path), num(p_num) {} diff --git a/foobar2000/SDK/playable_location.h b/foobar2000/SDK/playable_location.h new file mode 100644 index 0000000..d43c2d0 --- /dev/null +++ b/foobar2000/SDK/playable_location.h @@ -0,0 +1,92 @@ +#pragma once + +//playable_location stores location of a playable resource, currently implemented as file path and integer for indicating multiple playable "subsongs" per file +//also see: file_info.h +//for getting more info about resource referenced by a playable_location, see metadb.h + +//char* strings are all UTF-8 + +class NOVTABLE playable_location//interface (for passing around between DLLs) +{ +public: + virtual const char * get_path() const = 0; + virtual void set_path(const char*) = 0; + virtual t_uint32 get_subsong() const = 0; + virtual void set_subsong(t_uint32) = 0; + + void copy(const playable_location & p_other) { + set_path(p_other.get_path()); + set_subsong(p_other.get_subsong()); + } + + static int g_compare(const playable_location & p_item1,const playable_location & p_item2); + static bool g_equals( const playable_location & p_item1, const playable_location & p_item2); + + const playable_location & operator=(const playable_location & src) {copy(src);return *this;} + + bool operator==(const playable_location & p_other) const; + bool operator!=(const playable_location & p_other) const; + + void reset(); + + inline t_uint32 get_subsong_index() const {return get_subsong();} + inline void set_subsong_index(t_uint32 v) {set_subsong(v);} + + bool is_empty() const; + bool is_valid() const; + + + enum {case_sensitive = true}; + typedef pfc::comparator_strcmp path_comparator; + + class comparator { + public: + static int compare(const playable_location & v1, const playable_location & v2) {return g_compare(v1,v2);} + }; + static int path_compare( const char * p1, const char * p2 ); + +protected: + playable_location() {} + ~playable_location() {} +}; + +typedef playable_location * pplayable_location; +typedef playable_location const * pcplayable_location; +typedef playable_location & rplayable_location; +typedef playable_location const & rcplayable_location; + +class playable_location_impl : public playable_location//implementation +{ +public: + virtual const char * get_path() const; + virtual void set_path(const char* p_path); + virtual t_uint32 get_subsong() const; + virtual void set_subsong(t_uint32 p_subsong); + + const playable_location_impl & operator=(const playable_location & src); + + playable_location_impl(); + playable_location_impl(const char * p_path,t_uint32 p_subsong); + playable_location_impl(const playable_location & src); +private: + pfc::string_simple m_path; + t_uint32 m_subsong; +}; + +// usage: somefunction( make_playable_location("file://c:\blah.ogg",0) ); +// only for use as a parameter to a function taking const playable_location & +class make_playable_location : public playable_location +{ + const char * path; + t_uint32 num; + + virtual void set_path(const char*); + virtual void set_subsong(t_uint32); +public: + virtual const char * get_path() const; + virtual t_uint32 get_subsong() const; + + make_playable_location(const char * p_path,t_uint32 p_num); +}; + +pfc::string_base & operator<<(pfc::string_base & p_fmt,const playable_location & p_location); diff --git a/foobar2000/SDK/playback_control.cpp b/foobar2000/SDK/playback_control.cpp new file mode 100644 index 0000000..064fd13 --- /dev/null +++ b/foobar2000/SDK/playback_control.cpp @@ -0,0 +1,139 @@ +#include "foobar2000.h" + +static double parseFraction(const char * fraction) { + unsigned v = 0, d = 1; + while(pfc::char_is_numeric( *fraction) ) { + d *= 10; + v *= 10; + v += (unsigned) ( *fraction - '0' ); + ++fraction; + } + PFC_ASSERT( *fraction == 0 ); + return (double)v / (double)d; +} + +static double parse_time(const char * time) { + unsigned vTotal = 0, vCur = 0; + for(;;) { + char c = *time++; + if (c == 0) return (double) (vTotal + vCur); + else if (pfc::char_is_numeric( c ) ) { + vCur = vCur * 10 + (unsigned)(c-'0'); + } else if (c == ':') { + if (vCur >= 60) {PFC_ASSERT(!"Invalid input"); return 0; } + vTotal += vCur; vCur = 0; vTotal *= 60; + } else if (c == '.') { + return (double) (vTotal + vCur) + parseFraction(time); + } else { + PFC_ASSERT(!"Invalid input"); return 0; + } + } +} + +double playback_control::playback_get_length() +{ + double rv = 0; + metadb_handle_ptr ptr; + if (get_now_playing(ptr)) + { + rv = ptr->get_length(); + } + return rv; +} + +double playback_control::playback_get_length_ex() { + double rv = 0; + metadb_handle_ptr ptr; + if (get_now_playing(ptr)) + { + rv = ptr->get_length(); + if (rv <= 0) { + pfc::string8 temp; + titleformat_object::ptr script; + titleformat_compiler::get()->compile_force(script, "[%length_ex%]"); + this->playback_format_title(NULL, temp, script, NULL, display_level_titles); + if (temp.length() > 0) rv = parse_time(temp); + } + } + return rv; +} + + + + + + +void playback_control::userPrev() { + userActionHook(); + if (this->is_playing() && this->playback_can_seek() && this->playback_get_position() > 5) { + this->playback_seek(0); + } else { + this->previous(); + } +} + +void playback_control::userNext() { + userActionHook(); + this->start(track_command_next); +} + +void playback_control::userMute() { + userActionHook(); + this->volume_mute_toggle(); +} + +void playback_control::userStop() { + userActionHook(); + this->stop(); +} + +void playback_control::userPlay() { + userActionHook(); + this->play_or_pause(); +} + +void playback_control::userPause() { + userActionHook(); + nonUserPause(); +} + +void playback_control::nonUserPause() { + if (this->is_playing()) { + this->pause(true); + } +} + +void playback_control::userStart() { + userActionHook(); + if (this->is_playing()) { + this->pause(false); + } else { + this->start(); + } +} +static const double seekDelta = 30; +void playback_control::userFastForward() { + userActionHook(); + if (!this->playback_can_seek()) { + this->userNext(); return; + } + this->playback_seek_delta(seekDelta); +} + +void playback_control::userRewind() { + userActionHook(); + if (!this->playback_can_seek()) { + this->userPrev(); return; + } + double p = this->playback_get_position(); + if (p < 0) return; + if (p < seekDelta) { + if (p < seekDelta / 3) { + this->userPrev(); + } else { + this->playback_seek(0); + } + } else { + this->playback_seek_delta(-30); + } +} diff --git a/foobar2000/SDK/playback_control.h b/foobar2000/SDK/playback_control.h new file mode 100644 index 0000000..66f1ffe --- /dev/null +++ b/foobar2000/SDK/playback_control.h @@ -0,0 +1,194 @@ +//! Provides control for various playback-related operations. \n +//! All methods provided by this interface work from main app thread only. Calling from another thread will do nothing or trigger an exception. If you need to trigger one of playback_control methods from another thread, see main_thread_callback. \n +//! Do not call playback_control methods from inside any kind of global callback (e.g. playlist callback), otherwise race conditions may occur. \n +//! Use playback_control::get() to obtain an instance. +class NOVTABLE playback_control : public service_base { + FB2K_MAKE_SERVICE_COREAPI(playback_control); +public: + + // Playback stop reason enum. + enum t_stop_reason { + stop_reason_user = 0, + stop_reason_eof, + stop_reason_starting_another, + stop_reason_shutting_down, + }; + + // Playback start mode enum. + enum t_track_command { + track_command_default = 0, + track_command_play, + //! Plays the next track from the current playlist according to the current playback order. + track_command_next, + //! Plays the previous track from the current playlist according to the current playback order. + track_command_prev, + //! For internal use only, do not use. + track_command_settrack, + //! Plays a random track from the current playlist. + track_command_rand, + + //! For internal use only, do not use. + track_command_resume, + }; + + //! Retrieves now playing item handle. + //! @returns true on success, false on failure (not playing). + virtual bool get_now_playing(metadb_handle_ptr & p_out) = 0; + //! Starts playback. If playback is already active, existing process is stopped first. + //! @param p_command Specifies what track to start playback from. See t_track_Command enum for more info. + //! @param p_paused Specifies whether playback should be started as paused. + virtual void start(t_track_command p_command = track_command_play,bool p_paused = false) = 0; + //! Stops playback. + virtual void stop() = 0; + //! Returns whether playback is active. + virtual bool is_playing() = 0; + //! Returns whether playback is active and in paused state. + virtual bool is_paused() = 0; + //! Toggles pause state if playback is active. + //! @param p_state set to true when pausing or to false when unpausing. + virtual void pause(bool p_state) = 0; + + //! Retrieves stop-after-current-track option state. + virtual bool get_stop_after_current() = 0; + //! Alters stop-after-current-track option state. + virtual void set_stop_after_current(bool p_state) = 0; + + //! Alters playback volume level. + //! @param p_value volume in dB; 0 for full volume. + virtual void set_volume(float p_value) = 0; + //! Retrieves playback volume level. + //! @returns current playback volume level, in dB; 0 for full volume. + virtual float get_volume() = 0; + //! Alters playback volume level one step up. + virtual void volume_up() = 0; + //! Alters playback volume level one step down. + virtual void volume_down() = 0; + //! Toggles playback mute state. + virtual void volume_mute_toggle() = 0; + //! Seeks in currenly played track to specified time. + //! @param p_time target time in seconds. + virtual void playback_seek(double p_time) = 0; + //! Seeks in currently played track by specified time forward or back. + //! @param p_delta time in seconds to seek by; can be positive to seek forward or negative to seek back. + virtual void playback_seek_delta(double p_delta) = 0; + //! Returns whether currently played track is seekable. If it's not, playback_seek/playback_seek_delta calls will be ignored. + virtual bool playback_can_seek() = 0; + //! Returns current playback position within currently played track, in seconds. + virtual double playback_get_position() = 0; + + //! Type used to indicate level of dynamic playback-related info displayed. Safe to use with <> opereators, e.g. level above N always includes information rendered by level N. + enum t_display_level { + //! No playback-related info + display_level_none, + //! Static info and is_playing/is_paused stats + display_level_basic, + //! display_level_basic + dynamic track titles on e.g. live streams + display_level_titles, + //! display_level_titles + timing + VBR bitrate display etc + display_level_all, + }; + + //! Renders information about currently playing item. + //! @param p_hook Optional callback object overriding fields and functions; set to NULL if not used. + //! @param p_out String receiving the output on success. + //! @param p_script Titleformat script to use. Use titleformat_compiler service to create one. + //! @param p_filter Optional callback object allowing input to be filtered according to context (i.e. removal of linebreak characters present in tags when rendering playlist lines). Set to NULL when not used. + //! @param p_level Indicates level of dynamic playback-related info displayed. See t_display_level enum for more details. + //! @returns true on success, false when no item is currently being played. + virtual bool playback_format_title(titleformat_hook * p_hook,pfc::string_base & p_out,const service_ptr_t & p_script,titleformat_text_filter * p_filter,t_display_level p_level) = 0; + + + + //! Helper; renders info about any item, including currently playing item info if the item is currently played. + bool playback_format_title_ex(metadb_handle_ptr p_item,titleformat_hook * p_hook,pfc::string_base & p_out,const service_ptr_t & p_script,titleformat_text_filter * p_filter,t_display_level p_level) { + if (p_item.is_empty()) return playback_format_title(p_hook,p_out,p_script,p_filter,p_level); + metadb_handle_ptr temp; + if (get_now_playing(temp)) { + if (temp == p_item) { + return playback_format_title(p_hook,p_out,p_script,p_filter,p_level); + } + } + p_item->format_title(p_hook,p_out,p_script,p_filter); + return true; + } + + //! Helper; retrieves length of currently playing item. + double playback_get_length(); + // Extended version: queries dynamic track info for the rare cases where that is different from static info. + double playback_get_length_ex(); + + //! Toggles stop-after-current state. + void toggle_stop_after_current() {set_stop_after_current(!get_stop_after_current());} + //! Toggles pause state. + void toggle_pause() {pause(!is_paused());} + + //! Starts playback if playback is inactive, otherwise toggles pause. + void play_or_pause() {if (is_playing()) toggle_pause(); else start();} + void play_or_unpause() { if (is_playing()) pause(false); else start();} + + void previous() { start(track_command_prev); } + void next() { start(track_command_next); } + + //deprecated + inline void play_start(t_track_command p_command = track_command_play,bool p_paused = false) {start(p_command,p_paused);} + //deprecated + inline void play_stop() {stop();} + + bool is_muted() {return get_volume() == volume_mute;} + + static const int volume_mute = -100; + + + + // new fb2k mobile specific user command handlers + void userPrev(); + void userNext(); + void userMute(); + void userStop(); + void userPlay(); + void userPause(); + void userStart(); + void userFastForward(); + void userRewind(); + void nonUserPause(); + + // #$@! FiiO hack $!#@ + void userActionHook() {} +}; + +class playback_control_v2 : public playback_control { + FB2K_MAKE_SERVICE_COREAPI_EXTENSION(playback_control_v2,playback_control); +public: + //! Returns user-specified the step dB value for volume decrement/increment. + virtual float get_volume_step() = 0; +}; + +//! \since 1.2 +class playback_control_v3 : public playback_control_v2 { + FB2K_MAKE_SERVICE_COREAPI_EXTENSION(playback_control_v3, playback_control_v2); +public: + //! Custom volume API - for use with specific output devices only. \n + //! Note that custom volume SHOULD NOT EVER be presented as a slider where the user can immediately go to the maximum value. \n + //! Custom volume mode dispatches on_volume_changed callbacks on change, though the passed value is meaningless; \n + //! the components should query the current value from playback_control_v3. \n + //! Note that custom volume mode makes set_volume() / get_volume() meaningless, \n + //! but volume_up() / volume_down() / volume_mute_toggle() still work like they should (increment/decrement by one unit). + //! @returns whether custom volume mode is active. + virtual bool custom_volume_is_active() = 0; + //! Retrieves the current volume value for the custom volume mode. \n + //! The volume units are arbitrary and specified by the device maker; see also: custom_volume_min(), custom_volume_max(). + virtual int custom_volume_get() = 0; + //! Sets the current volume value for the custom volume mode. \n + //! The volume units are arbitrary and specified by the device maker; see also: custom_volume_min(), custom_volume_max(). + //! CAUTION: you should NOT allow the user to easily go immediately to any value, it might blow their speakers out! + virtual void custom_volume_set(int val) = 0; + //! Returns the minimum custom volume value for the current output device. + virtual int custom_volume_min() = 0; + //! Returns the maximum custom volume value for the current output device. + virtual int custom_volume_max() = 0; + + virtual void restart() = 0; +}; + +//for compatibility with old code +typedef playback_control play_control; diff --git a/foobar2000/SDK/playback_stream_capture.h b/foobar2000/SDK/playback_stream_capture.h new file mode 100644 index 0000000..f9a33e2 --- /dev/null +++ b/foobar2000/SDK/playback_stream_capture.h @@ -0,0 +1,25 @@ +#pragma once + +//! \since 1.0 +//! Implemented by components - register with playback_stream_capture methods. +class NOVTABLE playback_stream_capture_callback { +public: + //! Delivers a real-time chunk of audio data. \n + //! Audio is roughly synchronized with what can currently be heard. This API is provided for utility purposes such as streaming; if you want to implement a visualisation, use the visualisation_manager API instead. \n + //! Called only from the main thread. + virtual void on_chunk(const audio_chunk &) = 0; +protected: + playback_stream_capture_callback() {} + ~playback_stream_capture_callback() {} +}; + +//! \since 1.0 +//! Implemented by core. +class NOVTABLE playback_stream_capture : public service_base { + FB2K_MAKE_SERVICE_COREAPI(playback_stream_capture) +public: + //! Possible to call only from the main thread. + virtual void add_callback(playback_stream_capture_callback * ) = 0; + //! Possible to call only from the main thread. + virtual void remove_callback(playback_stream_capture_callback * ) = 0; +}; diff --git a/foobar2000/SDK/playlist.cpp b/foobar2000/SDK/playlist.cpp new file mode 100644 index 0000000..14ddbd3 --- /dev/null +++ b/foobar2000/SDK/playlist.cpp @@ -0,0 +1,931 @@ +#include "foobar2000.h" + +namespace { + class enum_items_callback_retrieve_item : public playlist_manager::enum_items_callback + { + metadb_handle_ptr m_item; + public: + enum_items_callback_retrieve_item() : m_item(0) {} + bool on_item(t_size p_index,const metadb_handle_ptr & p_location,bool b_selected) + { + assert(m_item.is_empty()); + m_item = p_location; + return false; + } + inline const metadb_handle_ptr & get_item() {return m_item;} + }; + + class enum_items_callback_retrieve_selection : public playlist_manager::enum_items_callback + { + bool m_state; + public: + enum_items_callback_retrieve_selection() : m_state(false) {} + bool on_item(t_size p_index,const metadb_handle_ptr & p_location,bool b_selected) + { + m_state = b_selected; + return false; + } + inline bool get_state() {return m_state;} + }; + + class enum_items_callback_retrieve_selection_mask : public playlist_manager::enum_items_callback + { + bit_array_var & m_out; + public: + enum_items_callback_retrieve_selection_mask(bit_array_var & p_out) : m_out(p_out) {} + bool on_item(t_size p_index,const metadb_handle_ptr & p_location,bool b_selected) + { + m_out.set(p_index,b_selected); + return true; + } + }; + + class enum_items_callback_retrieve_all_items : public playlist_manager::enum_items_callback + { + pfc::list_base_t & m_out; + public: + enum_items_callback_retrieve_all_items(pfc::list_base_t & p_out) : m_out(p_out) {m_out.remove_all();} + bool on_item(t_size p_index,const metadb_handle_ptr & p_location,bool b_selected) + { + m_out.add_item(p_location); + return true; + } + }; + + class enum_items_callback_retrieve_selected_items : public playlist_manager::enum_items_callback + { + pfc::list_base_t & m_out; + public: + enum_items_callback_retrieve_selected_items(pfc::list_base_t & p_out) : m_out(p_out) {m_out.remove_all();} + bool on_item(t_size p_index,const metadb_handle_ptr & p_location,bool b_selected) + { + if (b_selected) m_out.add_item(p_location); + return true; + } + }; + + class enum_items_callback_count_selection : public playlist_manager::enum_items_callback + { + t_size m_counter,m_max; + public: + enum_items_callback_count_selection(t_size p_max) : m_max(p_max), m_counter(0) {} + bool on_item(t_size p_index,const metadb_handle_ptr & p_location,bool b_selected) + { + if (b_selected) + { + if (++m_counter >= m_max) return false; + } + return true; + } + + inline t_size get_count() {return m_counter;} + }; + +} + +void playlist_manager::playlist_get_all_items(t_size p_playlist,pfc::list_base_t & out) +{ + playlist_get_items(p_playlist,out, pfc::bit_array_true()); +} + +void playlist_manager::playlist_get_selected_items(t_size p_playlist,pfc::list_base_t & out) +{ + enum_items_callback_retrieve_selected_items cb(out); + playlist_enum_items(p_playlist,cb,pfc::bit_array_true()); +} + +void playlist_manager::playlist_get_selection_mask(t_size p_playlist,bit_array_var & out) +{ + enum_items_callback_retrieve_selection_mask cb(out); + playlist_enum_items(p_playlist,cb,pfc::bit_array_true()); +} + +bool playlist_manager::playlist_is_item_selected(t_size p_playlist,t_size p_item) +{ + enum_items_callback_retrieve_selection callback; + playlist_enum_items(p_playlist,callback,pfc::bit_array_one(p_item)); + return callback.get_state(); +} + +metadb_handle_ptr playlist_manager::playlist_get_item_handle(t_size playlist, t_size item) { + metadb_handle_ptr temp; + if (!playlist_get_item_handle(temp, playlist, item)) throw pfc::exception_invalid_params(); + PFC_ASSERT( temp.is_valid() ); + return temp; + +} +bool playlist_manager::playlist_get_item_handle(metadb_handle_ptr & p_out,t_size p_playlist,t_size p_item) +{ + enum_items_callback_retrieve_item callback; + playlist_enum_items(p_playlist,callback,pfc::bit_array_one(p_item)); + p_out = callback.get_item(); + return p_out.is_valid(); +} + +void playlist_manager::g_make_selection_move_permutation(t_size * p_output,t_size p_count,const bit_array & p_selection,int p_delta) { + pfc::create_move_items_permutation(p_output,p_count,p_selection,p_delta); +} + +bool playlist_manager::playlist_move_selection(t_size p_playlist,int p_delta) { + if (p_delta==0) return true; + + t_size count = playlist_get_item_count(p_playlist); + + pfc::array_t order; order.set_size(count); + pfc::array_t selection; selection.set_size(count); + + pfc::bit_array_var_table mask(selection.get_ptr(),selection.get_size()); + playlist_get_selection_mask(p_playlist, mask); + g_make_selection_move_permutation(order.get_ptr(),count,mask,p_delta); + return playlist_reorder_items(p_playlist,order.get_ptr(),count); +} + +//retrieving status +t_size playlist_manager::activeplaylist_get_item_count() +{ + t_size playlist = get_active_playlist(); + if (playlist == pfc_infinite) return 0; + else return playlist_get_item_count(playlist); +} + +void playlist_manager::activeplaylist_enum_items(enum_items_callback & p_callback,const bit_array & p_mask) +{ + t_size playlist = get_active_playlist(); + if (playlist != pfc_infinite) playlist_enum_items(playlist,p_callback,p_mask); +} + +t_size playlist_manager::activeplaylist_get_focus_item() +{ + t_size playlist = get_active_playlist(); + if (playlist == pfc_infinite) return pfc_infinite; + else return playlist_get_focus_item(playlist); +} + +bool playlist_manager::activeplaylist_get_name(pfc::string_base & p_out) +{ + t_size playlist = get_active_playlist(); + if (playlist == pfc_infinite) return false; + else return playlist_get_name(playlist,p_out); +} + +//modifying playlist +bool playlist_manager::activeplaylist_reorder_items(const t_size * order,t_size count) +{ + t_size playlist = get_active_playlist(); + if (playlist != pfc_infinite) return playlist_reorder_items(playlist,order,count); + else return false; +} + +void playlist_manager::activeplaylist_set_selection(const bit_array & affected,const bit_array & status) +{ + t_size playlist = get_active_playlist(); + if (playlist != pfc_infinite) playlist_set_selection(playlist,affected,status); +} + +bool playlist_manager::activeplaylist_remove_items(const bit_array & mask) +{ + t_size playlist = get_active_playlist(); + if (playlist != pfc_infinite) return playlist_remove_items(playlist,mask); + else return false; +} + +bool playlist_manager::activeplaylist_replace_item(t_size p_item,const metadb_handle_ptr & p_new_item) +{ + t_size playlist = get_active_playlist(); + if (playlist != pfc_infinite) return playlist_replace_item(playlist,p_item,p_new_item); + else return false; +} + +void playlist_manager::activeplaylist_set_focus_item(t_size p_item) +{ + t_size playlist = get_active_playlist(); + if (playlist != pfc_infinite) playlist_set_focus_item(playlist,p_item); +} + +t_size playlist_manager::activeplaylist_insert_items(t_size p_base,const pfc::list_base_const_t & data,const bit_array & p_selection) +{ + t_size playlist = get_active_playlist(); + if (playlist != pfc_infinite) return playlist_insert_items(playlist,p_base,data,p_selection); + else return pfc_infinite; +} + +void playlist_manager::activeplaylist_ensure_visible(t_size p_item) +{ + t_size playlist = get_active_playlist(); + if (playlist != pfc_infinite) playlist_ensure_visible(playlist,p_item); +} + +bool playlist_manager::activeplaylist_rename(const char * p_name,t_size p_name_len) +{ + t_size playlist = get_active_playlist(); + if (playlist != pfc_infinite) return playlist_rename(playlist,p_name,p_name_len); + else return false; +} + +bool playlist_manager::activeplaylist_is_item_selected(t_size p_item) +{ + t_size playlist = get_active_playlist(); + if (playlist != pfc_infinite) return playlist_is_item_selected(playlist,p_item); + else return false; +} + +metadb_handle_ptr playlist_manager::activeplaylist_get_item_handle(t_size p_item) { + metadb_handle_ptr temp; + if (!activeplaylist_get_item_handle(temp, p_item)) throw pfc::exception_invalid_params(); + PFC_ASSERT( temp.is_valid() ); + return temp; +} +bool playlist_manager::activeplaylist_get_item_handle(metadb_handle_ptr & p_out,t_size p_item) +{ + t_size playlist = get_active_playlist(); + if (playlist != pfc_infinite) return playlist_get_item_handle(p_out,playlist,p_item); + else return false; +} + +void playlist_manager::activeplaylist_move_selection(int p_delta) +{ + t_size playlist = get_active_playlist(); + if (playlist != pfc_infinite) playlist_move_selection(playlist,p_delta); +} + +void playlist_manager::activeplaylist_get_selection_mask(bit_array_var & out) +{ + t_size playlist = get_active_playlist(); + if (playlist != pfc_infinite) playlist_get_selection_mask(playlist,out); +} + +void playlist_manager::activeplaylist_get_all_items(pfc::list_base_t & out) +{ + t_size playlist = get_active_playlist(); + if (playlist != pfc_infinite) playlist_get_all_items(playlist,out); +} + +void playlist_manager::activeplaylist_get_selected_items(pfc::list_base_t & out) +{ + t_size playlist = get_active_playlist(); + if (playlist != pfc_infinite) playlist_get_selected_items(playlist,out); +} + +bool playlist_manager::remove_playlist(t_size idx) +{ + return remove_playlists(pfc::bit_array_one(idx)); +} + +bool playlist_incoming_item_filter::process_location(const char * url,pfc::list_base_t & out,bool filter,const char * p_mask,const char * p_exclude,HWND p_parentwnd) +{ + return process_locations(pfc::list_single_ref_t(url),out,filter,p_mask,p_exclude,p_parentwnd); +} + +void playlist_manager::playlist_clear(t_size p_playlist) +{ + playlist_remove_items(p_playlist, pfc::bit_array_true()); +} + +void playlist_manager::activeplaylist_clear() +{ + t_size playlist = get_active_playlist(); + if (playlist != pfc_infinite) playlist_clear(playlist); +} + +bool playlist_manager::playlist_update_content(t_size playlist, metadb_handle_list_cref content, bool bUndoBackup) { + metadb_handle_list old; + playlist_get_all_items(playlist, old); + if (old.get_size() == 0) { + if (content.get_size() == 0) return false; + if (bUndoBackup) playlist_undo_backup(playlist); + playlist_add_items(playlist, content, pfc::bit_array_false()); + return true; + } + pfc::avltree_t itemsOld, itemsNew; + + for(t_size walk = 0; walk < old.get_size(); ++walk) itemsOld += old[walk]; + for(t_size walk = 0; walk < content.get_size(); ++walk) itemsNew += content[walk]; + pfc::bit_array_bittable removeMask(old.get_size()); + pfc::bit_array_bittable filterMask(content.get_size()); + bool gotNew = false, filterNew = false, gotRemove = false; + for(t_size walk = 0; walk < content.get_size(); ++walk) { + const bool state = !itemsOld.have_item(content[walk]); + if (state) gotNew = true; + else filterNew = true; + filterMask.set(walk, state); + } + for(t_size walk = 0; walk < old.get_size(); ++walk) { + const bool state = !itemsNew.have_item(old[walk]); + if (state) gotRemove = true; + removeMask.set(walk, state); + } + if (!gotNew && !gotRemove) return false; + if (bUndoBackup) playlist_undo_backup(playlist); + if (gotRemove) { + playlist_remove_items(playlist, removeMask); + } + if (gotNew) { + if (filterNew) { + metadb_handle_list temp(content); + temp.filter_mask(filterMask); + playlist_add_items(playlist, temp, pfc::bit_array_false()); + } else { + playlist_add_items(playlist, content, pfc::bit_array_false()); + } + } + + { + playlist_get_all_items(playlist, old); + pfc::array_t order; + if (pfc::guess_reorder_pattern >(order, old, content)) { + playlist_reorder_items(playlist, order.get_ptr(), order.get_size()); + } + } + return true; +} +bool playlist_manager::playlist_add_items(t_size playlist,const pfc::list_base_const_t & data,const bit_array & p_selection) +{ + return playlist_insert_items(playlist,pfc_infinite,data,p_selection) != pfc_infinite; +} + +bool playlist_manager::activeplaylist_add_items(const pfc::list_base_const_t & data,const bit_array & p_selection) +{ + t_size playlist = get_active_playlist(); + if (playlist != pfc_infinite) return playlist_add_items(playlist,data,p_selection); + else return false; +} + +bool playlist_manager::playlist_insert_items_filter(t_size p_playlist,t_size p_base,const pfc::list_base_const_t & p_data,bool p_select) +{ + metadb_handle_list temp; + if (!playlist_incoming_item_filter::get()->filter_items(p_data,temp)) + return false; + return playlist_insert_items(p_playlist,p_base,temp, pfc::bit_array_val(p_select)) != pfc_infinite; +} + +bool playlist_manager::activeplaylist_insert_items_filter(t_size p_base,const pfc::list_base_const_t & p_data,bool p_select) +{ + t_size playlist = get_active_playlist(); + if (playlist != pfc_infinite) return playlist_insert_items_filter(playlist,p_base,p_data,p_select); + else return false; +} + +bool playlist_manager::playlist_insert_locations(t_size p_playlist,t_size p_base,const pfc::list_base_const_t & p_urls,bool p_select,HWND p_parentwnd) +{ + metadb_handle_list temp; + if (!playlist_incoming_item_filter::get()->process_locations(p_urls,temp,true,0,0,p_parentwnd)) return false; + return playlist_insert_items(p_playlist,p_base,temp, pfc::bit_array_val(p_select)) != pfc_infinite; +} + +bool playlist_manager::activeplaylist_insert_locations(t_size p_base,const pfc::list_base_const_t & p_urls,bool p_select,HWND p_parentwnd) +{ + t_size playlist = get_active_playlist(); + if (playlist != pfc_infinite) return playlist_insert_locations(playlist,p_base,p_urls,p_select,p_parentwnd); + else return false; +} + +bool playlist_manager::playlist_add_items_filter(t_size p_playlist,const pfc::list_base_const_t & p_data,bool p_select) +{ + return playlist_insert_items_filter(p_playlist,pfc_infinite,p_data,p_select); +} + +bool playlist_manager::activeplaylist_add_items_filter(const pfc::list_base_const_t & p_data,bool p_select) +{ + return activeplaylist_insert_items_filter(pfc_infinite,p_data,p_select); +} + +bool playlist_manager::playlist_add_locations(t_size p_playlist,const pfc::list_base_const_t & p_urls,bool p_select,HWND p_parentwnd) +{ + return playlist_insert_locations(p_playlist,pfc_infinite,p_urls,p_select,p_parentwnd); +} +bool playlist_manager::activeplaylist_add_locations(const pfc::list_base_const_t & p_urls,bool p_select,HWND p_parentwnd) +{ + return activeplaylist_insert_locations(pfc_infinite,p_urls,p_select,p_parentwnd); +} + +void playlist_manager::reset_playing_playlist() +{ + set_playing_playlist(get_active_playlist()); +} + +void playlist_manager::playlist_clear_selection(t_size p_playlist) +{ + playlist_set_selection(p_playlist, pfc::bit_array_true(), pfc::bit_array_false()); +} + +void playlist_manager::activeplaylist_clear_selection() +{ + t_size playlist = get_active_playlist(); + if (playlist != pfc_infinite) playlist_clear_selection(playlist); +} + +void playlist_manager::activeplaylist_undo_backup() +{ + t_size playlist = get_active_playlist(); + if (playlist != pfc_infinite) playlist_undo_backup(playlist); +} + +bool playlist_manager::activeplaylist_undo_restore() +{ + t_size playlist = get_active_playlist(); + if (playlist != pfc_infinite) return playlist_undo_restore(playlist); + else return false; +} + +bool playlist_manager::activeplaylist_redo_restore() +{ + t_size playlist = get_active_playlist(); + if (playlist != pfc_infinite) return playlist_redo_restore(playlist); + else return false; +} + +void playlist_manager::playlist_remove_selection(t_size p_playlist,bool p_crop) +{ + pfc::bit_array_bittable table(playlist_get_item_count(p_playlist)); + playlist_get_selection_mask(p_playlist,table); + if (p_crop) playlist_remove_items(p_playlist, pfc::bit_array_not(table)); + else playlist_remove_items(p_playlist,table); +} + +void playlist_manager::activeplaylist_remove_selection(bool p_crop) +{ + t_size playlist = get_active_playlist(); + if (playlist != pfc_infinite) playlist_remove_selection(playlist,p_crop); +} + +void playlist_manager::activeplaylist_item_format_title(t_size p_item,titleformat_hook * p_hook,pfc::string_base & out,const service_ptr_t & p_script,titleformat_text_filter * p_filter,play_control::t_display_level p_playback_info_level) +{ + t_size playlist = get_active_playlist(); + if (playlist == pfc_infinite) out = "NJET"; + else playlist_item_format_title(playlist,p_item,p_hook,out,p_script,p_filter,p_playback_info_level); +} + +void playlist_manager::playlist_set_selection_single(t_size p_playlist,t_size p_item,bool p_state) +{ + playlist_set_selection(p_playlist, pfc::bit_array_one(p_item), pfc::bit_array_val(p_state)); +} + +void playlist_manager::activeplaylist_set_selection_single(t_size p_item,bool p_state) +{ + t_size playlist = get_active_playlist(); + if (playlist != pfc_infinite) playlist_set_selection_single(playlist,p_item,p_state); +} + +t_size playlist_manager::playlist_get_selection_count(t_size p_playlist,t_size p_max) +{ + enum_items_callback_count_selection callback(p_max); + playlist_enum_items(p_playlist,callback, pfc::bit_array_true()); + return callback.get_count(); +} + +t_size playlist_manager::activeplaylist_get_selection_count(t_size p_max) +{ + t_size playlist = get_active_playlist(); + if (playlist != pfc_infinite) return playlist_get_selection_count(playlist,p_max); + else return 0; +} + +bool playlist_manager::playlist_get_focus_item_handle(metadb_handle_ptr & p_out,t_size p_playlist) +{ + t_size index = playlist_get_focus_item(p_playlist); + if (index == pfc_infinite) return false; + return playlist_get_item_handle(p_out,p_playlist,index); +} + +bool playlist_manager::activeplaylist_get_focus_item_handle(metadb_handle_ptr & p_out) +{ + t_size playlist = get_active_playlist(); + if (playlist != pfc_infinite) return playlist_get_focus_item_handle(p_out,playlist); + else return false; +} + +t_size playlist_manager::find_playlist(const char * p_name,t_size p_name_length) +{ + t_size n, m = get_playlist_count(); + pfc::string_formatter temp; + for(n=0;n namebuffer; + namebuffer << new_playlist_text << " (" << walk << ")"; + if (find_playlist(namebuffer,pfc_infinite) == pfc_infinite) return create_playlist(namebuffer,pfc_infinite,p_index); + } +} + +bool playlist_manager::activeplaylist_sort_by_format(const char * spec,bool p_sel_only) +{ + t_size playlist = get_active_playlist(); + if (playlist != pfc_infinite) return playlist_sort_by_format(playlist,spec,p_sel_only); + else return false; +} + +bool playlist_manager::highlight_playing_item() +{ + t_size playlist,item; + if (!get_playing_item_location(&playlist,&item)) return false; + set_active_playlist(playlist); + playlist_set_focus_item(playlist,item); + playlist_set_selection(playlist, pfc::bit_array_true(), pfc::bit_array_one(item)); + playlist_ensure_visible(playlist,item); + return true; +} + +void playlist_manager::playlist_get_items(t_size p_playlist,pfc::list_base_t & out,const bit_array & p_mask) +{ + enum_items_callback_retrieve_all_items cb(out); + playlist_enum_items(p_playlist,cb,p_mask); +} + +void playlist_manager::activeplaylist_get_items(pfc::list_base_t & out,const bit_array & p_mask) +{ + t_size playlist = get_active_playlist(); + if (playlist != pfc_infinite) playlist_get_items(playlist,out,p_mask); + else out.remove_all(); +} + +void playlist_manager::active_playlist_fix() +{ + t_size playlist = get_active_playlist(); + if (playlist == pfc_infinite) + { + t_size max = get_playlist_count(); + if (max == 0) + { + create_playlist_autoname(); + } + set_active_playlist(0); + } +} + +namespace { + class enum_items_callback_remove_list : public playlist_manager::enum_items_callback + { + const metadb_handle_list & m_data; + bit_array_var & m_table; + t_size m_found; + public: + enum_items_callback_remove_list(const metadb_handle_list & p_data,bit_array_var & p_table) : m_data(p_data), m_table(p_table), m_found(0) {} + bool on_item(t_size p_index,const metadb_handle_ptr & p_location,bool b_selected) + { + bool found = m_data.bsearch_by_pointer(p_location) != pfc_infinite; + m_table.set(p_index,found); + if (found) m_found++; + return true; + } + + inline t_size get_found() const {return m_found;} + }; +} + +void playlist_manager::remove_items_from_all_playlists(const pfc::list_base_const_t & p_data) +{ + t_size playlist_num, playlist_max = get_playlist_count(); + if (playlist_max != pfc_infinite) + { + metadb_handle_list temp; + temp.add_items(p_data); + temp.sort_by_pointer(); + for(playlist_num = 0; playlist_num < playlist_max; playlist_num++ ) + { + t_size playlist_item_count = playlist_get_item_count(playlist_num); + if (playlist_item_count == pfc_infinite) break; + pfc::bit_array_bittable table(playlist_item_count); + enum_items_callback_remove_list callback(temp,table); + playlist_enum_items(playlist_num,callback, pfc::bit_array_true()); + if (callback.get_found()>0) + playlist_remove_items(playlist_num,table); + } + } +} + +bool playlist_manager::get_all_items(pfc::list_base_t & out) +{ + t_size n, m = get_playlist_count(); + if (m == pfc_infinite) return false; + enum_items_callback_retrieve_all_items callback(out); + for(n=0;n 0) + { + if (idx >= total) idx = total-1; + set_active_playlist(idx); + } + } + return true; + } + else return false; +} + + + +bool t_playback_queue_item::operator==(const t_playback_queue_item & p_item) const +{ + return m_handle == p_item.m_handle && m_playlist == p_item.m_playlist && m_item == p_item.m_item; +} + +bool t_playback_queue_item::operator!=(const t_playback_queue_item & p_item) const +{ + return m_handle != p_item.m_handle || m_playlist != p_item.m_playlist || m_item != p_item.m_item; +} + + + +bool playlist_manager::activeplaylist_execute_default_action(t_size p_item) { + t_size idx = get_active_playlist(); + if (idx == pfc_infinite) return false; + else return playlist_execute_default_action(idx,p_item); +} + +namespace { + class completion_notify_dfd : public completion_notify { + public: + completion_notify_dfd(const pfc::list_base_const_t & p_data,service_ptr_t p_notify) : m_data(p_data), m_notify(p_notify) {} + void on_completion(unsigned p_code) { + switch(p_code) { + case metadb_io::load_info_aborted: + m_notify->on_aborted(); + break; + default: + m_notify->on_completion(m_data); + break; + } + } + private: + metadb_handle_list m_data; + service_ptr_t m_notify; + }; +}; + +void dropped_files_data_impl::to_handles_async_ex(t_uint32 p_op_flags,HWND p_parentwnd,service_ptr_t p_notify) { + if (m_is_paths) { + playlist_incoming_item_filter_v2::get()->process_locations_async( + m_paths, + p_op_flags, + NULL, + NULL, + p_parentwnd, + p_notify); + } else { + t_uint32 flags = 0; + if (p_op_flags & playlist_incoming_item_filter_v2::op_flag_background) flags |= metadb_io_v2::op_flag_background; + if (p_op_flags & playlist_incoming_item_filter_v2::op_flag_delay_ui) flags |= metadb_io_v2::op_flag_delay_ui; + metadb_io_v2::get()->load_info_async(m_handles,metadb_io::load_info_default,p_parentwnd,flags,new service_impl_t(m_handles,p_notify)); + } +} +void dropped_files_data_impl::to_handles_async(bool p_filter,HWND p_parentwnd,service_ptr_t p_notify) { + to_handles_async_ex(p_filter ? 0 : playlist_incoming_item_filter_v2::op_flag_no_filter,p_parentwnd,p_notify); +} + +bool dropped_files_data_impl::to_handles(pfc::list_base_t & p_out,bool p_filter,HWND p_parentwnd) { + if (m_is_paths) { + return playlist_incoming_item_filter::get()->process_locations(m_paths,p_out,p_filter,NULL,NULL,p_parentwnd); + } else { + if (metadb_io::get()->load_info_multi(m_handles,metadb_io::load_info_default,p_parentwnd,true) == metadb_io::load_info_aborted) return false; + p_out = m_handles; + return true; + } +} + +void playlist_manager::playlist_activate_delta(int p_delta) { + const t_size total = get_playlist_count(); + if (total > 0) { + t_size active = get_active_playlist(); + + //clip p_delta to -(total-1)...(total-1) range + if (p_delta < 0) { + p_delta = - ( (-p_delta) % (t_ssize)total ); + } else { + p_delta = p_delta % total; + } + if (p_delta != 0) { + if (active == pfc_infinite) { + //special case when no playlist is active + if (p_delta > 0) { + active = (t_size)(p_delta - 1); + } else { + active = (total + p_delta);//p_delta is negative + } + } else { + active = (t_size) (active + total + p_delta) % total; + } + set_active_playlist(active % total); + } + } +} +namespace { + class enum_items_callback_get_selected_count : public playlist_manager::enum_items_callback { + public: + enum_items_callback_get_selected_count() : m_found() {} + t_size get_count() const {return m_found;} + bool on_item(t_size p_index,const metadb_handle_ptr & p_location,bool b_selected) { + if (b_selected) m_found++; + return true; + } + private: + t_size m_found; + }; +} +t_size playlist_manager::playlist_get_selected_count(t_size p_playlist,bit_array const & p_mask) { + enum_items_callback_get_selected_count callback; + playlist_enum_items(p_playlist,callback,p_mask); + return callback.get_count(); +} + +namespace { + class enum_items_callback_find_item : public playlist_manager::enum_items_callback { + public: + enum_items_callback_find_item(metadb_handle_ptr p_lookingFor) : m_result(pfc_infinite), m_lookingFor(p_lookingFor) {} + t_size result() const {return m_result;} + bool on_item(t_size p_index,const metadb_handle_ptr & p_location,bool b_selected) { + if (p_location == m_lookingFor) { + m_result = p_index; + return false; + } else { + return true; + } + } + private: + metadb_handle_ptr m_lookingFor; + t_size m_result; + }; + class enum_items_callback_find_item_selected : public playlist_manager::enum_items_callback { + public: + enum_items_callback_find_item_selected(metadb_handle_ptr p_lookingFor) : m_result(pfc_infinite), m_lookingFor(p_lookingFor) {} + t_size result() const {return m_result;} + bool on_item(t_size p_index,const metadb_handle_ptr & p_location,bool b_selected) { + if (b_selected && p_location == m_lookingFor) { + m_result = p_index; + return false; + } else { + return true; + } + } + private: + metadb_handle_ptr m_lookingFor; + t_size m_result; + }; +} + +bool playlist_manager::playlist_find_item(t_size p_playlist,metadb_handle_ptr p_item,t_size & p_result) { + enum_items_callback_find_item callback(p_item); + playlist_enum_items(p_playlist,callback,pfc::bit_array_true()); + t_size result = callback.result(); + if (result == pfc_infinite) return false; + p_result = result; + return true; +} +bool playlist_manager::playlist_find_item_selected(t_size p_playlist,metadb_handle_ptr p_item,t_size & p_result) { + enum_items_callback_find_item_selected callback(p_item); + playlist_enum_items(p_playlist,callback,pfc::bit_array_true()); + t_size result = callback.result(); + if (result == pfc_infinite) return false; + p_result = result; + return true; +} +t_size playlist_manager::playlist_set_focus_by_handle(t_size p_playlist,metadb_handle_ptr p_item) { + t_size index; + if (!playlist_find_item(p_playlist,p_item,index)) index = pfc_infinite; + playlist_set_focus_item(p_playlist,index); + return index; +} +bool playlist_manager::activeplaylist_find_item(metadb_handle_ptr p_item,t_size & p_result) { + t_size playlist = get_active_playlist(); + if (playlist == pfc_infinite) return false; + return playlist_find_item(playlist,p_item,p_result); +} +t_size playlist_manager::activeplaylist_set_focus_by_handle(metadb_handle_ptr p_item) { + t_size playlist = get_active_playlist(); + if (playlist == pfc_infinite) return pfc_infinite; + return playlist_set_focus_by_handle(playlist,p_item); +} + +pfc::com_ptr_t playlist_incoming_item_filter::create_dataobject_ex(metadb_handle_list_cref data) { + pfc::com_ptr_t temp; temp.attach( create_dataobject(data) ); PFC_ASSERT( temp.is_valid() ); return temp; +} + +void playlist_manager_v3::recycler_restore_by_id(t_uint32 id) { + t_size which = recycler_find_by_id(id); + if (which != ~0) recycler_restore(which); +} + +t_size playlist_manager_v3::recycler_find_by_id(t_uint32 id) { + const t_size total = recycler_get_count(); + for(t_size walk = 0; walk < total; ++walk) { + if (id == recycler_get_id(walk)) return walk; + } + return ~0; +} + + +void playlist_manager::on_file_rechaptered(const char * path, metadb_handle_list_cref newItems) { + if (newItems.get_size() == 0) return; + + const size_t numPlaylists = this->get_playlist_count(); + for( size_t walkPlaylist = 0; walkPlaylist < numPlaylists; ++ walkPlaylist ) { + if (!playlist_lock_is_present(walkPlaylist)) { + auto itemCount = [=] () -> unsigned { + return this->playlist_get_item_count( walkPlaylist ); + }; + auto itemHandle = [=] ( unsigned item ) -> metadb_handle_ptr { + return this->playlist_get_item_handle( walkPlaylist, item ); + }; + auto itemMatch = [=] ( unsigned item ) -> bool { + return metadb::path_compare(path, itemHandle(item)->get_path()) == 0; + }; + auto itemMatch2 = [=] ( metadb_handle_ptr item ) -> bool { + return metadb::path_compare(path, item->get_path() ) == 0; + }; + + for( size_t walkItem = 0; walkItem < itemCount(); ) { + + if (itemMatch( walkItem )) { + pfc::avltree_t subsongs; + unsigned base = walkItem; + bool bSel = false; + for( ++walkItem ; walkItem < itemCount() ; ++ walkItem ) { + auto handle = itemHandle( walkItem ); + if (! itemMatch2( handle ) ) break; + if (! subsongs.add_item_check(handle->get_subsong_index())) break; + + bSel = bSel || this->playlist_is_item_selected(walkPlaylist, walkItem); + } + + // REMOVE base ... walkItem range and insert newHandles at base + this->playlist_remove_items( walkPlaylist, pfc::bit_array_range(base, walkItem-base) ); + this->playlist_insert_items( walkPlaylist, base, newItems, pfc::bit_array_val( bSel ) ); + walkItem = base + newItems.get_size(); + } else { + ++walkItem; + } + } + } + } +} + +void playlist_manager::on_files_rechaptered( metadb_handle_list_cref newHandles ) { + pfc::map_t< const char*, metadb_handle_list, metadb::path_comparator > byPath; + + const size_t total = newHandles.get_count(); + for( size_t w = 0; w < total; ++w ) { + auto handle = newHandles[w]; + byPath[ handle->get_path() ] += handle; + } + + for( auto iter = byPath.first(); iter.is_valid(); ++ iter ) { + this->on_file_rechaptered( iter->m_key, iter->m_value ); + } +} diff --git a/foobar2000/SDK/playlist.h b/foobar2000/SDK/playlist.h new file mode 100644 index 0000000..669bfea --- /dev/null +++ b/foobar2000/SDK/playlist.h @@ -0,0 +1,897 @@ +#pragma once + +#include "titleformat.h" + +//! This interface allows filtering of playlist modification operations.\n +//! Implemented by components "locking" playlists; use playlist_manager::playlist_lock_install() etc to takeover specific playlist with your instance of playlist_lock. +class NOVTABLE playlist_lock : public service_base { +public: + enum { + filter_add = 1 << 0, + filter_remove = 1 << 1, + filter_reorder = 1 << 2, + filter_replace = 1 << 3, + filter_rename = 1 << 4, + filter_remove_playlist = 1 << 5, + filter_default_action = 1 << 6, + }; + + //! Queries whether specified item insertiion operation is allowed in the locked playlist. + //! @param p_base Index from which the items are being inserted. + //! @param p_data Items being inserted. + //! @param p_selection Caller-requested selection state of items being inserted. + //! @returns True to allow the operation, false to block it. + virtual bool query_items_add(t_size p_base, const pfc::list_base_const_t & p_data,const bit_array & p_selection) = 0; + //! Queries whether specified item reorder operation is allowed in the locked playlist. + //! @param p_order Pointer to array containing permutation defining requested reorder operation. + //! @param p_count Number of items in array pointed to by p_order. This should always be equal to number of items on the locked playlist. + //! @returns True to allow the operation, false to block it. + virtual bool query_items_reorder(const t_size * p_order,t_size p_count) = 0; + //! Queries whether specified item removal operation is allowed in the locked playlist. + //! @param p_mask Specifies which items from locked playlist are being removed. + //! @param p_force If set to true, the call is made only for notification purpose and items are getting removed regardless (after e.g. they have been physically removed). + //! @returns True to allow the operation, false to block it. Note that return value is ignored if p_force is set to true. + virtual bool query_items_remove(const bit_array & p_mask,bool p_force) = 0; + //! Queries whether specified item replacement operation is allowed in the locked playlist. + //! @param p_index Index of the item being replaced. + //! @param p_old Old value of the item being replaced. + //! @param p_new New value of the item being replaced. + //! @returns True to allow the operation, false to block it. + virtual bool query_item_replace(t_size p_index,const metadb_handle_ptr & p_old,const metadb_handle_ptr & p_new)=0; + //! Queries whether renaming the locked playlist is allowed. + //! @param p_new_name Requested new name of the playlist; a UTF-8 encoded string. + //! @param p_new_name_len Length limit of the name string, in bytes (actual string may be shorter if null terminator is encountered before). Set this to infinite to use plain null-terminated strings. + //! @returns True to allow the operation, false to block it. + virtual bool query_playlist_rename(const char * p_new_name,t_size p_new_name_len) = 0; + //! Queries whether removal of the locked playlist is allowed. Note that the lock will be released when the playlist is removed. + //! @returns True to allow the operation, false to block it. + virtual bool query_playlist_remove() = 0; + //! Executes "default action" (doubleclick etc) for specified playlist item. When the playlist is not locked, default action starts playback of the item. + //! @returns True if custom default action was executed, false to fall-through to default one for non-locked playlists (start playback). + virtual bool execute_default_action(t_size p_item) = 0; + //! Notifies lock about changed index of the playlist, in result of user reordering playlists or removing other playlists. + virtual void on_playlist_index_change(t_size p_new_index) = 0; + //! Notifies lock about the locked playlist getting removed. + virtual void on_playlist_remove() = 0; + //! Retrieves human-readable name of playlist lock to display. + virtual void get_lock_name(pfc::string_base & p_out) = 0; + //! Requests user interface of component controlling the playlist lock to be shown. + virtual void show_ui() = 0; + //! Queries which actions the lock filters. The return value must not change while the lock is registered with playlist_manager. The return value is a combination of one or more filter_* constants. + virtual t_uint32 get_filter_mask() = 0; + + FB2K_MAKE_SERVICE_INTERFACE(playlist_lock,service_base); +}; + +struct t_playback_queue_item { + metadb_handle_ptr m_handle; + t_size m_playlist,m_item; + + bool operator==(const t_playback_queue_item & p_item) const; + bool operator!=(const t_playback_queue_item & p_item) const; +}; + + +//! This service provides methods for all sorts of playlist interaction.\n +//! All playlist_manager methods are valid only from main app thread.\n +//! Usage: playlist_manager::get() to obtain an instance. +class NOVTABLE playlist_manager : public service_base +{ +public: + + //! Callback interface for playlist enumeration methods. + class NOVTABLE enum_items_callback { + public: + //! @returns True to continue enumeration, false to abort. + virtual bool on_item(t_size p_index,const metadb_handle_ptr & p_location,bool b_selected) = 0;//return false to stop + }; + + //! Retrieves number of playlists. + virtual t_size get_playlist_count() = 0; + //! Retrieves index of active playlist; infinite if no playlist is active. + virtual t_size get_active_playlist() = 0; + //! Sets active playlist (infinite to set no active playlist). + virtual void set_active_playlist(t_size p_index) = 0; + //! Retrieves playlist from which items to be played are taken from. + virtual t_size get_playing_playlist() = 0; + //! Sets playlist from which items to be played are taken from. + virtual void set_playing_playlist(t_size p_index) = 0; + //! Removes playlists according to specified mask. See also: bit_array. + virtual bool remove_playlists(const bit_array & p_mask) = 0; + //! Creates a new playlist. + //! @param p_name Name of playlist to create; a UTF-8 encoded string. + //! @param p_name_length Length limit of playlist name string, in bytes (actual string may be shorter if null terminator is encountered before). Set this to infinite to use plain null-terminated strings. + //! @param p_index Index at which to insert new playlist; set to infinite to put it at the end of playlist list. + //! @returns Actual index of newly inserted playlist, infinite on failure (call from invalid context). + virtual t_size create_playlist(const char * p_name,t_size p_name_length,t_size p_index) = 0; + //! Reorders the playlist list according to specified permutation. + //! @returns True on success, false on failure (call from invalid context). + virtual bool reorder(const t_size * p_order,t_size p_count) = 0; + + + //! Retrieves number of items on specified playlist. + virtual t_size playlist_get_item_count(t_size p_playlist) = 0; + //! Enumerates contents of specified playlist. + virtual void playlist_enum_items(t_size p_playlist,enum_items_callback & p_callback,const bit_array & p_mask) = 0; + //! Retrieves index of focus item on specified playlist; returns infinite when no item has focus. + virtual t_size playlist_get_focus_item(t_size p_playlist) = 0; + //! Retrieves name of specified playlist. Should never fail unless the parameters are invalid. + virtual bool playlist_get_name(t_size p_playlist,pfc::string_base & p_out) = 0; + + //! Reorders items in specified playlist according to specified permutation. + virtual bool playlist_reorder_items(t_size p_playlist,const t_size * p_order,t_size p_count) = 0; + //! Selects/deselects items on specified playlist. + //! @param p_playlist Index of playlist to alter. + //! @param p_affected Mask of items to alter. + //! @param p_status Mask of selected/deselected state to apply to items specified by p_affected. + virtual void playlist_set_selection(t_size p_playlist,const bit_array & p_affected,const bit_array & p_status) = 0; + //! Removes specified items from specified playlist. Returns true on success or false on failure (playlist locked). + virtual bool playlist_remove_items(t_size p_playlist,const bit_array & mask)=0; + //! Replaces specified item on specified playlist. Returns true on success or false on failure (playlist locked). + virtual bool playlist_replace_item(t_size p_playlist,t_size p_item,const metadb_handle_ptr & p_new_item) = 0; + //! Sets index of focus item on specified playlist; use infinite to set no focus item. + virtual void playlist_set_focus_item(t_size p_playlist,t_size p_item) = 0; + //! Inserts new items into specified playlist, at specified position. + virtual t_size playlist_insert_items(t_size p_playlist,t_size p_base,const pfc::list_base_const_t & data,const bit_array & p_selection) = 0; + //! Tells playlist renderers to make sure that specified item is visible. + virtual void playlist_ensure_visible(t_size p_playlist,t_size p_item) = 0; + //! Renames specified playlist. + //! @param p_name New name of playlist; a UTF-8 encoded string. + //! @param p_name_length Length limit of playlist name string, in bytes (actual string may be shorter if null terminator is encountered before). Set this to infinite to use plain null-terminated strings. + //! @returns True on success, false on failure (playlist locked). + virtual bool playlist_rename(t_size p_index,const char * p_name,t_size p_name_length) = 0; + + + //! Creates an undo restore point for specified playlist. + virtual void playlist_undo_backup(t_size p_playlist) = 0; + //! Reverts specified playlist to last undo restore point and generates a redo restore point. + //! @returns True on success, false on failure (playlist locked or no restore point available). + virtual bool playlist_undo_restore(t_size p_playlist) = 0; + //! Reverts specified playlist to next redo restore point and generates an undo restore point. + //! @returns True on success, false on failure (playlist locked or no restore point available). + virtual bool playlist_redo_restore(t_size p_playlist) = 0; + //! Returns whether an undo restore point is available for specified playlist. + virtual bool playlist_is_undo_available(t_size p_playlist) = 0; + //! Returns whether a redo restore point is available for specified playlist. + virtual bool playlist_is_redo_available(t_size p_playlist) = 0; + + //! Renders information about specified playlist item, using specified titleformatting script parameters. + //! @param p_playlist Index of playlist containing item being processed. + //! @param p_item Index of item being processed in the playlist containing it. + //! @param p_hook Titleformatting script hook to use; see titleformat_hook documentation for more info. Set to NULL when hook functionality is not needed. + //! @param p_out String object receiving results. + //! @param p_script Compiled titleformatting script to use; see titleformat_object cocumentation for more info. + //! @param p_filter Text filter to use; see titleformat_text_filter documentation for more info. Set to NULL when text filter functionality is not needed. + //! @param p_playback_info_level Level of playback related information requested. See playback_control::t_display_level documentation for more info. + virtual void playlist_item_format_title(t_size p_playlist,t_size p_item,titleformat_hook * p_hook,pfc::string_base & p_out,const service_ptr_t & p_script,titleformat_text_filter * p_filter,playback_control::t_display_level p_playback_info_level)=0; + + + //! Retrieves playlist position of currently playing item. + //! @param p_playlist Receives index of playlist containing currently playing item on success. + //! @param p_index Receives index of currently playing item in the playlist that contains it on success. + //! @returns True on success, false on failure (not playing or currently played item has been removed from the playlist it was on when starting). + virtual bool get_playing_item_location(t_size * p_playlist,t_size * p_index) = 0; + + //! Sorts specified playlist - entire playlist or selection only - by specified title formatting pattern, or randomizes the order. + //! @param p_playlist Index of playlist to alter. + //! @param p_pattern Title formatting pattern to sort by (an UTF-8 encoded null-termindated string). Set to NULL to randomize the order of items. + //! @param p_sel_only Set to false to sort/randomize whole playlist, or to true to sort/randomize only selection on the playlist. + //! @returns True on success, false on failure (playlist locked etc). + virtual bool playlist_sort_by_format(t_size p_playlist,const char * p_pattern,bool p_sel_only) = 0; + + //! For internal use only; p_items must be sorted by metadb::path_compare; use file_operation_callback static methods instead of calling this directly. + virtual void on_files_deleted_sorted(const pfc::list_base_const_t & p_items) = 0; + //! For internal use only; p_from must be sorted by metadb::path_compare; use file_operation_callback static methods instead of calling this directly. + virtual void on_files_moved_sorted(const pfc::list_base_const_t & p_from,const pfc::list_base_const_t & p_to) = 0; + + virtual bool playlist_lock_install(t_size p_playlist,const service_ptr_t & p_lock) = 0;//returns false when invalid playlist or already locked + virtual bool playlist_lock_uninstall(t_size p_playlist,const service_ptr_t & p_lock) = 0; + virtual bool playlist_lock_is_present(t_size p_playlist) = 0; + virtual bool playlist_lock_query_name(t_size p_playlist,pfc::string_base & p_out) = 0; + virtual bool playlist_lock_show_ui(t_size p_playlist) = 0; + virtual t_uint32 playlist_lock_get_filter_mask(t_size p_playlist) = 0; + + + //! Retrieves number of available playback order modes. + virtual t_size playback_order_get_count() = 0; + //! Retrieves name of specified playback order move. + //! @param p_index Index of playback order mode to query, from 0 to playback_order_get_count() return value - 1. + //! @returns Null-terminated UTF-8 encoded string containing name of the playback order mode. Returned pointer points to statically allocated string and can be safely stored without having to free it later. + virtual const char * playback_order_get_name(t_size p_index) = 0; + //! Retrieves GUID of specified playback order mode. Used for managing playback modes without relying on names. + //! @param p_index Index of playback order mode to query, from 0 to playback_order_get_count() return value - 1. + virtual GUID playback_order_get_guid(t_size p_index) = 0; + //! Retrieves index of active playback order mode. + virtual t_size playback_order_get_active() = 0; + //! Sets index of active playback order mode. + virtual void playback_order_set_active(t_size p_index) = 0; + + virtual void queue_remove_mask(bit_array const & p_mask) = 0; + virtual void queue_add_item_playlist(t_size p_playlist,t_size p_item) = 0; + virtual void queue_add_item(metadb_handle_ptr p_item) = 0; + virtual t_size queue_get_count() = 0; + virtual void queue_get_contents(pfc::list_base_t & p_out) = 0; + //! Returns index (0-based) on success, infinite on failure (item not in queue). + virtual t_size queue_find_index(t_playback_queue_item const & p_item) = 0; + + //! Registers a playlist callback; registered object receives notifications about any modifications of any of loaded playlists. + //! @param p_callback Callback interface to register. + //! @param p_flags Flags indicating which callback methods are requested. See playlist_callback::flag_* constants for more info. The main purpose of flags parameter is working set optimization by not calling methods that do nothing. + virtual void register_callback(class playlist_callback * p_callback,unsigned p_flags) = 0; + //! Registers a playlist callback; registered object receives notifications about any modifications of active playlist. + //! @param p_callback Callback interface to register. + //! @param p_flags Flags indicating which callback methods are requested. See playlist_callback_single::flag_* constants for more info. The main purpose of flags parameter is working set optimization by not calling methods that do nothing. + virtual void register_callback(class playlist_callback_single * p_callback,unsigned p_flags) = 0; + //! Unregisters a playlist callback (playlist_callback version). + virtual void unregister_callback(class playlist_callback * p_callback) = 0; + //! Unregisters a playlist callback (playlist_callback_single version). + virtual void unregister_callback(class playlist_callback_single * p_callback) = 0; + //! Modifies flags indicating which calback methods are requested (playlist_callback version). + virtual void modify_callback(class playlist_callback * p_callback,unsigned p_flags) = 0; + //! Modifies flags indicating which calback methods are requested (playlist_callback_single version). + virtual void modify_callback(class playlist_callback_single * p_callback,unsigned p_flags) = 0; + + //! Executes default doubleclick/enter action for specified item on specified playlist (starts playing the item unless overridden by a lock to do something else). + virtual bool playlist_execute_default_action(t_size p_playlist,t_size p_item) = 0; + + + //! Helper; removes all items from the playback queue. + void queue_flush() {queue_remove_mask(pfc::bit_array_true());} + //! Helper; returns whether there are items in the playback queue. + bool queue_is_active() {return queue_get_count() > 0;} + + //! Helper; highlights currently playing item; returns true on success or false on failure (not playing or currently played item has been removed from playlist since playback started). + bool highlight_playing_item(); + //! Helper; removes single playlist of specified index. + bool remove_playlist(t_size p_playlist); + //! Helper; removes single playlist of specified index, and switches to another playlist when possible. + bool remove_playlist_switch(t_size p_playlist); + + //! Helper; returns whether specified item on specified playlist is selected or not. + bool playlist_is_item_selected(t_size p_playlist,t_size p_item); + //! Helper; retrieves metadb_handle of the specified playlist item. Returns true on success, false on failure (invalid parameters). + bool playlist_get_item_handle(metadb_handle_ptr & p_out,t_size p_playlist,t_size p_item); + //! Helper; retrieves metadb_handle of the specified playlist item; throws pfc::exception_invalid_params() on failure. + metadb_handle_ptr playlist_get_item_handle(t_size playlist, t_size item); + + //! Moves selected items up/down in the playlist by specified offset. + //! @param p_playlist Index of playlist to alter. + //! @param p_delta Offset to move items by. Set it to a negative valuye to move up, or to a positive value to move down. + //! @returns True on success, false on failure (e.g. playlist locked). + bool playlist_move_selection(t_size p_playlist,int p_delta); + //! Retrieves selection map of specific playlist, using bit_array_var interface. + void playlist_get_selection_mask(t_size p_playlist,bit_array_var & out); + void playlist_get_items(t_size p_playlist,pfc::list_base_t & out,const bit_array & p_mask); + void playlist_get_all_items(t_size p_playlist,pfc::list_base_t & out); + void playlist_get_selected_items(t_size p_playlist,pfc::list_base_t & out); + + //! Clears contents of specified playlist (removes all items from it). + void playlist_clear(t_size p_playlist); + bool playlist_add_items(t_size playlist,const pfc::list_base_const_t & data,const bit_array & p_selection); + void playlist_clear_selection(t_size p_playlist); + void playlist_remove_selection(t_size p_playlist,bool p_crop = false); + + + //! Changes contents of the specified playlist to the specified items, trying to reuse existing playlist content as much as possible (preserving selection/focus/etc). Order of items in playlist not guaranteed to be the same as in the specified item list. + //! @returns true if the playlist has been altered, false if there was nothing to update. + bool playlist_update_content(t_size playlist, metadb_handle_list_cref content, bool bUndoBackup); + + //retrieving status + t_size activeplaylist_get_item_count(); + void activeplaylist_enum_items(enum_items_callback & p_callback,const bit_array & p_mask); + t_size activeplaylist_get_focus_item();//focus may be infinite if no item is focused + bool activeplaylist_get_name(pfc::string_base & p_out); + + //modifying playlist + bool activeplaylist_reorder_items(const t_size * order,t_size count); + void activeplaylist_set_selection(const bit_array & affected,const bit_array & status); + bool activeplaylist_remove_items(const bit_array & mask); + bool activeplaylist_replace_item(t_size p_item,const metadb_handle_ptr & p_new_item); + void activeplaylist_set_focus_item(t_size p_item); + t_size activeplaylist_insert_items(t_size p_base,const pfc::list_base_const_t & data,const bit_array & p_selection); + void activeplaylist_ensure_visible(t_size p_item); + bool activeplaylist_rename(const char * p_name,t_size p_name_len); + + void activeplaylist_undo_backup(); + bool activeplaylist_undo_restore(); + bool activeplaylist_redo_restore(); + + bool activeplaylist_is_item_selected(t_size p_item); + bool activeplaylist_get_item_handle(metadb_handle_ptr & item,t_size p_item); + metadb_handle_ptr activeplaylist_get_item_handle(t_size p_item); + void activeplaylist_move_selection(int p_delta); + void activeplaylist_get_selection_mask(bit_array_var & out); + void activeplaylist_get_items(pfc::list_base_t & out,const bit_array & p_mask); + void activeplaylist_get_all_items(pfc::list_base_t & out); + void activeplaylist_get_selected_items(pfc::list_base_t & out); + void activeplaylist_clear(); + + bool activeplaylist_add_items(const pfc::list_base_const_t & data,const bit_array & p_selection); + + bool playlist_insert_items_filter(t_size p_playlist,t_size p_base,const pfc::list_base_const_t & p_data,bool p_select); + bool activeplaylist_insert_items_filter(t_size p_base,const pfc::list_base_const_t & p_data,bool p_select); + + //! \deprecated (since 0.9.3) Use playlist_incoming_item_filter_v2::process_locations_async whenever possible + bool playlist_insert_locations(t_size p_playlist,t_size p_base,const pfc::list_base_const_t & p_urls,bool p_select,HWND p_parentwnd); + //! \deprecated (since 0.9.3) Use playlist_incoming_item_filter_v2::process_locations_async whenever possible + bool activeplaylist_insert_locations(t_size p_base,const pfc::list_base_const_t & p_urls,bool p_select,HWND p_parentwnd); + + bool playlist_add_items_filter(t_size p_playlist,const pfc::list_base_const_t & p_data,bool p_select); + bool activeplaylist_add_items_filter(const pfc::list_base_const_t & p_data,bool p_select); + + bool playlist_add_locations(t_size p_playlist,const pfc::list_base_const_t & p_urls,bool p_select,HWND p_parentwnd); + bool activeplaylist_add_locations(const pfc::list_base_const_t & p_urls,bool p_select,HWND p_parentwnd); + + void reset_playing_playlist(); + + void activeplaylist_clear_selection(); + void activeplaylist_remove_selection(bool p_crop = false); + + void activeplaylist_item_format_title(t_size p_item,titleformat_hook * p_hook,pfc::string_base & out,const service_ptr_t & p_script,titleformat_text_filter * p_filter,play_control::t_display_level p_playback_info_level); + + void playlist_set_selection_single(t_size p_playlist,t_size p_item,bool p_state); + void activeplaylist_set_selection_single(t_size p_item,bool p_state); + + t_size playlist_get_selection_count(t_size p_playlist,t_size p_max); + t_size activeplaylist_get_selection_count(t_size p_max); + + bool playlist_get_focus_item_handle(metadb_handle_ptr & p_item,t_size p_playlist); + bool activeplaylist_get_focus_item_handle(metadb_handle_ptr & item); + + t_size find_playlist(const char * p_name,t_size p_name_length = ~0); + t_size find_or_create_playlist(const char * p_name,t_size p_name_length = ~0); + t_size find_or_create_playlist_unlocked(const char * p_name,t_size p_name_length = ~0); + + t_size create_playlist_autoname(t_size p_index = ~0); + + bool activeplaylist_sort_by_format(const char * spec,bool p_sel_only); + + t_uint32 activeplaylist_lock_get_filter_mask(); + bool activeplaylist_is_undo_available(); + bool activeplaylist_is_redo_available(); + + bool activeplaylist_execute_default_action(t_size p_item); + + void remove_items_from_all_playlists(const pfc::list_base_const_t & p_data); + + void active_playlist_fix(); + + bool get_all_items(pfc::list_base_t & out); + + void playlist_activate_delta(int p_delta); + void playlist_activate_next() {playlist_activate_delta(1);} + void playlist_activate_previous() {playlist_activate_delta(-1);} + + + t_size playlist_get_selected_count(t_size p_playlist,bit_array const & p_mask); + t_size activeplaylist_get_selected_count(bit_array const & p_mask) {return playlist_get_selected_count(get_active_playlist(),p_mask);} + + bool playlist_find_item(t_size p_playlist,metadb_handle_ptr p_item,t_size & p_result);//inefficient, walks entire playlist + bool playlist_find_item_selected(t_size p_playlist,metadb_handle_ptr p_item,t_size & p_result);//inefficient, walks entire playlist + t_size playlist_set_focus_by_handle(t_size p_playlist,metadb_handle_ptr p_item); + bool activeplaylist_find_item(metadb_handle_ptr p_item,t_size & p_result);//inefficient, walks entire playlist + t_size activeplaylist_set_focus_by_handle(metadb_handle_ptr p_item); + + static void g_make_selection_move_permutation(t_size * p_output,t_size p_count,const bit_array & p_selection,int p_delta); + + //! Helper to update playlists after rechaptering a file. \n + //! You typically want to call metadb_io_v2::on_file_rechaptered() instead, as it will forcibly reload info first. + void on_file_rechaptered(const char * path, metadb_handle_list_cref items); + void on_files_rechaptered( metadb_handle_list_cref newHandles ); + + FB2K_MAKE_SERVICE_COREAPI(playlist_manager); +}; + +//! Extension of the playlist_manager service that manages playlist properties. +//! Playlist properties come in two flavors: persistent and runtime. +//! Persistent properties are blocks of binary that that will be preserved when the application is exited and restarted. +//! Runtime properties are service pointers that will be lost when the application exits. +//! \since 0.9.5 +class NOVTABLE playlist_manager_v2 : public playlist_manager { +public: + //! Write a persistent playlist property. + //! \param p_playlist Index of the playlist + //! \param p_property GUID that identifies the property + //! \param p_stream stream that contains the data that will be associated with the property + //! \param p_abort abort_callback that will be used when reading from p_stream + virtual void playlist_set_property(t_size p_playlist,const GUID & p_property,stream_reader * p_stream,t_size p_size_hint,abort_callback & p_abort) = 0; + //! Read a persistent playlist property. + //! \param p_playlist Index of the playlist + //! \param p_property GUID that identifies the property + //! \param p_stream stream that will receive the stored data + //! \param p_abort abort_callback that will be used when writing to p_stream + //! \return true if the property exists, false otherwise + virtual bool playlist_get_property(t_size p_playlist,const GUID & p_property,stream_writer * p_stream,abort_callback & p_abort) = 0; + //! Test existence of a persistent playlist property. + //! \param p_playlist Index of the playlist + //! \param p_property GUID that identifies the property + //! \return true if the property exists, false otherwise + virtual bool playlist_have_property(t_size p_playlist,const GUID & p_property) = 0; + //! Remove a persistent playlist property. + //! \param p_playlist Index of the playlist + //! \param p_property GUID that identifies the property + //! \return true if the property existed, false otherwise + virtual bool playlist_remove_property(t_size p_playlist,const GUID & p_property) = 0; + + //! Write a runtime playlist property. + //! \param p_playlist Index of the playlist + //! \param p_property GUID that identifies the property + //! \param p_data service pointer that will be associated with the property + virtual void playlist_set_runtime_property(t_size p_playlist,const GUID & p_property,service_ptr_t p_data) = 0; + //! Read a runtime playlist property. + //! \param p_playlist Index of the playlist + //! \param p_property GUID that identifies the property + //! \param p_data base service pointer reference that will receive the stored servive pointer + //! \return true if the property exists, false otherwise + virtual bool playlist_get_runtime_property(t_size p_playlist,const GUID & p_property,service_ptr_t & p_data) = 0; + //! Test existence of a runtime playlist property. + //! \param p_playlist Index of the playlist + //! \param p_property GUID that identifies the property + //! \return true if the property exists, false otherwise + virtual bool playlist_have_runtime_property(t_size p_playlist,const GUID & p_property) = 0; + //! Remove a runtime playlist property. + //! \param p_playlist Index of the playlist + //! \param p_property GUID that identifies the property + //! \return true if the property existed, false otherwise + virtual bool playlist_remove_runtime_property(t_size p_playlist,const GUID & p_property) = 0; + + //! Write a persistent playlist property. + //! \param p_playlist Index of the playlist + //! \param p_property GUID that identifies the property + //! \param p_data array that contains the data that will be associated with the property + template void playlist_set_property(t_size p_playlist,const GUID & p_property,const t_array & p_data) { + PFC_STATIC_ASSERT( sizeof(p_data[0]) == 1 ); + stream_reader_memblock_ref reader(p_data); + playlist_set_property(p_playlist,p_property,&reader,p_data.get_size(),fb2k::noAbort); + } + //! Read a persistent playlist property. + //! \param p_playlist Index of the playlist + //! \param p_property GUID that identifies the property + //! \param p_data array that will receive the stored data + //! \return true if the property exists, false otherwise + template bool playlist_get_property(t_size p_playlist,const GUID & p_property,t_array & p_data) { + PFC_STATIC_ASSERT( sizeof(p_data[0]) == 1 ); + typedef pfc::array_t t_temp; + t_temp temp; + { + stream_writer_buffer_append_ref_t reader(temp); + if (!playlist_get_property(p_playlist,p_property,&reader,fb2k::noAbort)) return false; + } + p_data = temp; + return true; + } + //! Read a runtime playlist property. + //! \param p_playlist Index of the playlist + //! \param p_property GUID that identifies the property + //! \param p_data specific service pointer reference that will receive the stored servive pointer + //! \return true if the property exists and can be converted to the type of p_data, false otherwise + template bool playlist_get_runtime_property(t_size p_playlist,const GUID & p_property,service_ptr_t<_t_interface> & p_data) { + service_ptr_t ptr; + if (!playlist_get_runtime_property(p_playlist,p_property,ptr)) return false; + return ptr->service_query_t(p_data); + } + + //! Write a persistent playlist property. + //! \param p_playlist Index of the playlist + //! \param p_property GUID that identifies the property + //! \param p_value integer that will be associated with the property + template + void playlist_set_property_int(t_size p_playlist,const GUID & p_property,_t_int p_value) { + pfc::array_t temp; temp.set_size(sizeof(_t_int)); + pfc::encode_little_endian(temp.get_ptr(),p_value); + playlist_set_property(p_playlist,p_property,temp); + } + //! Read a persistent playlist property. + //! \param p_playlist Index of the playlist + //! \param p_property GUID that identifies the property + //! \param p_value integer reference that will receive the stored data + //! \return true if the property exists and if the data is compatible with p_value, false otherwise + template + bool playlist_get_property_int(t_size p_playlist,const GUID & p_property,_t_int & p_value) { + pfc::array_t temp; + if (!playlist_get_property(p_playlist,p_property,temp)) return false; + if (temp.get_size() != sizeof(_t_int)) return false; + pfc::decode_little_endian(p_value,temp.get_ptr()); + return true; + } + + FB2K_MAKE_SERVICE_COREAPI_EXTENSION(playlist_manager_v2,playlist_manager) +}; + +//! \since 0.9.5 +class NOVTABLE playlist_manager_v3 : public playlist_manager_v2 { + FB2K_MAKE_SERVICE_COREAPI_EXTENSION(playlist_manager_v3,playlist_manager_v2) +public: + virtual t_size recycler_get_count() = 0; + virtual void recycler_get_content(t_size which, metadb_handle_list_ref out) = 0; + virtual void recycler_get_name(t_size which, pfc::string_base & out) = 0; + virtual t_uint32 recycler_get_id(t_size which) = 0; + virtual void recycler_purge(const bit_array & mask) = 0; + virtual void recycler_restore(t_size which) = 0; + + void recycler_restore_by_id(t_uint32 id); + t_size recycler_find_by_id(t_uint32 id); +}; + +//! \since 0.9.5.4 +class NOVTABLE playlist_manager_v4 : public playlist_manager_v3 { + FB2K_MAKE_SERVICE_COREAPI_EXTENSION(playlist_manager_v4, playlist_manager_v3) +public: + virtual void playlist_get_sideinfo(t_size which, stream_writer * stream, abort_callback & abort) = 0; + virtual t_size create_playlist_ex(const char * p_name,t_size p_name_length,t_size p_index, metadb_handle_list_cref content, stream_reader * sideInfo, abort_callback & abort) = 0; +}; + +class NOVTABLE playlist_callback +{ +public: + virtual void on_items_added(t_size p_playlist,t_size p_start, const pfc::list_base_const_t & p_data,const bit_array & p_selection)=0;//inside any of these methods, you can call playlist APIs to get exact info about what happened (but only methods that read playlist state, not those that modify it) + virtual void on_items_reordered(t_size p_playlist,const t_size * p_order,t_size p_count)=0;//changes selection too; doesnt actually change set of items that are selected or item having focus, just changes their order + virtual void on_items_removing(t_size p_playlist,const bit_array & p_mask,t_size p_old_count,t_size p_new_count)=0;//called before actually removing them + virtual void on_items_removed(t_size p_playlist,const bit_array & p_mask,t_size p_old_count,t_size p_new_count)=0; + virtual void on_items_selection_change(t_size p_playlist,const bit_array & p_affected,const bit_array & p_state) = 0; + virtual void on_item_focus_change(t_size p_playlist,t_size p_from,t_size p_to)=0;//focus may be -1 when no item has focus; reminder: focus may also change on other callbacks + + virtual void on_items_modified(t_size p_playlist,const bit_array & p_mask)=0; + virtual void on_items_modified_fromplayback(t_size p_playlist,const bit_array & p_mask,play_control::t_display_level p_level)=0; + + struct t_on_items_replaced_entry + { + t_size m_index; + metadb_handle_ptr m_old,m_new; + }; + + virtual void on_items_replaced(t_size p_playlist,const bit_array & p_mask,const pfc::list_base_const_t & p_data)=0; + + virtual void on_item_ensure_visible(t_size p_playlist,t_size p_idx)=0; + + virtual void on_playlist_activate(t_size p_old,t_size p_new) = 0; + virtual void on_playlist_created(t_size p_index,const char * p_name,t_size p_name_len) = 0; + virtual void on_playlists_reorder(const t_size * p_order,t_size p_count) = 0; + virtual void on_playlists_removing(const bit_array & p_mask,t_size p_old_count,t_size p_new_count) = 0; + virtual void on_playlists_removed(const bit_array & p_mask,t_size p_old_count,t_size p_new_count) = 0; + virtual void on_playlist_renamed(t_size p_index,const char * p_new_name,t_size p_new_name_len) = 0; + + virtual void on_default_format_changed() = 0; + virtual void on_playback_order_changed(t_size p_new_index) = 0; + virtual void on_playlist_locked(t_size p_playlist,bool p_locked) = 0; + + enum { + flag_on_items_added = 1 << 0, + flag_on_items_reordered = 1 << 1, + flag_on_items_removing = 1 << 2, + flag_on_items_removed = 1 << 3, + flag_on_items_selection_change = 1 << 4, + flag_on_item_focus_change = 1 << 5, + flag_on_items_modified = 1 << 6, + flag_on_items_modified_fromplayback = 1 << 7, + flag_on_items_replaced = 1 << 8, + flag_on_item_ensure_visible = 1 << 9, + flag_on_playlist_activate = 1 << 10, + flag_on_playlist_created = 1 << 11, + flag_on_playlists_reorder = 1 << 12, + flag_on_playlists_removing = 1 << 13, + flag_on_playlists_removed = 1 << 14, + flag_on_playlist_renamed = 1 << 15, + flag_on_default_format_changed = 1 << 16, + flag_on_playback_order_changed = 1 << 17, + flag_on_playlist_locked = 1 << 18, + + flag_all = ~0, + flag_item_ops = flag_on_items_added | flag_on_items_reordered | flag_on_items_removing | flag_on_items_removed | flag_on_items_selection_change | flag_on_item_focus_change | flag_on_items_modified | flag_on_items_modified_fromplayback | flag_on_items_replaced | flag_on_item_ensure_visible, + flag_playlist_ops = flag_on_playlist_activate | flag_on_playlist_created | flag_on_playlists_reorder | flag_on_playlists_removing | flag_on_playlists_removed | flag_on_playlist_renamed | flag_on_playlist_locked, + }; +protected: + playlist_callback() {} + ~playlist_callback() {} +}; + +class NOVTABLE playlist_callback_static : public service_base, public playlist_callback +{ +public: + virtual unsigned get_flags() = 0; + + FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(playlist_callback_static); +}; + +class NOVTABLE playlist_callback_single +{ +public: + virtual void on_items_added(t_size p_base, const pfc::list_base_const_t & p_data,const bit_array & p_selection)=0;//inside any of these methods, you can call playlist APIs to get exact info about what happened (but only methods that read playlist state, not those that modify it) + virtual void on_items_reordered(const t_size * p_order,t_size p_count)=0;//changes selection too; doesnt actually change set of items that are selected or item having focus, just changes their order + virtual void on_items_removing(const bit_array & p_mask,t_size p_old_count,t_size p_new_count)=0;//called before actually removing them + virtual void on_items_removed(const bit_array & p_mask,t_size p_old_count,t_size p_new_count)=0; + virtual void on_items_selection_change(const bit_array & p_affected,const bit_array & p_state) = 0; + virtual void on_item_focus_change(t_size p_from,t_size p_to)=0;//focus may be -1 when no item has focus; reminder: focus may also change on other callbacks + virtual void on_items_modified(const bit_array & p_mask)=0; + virtual void on_items_modified_fromplayback(const bit_array & p_mask,play_control::t_display_level p_level)=0; + virtual void on_items_replaced(const bit_array & p_mask,const pfc::list_base_const_t & p_data)=0; + virtual void on_item_ensure_visible(t_size p_idx)=0; + + virtual void on_playlist_switch() = 0; + virtual void on_playlist_renamed(const char * p_new_name,t_size p_new_name_len) = 0; + virtual void on_playlist_locked(bool p_locked) = 0; + + virtual void on_default_format_changed() = 0; + virtual void on_playback_order_changed(t_size p_new_index) = 0; + + enum { + flag_on_items_added = 1 << 0, + flag_on_items_reordered = 1 << 1, + flag_on_items_removing = 1 << 2, + flag_on_items_removed = 1 << 3, + flag_on_items_selection_change = 1 << 4, + flag_on_item_focus_change = 1 << 5, + flag_on_items_modified = 1 << 6, + flag_on_items_modified_fromplayback = 1 << 7, + flag_on_items_replaced = 1 << 8, + flag_on_item_ensure_visible = 1 << 9, + flag_on_playlist_switch = 1 << 10, + flag_on_playlist_renamed = 1 << 11, + flag_on_playlist_locked = 1 << 12, + flag_on_default_format_changed = 1 << 13, + flag_on_playback_order_changed = 1 << 14, + flag_all = ~0, + }; +protected: + playlist_callback_single() {} + ~playlist_callback_single() {} +}; + +//! playlist_callback implementation helper - registers itself on creation / unregisters on destruction. Must not be instantiated statically! +class playlist_callback_impl_base : public playlist_callback { +public: + playlist_callback_impl_base(t_uint32 p_flags = 0) { + playlist_manager::get()->register_callback(this,p_flags); + } + ~playlist_callback_impl_base() { + playlist_manager::get()->unregister_callback(this); + } + void set_callback_flags(t_uint32 p_flags) { + playlist_manager::get()->modify_callback(this,p_flags); + } + //dummy implementations - avoid possible pure virtual function calls! + void on_items_added(t_size p_playlist,t_size p_start, const pfc::list_base_const_t & p_data,const bit_array & p_selection) {} + void on_items_reordered(t_size p_playlist,const t_size * p_order,t_size p_count) {} + void on_items_removing(t_size p_playlist,const bit_array & p_mask,t_size p_old_count,t_size p_new_count) {} + void on_items_removed(t_size p_playlist,const bit_array & p_mask,t_size p_old_count,t_size p_new_count) {} + void on_items_selection_change(t_size p_playlist,const bit_array & p_affected,const bit_array & p_state) {} + void on_item_focus_change(t_size p_playlist,t_size p_from,t_size p_to) {} + + void on_items_modified(t_size p_playlist,const bit_array & p_mask) {} + void on_items_modified_fromplayback(t_size p_playlist,const bit_array & p_mask,play_control::t_display_level p_level) {} + + void on_items_replaced(t_size p_playlist,const bit_array & p_mask,const pfc::list_base_const_t & p_data) {} + + void on_item_ensure_visible(t_size p_playlist,t_size p_idx) {} + + void on_playlist_activate(t_size p_old,t_size p_new) {} + void on_playlist_created(t_size p_index,const char * p_name,t_size p_name_len) {} + void on_playlists_reorder(const t_size * p_order,t_size p_count) {} + void on_playlists_removing(const bit_array & p_mask,t_size p_old_count,t_size p_new_count) {} + void on_playlists_removed(const bit_array & p_mask,t_size p_old_count,t_size p_new_count) {} + void on_playlist_renamed(t_size p_index,const char * p_new_name,t_size p_new_name_len) {} + + void on_default_format_changed() {} + void on_playback_order_changed(t_size p_new_index) {} + void on_playlist_locked(t_size p_playlist,bool p_locked) {} +}; + +//! playlist_callback_single implementation helper - registers itself on creation / unregisters on destruction. Must not be instantiated statically! +class playlist_callback_single_impl_base : public playlist_callback_single { +protected: + playlist_callback_single_impl_base(t_uint32 p_flags = 0) { + playlist_manager::get()->register_callback(this,p_flags); + } + void set_callback_flags(t_uint32 p_flags) { + playlist_manager::get()->modify_callback(this,p_flags); + } + ~playlist_callback_single_impl_base() { + playlist_manager::get()->unregister_callback(this); + } + + //dummy implementations - avoid possible pure virtual function calls! + void on_items_added(t_size p_base, const pfc::list_base_const_t & p_data,const bit_array & p_selection) {} + void on_items_reordered(const t_size * p_order,t_size p_count) {} + void on_items_removing(const bit_array & p_mask,t_size p_old_count,t_size p_new_count) {} + void on_items_removed(const bit_array & p_mask,t_size p_old_count,t_size p_new_count) {} + void on_items_selection_change(const bit_array & p_affected,const bit_array & p_state) {} + void on_item_focus_change(t_size p_from,t_size p_to) {} + void on_items_modified(const bit_array & p_mask) {} + void on_items_modified_fromplayback(const bit_array & p_mask,play_control::t_display_level p_level) {} + void on_items_replaced(const bit_array & p_mask,const pfc::list_base_const_t & p_data) {} + void on_item_ensure_visible(t_size p_idx) {} + + void on_playlist_switch() {} + void on_playlist_renamed(const char * p_new_name,t_size p_new_name_len) {} + void on_playlist_locked(bool p_locked) {} + + void on_default_format_changed() {} + void on_playback_order_changed(t_size p_new_index) {} + + PFC_CLASS_NOT_COPYABLE(playlist_callback_single_impl_base,playlist_callback_single_impl_base); +}; + +class playlist_callback_single_static : public service_base, public playlist_callback_single +{ +public: + virtual unsigned get_flags() = 0; + + FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(playlist_callback_single_static); +}; + + +//! Class used for async processing of IDataObject. Content of IDataObject can be dumped into dropped_files_data without any time-consuming operations - won't block calling app when used inside drag&drop handler - and actual time-consuming processing (listing directories and reading infos) can be done later.\n +//! \deprecated In 0.9.3 and up, instead of going thru dropped_files_data, you can use playlist_incoming_item_filter_v2::process_dropped_files_async(). +class NOVTABLE dropped_files_data { +public: + virtual void set_paths(pfc::string_list_const const & p_paths) = 0; + virtual void set_handles(const pfc::list_base_const_t & p_handles) = 0; +protected: + dropped_files_data() {} + ~dropped_files_data() {} +}; + + +class NOVTABLE playlist_incoming_item_filter : public service_base { + FB2K_MAKE_SERVICE_COREAPI(playlist_incoming_item_filter); +public: + //! Pre-sorts incoming items according to user-configured settings, removes duplicates. \n + //! As of 1.4, this is the same as sort_by_pointer_remove_duplicates() + sort_by_format( get_incoming_item_sorter() ), see playlist_incoming_item_filter_v4 \n + //! This method is valid in main thread only. However, using playlist_incoming_item_filter_v4::get_incoming_item_sorter() lets you do the same off main thread. + //! @param in Items to process. + //! @param out Receives processed item list. \n + //! @returns True when there's one or more item in the output list, false when the output list is empty. + virtual bool filter_items(metadb_handle_list_cref in,metadb_handle_list_ref out) = 0; + + //! Converts one or more paths to a list of metadb_handles; displays a progress dialog.\n + //! Note that this function creates modal dialog and does not return until the operation has completed. + //! @returns True on success, false on user abort. + //! \deprecated Use playlist_incoming_item_filter_v2::process_locations_async() when possible. + virtual bool process_locations(const pfc::list_base_const_t & p_urls,pfc::list_base_t & p_out,bool p_filter,const char * p_restrict_mask_override, const char * p_exclude_mask_override,HWND p_parentwnd) = 0; + + //! Converts an IDataObject to a list of metadb_handles. + //! Using this function is strongly disrecommended as it implies blocking the drag&drop source app (as well as our app).\n + //! @returns True on success, false on user abort or unknown data format. + //! \deprecated Use playlist_incoming_item_filter_v2::process_dropped_files_async() when possible. + virtual bool process_dropped_files(interface IDataObject * pDataObject,pfc::list_base_t & p_out,bool p_filter,HWND p_parentwnd) = 0; + + //! Checks whether IDataObject contains one of known data formats that can be translated to a list of metadb_handles. + virtual bool process_dropped_files_check(interface IDataObject * pDataObject) = 0; + + //! Checks whether IDataObject contains our own private data format (drag&drop within the app etc). + virtual bool process_dropped_files_check_if_native(interface IDataObject * pDataObject) = 0; + + //! Creates an IDataObject from specified metadb_handle list. The caller is responsible for releasing the returned object. It is recommended that you use create_dataobject_ex() to get an autopointer that ensures proper deletion. + virtual interface IDataObject * create_dataobject(const pfc::list_base_const_t & p_data) = 0; + + //! Checks whether IDataObject contains one of known data formats that can be translated to a list of metadb_handles.\n + //! This function also returns drop effects to use (see: IDropTarget::DragEnter(), IDropTarget::DragOver() ). In certain cases, drag effects are necessary for drag&drop to work at all (such as dragging links from IE).\n + virtual bool process_dropped_files_check_ex(interface IDataObject * pDataObject, DWORD * p_effect) = 0; + + //! Dumps IDataObject content to specified dropped_files_data object, without any time-consuming processing.\n + //! Using this function instead of process_dropped_files() and processing dropped_files_data outside drop handler allows you to avoid blocking drop source app when processing large directories etc.\n + //! Note: since 0.9.3, it is recommended to use playlist_incoming_item_filter_v2::process_dropped_files_async() instead. + //! @returns True on success, false when IDataObject does not contain any of known data formats. + virtual bool process_dropped_files_delayed(dropped_files_data & p_out,interface IDataObject * pDataObject) = 0; + + //! Helper - calls process_locations() with a single URL. See process_locations() for more info. + bool process_location(const char * url,pfc::list_base_t & out,bool filter,const char * p_mask,const char * p_exclude,HWND p_parentwnd); + //! Helper - returns a pfc::com_ptr_t<> rather than a raw pointer. + pfc::com_ptr_t create_dataobject_ex(metadb_handle_list_cref data); +}; + +//! For use with playlist_incoming_item_filter_v2::process_locations_async(). +//! \since 0.9.3 +class NOVTABLE process_locations_notify : public service_base { +public: + virtual void on_completion(const pfc::list_base_const_t & p_items) = 0; + virtual void on_aborted() = 0; + + FB2K_MAKE_SERVICE_INTERFACE(process_locations_notify,service_base); +}; + +typedef service_ptr_t process_locations_notify_ptr; + +//! \since 0.9.3 +class NOVTABLE playlist_incoming_item_filter_v2 : public playlist_incoming_item_filter { + FB2K_MAKE_SERVICE_COREAPI_EXTENSION(playlist_incoming_item_filter_v2, playlist_incoming_item_filter) +public: + enum { + //! Set this to disable presorting (according to user settings) and duplicate removal in output list. Should be unset in most cases. + op_flag_no_filter = 1 << 0, + //! Set this flag to make the progress dialog not steal focus on creation. + op_flag_background = 1 << 1, + //! Set this flag to delay the progress dialog becoming visible, so it does not appear at all during short operations. Also implies op_flag_background effect. + op_flag_delay_ui = 1 << 2, + }; + + //! Converts one or more paths to a list of metadb_handles. The function returns immediately; specified callback object receives results when the operation has completed. + //! @param p_urls List of paths to process. + //! @param p_op_flags Can be null, or one or more of op_flag_* enum values combined, altering behaviors of the operation. + //! @param p_restrict_mask_override Override of "restrict incoming items to" setting. Pass NULL to use the value from preferences. + //! @param p_exclude_mask_override Override of "exclude file types" setting. Pass NULL to use value from preferences. + //! @param p_parentwnd Parent window for spawned progress dialogs. + //! @param p_notify Callback receiving notifications about success/abort of the operation as well as output item list. + virtual void process_locations_async(const pfc::list_base_const_t & p_urls,t_uint32 p_op_flags,const char * p_restrict_mask_override, const char * p_exclude_mask_override,HWND p_parentwnd,process_locations_notify_ptr p_notify) = 0; + + //! Converts an IDataObject to a list of metadb_handles. The function returns immediately; specified callback object receives results when the operation has completed. + //! @param p_dataobject IDataObject to process. + //! @param p_op_flags Can be null, or one or more of op_flag_* enum values combined, altering behaviors of the operation. + //! @param p_parentwnd Parent window for spawned progress dialogs. + //! @param p_notify Callback receiving notifications about success/abort of the operation as well as output item list. + virtual void process_dropped_files_async(interface IDataObject * p_dataobject,t_uint32 p_op_flags,HWND p_parentwnd,process_locations_notify_ptr p_notify) = 0; +}; + +//! \since 0.9.5 +class playlist_incoming_item_filter_v3 : public playlist_incoming_item_filter_v2 { + FB2K_MAKE_SERVICE_COREAPI_EXTENSION(playlist_incoming_item_filter_v3, playlist_incoming_item_filter_v2) +public: + virtual bool auto_playlist_name(metadb_handle_list_cref data,pfc::string_base & out) = 0; +}; + +//! \since 1.4 +class playlist_incoming_item_filter_v4 : public playlist_incoming_item_filter_v3 { + FB2K_MAKE_SERVICE_COREAPI_EXTENSION(playlist_incoming_item_filter_v4, playlist_incoming_item_filter_v3); +public: + //! Retrieves title formatting pattern for sorting incoming files. \n + //! Valid from main thread only - however you can use the value for off-main-thread operations. + virtual void get_incoming_item_sort_pattern( pfc::string_base & out ) = 0; + //! Retrieves shared title formatting object for sorting incoming files. \n + //! This is the same as compiling the string returned from get_incoming_item_sort_pattern, except the returned object is shared with others using this API. \n + //! Valid from main thread only - however you can use the returned object for off-main-thread operations. + virtual titleformat_object::ptr get_incoming_item_sorter() = 0; +}; + +//! Implementation of dropped_files_data. +class dropped_files_data_impl : public dropped_files_data { +public: + dropped_files_data_impl() : m_is_paths(false) {} + void set_paths(pfc::string_list_const const & p_paths) { + m_is_paths = true; + m_paths = p_paths; + } + void set_handles(const pfc::list_base_const_t & p_handles) { + m_is_paths = false; + m_handles = p_handles; + } + + void to_handles_async(bool p_filter,HWND p_parentwnd,service_ptr_t p_notify); + //! @param p_op_flags Can be null, or one or more of playlist_incoming_item_filter_v2::op_flag_* enum values combined, altering behaviors of the operation. + void to_handles_async_ex(t_uint32 p_op_flags,HWND p_parentwnd,service_ptr_t p_notify); + bool to_handles(pfc::list_base_t & p_out,bool p_filter,HWND p_parentwnd); +private: + pfc::string_list_impl m_paths; + metadb_handle_list m_handles; + bool m_is_paths; +}; + + +class NOVTABLE playback_queue_callback : public service_base +{ +public: + enum t_change_origin { + changed_user_added, + changed_user_removed, + changed_playback_advance, + }; + virtual void on_changed(t_change_origin p_origin) = 0; + + FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(playback_queue_callback); +}; + + +class playlist_lock_change_notify : private playlist_callback_single_impl_base { +public: + playlist_lock_change_notify() : playlist_callback_single_impl_base(flag_on_playlist_switch|flag_on_playlist_locked) {} +protected: + virtual void on_lock_state_change() {} + bool is_playlist_command_available(t_uint32 what) const { + auto api = playlist_manager::get(); + const t_size active = api->get_active_playlist(); + if (active == ~0) return false; + return (api->playlist_lock_get_filter_mask(active) & what) == 0; + } +private: + void on_playlist_switch() {on_lock_state_change();} + void on_playlist_locked(bool p_locked) {on_lock_state_change();} +}; diff --git a/foobar2000/SDK/playlist_loader.cpp b/foobar2000/SDK/playlist_loader.cpp new file mode 100644 index 0000000..8f0bf75 --- /dev/null +++ b/foobar2000/SDK/playlist_loader.cpp @@ -0,0 +1,362 @@ +#include "foobar2000.h" + +#if FOOBAR2000_TARGET_VERSION >= 76 +static void process_path_internal(const char * p_path,const service_ptr_t & p_reader,playlist_loader_callback::ptr callback, abort_callback & abort,playlist_loader_callback::t_entry_type type,const t_filestats & p_stats); + +namespace { + class archive_callback_impl : public archive_callback { + public: + archive_callback_impl(playlist_loader_callback::ptr p_callback, abort_callback & p_abort) : m_callback(p_callback), m_abort(p_abort) {} + bool on_entry(archive * owner,const char * p_path,const t_filestats & p_stats,const service_ptr_t & p_reader) + { + process_path_internal(p_path,p_reader,m_callback,m_abort,playlist_loader_callback::entry_directory_enumerated,p_stats); + return !m_abort.is_aborting(); + } + bool is_aborting() const {return m_abort.is_aborting();} + abort_callback_event get_abort_event() const {return m_abort.get_abort_event();} + private: + const playlist_loader_callback::ptr m_callback; + abort_callback & m_abort; + }; +} + +bool playlist_loader::g_try_load_playlist(file::ptr fileHint,const char * p_path,playlist_loader_callback::ptr p_callback, abort_callback & p_abort) { + pfc::string8 filepath; + + filesystem::g_get_canonical_path(p_path,filepath); + + pfc::string_extension extension(filepath); + + service_ptr_t l_file = fileHint; + + if (l_file.is_empty()) { + filesystem::ptr fs; + if (filesystem::g_get_interface(fs,filepath)) { + if (fs->supports_content_types()) { + fs->open(l_file,filepath,filesystem::open_mode_read,p_abort); + } + } + } + + { + service_enum_t e; + + if (l_file.is_valid()) { + pfc::string8 content_type; + if (l_file->get_content_type(content_type)) { + service_ptr_t l; + e.reset(); while(e.next(l)) { + if (l->is_our_content_type(content_type)) { + try { + TRACK_CODE("playlist_loader::open",l->open(filepath,l_file,p_callback, p_abort)); + return true; + } catch(exception_io_unsupported_format) { + l_file->reopen(p_abort); + } + } + } + } + } + + if (extension.length()>0) { + playlist_loader::ptr l; + e.reset(); while(e.next(l)) { + if (stricmp_utf8(l->get_extension(),extension) == 0) { + if (l_file.is_empty()) filesystem::g_open_read(l_file,filepath,p_abort); + try { + TRACK_CODE("playlist_loader::open",l->open(filepath,l_file,p_callback,p_abort)); + return true; + } catch(exception_io_unsupported_format) { + l_file->reopen(p_abort); + } + } + } + } + } + + return false; +} + +void playlist_loader::g_load_playlist_filehint(file::ptr fileHint,const char * p_path,playlist_loader_callback::ptr p_callback, abort_callback & p_abort) { + if (!g_try_load_playlist(fileHint, p_path, p_callback, p_abort)) throw exception_io_unsupported_format(); +} + +void playlist_loader::g_load_playlist(const char * p_path,playlist_loader_callback::ptr callback, abort_callback & abort) { + g_load_playlist_filehint(NULL,p_path,callback,abort); +} + +static void index_tracks_helper(const char * p_path,const service_ptr_t & p_reader,const t_filestats & p_stats,playlist_loader_callback::t_entry_type p_type,playlist_loader_callback::ptr p_callback, abort_callback & p_abort,bool & p_got_input) +{ + TRACK_CALL_TEXT("index_tracks_helper"); + if (p_reader.is_empty() && filesystem::g_is_remote_safe(p_path)) + { + TRACK_CALL_TEXT("remote"); + metadb_handle_ptr handle; + p_callback->handle_create(handle,make_playable_location(p_path,0)); + p_got_input = true; + p_callback->on_entry(handle,p_type,p_stats,true); + } else { + TRACK_CALL_TEXT("hintable"); + service_ptr_t instance; + try { + input_entry::g_open_for_info_read(instance,p_reader,p_path,p_abort); + } catch(exception_io_unsupported_format) { + // specifically bail + throw; + } catch(exception_io) { + // broken file or some other error, open() failed - show it anyway + metadb_handle_ptr handle; + p_callback->handle_create(handle, make_playable_location(p_path, 0)); + p_callback->on_entry(handle, p_type, p_stats, true); + return; + } + + t_filestats stats = instance->get_file_stats(p_abort); + + t_uint32 subsong,subsong_count = instance->get_subsong_count(); + bool bInfoGetError = false; + for(subsong=0;subsongget_subsong(subsong); + p_callback->handle_create(handle,make_playable_location(p_path,index)); + + p_got_input = true; + if (! bInfoGetError && p_callback->want_info(handle,p_type,stats,true) ) + { + file_info_impl info; + try { + TRACK_CODE("get_info",instance->get_info(index,info,p_abort)); + } catch(...) { + bInfoGetError = true; + } + if (! bInfoGetError ) { + p_callback->on_entry_info(handle,p_type,stats,info,true); + } + } + else + { + p_callback->on_entry(handle,p_type,stats,true); + } + } + } +} + +static void track_indexer__g_get_tracks_wrap(const char * p_path,const service_ptr_t & p_reader,const t_filestats & p_stats,playlist_loader_callback::t_entry_type p_type,playlist_loader_callback::ptr p_callback, abort_callback & p_abort) { + bool got_input = false; + bool fail = false; + try { + index_tracks_helper(p_path,p_reader,p_stats,p_type,p_callback,p_abort, got_input); + } catch(exception_aborted) { + throw; + } catch(exception_io_unsupported_format) { + fail = true; + } catch(std::exception const & e) { + fail = true; + FB2K_console_formatter() << "could not enumerate tracks (" << e << ") on:\n" << file_path_display(p_path); + } + if (fail) { + if (!got_input && !p_abort.is_aborting()) { + if (p_type == playlist_loader_callback::entry_user_requested) + { + metadb_handle_ptr handle; + p_callback->handle_create(handle,make_playable_location(p_path,0)); + p_callback->on_entry(handle,p_type,p_stats,true); + } + } + } +} + +namespace { + + static bool queryAddHidden() { + // {2F9F4956-363F-4045-9531-603B1BF39BA8} + static const GUID guid_cfg_addhidden = + { 0x2f9f4956, 0x363f, 0x4045,{ 0x95, 0x31, 0x60, 0x3b, 0x1b, 0xf3, 0x9b, 0xa8 } }; + + advconfig_entry_checkbox::ptr ptr; + if (advconfig_entry::g_find_t(ptr, guid_cfg_addhidden)) { + return ptr->get_state(); + } + return false; + } + + // SPECIAL HACK + // filesystem service does not present file hidden attrib but we want to weed files/folders out + // so check separately on all native paths (inefficient but meh) + class directory_callback_myimpl : public directory_callback + { + public: + directory_callback_myimpl() : m_addHidden(queryAddHidden()) {} + + bool on_entry(filesystem * owner,abort_callback & p_abort,const char * url,bool is_subdirectory,const t_filestats & p_stats) { + p_abort.check(); + + filesystem_v2::ptr v2; + v2 &= owner; + + if ( is_subdirectory ) { + if (v2.is_valid()) { + v2->list_directory_ex(url, *this, flags(), p_abort); + } else { + owner->list_directory(url, *this, p_abort ); + } + } else { + // In fb2k 1.4 the default filesystem is v2 and performs hidden file checks +#if FOOBAR2000_TARGET_VERSION < 79 + if ( ! m_addHidden && v2.is_empty() ) { + const char * n = url; + if (_extract_native_path_ptr(n)) { + DWORD att = uGetFileAttributes(n); + if (att == ~0 || (att & FILE_ATTRIBUTE_HIDDEN) != 0) return true; + } + } +#endif + auto i = m_entries.insert_last(); + i->m_path = url; + i->m_stats = p_stats; + } + return true; + } + + uint32_t flags() const { + uint32_t flags = listMode::filesAndFolders; + if (m_addHidden) flags |= listMode::hidden; + return flags; + } + + const bool m_addHidden; + struct entry_t { + pfc::string8 m_path; + t_filestats m_stats; + }; + pfc::chain_list_v2_t m_entries; + + }; +} + + +static void process_path_internal(const char * p_path,const service_ptr_t & p_reader,playlist_loader_callback::ptr callback, abort_callback & abort,playlist_loader_callback::t_entry_type type,const t_filestats & p_stats) +{ + //p_path must be canonical + + abort.check(); + + callback->on_progress(p_path); + + + { + if (p_reader.is_empty() && type != playlist_loader_callback::entry_directory_enumerated) { + try { + directory_callback_myimpl results; + auto fs = filesystem::get(p_path); + filesystem_v2::ptr v2; + if ( v2 &= fs ) v2->list_directory_ex(p_path, results, results.flags(), abort ); + else fs->list_directory(p_path, results, abort); + for( auto i = results.m_entries.first(); i.is_valid(); ++i ) { + process_path_internal(i->m_path, 0, callback, abort, playlist_loader_callback::entry_directory_enumerated, i->m_stats ); + } + return; + } catch(exception_aborted) {throw;} + catch(...) { + //do nothing, fall thru + //fixme - catch only filesystem exceptions? + } + } + + bool found = false; + + + { + archive_callback_impl archive_results(callback, abort); + service_enum_t e; + service_ptr_t f; + while(e.next(f)) { + abort.check(); + service_ptr_t arch; + if (f->service_query_t(arch) && arch->is_our_archive(p_path)) { + if (p_reader.is_valid()) p_reader->reopen(abort); + + try { + TRACK_CODE("archive::archive_list",arch->archive_list(p_path,p_reader,archive_results,true)); + return; + } catch(exception_aborted) {throw;} + catch(...) {} + } + } + } + } + + + + { + service_ptr_t ptr; + if (link_resolver::g_find(ptr,p_path)) + { + if (p_reader.is_valid()) p_reader->reopen(abort); + + pfc::string8 temp; + try { + TRACK_CODE("link_resolver::resolve",ptr->resolve(p_reader,p_path,temp,abort)); + + track_indexer__g_get_tracks_wrap(temp,0,filestats_invalid,playlist_loader_callback::entry_from_playlist,callback, abort); + return;//success + } catch(exception_aborted) {throw;} + catch(...) {} + } + } + + if (callback->is_path_wanted(p_path,type)) { + track_indexer__g_get_tracks_wrap(p_path,p_reader,p_stats,type,callback, abort); + } +} + +void playlist_loader::g_process_path(const char * p_filename,playlist_loader_callback::ptr callback, abort_callback & abort,playlist_loader_callback::t_entry_type type) +{ + TRACK_CALL_TEXT("playlist_loader::g_process_path"); + + file_path_canonical filename(p_filename); + + process_path_internal(filename,0,callback,abort, type,filestats_invalid); +} + +void playlist_loader::g_save_playlist(const char * p_filename,const pfc::list_base_const_t & data,abort_callback & p_abort) +{ + TRACK_CALL_TEXT("playlist_loader::g_save_playlist"); + pfc::string8 filename; + filesystem::g_get_canonical_path(p_filename,filename); + try { + service_ptr_t r; + filesystem::g_open(r,filename,filesystem::open_mode_write_new,p_abort); + + pfc::string_extension ext(filename); + + service_enum_t e; + service_ptr_t l; + if (e.first(l)) do { + if (l->can_write() && !stricmp_utf8(ext,l->get_extension())) { + try { + TRACK_CODE("playlist_loader::write",l->write(filename,r,data,p_abort)); + return; + } catch(exception_io_data) {} + } + } while(e.next(l)); + throw exception_io_data(); + } catch(...) { + try {filesystem::g_remove(filename,p_abort);} catch(...) {} + throw; + } +} + + +bool playlist_loader::g_process_path_ex(const char * filename,playlist_loader_callback::ptr callback, abort_callback & abort,playlist_loader_callback::t_entry_type type) +{ + if (g_try_load_playlist(NULL, filename, callback, abort)) return true; + //not a playlist format + g_process_path(filename,callback,abort,type); + return false; +} + +#endif \ No newline at end of file diff --git a/foobar2000/SDK/playlist_loader.h b/foobar2000/SDK/playlist_loader.h new file mode 100644 index 0000000..de1de4b --- /dev/null +++ b/foobar2000/SDK/playlist_loader.h @@ -0,0 +1,132 @@ +#if FOOBAR2000_TARGET_VERSION >= 76 +//! Callback interface receiving item locations from playlist loader. \n +//! Typically, you call one of standard services such as playlist_incoming_item_filter instead of implementing this interface and calling playlist_loader methods directly. +class NOVTABLE playlist_loader_callback : public service_base { + FB2K_MAKE_SERVICE_INTERFACE(playlist_loader_callback, service_base) +public: + //! Enumeration type representing origin of item passed to playlist_loader_callback. + enum t_entry_type { + //! User-requested (such as directly dropped to window or picked in openfiledialog). + entry_user_requested, + //! From directory content enumeration. + entry_directory_enumerated, + //! Referenced by playlist file. + entry_from_playlist, + }; + //! Indicates specified path being processed; provided for updating GUI. Note that optimally GUI should not be updated every time this is called because that could introduce a bottleneck. + virtual void on_progress(const char * p_path) = 0; + + //! Receives an item from one of playlist_loader functions. + //! @param p_item Item location, in form of metadb_handle pointer. + //! @param p_type Origin of this item - see t_entry_type for more info. + //! @param p_stats File stats of this item; set to filestats_invalid if not available. + //! @param p_fresh Fresh flag; indicates whether stats are directly from filesystem (true) or as stored earlier in a playlist file (false). + virtual void on_entry(const metadb_handle_ptr & p_item,t_entry_type p_type,const t_filestats & p_stats,bool p_fresh) = 0; + //! Queries whether file_info for specified item is requested. In typical scenario, if want_info() returns false, on_entry() will be called with same parameters; otherwise caller will attempt to read info from the item and call on_entry_info() with same parameters and file_info read from the item. + //! @param p_item Item location, in form of metadb_handle pointer. + //! @param p_type Origin of this item - see t_entry_type for more info. + //! @param p_stats File stats of this item; set to filestats_invalid if not available. + //! @param p_fresh Fresh flag; indicates whether stats are directly from filesystem (true) or as stored earlier in a playlist file (false). + virtual bool want_info(const metadb_handle_ptr & p_item,t_entry_type p_type,const t_filestats & p_stats,bool p_fresh) = 0; + //! Receives an item from one of playlist_loader functions; including file_info data. Except for file_info to be typically used as hint for metadb backend, behavior of this method is same as on_entry(). + //! @param p_item Item location, in form of metadb_handle pointer. + //! @param p_type Origin of this item - see t_entry_type for more info. + //! @param p_stats File stats of this item; set to filestats_invalid if not available. + //! @param p_info Information about the item, read from the file directly (if p_fresh is set to true) or from e.g. playlist file (if p_fresh is set to false). + //! @param p_fresh Fresh flag; indicates whether stats are directly from filesystem (true) or as stored earlier in a playlist file (false). + virtual void on_entry_info(const metadb_handle_ptr & p_item,t_entry_type p_type,const t_filestats & p_stats,const file_info & p_info,bool p_fresh) = 0; + + //! Same as metadb::handle_create(); provided here to avoid repeated metadb instantiation bottleneck since calling code will need this function often. + virtual void handle_create(metadb_handle_ptr & p_out,const playable_location & p_location) = 0; + + //! Returns whether further on_entry() calls for this file are wanted. Typically always returns true, can be used to optimize cases when directories are searched for files matching specific pattern only so unwanted files aren't parsed unnecessarily. + //! @param path Canonical path to the media file being processed. + virtual bool is_path_wanted(const char * path, t_entry_type type) = 0; + + virtual bool want_browse_info(const metadb_handle_ptr & p_item,t_entry_type p_type,t_filetimestamp ts) = 0; + virtual void on_browse_info(const metadb_handle_ptr & p_item,t_entry_type p_type,const file_info & info, t_filetimestamp ts) = 0; +}; + +//! \since 1.3 +//! Extended version of playlist_loader_callback, allowing caller to pass pre-made metadb_info_container \n +class NOVTABLE playlist_loader_callback_v2 : public playlist_loader_callback { + FB2K_MAKE_SERVICE_INTERFACE(playlist_loader_callback_v2, playlist_loader_callback) +public: + virtual void on_entry_info_v2(const metadb_handle_ptr & p_item,t_entry_type p_type,metadb_info_container::ptr info,bool p_fresh) = 0; + virtual void on_browse_info_v2(const metadb_handle_ptr & p_item,t_entry_type p_type,metadb_info_container::ptr info) = 0; +private: +}; + + +//! Service handling playlist file operations. There are multiple implementations handling different playlist formats; you can add new implementations to allow new custom playlist file formats to be read or written.\n +//! Also provides static helper functions for turning a filesystem path into a list of playable item locations. \n +//! Note that you should typically call playlist_incoming_item_filter methods instead of calling playlist_loader methods directly to get a list of playable items from a specified path; this way you get a core-implemented threading and abortable dialog displaying progress.\n +//! To register your own implementation, use playlist_loader_factory_t template.\n +//! To call existing implementations, use static helper methods of playlist_loader class. +class NOVTABLE playlist_loader : public service_base { +public: + //! Parses specified playlist file into list of playable locations. Throws exception_io or derivatives on failure, exception_aborted on abort. If specified file is not a recognized playlist file, exception_io_unsupported_format is thrown. + //! @param p_path Path of playlist file to parse. Used for relative path handling purposes (p_file parameter is used for actual file access). + //! @param p_file File interface to use for reading. Read/write pointer must be set to beginning by caller before calling. + //! @param p_callback Callback object receiving enumerated playable item locations. + virtual void open(const char * p_path, const service_ptr_t & p_file,playlist_loader_callback::ptr p_callback, abort_callback & p_abort) = 0; + //! Writes a playlist file containing specific item list to specified file. Will fail (pfc::exception_not_implemented) if specified playlist_loader is read-only (can_write() returns false). + //! @param p_path Path of playlist file to write. Used for relative path handling purposes (p_file parameter is used for actual file access). + //! @param p_file File interface to use for writing. Caller should ensure that the file is empty (0 bytes long) before calling. + //! @param p_data List of items to save to playlist file. + //! @param p_abort abort_callback object signaling user aborting the operation. Note that aborting a save playlist operation will most likely leave user with corrupted/incomplete file. + virtual void write(const char * p_path, const service_ptr_t & p_file,metadb_handle_list_cref p_data,abort_callback & p_abort) = 0; + //! Returns extension of file format handled by this playlist_loader implementation (a UTF-8 encoded null-terminated string). + virtual const char * get_extension() = 0; + //! Returns whether this playlist_loader implementation supports writing. If can_write() returns false, all write() calls will fail. + virtual bool can_write() = 0; + //! Returns whether specified content type is one of playlist types supported by this playlist_loader implementation or not. + //! @param p_content_type Content type to query, a UTF-8 encoded null-terminated string. + virtual bool is_our_content_type(const char* p_content_type) = 0; + //! Returns whether playlist format extension supported by this implementation should be listed on file types associations page. + virtual bool is_associatable() = 0; + + //! Attempts to load a playlist file from specified filesystem path. Throws exception_io or derivatives on failure, exception_aborted on abort. If specified file is not a recognized playlist file, exception_io_unsupported_format is thrown. \n + //! Equivalent to g_load_playlist_filehint(NULL,p_path,p_callback). + //! @param p_path Filesystem path to load playlist from, a UTF-8 encoded null-terminated string. + //! @param p_callback Callback object receiving enumerated playable item locations as well as signaling user aborting the operation. + static void g_load_playlist(const char * p_path,playlist_loader_callback::ptr p_callback, abort_callback & p_abort); + + //! Attempts to load a playlist file from specified filesystem path. Throws exception_io or derivatives on failure, exception_aborted on abort. If specified file is not a recognized playlist file, exception_io_unsupported_format is thrown. + //! @param p_path Filesystem path to load playlist from, a UTF-8 encoded null-terminated string. + //! @param p_callback Callback object receiving enumerated playable item locations as well as signaling user aborting the operation. + //! @param fileHint File object to read from, can be NULL if not available. + static void g_load_playlist_filehint(file::ptr fileHint,const char * p_path,playlist_loader_callback::ptr p_callback, abort_callback & p_abort); + + //! Attempts to load a playlist file from specified filesystem path. Throws exception_io or derivatives on failure, exception_aborted on abort. If specified file is not a recognized playlist file, returns false; returns true upon successful playlist load. + //! @param p_path Filesystem path to load playlist from, a UTF-8 encoded null-terminated string. + //! @param p_callback Callback object receiving enumerated playable item locations as well as signaling user aborting the operation. + //! @param fileHint File object to read from, can be NULL if not available. + static bool g_try_load_playlist(file::ptr fileHint,const char * p_path,playlist_loader_callback::ptr p_callback, abort_callback & p_abort); + + //! Saves specified list of locations into a playlist file. Throws exception_io or derivatives on failure, exception_aborted on abort. + //! @param p_path Filesystem path to save playlist to, a UTF-8 encoded null-terminated string. + //! @param p_data List of items to save to playlist file. + //! @param p_abort abort_callback object signaling user aborting the operation. Note that aborting a save playlist operation will most likely leave user with corrupted/incomplete file. + static void g_save_playlist(const char * p_path,metadb_handle_list_cref p_data,abort_callback & p_abort); + + //! Processes specified path to generate list of playable items. Includes recursive directory/archive enumeration. \n + //! Does not touch playlist files encountered - use g_process_path_ex() if specified path is possibly a playlist file; playlist files found inside directories or archives are ignored regardless.\n + //! Warning: caller must handle exceptions which will occur in case of I/O failure. + //! @param p_path Filesystem path to process; a UTF-8 encoded null-terminated string. + //! @param p_callback Callback object receiving enumerated playable item locations as well as signaling user aborting the operation. + //! @param p_type Origin of p_path string. Reserved for internal use in recursive calls, should be left at default value; it controls various internal behaviors. + static void g_process_path(const char * p_path,playlist_loader_callback::ptr p_callback, abort_callback & p_abort,playlist_loader_callback::t_entry_type p_type = playlist_loader_callback::entry_user_requested); + + //! Calls attempts to process specified path as a playlist; if that fails (i.e. not a playlist), calls g_process_path with same parameters. See g_process_path for parameter descriptions. \n + //! Warning: caller must handle exceptions which will occur in case of I/O failure or playlist parsing failure. + //! @returns True if specified path was processed as a playlist file, false otherwise (relevant in some scenarios where output is sorted after loading, playlist file contents should not be sorted). + static bool g_process_path_ex(const char * p_path,playlist_loader_callback::ptr p_callback, abort_callback & p_abort,playlist_loader_callback::t_entry_type p_type = playlist_loader_callback::entry_user_requested); + + FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(playlist_loader); +}; + +template +class playlist_loader_factory_t : public service_factory_single_t {}; + +#endif \ No newline at end of file diff --git a/foobar2000/SDK/popup_message.cpp b/foobar2000/SDK/popup_message.cpp new file mode 100644 index 0000000..9c24518 --- /dev/null +++ b/foobar2000/SDK/popup_message.cpp @@ -0,0 +1,137 @@ +#include "foobar2000.h" + +void popup_message::g_show_ex(const char * p_msg,unsigned p_msg_length,const char * p_title,unsigned p_title_length,t_icon p_icon) +{ + // Do not force instantiate, not all platforms have this + service_enum_t< popup_message > e; + service_ptr_t< popup_message > m; + if (e.first( m ) ) { + m->show_ex( p_msg, p_msg_length, p_title, p_title_length, p_icon ); + } +} + + +void popup_message::g_complain(const char * what) { + g_show(what, "Information", icon_error); +} + +void popup_message::g_complain(const char * p_whatFailed, const std::exception & p_exception) { + g_complain(p_whatFailed,p_exception.what()); +} +void popup_message::g_complain(const char * p_whatFailed, const char * msg) { + g_complain( PFC_string_formatter() << p_whatFailed << ": " << msg ); +} + +#if FOOBAR2000_TARGET_VERSION >= 80 +void popup_message_v3::show_query( const char * title, const char * msg, unsigned buttons, completion_notify::ptr reply) { + query_t q; + q.title = title; q.msg = msg; q.buttons = buttons; q.reply = reply; + this->show_query( q ); +} +#endif + + +#ifdef FOOBAR2000_DESKTOP_WINDOWS +void popup_message_v2::g_show(HWND parent, const char * msg, const char * title) { + service_enum_t< popup_message_v2 > e; + service_ptr_t< popup_message_v2 > m; + if (e.first( m )) { + m->show(parent, msg, title); + } else { + popup_message::g_show( msg, title ); + } +} +void popup_message_v2::g_complain(HWND parent, const char * whatFailed, const char * msg) { + g_show(parent, PFC_string_formatter() << whatFailed << ": " << msg); +} +void popup_message_v2::g_complain(HWND parent, const char * whatFailed, const std::exception & e) { + g_complain(parent, whatFailed, e.what()); +} +#endif // FOOBAR2000_DESKTOP_WINDOWS + +#ifdef FOOBAR2000_MODERN +void fb2k::showToast( const char * msg ) { + fb2k::popup_toast::arg_t arg; + fb2k::popup_toast::get()->show_toast(msg, arg); +} + +void fb2k::showToastLongDuration( const char * msg ) { + fb2k::popup_toast::arg_t arg; + arg.longDuration = true; + fb2k::popup_toast::get()->show_toast(msg, arg); +} +void popup_message::g_showToast(const char * msg) { + fb2k::showToast( msg ); +} +void popup_message::g_showToastLongDuration(const char * msg) { + fb2k::showToastLongDuration( msg ); +} + +#endif // FOOBAR2000_MODERN + +#if defined(FOOBAR2000_DESKTOP_WINDOWS) && FOOBAR2000_TARGET_VERSION >= 80 +int popup_message_v3::messageBox(HWND parent, const char* msg, const char* title, UINT flags) { + query_t q = {}; + q.title = title; + q.msg = msg; + q.wndParent = parent; + + switch (flags & 0xF) { + default: + case MB_OK: + q.buttons = buttonOK; + q.defButton = buttonOK; + break; + case MB_OKCANCEL: + q.buttons = buttonOK | buttonCancel; + q.defButton = (flags & MB_DEFBUTTON2) ? buttonCancel : buttonOK; + break; + case MB_ABORTRETRYIGNORE: + q.buttons = buttonAbort | buttonRetry | buttonIgnore; + if (flags & MB_DEFBUTTON3) q.defButton = buttonIgnore; + else if (flags & MB_DEFBUTTON2) q.defButton = buttonRetry; + else q.defButton = buttonAbort; + break; + case MB_YESNOCANCEL: + q.buttons = buttonYes | buttonNo | buttonCancel; + if (flags & MB_DEFBUTTON3) q.defButton = buttonCancel; + else if (flags & MB_DEFBUTTON2) q.defButton = buttonNo; + else q.defButton = buttonYes; + break; + case MB_YESNO: + q.buttons = buttonYes | buttonNo; + q.defButton = (flags & MB_DEFBUTTON2) ? buttonNo : buttonYes; + break; + case MB_RETRYCANCEL: + q.buttons = buttonRetry | buttonCancel; + q.defButton = (flags & MB_DEFBUTTON2) ? buttonCancel : buttonRetry; + break; + } + switch (flags & 0xF0) { + case MB_ICONHAND: + q.icon = iconWarning; + break; + case MB_ICONQUESTION: + q.icon = iconQuestion; + break; + case MB_ICONEXCLAMATION: + q.icon = iconError; + break; + case MB_ICONASTERISK: + q.icon = iconInformation; + break; + } + + uint32_t status = this->show_query_modal(q); + + if (status & buttonOK) return IDOK; + if (status & buttonCancel) return IDCANCEL; + if (status & buttonYes) return IDYES; + if (status & buttonNo) return IDNO; + if (status & buttonRetry) return IDRETRY; + if (status & buttonAbort) return IDABORT; + if (status & buttonIgnore) return IDIGNORE; + + return -1; +} +#endif diff --git a/foobar2000/SDK/popup_message.h b/foobar2000/SDK/popup_message.h new file mode 100644 index 0000000..f3e7cbf --- /dev/null +++ b/foobar2000/SDK/popup_message.h @@ -0,0 +1,141 @@ +#pragma once + +#include // UINT_MAX + +#include "completion_notify.h" + +//! This interface allows you to show generic nonmodal noninteractive dialog with a text message. This should be used instead of MessageBox where possible.\n +//! Usage: use popup_message::g_show / popup_message::g_show_ex static helpers, or popup_message::get() to obtain an instance.\n +//! Note that all strings are UTF-8. + +class NOVTABLE popup_message : public service_base { +public: + enum t_icon {icon_information, icon_error, icon_query}; + //! Activates the popup dialog; returns immediately (the dialog remains visible). + //! @param p_msg Message to show (UTF-8 encoded string). + //! @param p_msg_length Length limit of message string to show, in bytes (actual string may be shorter if null terminator is encountered before). Set this to infinite to use plain null-terminated strings. + //! @param p_title Title of dialog to show (UTF-8 encoded string). + //! @param p_title_length Length limit of the title string, in bytes (actual string may be shorter if null terminator is encountered before). Set this to infinite to use plain null-terminated strings. + //! @param p_icon Icon of the dialog - can be set to icon_information, icon_error or icon_query. + virtual void show_ex(const char * p_msg,unsigned p_msg_length,const char * p_title,unsigned p_title_length,t_icon p_icon = icon_information) = 0; + + //! Activates the popup dialog; returns immediately (the dialog remains visible); helper function built around show_ex(), takes null terminated strings with no length limit parameters. + //! @param p_msg Message to show (UTF-8 encoded string). + //! @param p_title Title of dialog to show (UTF-8 encoded string). + //! @param p_icon Icon of the dialog - can be set to icon_information, icon_error or icon_query. + inline void show(const char * p_msg,const char * p_title,t_icon p_icon = icon_information) {show_ex(p_msg,UINT_MAX,p_title,UINT_MAX,p_icon);} + + //! Static helper function instantiating the service and activating the message dialog. See show_ex() for description of parameters. + static void g_show_ex(const char * p_msg,unsigned p_msg_length,const char * p_title,unsigned p_title_length,t_icon p_icon = icon_information); + //! Static helper function instantiating the service and activating the message dialog. See show() for description of parameters. + static inline void g_show(const char * p_msg,const char * p_title,t_icon p_icon = icon_information) {g_show_ex(p_msg,UINT_MAX,p_title,UINT_MAX,p_icon);} + + //! Shows generic box with a failure message + static void g_complain(const char * what); + //! : + static void g_complain(const char * p_whatFailed, const std::exception & p_exception); + //! : + static void g_complain(const char * p_whatFailed, const char * msg); + +#ifdef FOOBAR2000_MODERN + static void g_showToast(const char * msg); + static void g_showToastLongDuration(const char * msg); +#endif + + FB2K_MAKE_SERVICE_COREAPI(popup_message); +}; + +#define EXCEPTION_TO_POPUP_MESSAGE(CODE,LABEL) try { CODE; } catch(std::exception const & e) {popup_message::g_complain(LABEL,e);} + +#ifdef FOOBAR2000_DESKTOP_WINDOWS +//! \since 1.1 +class NOVTABLE popup_message_v2 : public service_base { + FB2K_MAKE_SERVICE_COREAPI(popup_message_v2); +public: + virtual void show(HWND parent, const char * msg, t_size msg_length, const char * title, t_size title_length) = 0; + void show(HWND parent, const char * msg, const char * title) {show(parent, msg, ~0, title, ~0);} + + static void g_show(HWND parent, const char * msg, const char * title = "Information"); + static void g_complain(HWND parent, const char * whatFailed, const char * msg); + static void g_complain(HWND parent, const char * whatFailed, const std::exception & e); +}; +#endif + +#ifdef FOOBAR2000_MODERN +namespace fb2k { + class popup_toast : public service_base { + FB2K_MAKE_SERVICE_COREAPI( popup_toast ); + public: + struct arg_t { + bool longDuration = false; + }; + virtual void show_toast(const char * msg, arg_t const & arg) = 0; + }; + void showToast( const char * msg ); + void showToastLongDuration( const char * msg ); + + class toastFormatter : public pfc::string_formatter { + public: + ~toastFormatter() { + if ( this->length() > 0 ) showToast( c_str() ); + } + }; +} + +#define FB2K_Toast() ::fb2k::toastFormatter()._formatter() +#endif + +#if FOOBAR2000_TARGET_VERSION >= 80 +//! \since 1.5 +class NOVTABLE popup_message_v3 : public service_base { + FB2K_MAKE_SERVICE_COREAPI(popup_message_v3); +public: + + //! show_query button codes. \n + //! Combine one or more of these to create a button mask to pass to show_query(). + enum { + buttonOK = 1 << 0, + buttonCancel = 1 << 1, + buttonYes = 1 << 2, + buttonNo = 1 << 3, + buttonRetry = 1 << 4, + buttonAbort = 1 << 5, + buttonIgnore = 1 << 6, + + flagDoNotAskAgain = 1 << 16, + + iconNone = 0, + iconInformation, + iconQuestion, + iconWarning, + iconError, + }; + + struct query_t { + const char * title = nullptr; + const char * msg = nullptr; + uint32_t buttons = 0; + uint32_t defButton = 0; + uint32_t icon = iconNone; + completion_notify::ptr reply; +#ifdef _WIN32 + HWND wndParent = NULL; +#endif + const char * msgDoNotAskAgain = nullptr; + }; + + //! Shows an interactive query presenting the user with multiple actions to choose from. + virtual void show_query(query_t const &) = 0; + + //! Modal version of show_query. Reply part of the argument can be empty; the status code will be returned. + virtual uint32_t show_query_modal(query_t const &) = 0; + +#ifdef FOOBAR2000_DESKTOP_WINDOWS + // Minimalist MessageBox() reimplementation wrapper + int messageBox(HWND, const char*, const char*, UINT); +#endif + + //! Old method wrapper + void show_query( const char * title, const char * msg, unsigned buttons, completion_notify::ptr reply); +}; +#endif // FOOBAR2000_TARGET_VERSION >= 80 diff --git a/foobar2000/SDK/preferences_page.cpp b/foobar2000/SDK/preferences_page.cpp new file mode 100644 index 0000000..01da1f7 --- /dev/null +++ b/foobar2000/SDK/preferences_page.cpp @@ -0,0 +1,10 @@ +#include "foobar2000.h" + +void preferences_page::get_help_url_helper(pfc::string_base & out, const char * category, const GUID & id, const char * name) { + out.reset(); + out << "http://help.foobar2000.org/" << core_version_info::g_get_version_string() << "/" << category << "/" << pfc::print_guid(id) << "/" << name; +} +bool preferences_page::get_help_url(pfc::string_base & p_out) { + get_help_url_helper(p_out,"preferences",get_guid(), get_name()); + return true; +} diff --git a/foobar2000/SDK/preferences_page.h b/foobar2000/SDK/preferences_page.h new file mode 100644 index 0000000..6059cc4 --- /dev/null +++ b/foobar2000/SDK/preferences_page.h @@ -0,0 +1,150 @@ +#pragma once + +//! Implementing this service will generate a page in preferences dialog. Use preferences_page_factory_t template to register. \n +//! In 1.0 and newer you should always derive from preferences_page_v3 rather than from preferences_page directly. +class NOVTABLE preferences_page : public service_base { +public: + //! Obsolete. + virtual HWND create(HWND p_parent) = 0; + //! Retrieves name of the prefernces page to be displayed in preferences tree (static string). + virtual const char * get_name() = 0; + //! Retrieves GUID of the page. + virtual GUID get_guid() = 0; + //! Retrieves GUID of parent page/branch of this page. See preferences_page::guid_* constants for list of standard parent GUIDs. Can also be a GUID of another page or a branch (see: preferences_branch). + virtual GUID get_parent_guid() = 0; + //! Obsolete. + virtual bool reset_query() = 0; + //! Obsolete. + virtual void reset() = 0; + //! Retrieves help URL. Without overriding it, it will redirect to foobar2000 wiki. + virtual bool get_help_url(pfc::string_base & p_out); + + static void get_help_url_helper(pfc::string_base & out, const char * category, const GUID & id, const char * name); + + static const GUID guid_root, guid_hidden, guid_tools,guid_core,guid_display,guid_playback,guid_visualisations,guid_input,guid_tag_writing,guid_media_library, guid_tagging, guid_output, guid_advanced, guid_components; + //! \since 1.5 + static const GUID guid_input_info_filter; + + FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(preferences_page); +}; + +class NOVTABLE preferences_page_v2 : public preferences_page { +public: + //! Allows custom sorting order of preferences pages. Return lower value for higher priority (lower resulting index in the list). When sorting priority of two items matches, alphabetic sorting is used. Return 0 to use default alphabetic sorting without overriding priority. + virtual double get_sort_priority() {return 0;} + + FB2K_MAKE_SERVICE_INTERFACE(preferences_page_v2,preferences_page); +}; + +template +class preferences_page_factory_t : public service_factory_single_t {}; + +//! Creates a preferences branch - an empty page that only serves as a parent for other pages and is hidden when no child pages exist. Instead of implementing this, simply use preferences_branch_factory class to declare a preferences branch with specified parameters. +class NOVTABLE preferences_branch : public service_base { +public: + //! Retrieves name of the preferences branch. + virtual const char * get_name() = 0; + //! Retrieves GUID of the preferences branch. Use this GUID as parent GUID for pages/branches nested in this branch. + virtual GUID get_guid() = 0; + //! Retrieves GUID of parent page/branch of this branch. See preferences_page::guid_* constants for list of standard parent GUIDs. Can also be a GUID of another branch or a page. + virtual GUID get_parent_guid() = 0; + + + FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(preferences_branch); +}; + +class preferences_branch_v2 : public preferences_branch { +public: + //! Allows custom sorting order of preferences pages. Return lower value for higher priority (lower resulting index in the list). When sorting priority of two items matches, alphabetic sorting is used. Return 0 to use default alphabetic sorting without overriding priority. + virtual double get_sort_priority() {return 0;} + + FB2K_MAKE_SERVICE_INTERFACE(preferences_branch_v2,preferences_branch); +}; + +class preferences_branch_impl : public preferences_branch_v2 { +public: + preferences_branch_impl(const GUID & p_guid,const GUID & p_parent,const char * p_name,double p_sort_priority = 0) : m_guid(p_guid), m_parent(p_parent), m_name(p_name), m_sort_priority(p_sort_priority) {} + const char * get_name() {return m_name;} + GUID get_guid() {return m_guid;} + GUID get_parent_guid() {return m_parent;} + double get_sort_priority() {return m_sort_priority;} +private: + const GUID m_guid,m_parent; + const pfc::string8 m_name; + const double m_sort_priority; +}; + +typedef service_factory_single_t _preferences_branch_factory; + +//! Instantiating this class declares a preferences branch with specified parameters.\n +//! Usage: static preferences_branch_factory g_mybranch(mybranchguid,parentbranchguid,"name of my preferences branch goes here"); +class preferences_branch_factory : public _preferences_branch_factory { +public: + preferences_branch_factory(const GUID & p_guid,const GUID & p_parent,const char * p_name,double p_sort_priority = 0) : _preferences_branch_factory(p_guid,p_parent,p_name,p_sort_priority) {} +}; + + + + +class preferences_state { +public: + enum { + changed = 1, + needs_restart = 2, + needs_restart_playback = 4, + resettable = 8, + + //! \since 1.1 + //! Indicates that the dialog is currently busy and cannot be applied or cancelled. Do not use without a good reason! \n + //! This flag was introduced in 1.1. It will not be respected in earlier foobar2000 versions. It is recommended not to use this flag unless you are absolutely sure that you need it and take appropriate precautions. \n + //! Note that this has no power to entirely prevent your preferences page from being destroyed/cancelled as a result of app shutdown if the user dismisses the warnings, but you won't be getting an "apply" call while this is set. + busy = 16, + + //! \since 1.4.1 + needs_rescan_library = 32, + }; +}; + +class preferences_page_callback : public service_base { + FB2K_MAKE_SERVICE_INTERFACE(preferences_page_callback, service_base) +public: + virtual void on_state_changed() = 0; +}; + +//! \since 1.0 +//! Implements a preferences page instance. \n +//! Instantiated through preferences_page_v3::instantiate(). \n +//! Note that the window will be destroyed by the caller before the last reference to the preferences_page_instance is released. \n +//! WARNING: misguided use of modal dialogs - or ANY windows APIs that might spawn such dialogs - may result in conditions when the owner dialog (along with your page) is destroyed somewhere inside your message handler, also releasing references to your object. \n +//! It is recommended to use window_service_impl_t<> from ATLHelpers to instantiate preferences_page_instances, or preferences_page_impl<> framework for your preferences_page code to cleanly workaround such cases. +class preferences_page_instance : public service_base { + FB2K_MAKE_SERVICE_INTERFACE(preferences_page_instance, service_base) +public: + //! @returns a combination of preferences_state constants. + virtual t_uint32 get_state() = 0; + //! @returns the window handle. + virtual HWND get_wnd() = 0; + //! Applies preferences changes. + virtual void apply() = 0; + //! Resets this page's content to the default values. Does not apply any changes - lets user preview the changes before hitting "apply". + virtual void reset() = 0; +}; + +//! \since 1.0 +//! Implements a preferences page. +class preferences_page_v3 : public preferences_page_v2 { + FB2K_MAKE_SERVICE_INTERFACE(preferences_page_v3, preferences_page_v2) +public: + virtual preferences_page_instance::ptr instantiate(HWND parent, preferences_page_callback::ptr callback) = 0; +private: + HWND create(HWND) {throw pfc::exception_not_implemented();} //stub + bool reset_query() {return false;} //stub - the new apply-friendly reset should be used instead. + void reset() {} //stub +}; + +//! \since 1.5 +class NOVTABLE preferences_page_v4 : public preferences_page_v3 { + FB2K_MAKE_SERVICE_INTERFACE(preferences_page_v4, preferences_page_v3); +public: + virtual bool is_hidden() = 0; +}; diff --git a/foobar2000/SDK/progress_meter.h b/foobar2000/SDK/progress_meter.h new file mode 100644 index 0000000..0d120da --- /dev/null +++ b/foobar2000/SDK/progress_meter.h @@ -0,0 +1,20 @@ +//! Interface for setting current operation progress state to be visible on Windows 7 taskbar. Use progress_meter::get()->acquire() to instantiate. +class NOVTABLE progress_meter_instance : public service_base { + FB2K_MAKE_SERVICE_INTERFACE(progress_meter_instance, service_base); +public: + //! Sets the current progress state. + //! @param value Progress state, in 0..1 range. + virtual void set_progress(float value) = 0; + //! Toggles paused state. + virtual void set_pause(bool isPaused) = 0; + + static bool serviceRequiresMainThreadDestructor() { return true; } +}; + +//! Entrypoint interface for instantiating progress_meter_instance objects. +class NOVTABLE progress_meter : public service_base { + FB2K_MAKE_SERVICE_COREAPI(progress_meter); +public: + //! Creates a progress_meter_instance object. + virtual progress_meter_instance::ptr acquire() = 0; +}; diff --git a/foobar2000/SDK/replaygain.cpp b/foobar2000/SDK/replaygain.cpp new file mode 100644 index 0000000..bcaf070 --- /dev/null +++ b/foobar2000/SDK/replaygain.cpp @@ -0,0 +1,226 @@ +#include "foobar2000.h" + +void t_replaygain_config::reset() +{ + m_source_mode = source_mode_none; + m_processing_mode = processing_mode_none; + m_preamp_without_rg = 0; + m_preamp_with_rg = 0; +} + +audio_sample t_replaygain_config::query_scale(const file_info & info) const +{ + return query_scale(info.get_replaygain()); +} + +audio_sample t_replaygain_config::query_scale(const replaygain_info & info) const { + const audio_sample peak_margin = 1.0;//used to be 0.999 but it must not trigger on lossless + + audio_sample peak = peak_margin; + audio_sample gain = 0; + + bool have_rg_gain = false, have_rg_peak = false; + + if (m_source_mode == source_mode_track || m_source_mode == source_mode_album) + { + if (m_source_mode == source_mode_track) + { + if (info.is_track_gain_present()) {gain = info.m_track_gain; have_rg_gain = true; } + else if (info.is_album_gain_present()) {gain = info.m_album_gain; have_rg_gain = true; } + if (info.is_track_peak_present()) {peak = info.m_track_peak; have_rg_peak = true; } + else if (info.is_album_peak_present()) {peak = info.m_album_peak; have_rg_peak = true; } + } + else + { + if (info.is_album_gain_present()) {gain = info.m_album_gain; have_rg_gain = true; } + else if (info.is_track_gain_present()) {gain = info.m_track_gain; have_rg_gain = true; } + if (info.is_album_peak_present()) {peak = info.m_album_peak; have_rg_peak = true; } + else if (info.is_track_peak_present()) {peak = info.m_track_peak; have_rg_peak = true; } + } + } + + gain += have_rg_gain ? m_preamp_with_rg : m_preamp_without_rg; + + audio_sample scale = 1.0; + + if (m_processing_mode == processing_mode_gain || m_processing_mode == processing_mode_gain_and_peak) + { + scale *= audio_math::gain_to_scale(gain); + } + + if ((m_processing_mode == processing_mode_peak || m_processing_mode == processing_mode_gain_and_peak) && have_rg_peak) + { + if (scale * peak > peak_margin) + scale = (audio_sample)(peak_margin / peak); + } + + return scale; +} + +audio_sample t_replaygain_config::query_scale(const metadb_handle_ptr & p_object) const +{ + return query_scale(p_object->get_async_info_ref()->info()); +} + +audio_sample replaygain_manager::core_settings_query_scale(const file_info & p_info) +{ + t_replaygain_config temp; + get_core_settings(temp); + return temp.query_scale(p_info); +} + +audio_sample replaygain_manager::core_settings_query_scale(const metadb_handle_ptr & info) +{ + t_replaygain_config temp; + get_core_settings(temp); + return temp.query_scale(info); +} + +//enum t_source_mode {source_mode_none,source_mode_track,source_mode_album}; +//enum t_processing_mode {processing_mode_none,processing_mode_gain,processing_mode_gain_and_peak,processing_mode_peak}; +namespace { +class format_dbdelta +{ +public: + format_dbdelta(double p_val); + operator const char*() const {return m_buffer;} +private: + pfc::string_fixed_t<128> m_buffer; +}; +static const char * querysign(int val) { + return val<0 ? "-" : val>0 ? "+" : "\xc2\xb1"; +} + +format_dbdelta::format_dbdelta(double p_val) { + int val = (int)(p_val * 10); + m_buffer << querysign(val) << (abs(val)/10) << "." << (abs(val)%10) << "dB"; +} +} +void t_replaygain_config::format_name(pfc::string_base & p_out) const +{ + switch(m_processing_mode) + { + case processing_mode_none: + p_out = "None."; + break; + case processing_mode_gain: + switch(m_source_mode) + { + case source_mode_none: + if (m_preamp_without_rg == 0) p_out = "None."; + else p_out = PFC_string_formatter() << "Preamp : " << format_dbdelta(m_preamp_without_rg); + break; + case source_mode_track: + { + pfc::string_formatter fmt; + fmt << "Apply track gain"; + if (m_preamp_without_rg != 0 || m_preamp_with_rg != 0) fmt << ", with preamp"; + fmt << "."; + p_out = fmt; + } + break; + case source_mode_album: + { + pfc::string_formatter fmt; + fmt << "Apply album gain"; + if (m_preamp_without_rg != 0 || m_preamp_with_rg != 0) fmt << ", with preamp"; + fmt << "."; + p_out = fmt; + } + break; + }; + break; + case processing_mode_gain_and_peak: + switch(m_source_mode) + { + case source_mode_none: + if (m_preamp_without_rg >= 0) p_out = "None."; + else p_out = PFC_string_formatter() << "Preamp : " << format_dbdelta(m_preamp_without_rg); + break; + case source_mode_track: + { + pfc::string_formatter fmt; + fmt << "Apply track gain"; + if (m_preamp_without_rg != 0 || m_preamp_with_rg != 0) fmt << ", with preamp"; + fmt << ", prevent clipping according to track peak."; + p_out = fmt; + } + break; + case source_mode_album: + { + pfc::string_formatter fmt; + fmt << "Apply album gain"; + if (m_preamp_without_rg != 0 || m_preamp_with_rg != 0) fmt << ", with preamp"; + fmt << ", prevent clipping according to album peak."; + p_out = fmt; + } + break; + }; + break; + case processing_mode_peak: + switch(m_source_mode) + { + case source_mode_none: + p_out = "None."; + break; + case source_mode_track: + p_out = "Prevent clipping according to track peak."; + break; + case source_mode_album: + p_out = "Prevent clipping according to album peak."; + break; + }; + break; + } +} + +bool t_replaygain_config::is_active() const +{ + switch(m_processing_mode) + { + case processing_mode_none: + return false; + case processing_mode_gain: + switch(m_source_mode) + { + case source_mode_none: + return m_preamp_without_rg != 0; + case source_mode_track: + return true; + case source_mode_album: + return true; + }; + return false; + case processing_mode_gain_and_peak: + switch(m_source_mode) + { + case source_mode_none: + return m_preamp_without_rg < 0; + case source_mode_track: + return true; + case source_mode_album: + return true; + }; + return false; + case processing_mode_peak: + switch(m_source_mode) + { + case source_mode_none: + return false; + case source_mode_track: + return true; + case source_mode_album: + return true; + }; + return false; + default: + return false; + } +} + + +replaygain_scanner::ptr replaygain_scanner_entry::instantiate( uint32_t flags ) { + replaygain_scanner_entry_v2::ptr p2; + if ( p2 &= this ) return p2->instantiate( flags ); + else return instantiate(); +} \ No newline at end of file diff --git a/foobar2000/SDK/replaygain.h b/foobar2000/SDK/replaygain.h new file mode 100644 index 0000000..cdd47ff --- /dev/null +++ b/foobar2000/SDK/replaygain.h @@ -0,0 +1,89 @@ +//! Structure storing ReplayGain configuration: album/track source data modes, gain/peak processing modes and preamp values. +struct t_replaygain_config +{ + enum /*t_source_mode*/ { + source_mode_none, + source_mode_track, + source_mode_album, + // New in 1.3.8 + // SPECIAL MODE valid only for playback settings; if set, track gain will be used for random & shuffle-tracks modes, album for shuffle albums & ordered playback. + source_mode_byPlaybackOrder + }; + enum /*t_processing_mode*/ {processing_mode_none,processing_mode_gain,processing_mode_gain_and_peak,processing_mode_peak}; + typedef t_uint32 t_source_mode; typedef t_uint32 t_processing_mode; + + t_replaygain_config() {reset();} + t_replaygain_config(t_source_mode p_source_mode,t_processing_mode p_processing_mode,float p_preamp_without_rg, float p_preamp_with_rg) + : m_source_mode(p_source_mode), m_processing_mode(p_processing_mode), m_preamp_without_rg(p_preamp_without_rg), m_preamp_with_rg(p_preamp_with_rg) {} + + + t_source_mode m_source_mode; + t_processing_mode m_processing_mode; + float m_preamp_without_rg, m_preamp_with_rg;//preamp values in dB + + void reset(); + audio_sample query_scale(const file_info & info) const; + audio_sample query_scale(const metadb_handle_ptr & info) const; + audio_sample query_scale(const replaygain_info & info) const; + + void format_name(pfc::string_base & p_out) const; + bool is_active() const; + + static bool equals(const t_replaygain_config & v1, const t_replaygain_config & v2) { + return v1.m_source_mode == v2.m_source_mode && v1.m_processing_mode == v2.m_processing_mode && v1.m_preamp_without_rg == v2.m_preamp_without_rg && v1.m_preamp_with_rg == v2.m_preamp_with_rg; + } + bool operator==(const t_replaygain_config & other) const {return equals(*this, other);} + bool operator!=(const t_replaygain_config & other) const {return !equals(*this, other);} +}; + +FB2K_STREAM_READER_OVERLOAD(t_replaygain_config) { + return stream >> value.m_source_mode >> value.m_processing_mode >> value.m_preamp_with_rg >> value.m_preamp_without_rg; +} +FB2K_STREAM_WRITER_OVERLOAD(t_replaygain_config) { + return stream << value.m_source_mode << value.m_processing_mode << value.m_preamp_with_rg << value.m_preamp_without_rg; +} + +//! Core service providing methods to retrieve/alter playback ReplayGain settings, as well as use ReplayGain configuration dialog. +class NOVTABLE replaygain_manager : public service_base { +public: + //! Retrieves playback ReplayGain settings. + virtual void get_core_settings(t_replaygain_config & p_out) = 0; + + //! Creates embedded version of ReplayGain settings dialog. Note that embedded dialog sends WM_COMMAND with id/BN_CLICKED to parent window when user makes changes to settings. + virtual HWND configure_embedded(const t_replaygain_config & p_initdata,HWND p_parent,unsigned p_id,bool p_from_modal) = 0; + //! Retrieves settings from embedded version of ReplayGain settings dialog. + virtual void configure_embedded_retrieve(HWND wnd,t_replaygain_config & p_data) = 0; + + //! Shows popup/modal version of ReplayGain settings dialog. Returns true when user changed the settings, false when user cancelled the operation. Title parameter can be null to use default one. + virtual bool configure_popup(t_replaygain_config & p_data,HWND p_parent,const char * p_title) = 0; + + //! Alters playback ReplayGain settings. + virtual void set_core_settings(const t_replaygain_config & p_config) = 0; + + //! New in 1.0 + virtual void configure_embedded_set(HWND wnd, t_replaygain_config const & p_data) = 0; + //! New in 1.0 + virtual void get_core_defaults(t_replaygain_config & out) = 0; + + //! Helper; queries scale value for specified item according to core playback settings. + audio_sample core_settings_query_scale(const file_info & p_info); + //! Helper; queries scale value for specified item according to core playback settings. + audio_sample core_settings_query_scale(const metadb_handle_ptr & info); + + FB2K_MAKE_SERVICE_COREAPI(replaygain_manager); +}; + +//! \since 1.4 +class NOVTABLE replaygain_core_settings_notify { +public: + virtual void on_changed( t_replaygain_config const & cfg ) = 0; +}; + +//! \since 1.4 +//! Adds new method for getting notified about core RG settings changing +class NOVTABLE replaygain_manager_v2 : public replaygain_manager { + FB2K_MAKE_SERVICE_COREAPI_EXTENSION( replaygain_manager_v2, replaygain_manager ); +public: + virtual void add_notify(replaygain_core_settings_notify *) = 0; + virtual void remove_notify(replaygain_core_settings_notify *) = 0; +}; \ No newline at end of file diff --git a/foobar2000/SDK/replaygain_info.cpp b/foobar2000/SDK/replaygain_info.cpp new file mode 100644 index 0000000..663d71e --- /dev/null +++ b/foobar2000/SDK/replaygain_info.cpp @@ -0,0 +1,161 @@ +#include "foobar2000.h" + +#ifdef _MSC_VER +#define RG_FPU() fpu_control_roundnearest bah; +#else +#define RG_FPU() +#endif + +bool replaygain_info::g_format_gain(float p_value,char p_buffer[text_buffer_size]) +{ + RG_FPU(); + if (p_value == gain_invalid) + { + p_buffer[0] = 0; + return false; + } + else + { + pfc::float_to_string(p_buffer,text_buffer_size - 4,p_value,2,true); + strcat(p_buffer," dB"); + return true; + } +} + +bool replaygain_info::g_format_peak_db(float p_value, char p_buffer[text_buffer_size]) { + const float lo = 1.0 / (float)(1 << 24); + if ( p_value == peak_invalid || p_value < lo ) return false; + return g_format_gain((float)audio_math::scale_to_gain(p_value), p_buffer); + +} + +bool replaygain_info::g_format_peak(float p_value,char p_buffer[text_buffer_size]) +{ + RG_FPU(); + if (p_value == peak_invalid) + { + p_buffer[0] = 0; + return false; + } + else + { + pfc::float_to_string(p_buffer,text_buffer_size,p_value,6,false); + return true; + } +} + +void replaygain_info::reset() +{ + m_album_gain = gain_invalid; + m_track_gain = gain_invalid; + m_album_peak = peak_invalid; + m_track_peak = peak_invalid; +} + +#define meta_album_gain "replaygain_album_gain" +#define meta_album_peak "replaygain_album_peak" +#define meta_track_gain "replaygain_track_gain" +#define meta_track_peak "replaygain_track_peak" + +bool replaygain_info::g_is_meta_replaygain(const char * p_name,t_size p_name_len) +{ + return + stricmp_utf8_ex(p_name,p_name_len,meta_album_gain,~0) == 0 || + stricmp_utf8_ex(p_name,p_name_len,meta_album_peak,~0) == 0 || + stricmp_utf8_ex(p_name,p_name_len,meta_track_gain,~0) == 0 || + stricmp_utf8_ex(p_name,p_name_len,meta_track_peak,~0) == 0; +} + +bool replaygain_info::set_from_meta_ex(const char * p_name,t_size p_name_len,const char * p_value,t_size p_value_len) +{ + RG_FPU(); + if (stricmp_utf8_ex(p_name,p_name_len,meta_album_gain,~0) == 0) + { + m_album_gain = (float)pfc::string_to_float(p_value,p_value_len); + return true; + } + else if (stricmp_utf8_ex(p_name,p_name_len,meta_album_peak,~0) == 0) + { + m_album_peak = (float)pfc::string_to_float(p_value,p_value_len); + if (m_album_peak < 0) m_album_peak = 0; + return true; + } + else if (stricmp_utf8_ex(p_name,p_name_len,meta_track_gain,~0) == 0) + { + m_track_gain = (float)pfc::string_to_float(p_value,p_value_len); + return true; + } + else if (stricmp_utf8_ex(p_name,p_name_len,meta_track_peak,~0) == 0) + { + m_track_peak = (float)pfc::string_to_float(p_value,p_value_len); + if (m_track_peak < 0) m_track_peak = 0; + return true; + } + else return false; +} + + +t_size replaygain_info::get_value_count() +{ + t_size ret = 0; + if (is_album_gain_present()) ret++; + if (is_album_peak_present()) ret++; + if (is_track_gain_present()) ret++; + if (is_track_peak_present()) ret++; + return ret; +} + +float replaygain_info::anyGain(bool bPreferAlbum) const { + if ( bPreferAlbum ) { + if ( this->is_album_gain_present() ) return this->m_album_gain; + return this->m_track_gain; + } else { + if ( this->is_track_gain_present() ) return this->m_track_gain; + return this->m_album_gain; + } +} + +float replaygain_info::g_parse_gain_text(const char * p_text, t_size p_text_len) { + RG_FPU(); + if (p_text != 0 && p_text_len > 0 && *p_text != 0) + return (float)pfc::string_to_float(p_text, p_text_len); + else + return gain_invalid; +} + +void replaygain_info::set_album_gain_text(const char * p_text,t_size p_text_len) { + m_album_gain = g_parse_gain_text(p_text, p_text_len); +} + +void replaygain_info::set_track_gain_text(const char * p_text,t_size p_text_len) +{ + m_track_gain = g_parse_gain_text(p_text, p_text_len); +} + +void replaygain_info::set_album_peak_text(const char * p_text,t_size p_text_len) +{ + RG_FPU(); + if (p_text != 0 && p_text_len > 0 && *p_text != 0) + m_album_peak = (float)pfc::string_to_float(p_text,p_text_len); + else + remove_album_peak(); +} + +void replaygain_info::set_track_peak_text(const char * p_text,t_size p_text_len) +{ + RG_FPU(); + if (p_text != 0 && p_text_len > 0 && *p_text != 0) + m_track_peak = (float)pfc::string_to_float(p_text,p_text_len); + else + remove_track_peak(); +} + +replaygain_info replaygain_info::g_merge(replaygain_info r1,replaygain_info r2) +{ + replaygain_info ret = r1; + if (!ret.is_album_gain_present()) ret.m_album_gain = r2.m_album_gain; + if (!ret.is_album_peak_present()) ret.m_album_peak = r2.m_album_peak; + if (!ret.is_track_gain_present()) ret.m_track_gain = r2.m_track_gain; + if (!ret.is_track_peak_present()) ret.m_track_peak = r2.m_track_peak; + return ret; +} diff --git a/foobar2000/SDK/replaygain_scanner.h b/foobar2000/SDK/replaygain_scanner.h new file mode 100644 index 0000000..03cceda --- /dev/null +++ b/foobar2000/SDK/replaygain_scanner.h @@ -0,0 +1,112 @@ +#pragma once + + + +//! Container of ReplayGain scan results from one or more tracks. +class replaygain_result : public service_base { + FB2K_MAKE_SERVICE_INTERFACE(replaygain_result, service_base); +public: + //! Retrieves the gain value, in dB. + virtual float get_gain() = 0; + //! Retrieves the peak value, normalized to 0..1 range (audio_sample value). + virtual float get_peak() = 0; + //! Merges ReplayGain scan results from different tracks. Merge results from all tracks in an album to get album gain/peak values. \n + //! This function returns a newly created replaygain_result object. Existing replaygain_result objects remain unaltered. + virtual replaygain_result::ptr merge(replaygain_result::ptr other) = 0; + + replaygain_info make_track_info() { + replaygain_info ret = replaygain_info_invalid; ret.m_track_gain = this->get_gain(); ret.m_track_peak = this->get_peak(); return ret; + } +}; + +//! Instance of a ReplayGain scanner. \n +//! Use replaygain_scanner_entry::instantiate() to create a replaygain_scanner object; see replaygain_scanner_entry for more info. \n +//! Typical use: call process_chunk() with each chunk read from your track, call finalize() to obtain results for this track and reset replaygain_scanner's state for scanning another track; to obtain album gain/peak values, merge results (replaygain_result::merge) from all tracks. \n +class replaygain_scanner : public service_base { + FB2K_MAKE_SERVICE_INTERFACE(replaygain_scanner, service_base); +public: + //! Processes a PCM chunk. \n + //! May throw exception_io_data if the chunk contains something that can't be processed properly. + virtual void process_chunk(const audio_chunk & chunk) = 0; + //! Finalizes the scanning process; resets scanner's internal state and returns results for the track we've just scanned. \n + //! After calling finalize(), scanner's state becomes the same as after instantiation; you can continue with processing another track without creating a new scanner object. + virtual replaygain_result::ptr finalize() = 0; +}; + + +//! Entrypoint class for instantiating replaygain_scanner objects. \n +//! This service is OPTIONAL; it's available from foobar2000 0.9.5.3 up but only if the ReplayGain Scanner component is installed. \n +//! It is recommended that you use replaygain_scanner like this: \n +//! replaygain_scanner_entry::ptr theAPI; \n +//! if (replaygain_scanner_entry::tryGet(theAPI)) { \n +//! myInstance = theAPI->instantiate(); \n +//! } else { \n +//! no foo_rgscan installed - complain/fail/etc \n +//! } \n +//! Note that replaygain_scanner_entry::get() is provided for convenience - it WILL crash with no foo_rgscan present. Use it only after prior checks. +class replaygain_scanner_entry : public service_base { + FB2K_MAKE_SERVICE_COREAPI(replaygain_scanner_entry); +public: + //! Instantiates a replaygain_scanner object. + virtual replaygain_scanner::ptr instantiate() = 0; + + //! Helper; uses replaygain_scanner_entry_v2 if available; see replaygain_scanner_entry_v2. + replaygain_scanner::ptr instantiate( uint32_t flags ); +}; + +//! \since 1.4 +//! This service is OPTIONAL; it's available from foobar2000 v1.4 up but only if the ReplayGain Scanner component is installed. \n +//! Use tryGet() to instantiate - get() only after prior verification of availability. +class replaygain_scanner_entry_v2 : public replaygain_scanner_entry { + FB2K_MAKE_SERVICE_COREAPI_EXTENSION(replaygain_scanner_entry_v2, replaygain_scanner_entry) +public: + enum { + flagScanPeak = 1 << 0, + flagScanGain = 1 << 1, + }; + + //! Extended instantiation method. \n + //! Allows you to declare which parts of the scanning process are relevant for you + //! so irrelevant parts of the processing can be skipped. + //! For an example, if you don't care about the peak, specify only flagScanGain - + //! as peak scan while normally cheap may be very expensive with extreme oversampling specified by user. + virtual replaygain_scanner::ptr instantiate(uint32_t flags) = 0; +}; + +//! Internal service introduced in 1.5. No guarantees about compatibility. May be changed or removed at any time. +class replaygain_scanner_config : public service_base { + FB2K_MAKE_SERVICE_COREAPI(replaygain_scanner_config); +public: + virtual void get_album_pattern( pfc::string_base & out ) = 0; + virtual uint64_t get_read_size_bytes() = 0; +}; + +#ifdef FOOBAR2000_DESKTOP +//! \since 1.4 +//! A class for applying gain to compressed audio packets such as MP3 or AAC. \n +//! Implemented by foo_rgscan for common formats. May be extended to allow foo_rgscan to manipulate more different codecs. +class replaygain_alter_stream : public service_base { + FB2K_MAKE_SERVICE_INTERFACE(replaygain_alter_stream, service_base) +public: + //! @returns The amount to which all adjustments are quantized for this format. Essential for caller to be able to correctly prevent clipping. + virtual float get_adjustment_step( ) = 0; + //! Sets the adjustment in decibels. Note that the actual applied adjustment will be quantized with nearest-rounding to a multiple of get_adjustment_step() value. + virtual void set_adjustment( float deltaDB ) = 0; + //! Passes the first frame playload. This serves as a hint and may be safely ignored for most formats. However in some cases - MP3 vs MP2 in particular - you do knot know what exact format you're dealing with until you've examined the first frame. \n + //! If you're calling this service, always feed the first frame before calling get_adjustment_step(). + virtual void on_first_frame( const void * frame, size_t bytes ) = 0; + //! Applies gain to the frame. \n + //! May throw exception_io_data if the frame payload is corrupted and cannot be altered. The user will be informed about bad frame statistics, however the operation will continue until EOF. + virtual void alter_frame( void * frame, size_t bytes ) = 0; +}; + +//! \since 1.4 +//! Entrypoint class for instantiating replaygain_alter_stream. Walk with service_enum_t<> to find one that supports specific format. +class replaygain_alter_stream_entry : public service_base { + FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(replaygain_alter_stream_entry); +public: + //! @returns Newly created replaygain_alter_stream object. Null if this format is not supported by this implementation. + //! Arguments as per packet_decoder::g_open(). + virtual replaygain_alter_stream::ptr open(const GUID & p_owner, size_t p_param1, const void * p_param2, size_t p_param2size ) = 0; +}; +#endif diff --git a/foobar2000/SDK/resampler.h b/foobar2000/SDK/resampler.h new file mode 100644 index 0000000..161642a --- /dev/null +++ b/foobar2000/SDK/resampler.h @@ -0,0 +1,75 @@ +#pragma once + +#ifdef FOOBAR2000_HAVE_DSP + +//! A resampler DSP entry. \n +//! It is STRICTLY REQUIRED that the output is: \n +//! (A) In the requested sample rate (specified when creating the preset), \n +//! (B) .. or untouched if the conversion cannot be performed / there's no conversion to be performed (input rate == output rate). \n +//! Not every resampler supports every possible sample rate conversion ratio. Bundled PPHS resampler (always installed since foobar2000 v1.6) does accept every possible conversion. +class NOVTABLE resampler_entry : public dsp_entry +{ +public: + virtual bool is_conversion_supported(unsigned p_srate_from,unsigned p_srate_to) = 0; + virtual bool create_preset(dsp_preset & p_out,unsigned p_target_srate,float p_qualityscale) = 0;//p_qualityscale is 0...1 + virtual float get_priority() = 0;//value is 0...1, where high-quality (SSRC etc) has 1 + + static bool g_get_interface(service_ptr_t & p_out,unsigned p_srate_from,unsigned p_srate_to); + static bool g_create(service_ptr_t & p_out,unsigned p_srate_from,unsigned p_srate_to,float p_qualityscale); + static bool g_create_preset(dsp_preset & p_out,unsigned p_srate_from,unsigned p_srate_to,float p_qualityscale); + + FB2K_MAKE_SERVICE_INTERFACE(resampler_entry,dsp_entry); +}; + +template +class resampler_entry_impl_t : public dsp_entry_impl_t +{ +public: + bool is_conversion_supported(unsigned p_srate_from,unsigned p_srate_to) {return T::g_is_conversion_supported(p_srate_from,p_srate_to);} + bool create_preset(dsp_preset & p_out,unsigned p_target_srate,float p_qualityscale) {return T::g_create_preset(p_out,p_target_srate,p_qualityscale);} + float get_priority() {return T::g_get_priority();} +}; + +template +class resampler_factory_t : public service_factory_single_t > {}; + +#ifdef FOOBAR2000_DESKTOP + +//! \since 1.4 +//! Supersedes resampler_entry::get_priority, allows the user to specify which resampler should be preferred when a component asks for one. +class resampler_manager : public service_base { + FB2K_MAKE_SERVICE_COREAPI(resampler_manager); +public: + //! Locate the preferred resampler that is capable of performing conversion from the source to destination rate. \n + //! If input sample rate is not known in advance or may change in mid-conversion, it's recommended to use make_chain() instead to full obey user settings. + virtual resampler_entry::ptr get_resampler( unsigned rateFrom, unsigned rateTo ) = 0; + + //! Compatibility wrapper, see resampler_manager_v2::make_chain(). + void make_chain_(dsp_chain_config& outChain, unsigned rateFrom, unsigned rateTo, float qualityScale); +}; + +class dsp_chain_config; + +//! \since 1.6 +class resampler_manager_v2 : public resampler_manager { + FB2K_MAKE_SERVICE_COREAPI_EXTENSION(resampler_manager_v2, resampler_manager); +public: + //! Make a chain of resamplers. \n + //! Pass the intended sample rates for rateFrom & rateTo. Pass 0 rateFrom if it is not known in advance or may change in mid-conversion. + //! With rateFrom known in advance, the chain should hold only one DSP. \n + //! If rateFrom is not known in advance, multiple DSPs may be returned - a preferred one that accepts common conversion ratios but not all of them, and a fallback one that handles every scenario if the first one failed. \n + //! For an example, by default, SSRC (higher quality) is used, but PPHS (more compatible) is added to clean up odd sample rates that SSRC failed to process. \n + //! Note that it is required that resamplers pass untouched data if no resampling is performed, so additional DSPs have no effect on the audio coming thru, as just one resampler will actually do anything. + virtual void make_chain( dsp_chain_config & outChain, unsigned rateFrom, unsigned rateTo, float qualityScale) = 0; +}; + +//! \since 1.6.1 +class resampler_manager_v3 : public resampler_manager_v2 { + FB2K_MAKE_SERVICE_COREAPI_EXTENSION(resampler_manager_v3, resampler_manager_v2); +public: + //! Extended make_chain that also manipulates channel layout. + virtual void make_chain_v3(dsp_chain_config& outChain, unsigned rateFromm, unsigned rateTo, float qualityScale, unsigned chmask) = 0; +}; +#endif + +#endif // FOOBAR2000_HAVE_DSP diff --git a/foobar2000/SDK/search_tools.cpp b/foobar2000/SDK/search_tools.cpp new file mode 100644 index 0000000..d5fc210 --- /dev/null +++ b/foobar2000/SDK/search_tools.cpp @@ -0,0 +1,7 @@ +#include "foobar2000.h" + +void search_filter_manager::show_manual() { + pfc::string8 temp; + get_manual(temp); + popup_message::g_show(temp,"Search Expression Reference"); +} diff --git a/foobar2000/SDK/search_tools.h b/foobar2000/SDK/search_tools.h new file mode 100644 index 0000000..e3bb30b --- /dev/null +++ b/foobar2000/SDK/search_tools.h @@ -0,0 +1,71 @@ +//! Instance of a search filter object. New in 0.9.5. \n +//! This object contains a preprocessed search query; used to perform filtering similar to Media Library Search or Album List's "filter" box. \n +//! Use search_filter_manager API to instantiate search_filter objects. +class search_filter : public service_base { +public: +protected: + //! For backwards compatibility with older (0.9.5 alpha) revisions of this API. Do not call. + virtual bool test_locked(const metadb_handle_ptr & p_item,const file_info * p_info) = 0; +public: + + //! Use this to run this filter on a group of items. + //! @param data Items to test. + //! @param out Pointer to a buffer (size at least equal to number of items in the source list) receiving the results. + virtual void test_multi(metadb_handle_list_cref data, bool * out) = 0; + + FB2K_MAKE_SERVICE_INTERFACE(search_filter,service_base); +}; + +//! New in 0.9.5.3. You can obtain a search_filter_v2 pointer by using service_query() on a search_filter pointer, or from search_filter_manager_v2::create_ex(). +class search_filter_v2 : public search_filter { +public: + virtual bool get_sort_pattern(titleformat_object::ptr & out, int & direction) = 0; + + //! Abortable version of test_multi(). If the abort_callback object becomes signaled while the operation is being performed, contents of the output buffer are undefined and the operation will fail with exception_aborted. + virtual void test_multi_ex(metadb_handle_list_cref data, bool * out, abort_callback & abort) = 0; + + //! Helper; removes non-matching items from the list. + void test_multi_here(metadb_handle_list & ref, abort_callback & abort); + + FB2K_MAKE_SERVICE_INTERFACE(search_filter_v2, search_filter) +}; + +//! New in 0.9.5.4. You can obtain a search_filter_v2 pointer by using service_query() on a search_filter/search_filter_v2 pointer. +class search_filter_v3 : public search_filter_v2 { +public: + //! Returns whether the sort pattern returned by get_sort_pattern() was set by the user explicitly using "SORT BY" syntax or whether it was determined implicitly from some other part of the query. It's recommended to use this to determine whether to create a force-sorted autoplaylist or not. + virtual bool is_sort_explicit() = 0; + + FB2K_MAKE_SERVICE_INTERFACE(search_filter_v3, search_filter_v2) +}; + +//! Entrypoint class to instantiate search_filter objects. New in 0.9.5. +class search_filter_manager : public service_base { +public: + //! Creates a search_filter object. Throws an exception on failure (such as an error in the query). It's recommended that you relay the exception message to the user if this function fails. + virtual search_filter::ptr create(const char * p_query) = 0; + + //! Retrieves the search expression manual string. See also: show_manual(). + virtual void get_manual(pfc::string_base & p_out) = 0; + + void show_manual(); + + FB2K_MAKE_SERVICE_COREAPI(search_filter_manager); +}; + +//! New in 0.9.5.3. +class search_filter_manager_v2 : public search_filter_manager { +public: + enum { + KFlagAllowSort = 1 << 0, + KFlagSuppressNotify = 1 << 1, + }; + //! Creates a search_filter object. Throws an exception on failure (such as an error in the query). It's recommended that you relay the exception message to the user if this function fails. + //! @param changeNotify A completion_notify callback object that will get called each time the query's behavior changes as a result of some external event (such as system time change). The caller must refresh query results each time this callback is triggered. The status parameter of its on_completion() parameter is unused and always set to zero. + virtual search_filter_v2::ptr create_ex(const char * query, completion_notify::ptr changeNotify, t_uint32 flags) = 0; + + //! Opens the search query syntax reference document, typically an external HTML in user's default web browser. + virtual void show_manual() = 0; + + FB2K_MAKE_SERVICE_COREAPI_EXTENSION(search_filter_manager_v2, search_filter_manager); +}; diff --git a/foobar2000/SDK/service.cpp b/foobar2000/SDK/service.cpp new file mode 100644 index 0000000..845db07 --- /dev/null +++ b/foobar2000/SDK/service.cpp @@ -0,0 +1,68 @@ +#include "foobar2000.h" +#include "component.h" + +foobar2000_api * g_foobar2000_api = NULL; + +service_class_ref service_factory_base::enum_find_class(const GUID & p_guid) +{ + PFC_ASSERT(core_api::are_services_available() && g_foobar2000_api); + return g_foobar2000_api->service_enum_find_class(p_guid); +} + +bool service_factory_base::enum_create(service_ptr_t & p_out,service_class_ref p_class,t_size p_index) +{ + PFC_ASSERT(core_api::are_services_available() && g_foobar2000_api); + return g_foobar2000_api->service_enum_create(p_out,p_class,p_index); +} + +t_size service_factory_base::enum_get_count(service_class_ref p_class) +{ + PFC_ASSERT(core_api::are_services_available() && g_foobar2000_api); + return g_foobar2000_api->service_enum_get_count(p_class); +} + +service_factory_base * service_factory_base::__internal__list = NULL; + + +namespace service_impl_helper { + void release_object_delayed(service_base * ptr) { + ptr->service_add_ref(); + fb2k::inMainThread( [ptr] { + try { ptr->service_release(); } catch(...) {} + } ); + } +}; + + +void _standard_api_create_internal(service_ptr & out, const GUID & classID) { + service_class_ref c = service_factory_base::enum_find_class(classID); + switch(service_factory_base::enum_get_count(c)) { + case 0: +#if PFC_DEBUG + if ( core_api::are_services_available() ) { + FB2K_DebugLog() << "Service not found of type: " << pfc::print_guid(classID); + } +#endif + throw exception_service_not_found(); + case 1: + PFC_ASSERT_SUCCESS( service_factory_base::enum_create(out, c, 0) ); + break; + default: + throw exception_service_duplicated(); + } +} + +bool _standard_api_try_get_internal(service_ptr & out, const GUID & classID) { + service_class_ref c = service_factory_base::enum_find_class(classID); + switch (service_factory_base::enum_get_count(c)) { + case 1: + PFC_ASSERT_SUCCESS(service_factory_base::enum_create(out, c, 0)); + return true; + default: + return false; + } +} + +void _standard_api_get_internal(service_ptr & out, const GUID & classID) { + if (!_standard_api_try_get_internal(out, classID) ) uBugCheck(); +} \ No newline at end of file diff --git a/foobar2000/SDK/service.h b/foobar2000/SDK/service.h new file mode 100644 index 0000000..6ac530c --- /dev/null +++ b/foobar2000/SDK/service.h @@ -0,0 +1,845 @@ +#pragma once + +#include // std::forward + +typedef const void* service_class_ref; + +PFC_DECLARE_EXCEPTION(exception_service_not_found,pfc::exception,"Service not found"); +PFC_DECLARE_EXCEPTION(exception_service_extension_not_found,pfc::exception,"Service extension not found"); +PFC_DECLARE_EXCEPTION(exception_service_duplicated,pfc::exception,"Service duplicated"); + +#ifdef _MSC_VER +#define FOOGUIDDECL __declspec(selectany) +#else +#define FOOGUIDDECL +#endif + + +#define DECLARE_GUID(NAME,A,S,D,F,G,H,J,K,L,Z,X) FOOGUIDDECL const GUID NAME = {A,S,D,{F,G,H,J,K,L,Z,X}}; +#define DECLARE_CLASS_GUID(NAME,A,S,D,F,G,H,J,K,L,Z,X) FOOGUIDDECL const GUID NAME::class_guid = {A,S,D,{F,G,H,J,K,L,Z,X}}; + +//Must be templated instead of taking service_base* because of multiple inheritance issues. +template static void service_release_safe(T * p_ptr) throw() { + if (p_ptr != NULL) PFC_ASSERT_NO_EXCEPTION( p_ptr->service_release() ); +} + +//Must be templated instead of taking service_base* because of multiple inheritance issues. +template static void service_add_ref_safe(T * p_ptr) throw() { + if (p_ptr != NULL) PFC_ASSERT_NO_EXCEPTION( p_ptr->service_add_ref() ); +} + +class service_base; + +template +class service_ptr_base_t { +public: + inline T* get_ptr() const throw() {return m_ptr;} + typedef T obj_t; +protected: + T * m_ptr; +}; + +// forward declaration +template class service_nnptr_t; + +template struct forced_cast_t { + T* ptr; +}; + +//! Autopointer class to be used with all services. Manages reference counter calls behind-the-scenes. +template +class service_ptr_t : public service_ptr_base_t { +private: + typedef service_ptr_t t_self; + + template void _init(t_source * in) throw() { + this->m_ptr = in; + if (this->m_ptr) this->m_ptr->service_add_ref(); + } + template void _init(t_source && in) throw() { + this->m_ptr = in.detach(); + } +public: + service_ptr_t() throw() {this->m_ptr = NULL;} + service_ptr_t(T * p_ptr) throw() {_init(p_ptr);} + service_ptr_t(const t_self & p_source) throw() {_init(p_source.get_ptr());} + service_ptr_t(t_self && p_source) throw() {_init(std::move(p_source));} + template service_ptr_t(t_source * p_ptr) throw() {_init(p_ptr);} + template service_ptr_t(const service_ptr_base_t & p_source) throw() {_init(p_source.get_ptr());} + template service_ptr_t(const service_nnptr_t & p_source) throw() { this->m_ptr = p_source.get_ptr(); this->m_ptr->service_add_ref(); } + template service_ptr_t(service_ptr_t && p_source) throw() { _init(std::move(p_source)); } + + ~service_ptr_t() throw() {service_release_safe(this->m_ptr);} + + template + void copy(t_source * p_ptr) throw() { + service_add_ref_safe(p_ptr); + service_release_safe(this->m_ptr); + this->m_ptr = pfc::safe_ptr_cast(p_ptr); + } + + template + void copy(const service_ptr_base_t & p_source) throw() {copy(p_source.get_ptr());} + + template + void copy(service_ptr_t && p_source) throw() {attach(p_source.detach());} + + + inline const t_self & operator=(const t_self & p_source) throw() {copy(p_source); return *this;} + inline const t_self & operator=(t_self && p_source) throw() {copy(std::move(p_source)); return *this;} + inline const t_self & operator=(T * p_ptr) throw() {copy(p_ptr); return *this;} + + template inline t_self & operator=(const service_ptr_base_t & p_source) throw() {copy(p_source); return *this;} + template inline t_self & operator=(service_ptr_t && p_source) throw() {copy(std::move(p_source)); return *this;} + template inline t_self & operator=(t_source * p_ptr) throw() {copy(p_ptr); return *this;} + + template inline t_self & operator=(const service_nnptr_t & p_ptr) throw() { + service_release_safe(this->m_ptr); + t_source * ptr = p_ptr.get_ptr(); + ptr->service_add_ref(); + this->m_ptr = ptr; + return *this; + } + + inline void reset() throw() { release(); } + + inline void release() throw() { + service_release_safe(this->m_ptr); + this->m_ptr = NULL; + } + + + inline T* operator->() const throw() { +#if PFC_DEBUG + if (this->m_ptr == NULL) { + FB2K_DebugLog() << "service_ptr operator-> on a null pointer, type: " << T::debugServiceName(); + uBugCheck(); + } +#endif + return this->m_ptr; + } + + inline T* get_ptr() const throw() {return this->m_ptr;} + + inline bool is_valid() const throw() {return this->m_ptr != NULL;} + inline bool is_empty() const throw() {return this->m_ptr == NULL;} + + inline bool operator==(const service_ptr_base_t & p_item) const throw() {return this->m_ptr == p_item.get_ptr();} + inline bool operator!=(const service_ptr_base_t & p_item) const throw() {return this->m_ptr != p_item.get_ptr();} + + inline bool operator>(const service_ptr_base_t & p_item) const throw() {return this->m_ptr > p_item.get_ptr();} + inline bool operator<(const service_ptr_base_t & p_item) const throw() {return this->m_ptr < p_item.get_ptr();} + + inline bool operator==(T * p_item) const throw() {return this->m_ptr == p_item;} + inline bool operator!=(T * p_item) const throw() {return this->m_ptr != p_item;} + + inline bool operator>(T * p_item) const throw() {return this->m_ptr > p_item;} + inline bool operator<(T * p_item) const throw() {return this->m_ptr < p_item;} + + template + inline t_self & operator<<(service_ptr_t & p_source) throw() {attach(p_source.detach());return *this;} + template + inline t_self & operator>>(service_ptr_t & p_dest) throw() {p_dest.attach(detach());return *this;} + + + inline T* _duplicate_ptr() const throw() {//should not be used ! temporary ! + service_add_ref_safe(this->m_ptr); + return this->m_ptr; + } + + inline T* detach() throw() { + return pfc::replace_null_t(this->m_ptr); + } + + template + inline void attach(t_source * p_ptr) throw() { + service_release_safe(this->m_ptr); + this->m_ptr = pfc::safe_ptr_cast(p_ptr); + } + + T & operator*() const throw() {return *this->m_ptr;} + + service_ptr_t & _as_base_ptr() { + PFC_ASSERT( _as_base_ptr_check() ); + return *reinterpret_cast*>(this); + } + static bool _as_base_ptr_check() { + return static_cast((T*)NULL) == reinterpret_cast((T*)NULL); + } + + //! Forced cast operator - obtains a valid service pointer to the expected class or crashes the app if such pointer cannot be obtained. + template + void operator ^= ( otherPtr_t other ) { + if (other.is_empty()) release(); + else forcedCastFrom(other); + } + template + void operator ^= ( otherObj_t * other ) { + if (other == nullptr) release(); + else forcedCastFrom( other ); + } + + bool testForInterface(const GUID & guid) const { + if (this->m_ptr == nullptr) return false; + service_ptr_t dummy; + return this->m_ptr->service_query(dummy, guid); + } + //! Conditional cast operator - attempts to obtain a vaild service pointer to the expected class; returns true on success, false on failure. + template + bool operator &= ( otherPtr_t other ) { + if (other.is_empty()) return false; + return other->cast(*this); + } + template + bool operator &= ( otherObj_t * other ) { + if (other == nullptr) return false; + return other->cast( *this ); + } + + template + void operator=(forced_cast_t other) { + if (other.ptr == NULL) release(); + else forcedCastFrom(other.ptr); + } + + //! Alternate forcedCast syntax, for contexts where operator^= fails to compile. \n + //! Usage: target = source.forcedCast(); + forced_cast_t forcedCast() const { + forced_cast_t r = { this->m_ptr }; + return r; + } + + template + void forcedCastFrom(source_t const & other) { + if (!other->cast(*this)) { +#if PFC_DEBUG + FB2K_DebugLog() << "Forced cast failure: " << pfc::print_guid(T::class_guid); +#endif + uBugCheck(); + } + } +}; + +//! Autopointer class to be used with all services. Manages reference counter calls behind-the-scenes. \n +//! This assumes that the pointers are valid all the time (can't point to null). Mainly intended to be used for scenarios where null pointers are not valid and relevant code should crash ASAP if somebody passes invalid pointers around. \n +//! You want to use service_ptr_t<> rather than service_nnptr_t<> most of the time. +template +class service_nnptr_t : public service_ptr_base_t { +private: + typedef service_nnptr_t t_self; + + template void _init(t_source * in) { + this->m_ptr = in; + this->m_ptr->service_add_ref(); + } + service_nnptr_t() throw() {pfc::crash();} +public: + service_nnptr_t(T * p_ptr) throw() {_init(p_ptr);} + service_nnptr_t(const t_self & p_source) throw() {_init(p_source.get_ptr());} + template service_nnptr_t(t_source * p_ptr) throw() {_init(p_ptr);} + template service_nnptr_t(const service_ptr_base_t & p_source) throw() {_init(p_source.get_ptr());} + + template service_nnptr_t(service_ptr_t && p_source) throw() {this->m_ptr = p_source.detach();} + + ~service_nnptr_t() throw() {this->m_ptr->service_release();} + + template + void copy(t_source * p_ptr) throw() { + p_ptr->service_add_ref(); + this->m_ptr->service_release(); + this->m_ptr = pfc::safe_ptr_cast(p_ptr); + } + + template + void copy(const service_ptr_base_t & p_source) throw() {copy(p_source.get_ptr());} + + + inline const t_self & operator=(const t_self & p_source) throw() {copy(p_source); return *this;} + inline const t_self & operator=(T * p_ptr) throw() {copy(p_ptr); return *this;} + + template inline t_self & operator=(const service_ptr_base_t & p_source) throw() {copy(p_source); return *this;} + template inline t_self & operator=(t_source * p_ptr) throw() {copy(p_ptr); return *this;} + template inline t_self & operator=(service_ptr_t && p_source) throw() {this->m_ptr->service_release(); this->m_ptr = p_source.detach();} + + + inline T* operator->() const throw() {PFC_ASSERT(this->m_ptr != NULL);return this->m_ptr;} + + inline T* get_ptr() const throw() {return this->m_ptr;} + + inline bool is_valid() const throw() {return true;} + inline bool is_empty() const throw() {return false;} + + inline bool operator==(const service_ptr_base_t & p_item) const throw() {return this->m_ptr == p_item.get_ptr();} + inline bool operator!=(const service_ptr_base_t & p_item) const throw() {return this->m_ptr != p_item.get_ptr();} + + inline bool operator>(const service_ptr_base_t & p_item) const throw() {return this->m_ptr > p_item.get_ptr();} + inline bool operator<(const service_ptr_base_t & p_item) const throw() {return this->m_ptr < p_item.get_ptr();} + + inline bool operator==(T * p_item) const throw() {return this->m_ptr == p_item;} + inline bool operator!=(T * p_item) const throw() {return this->m_ptr != p_item;} + + inline bool operator>(T * p_item) const throw() {return this->m_ptr > p_item;} + inline bool operator<(T * p_item) const throw() {return this->m_ptr < p_item;} + + inline T* _duplicate_ptr() const throw() {//should not be used ! temporary ! + service_add_ref_safe(this->m_ptr); + return this->m_ptr; + } + + T & operator*() const throw() {return *this->m_ptr;} + + service_ptr_t & _as_base_ptr() { + PFC_ASSERT( _as_base_ptr_check() ); + return *reinterpret_cast*>(this); + } + static bool _as_base_ptr_check() { + return static_cast((T*)NULL) == reinterpret_cast((T*)NULL); + } + + forced_cast_t forcedCast() const { + forced_cast_t r = { this->m_ptr }; + return r; + } +}; + +namespace pfc { + class traits_service_ptr : public traits_default { + public: + enum { realloc_safe = true, constructor_may_fail = false}; + }; + + template class traits_t > : public traits_service_ptr {}; + template class traits_t > : public traits_service_ptr {}; +} + + +//! For internal use, see FB2K_MAKE_SERVICE_INTERFACE +#define FB2K_MAKE_SERVICE_INTERFACE_EX(THISCLASS,PARENTCLASS,IS_CORE_API) \ + public: \ + typedef THISCLASS t_interface; \ + typedef PARENTCLASS t_interface_parent; \ + \ + static const GUID class_guid; \ + \ + typedef service_ptr_t ptr; \ + typedef service_nnptr_t nnptr; \ + typedef ptr ref; \ + typedef nnptr nnref; \ + static const char * debugServiceName() { return #THISCLASS; } \ + enum { _is_core_api = IS_CORE_API }; \ + protected: \ + THISCLASS() {} \ + ~THISCLASS() {} \ + private: \ + const THISCLASS & operator=(const THISCLASS &) = delete; \ + THISCLASS(const THISCLASS &) = delete; \ + private: \ + void _private_service_declaration_selftest() { \ + static_assert( pfc::is_same_type::value, "t_interface sanity" ); \ + static_assert( ! pfc::is_same_type::value, "parent class sanity"); \ + static_assert( pfc::is_same_type::value || IS_CORE_API == PARENTCLASS::_is_core_api, "is_core_api sanity" ); \ + _validate_service_class_helper(); /*service_base must be reachable by walking t_interface_parent*/ \ + pfc::implicit_cast(this); /*this class must derive from service_base, directly or indirectly, and be implictly castable to it*/ \ + } + +#define FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT_EX(THISCLASS, IS_CORE_API) \ + public: \ + typedef THISCLASS t_interface_entrypoint; \ + static service_enum_t enumerate() { return service_enum_t(); } \ + FB2K_MAKE_SERVICE_INTERFACE_EX(THISCLASS,service_base, IS_CORE_API) + +//! Helper macro for use when defining a service class. Generates standard features of a service, without ability to register using service_factory / enumerate using service_enum_t. \n +//! This is used for declaring services that are meant to be instantiated by means other than service_enum_t (or non-entrypoint services), or extensions of services (including extension of entrypoint services). \n +//! Sample non-entrypoint declaration: class myclass : public service_base {...; FB2K_MAKE_SERVICE_INTERFACE(myclass, service_base); }; \n +//! Sample extension declaration: class myclass : public myotherclass {...; FB2K_MAKE_SERVICE_INTERFACE(myclass, myotherclass); }; \n +//! This macro is intended for use ONLY WITH INTERFACE CLASSES, not with implementation classes. +#define FB2K_MAKE_SERVICE_INTERFACE(THISCLASS, PARENTCLASS) FB2K_MAKE_SERVICE_INTERFACE_EX(THISCLASS, PARENTCLASS, false) + +//! Helper macro for use when defining an entrypoint service class. Generates standard features of a service, including ability to register using service_factory and enumerate using service_enum. \n +//! Sample declaration: class myclass : public service_base {...; FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(myclass); }; \n +//! Note that entrypoint service classes must directly derive from service_base, and not from another service class. +//! This macro is intended for use ONLY WITH INTERFACE CLASSES, not with implementation classes. +#define FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(THISCLASS) FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT_EX(THISCLASS, false) + + +#define FB2K_MAKE_SERVICE_COREAPI(THISCLASS) \ + FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT_EX( THISCLASS, true ) \ +public: \ + static ptr get() { return fb2k::std_api_get(); } \ + static bool tryGet(ptr & out) { return fb2k::std_api_try_get(out); } \ + static ptr tryGet() { ptr ret; tryGet(ret); return ret; } + +#define FB2K_MAKE_SERVICE_COREAPI_EXTENSION(THISCLASS, BASECLASS) \ + FB2K_MAKE_SERVICE_INTERFACE_EX( THISCLASS, BASECLASS, true ) \ +public: \ + static ptr get() { return fb2k::std_api_get(); } \ + static bool tryGet(ptr & out) { return fb2k::std_api_try_get(out); } \ + static ptr tryGet() { ptr ret; tryGet(ret); return ret; } + + + +//! Alternate way of declaring services, begin/end macros wrapping the whole class declaration +#define FB2K_DECLARE_SERVICE_BEGIN(THISCLASS,BASECLASS) \ + class NOVTABLE THISCLASS : public BASECLASS { \ + FB2K_MAKE_SERVICE_INTERFACE(THISCLASS,BASECLASS); \ + public: + +//! Alternate way of declaring services, begin/end macros wrapping the whole class declaration +#define FB2K_DECLARE_SERVICE_END() \ + }; + +//! Alternate way of declaring services, begin/end macros wrapping the whole class declaration +#define FB2K_DECLARE_SERVICE_ENTRYPOINT_BEGIN(THISCLASS) \ + class NOVTABLE THISCLASS : public service_base { \ + FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(THISCLASS) \ + public: + +class service_base; +typedef service_ptr_t service_ptr; +typedef service_nnptr_t service_nnptr; + +//! Base class for all service classes.\n +//! Provides interfaces for reference counter and querying for different interfaces supported by the object.\n +class NOVTABLE service_base +{ +public: + //! Decrements reference count; deletes the object if reference count reaches zero. This is normally not called directly but managed by service_ptr_t<> template. \n + //! Implemented by service_impl_* classes. + //! @returns New reference count. For debug purposes only, in certain conditions return values may be unreliable. + virtual int service_release() throw() = 0; + //! Increments reference count. This is normally not called directly but managed by service_ptr_t<> template. \n + //! Implemented by service_impl_* classes. + //! @returns New reference count. For debug purposes only, in certain conditions return values may be unreliable. + virtual int service_add_ref() throw() = 0; + //! Queries whether the object supports specific interface and retrieves a pointer to that interface. This is normally not called directly but managed by service_query_t<> function template. \n + //! Checks the parameter against GUIDs of interfaces supported by this object, if the GUID is one of supported interfaces, p_out is set to service_base pointer that can be static_cast<>'ed to queried interface and the method returns true; otherwise the method returns false. \n + //! Implemented by service_impl_* classes. \n + //! Note that service_query() implementation semantics (but not usage semantics) changed in SDK for foobar2000 1.4; they used to be auto-implemented by each service interface (via FB2K_MAKE_SERVICE_INTERFACE macro); they're now implemented in service_impl_* instead. See SDK readme for more details. \n + virtual bool service_query(service_ptr & p_out,const GUID & p_guid) = 0; + + //! Queries whether the object supports specific interface and retrieves a pointer to that interface. + //! @param p_out Receives pointer to queried interface on success. + //! returns true on success, false on failure (interface not supported by the object). + template + bool service_query_t(service_ptr_t & p_out) + { + pfc::assert_same_type(); + return service_query( *reinterpret_cast*>(&p_out),T::class_guid); + } + //! New shortened version, same as service_query_t. + template + bool cast( outPtr_t & outPtr ) { return service_query_t( outPtr ); } + + typedef service_base t_interface; + enum { _is_core_api = false }; + + static const char * debugServiceName() {return "service_base"; } + + static bool serviceRequiresMainThreadDestructor() { return false; } + +#ifdef FOOBAR2000_MODERN + static bool shouldRegisterService() { return true; } +#endif + + service_base * as_service_base() { return this; } +protected: + service_base() {} + ~service_base() {} + + static bool service_query_walk(service_ptr &, const GUID &, service_base *) { + return false; + } + + template static bool service_query_walk(service_ptr & out, const GUID & guid, interface_t * in) { + if (guid == interface_t::class_guid) { + out = in; return true; + } + typename interface_t::t_interface_parent * chain = in; + return service_query_walk(out, guid, chain); + } + template static bool handle_service_query(service_ptr & out, const GUID & guid, class_t * in) { + typename class_t::t_interface * in2 = in; + return service_query_walk( out, guid, in2 ); + } +private: + service_base(const service_base&) = delete; + const service_base & operator=(const service_base&) = delete; +}; + +template +inline void _validate_service_class_helper() { + _validate_service_class_helper(); +} + +template<> +inline void _validate_service_class_helper() {} + + +#include "service_impl.h" + +class NOVTABLE service_factory_base { +protected: + inline service_factory_base(const GUID & p_guid, service_factory_base * & factoryList = __internal__list) : m_guid(p_guid) { PFC_ASSERT(!core_api::are_services_available()); __internal__next = factoryList; factoryList = this; } +public: + inline const GUID & get_class_guid() const {return m_guid;} + + static service_class_ref enum_find_class(const GUID & p_guid); + static bool enum_create(service_ptr_t & p_out,service_class_ref p_class,t_size p_index); + static t_size enum_get_count(service_class_ref p_class); + + inline static bool is_service_present(const GUID & g) {return enum_get_count(enum_find_class(g))>0;} + + //! Throws std::bad_alloc or another exception on failure. + virtual void instance_create(service_ptr_t & p_out) = 0; +#ifdef FOOBAR2000_MODERN + virtual bool should_register() { return true; } +#endif + + //! FOR INTERNAL USE ONLY + static service_factory_base *__internal__list; + //! FOR INTERNAL USE ONLY + service_factory_base * __internal__next; +private: + const GUID & m_guid; +}; + +template +class service_factory_traits { +public: + static service_factory_base * & factory_list() {return service_factory_base::__internal__list;} +}; + +template +class service_factory_base_t : public service_factory_base { +public: + service_factory_base_t() : service_factory_base(B::class_guid, service_factory_traits::factory_list()) { + pfc::assert_same_type(); + } +}; + +template static void _validate_service_ptr(service_ptr_t const & ptr) { + PFC_ASSERT( ptr.is_valid() ); + service_ptr_t test; + PFC_ASSERT( ptr->service_query_t(test) ); +} + +#ifdef _DEBUG +#define FB2K_ASSERT_VALID_SERVICE_PTR(ptr) _validate_service_ptr(ptr) +#else +#define FB2K_ASSERT_VALID_SERVICE_PTR(ptr) +#endif + +template static bool service_enum_create_t(service_ptr_t & p_out,t_size p_index) { + pfc::assert_same_type(); + service_ptr_t ptr; + if (service_factory_base::enum_create(ptr,service_factory_base::enum_find_class(T::class_guid),p_index)) { + p_out = static_cast(ptr.get_ptr()); + return true; + } else { + p_out.release(); + return false; + } +} + +template static service_class_ref _service_find_class() { + pfc::assert_same_type(); + return service_factory_base::enum_find_class(T::class_guid); +} + +template +static bool _service_instantiate_helper(service_ptr_t & out, service_class_ref servClass, t_size index) { + /*if (out._as_base_ptr_check()) { + const bool state = service_factory_base::enum_create(out._as_base_ptr(), servClass, index); + if (state) { FB2K_ASSERT_VALID_SERVICE_PTR(out); } + return state; + } else */{ + service_ptr temp; + const bool state = service_factory_base::enum_create(temp, servClass, index); + if (state) { + out.attach( static_cast( temp.detach() ) ); + FB2K_ASSERT_VALID_SERVICE_PTR( out ); + } + return state; + } +} + +template class service_class_helper_t { +public: + service_class_helper_t() : m_class(service_factory_base::enum_find_class(T::class_guid)) { + pfc::assert_same_type(); + } + t_size get_count() const { + return service_factory_base::enum_get_count(m_class); + } + + bool create(service_ptr_t & p_out,t_size p_index) const { + return _service_instantiate_helper(p_out, m_class, p_index); + } + + service_ptr_t create(t_size p_index) const { + service_ptr_t temp; + if (!create(temp,p_index)) uBugCheck(); + return temp; + } + service_class_ref get_class() const {return m_class;} +private: + service_class_ref m_class; +}; + +void _standard_api_create_internal(service_ptr & out, const GUID & classID); +void _standard_api_get_internal(service_ptr & out, const GUID & classID); +bool _standard_api_try_get_internal(service_ptr & out, const GUID & classID); + +template inline void standard_api_create_t(service_ptr_t & p_out) { + if (pfc::is_same_type::value) { + _standard_api_create_internal(p_out._as_base_ptr(), T::class_guid); + FB2K_ASSERT_VALID_SERVICE_PTR(p_out); + } else { + service_ptr_t temp; + standard_api_create_t(temp); + if (!temp->service_query_t(p_out)) { +#if PFC_DEBUG + FB2K_DebugLog() << "Service extension not found: " << T::debugServiceName() << " (" << pfc::print_guid(T::class_guid) << ") of base type: " << T::t_interface_entrypoint::debugServiceName() << " (" << pfc::print_guid(T::t_interface_entrypoint::class_guid) << ")"; +#endif + throw exception_service_extension_not_found(); + } + } +} + +template inline void standard_api_create_t(T* & p_out) { + p_out = NULL; + standard_api_create_t( *reinterpret_cast< service_ptr_t * >( & p_out ) ); +} + +template inline service_ptr_t standard_api_create_t() { + service_ptr_t temp; + standard_api_create_t(temp); + return temp; +} + +template +inline bool static_api_test_t() { + typedef typename T::t_interface_entrypoint EP; + service_class_helper_t helper; + if (helper.get_count() != 1) return false; + if (!pfc::is_same_type::value) { + service_ptr_t t; + if (!helper.create(0)->service_query_t(t)) return false; + } + return true; +} + +#define FB2K_API_AVAILABLE(API) static_api_test_t() + +//! Helper template used to easily access core services. \n +//! Usage: static_api_ptr_t api; api->dosomething(); \n +//! Can be used at any point of code, WITH EXCEPTION of static objects that are initialized during the DLL loading process before the service system is initialized; such as static static_api_ptr_t objects or having static_api_ptr_t instances as members of statically created objects. \n +//! Throws exception_service_not_found if service could not be reached (which can be ignored for core APIs that are always present unless there is some kind of bug in the code). \n +//! This class is provided for backwards compatibility. The recommended way to do this stuff is now someclass::get() / someclass::tryGet(). +template +class static_api_ptr_t { +private: + typedef static_api_ptr_t t_self; +public: + static_api_ptr_t() { + standard_api_create_t(m_ptr); + } + t_interface* operator->() const {return m_ptr;} + t_interface * get_ptr() const {return m_ptr;} + ~static_api_ptr_t() {m_ptr->service_release();} + + static_api_ptr_t(const t_self & in) { + m_ptr = in.m_ptr; m_ptr->service_add_ref(); + } + const t_self & operator=(const t_self & in) {return *this;} //obsolete, each instance should carry the same pointer +private: + t_interface * m_ptr; +}; + +template +class service_enum_t { +public: + service_enum_t() : m_index(0) { + pfc::assert_same_type(); + } + void reset() {m_index = 0;} + + template + bool first(service_ptr_t & p_out) { + reset(); + return next(p_out); + } + + template + bool next(service_ptr_t & p_out) { + pfc::assert_same_type(); + if (pfc::is_same_type::value) { + return _next(reinterpret_cast&>(p_out)); + } else { + service_ptr_t temp; + while(_next(temp)) { + if (temp->service_query_t(p_out)) return true; + } + return false; + } + } + + service_ptr_t get() const { + PFC_ASSERT(!finished()); + return m_helper.create(m_index); + } + + void operator++() { + PFC_ASSERT(!finished()); + ++m_index; + } + void operator++(int) { + PFC_ASSERT(!finished()); + ++m_index; + } + + bool finished() const { + return m_index >= m_helper.get_count(); + } + + service_ptr_t operator*() const { + return get(); + } +private: + bool _next(service_ptr_t & p_out) { + return m_helper.create(p_out,m_index++); + } + unsigned m_index; + service_class_helper_t m_helper; +}; + +//! New fb2k service enumeration syntax +//! for(auto e = FB2K_ENUMERATE(someclass); !e.finished(); ++e) { auto srv = *e; srv->do_stuff(); } +#define FB2K_ENUMERATE(what_t) service_enum_t() + +namespace fb2k { + //! Modern get-std-api helper. \n + //! Does not throw exceptions, crashes on failure. \n + //! If failure is possible, use std_api_try_get() instead and handle false return value. + template + service_ptr_t std_api_get() { + typedef typename api_t::t_interface_entrypoint entrypoint_t; + service_ptr_t ret; + if (pfc::is_same_type::value) { + _standard_api_get_internal(ret._as_base_ptr(), api_t::class_guid); + } else { + ret ^= std_api_get(); + } + return ret; + } + + //! Modern get-std-api helper. \n + //! Returns true on scucess (ret ptr is valid), false on failure (API not found). + template + bool std_api_try_get( service_ptr_t & ret ) { + typedef typename api_t::t_interface_entrypoint entrypoint_t; + if (pfc::is_same_type::value) { + return _standard_api_try_get_internal(ret._as_base_ptr(), api_t::class_guid); + } else { + service_ptr_t temp; + if (! std_api_try_get( temp ) ) return false; + return ret &= temp; + } + } +} + + + +template +class service_factory_t : public service_factory_base_t { +public: + void instance_create(service_ptr_t & p_out) override { + p_out = pfc::implicit_cast(pfc::implicit_cast(pfc::implicit_cast( new service_impl_t ))); + } +#ifdef FOOBAR2000_MODERN + bool should_register() override { return T::shouldRegisterService(); } +#endif +}; + + +template +class service_factory_single_t : public service_factory_base_t { + service_impl_single_t g_instance; +public: + template service_factory_single_t(arg_t && ... arg) : g_instance(std::forward(arg) ...) {} + + void instance_create(service_ptr_t & p_out) override { + p_out = pfc::implicit_cast(pfc::implicit_cast(pfc::implicit_cast(&g_instance))); + } +#ifdef FOOBAR2000_MODERN + bool should_register() override { return g_instance.shouldRegisterService(); } +#endif + + inline T& get_static_instance() { return g_instance; } + inline const T& get_static_instance() const { return g_instance; } +}; + +//! Alternate service_factory_single, shared instance created on first access and never deallocated. \n +//! Addresses the problem of dangling references to our object getting invoked or plainly de-refcounted during late shutdown. +template +class service_factory_single_v2_t : public service_factory_base_t { +public: + T * get() { + static T * g_instance = new service_impl_single_t; + return g_instance; + } + void instance_create(service_ptr_t & p_out) override { + p_out = pfc::implicit_cast(pfc::implicit_cast(get())); + } +#ifdef FOOBAR2000_MODERN + bool should_register() override { return T::shouldRegisterService(); } +#endif +}; + +template +class service_factory_single_ref_t : public service_factory_base_t +{ +private: + T & instance; +public: + service_factory_single_ref_t(T& param) : instance(param) {} + + void instance_create(service_ptr_t & p_out) override { + p_out = pfc::implicit_cast(pfc::implicit_cast(pfc::implicit_cast(&instance))); + } +#ifdef FOOBAR2000_MODERN + bool should_register() override { return instance.shouldRegisterService(); } +#endif + + inline T& get_static_instance() { return instance; } +}; + +template +class service_factory_single_transparent_t : public service_factory_base_t, public service_impl_single_t +{ +public: + template service_factory_single_transparent_t(arg_t && ... arg) : service_impl_single_t( std::forward(arg) ...) {} + + void instance_create(service_ptr_t & p_out) override { + p_out = pfc::implicit_cast(pfc::implicit_cast(pfc::implicit_cast(this))); + } +#ifdef FOOBAR2000_MODERN + bool should_register() override { return this->shouldRegisterService(); } +#endif + + inline T& get_static_instance() {return *(T*)this;} + inline const T& get_static_instance() const {return *(const T*)this;} +}; + + +#ifdef _MSC_VER +#define FB2K_SERVICE_FACTORY_ATTR +#else +#define FB2K_SERVICE_FACTORY_ATTR __attribute__ (( __used__ )) +#endif + +#define FB2K_SERVICE_FACTORY( TYPE ) static ::service_factory_single_t< TYPE > g_##TYPE##factory FB2K_SERVICE_FACTORY_ATTR; +#define FB2K_SERVICE_FACTORY_DYNAMIC( TYPE ) static ::service_factory_t< TYPE > g_##TYPE##factory FB2K_SERVICE_FACTORY_ATTR; + + +#define FB2K_FOR_EACH_SERVICE(type, call) {service_enum_t e; service_ptr_t ptr; while(e.next(ptr)) {ptr->call;} } diff --git a/foobar2000/SDK/service_by_guid.h b/foobar2000/SDK/service_by_guid.h new file mode 100644 index 0000000..d638042 --- /dev/null +++ b/foobar2000/SDK/service_by_guid.h @@ -0,0 +1,104 @@ +#pragma once +#ifdef FOOBAR2000_MODERN +#include +#endif + + + +template +static bool service_by_guid_fallback(service_ptr_t & out, const GUID & id) { + service_enum_t e; + service_ptr_t ptr; + while(e.next(ptr)) { + if (ptr->get_guid() == id) {out = ptr; return true;} + } + return false; +} + +template +class service_by_guid_data { +public: + service_by_guid_data() : m_inited(), m_servClass() {} + + bool ready() const {return m_inited;} + + // Caller must ensure initialize call before create() as well as thread safety of initialize() calls. The rest of this class is thread safe (only reads member data). + void initialize() { + if (m_inited) return; + pfc::assert_same_type< what, typename what::t_interface_entrypoint >(); + m_servClass = service_factory_base::enum_find_class(what::class_guid); + const t_size servCount = service_factory_base::enum_get_count(m_servClass); + for(t_size walk = 0; walk < servCount; ++walk) { + service_ptr_t temp; + if (_service_instantiate_helper(temp, m_servClass, walk)) { + m_order.set(temp->get_guid(), walk); + } + } + m_inited = true; + } + + bool create(service_ptr_t & out, const GUID & theID) const { + PFC_ASSERT(m_inited); + t_size index; + if (!m_order.query(theID,index)) return false; + return _service_instantiate_helper(out, m_servClass, index); + } + service_ptr_t create(const GUID & theID) const { + service_ptr_t temp; if (!crete(temp,theID)) throw exception_service_not_found(); return temp; + } + +private: + volatile bool m_inited; + pfc::map_t m_order; + service_class_ref m_servClass; +}; + +template +class _service_by_guid_data_container { +public: + static service_by_guid_data data; +}; +template service_by_guid_data _service_by_guid_data_container::data; + + +template +static void service_by_guid_init() { + service_by_guid_data & data = _service_by_guid_data_container::data; + data.initialize(); +} +template +static bool service_by_guid(service_ptr_t & out, const GUID & theID) { + pfc::assert_same_type< what, typename what::t_interface_entrypoint >(); + service_by_guid_data & data = _service_by_guid_data_container::data; + if (data.ready()) { + //fall-thru + } else if (core_api::is_main_thread()) { + data.initialize(); + } else { +#if PFC_DEBUG + FB2K_DebugLog() << "Warning: service_by_guid() used in non-main thread without initialization, using fallback"; +#endif + return service_by_guid_fallback(out,theID); + } + return data.create(out,theID); +} +template +static service_ptr_t service_by_guid(const GUID & theID) { + service_ptr_t temp; + if (!service_by_guid(temp, theID)) { +#if PFC_DEBUG + FB2K_DebugLog() << "service_by_guid failure: " << what::debugServiceName() << " : " << pfc::print_guid( theID ); +#endif + throw exception_service_not_found(); + } + return temp; +} + + + + +class comparator_service_guid { +public: + template static int compare(const what & v1, const what & v2) { return pfc::compare_t(v1->get_guid(), v2->get_guid()); } +}; + diff --git a/foobar2000/SDK/service_compat.h b/foobar2000/SDK/service_compat.h new file mode 100644 index 0000000..d1a960e --- /dev/null +++ b/foobar2000/SDK/service_compat.h @@ -0,0 +1,38 @@ +#pragma once + +// Obsolete features + +//! Special hack to ensure errors when someone tries to ->service_add_ref()/->service_release() on a service_ptr_t +template class service_obscure_refcounting : public T { +private: + int service_add_ref() throw(); + int service_release() throw(); +}; + +//! Converts a service interface pointer to a pointer that obscures service counter functionality. +template static inline service_obscure_refcounting* service_obscure_refcounting_cast(T * p_source) throw() {return static_cast*>(p_source);} + +template class t_alloc = pfc::alloc_fast> +class service_list_t : public pfc::list_t, t_alloc > +{ +}; + +//! Helper; simulates array with instance of each available implementation of given service class. +template class service_instance_array_t { +public: + typedef service_ptr_t t_ptr; + service_instance_array_t() { + service_class_helper_t helper; + const t_size count = helper.get_count(); + m_data.set_size(count); + for(t_size n=0;n m_data; +}; diff --git a/foobar2000/SDK/service_impl.h b/foobar2000/SDK/service_impl.h new file mode 100644 index 0000000..c1c026b --- /dev/null +++ b/foobar2000/SDK/service_impl.h @@ -0,0 +1,124 @@ +#pragma once + +// service_impl.h +// This header provides functionality for spawning your own service objects. +// Various service_impl_* classes implement service_base methods (reference counting, query for interface) on top of your class. +// service_impl_* are top level ("sealed" in C# terms) classes; they derive from your classes but you should never derive from them. + +#include + +namespace service_impl_helper { + //! Helper function to defer destruction of a service object. \n + //! Enqueues a main_thread_callback to release the object at a later time, escaping the current scope. \n + //! Important: this takes a raw service_base* - not an autoptr - to ensure that the last reference can be released in main thread. \n + void release_object_delayed(service_base* obj); +}; + +//! Multi inheritance helper. \n +//! Please note that use of multi inheritance is not recommended. Most components will never need this. \n +//! This class handles multi inherited service_query() for you. \n +//! Usage: class myclass : public service_multi_inherit {...}; \n +//! It's also legal to chain it: service_multi_inherit > and so on. +template +class service_multi_inherit : public class1_t, public class2_t { + typedef service_multi_inherit self_t; +public: + static bool handle_service_query(service_ptr & out, const GUID & guid, self_t * in) { + return service_base::handle_service_query(out, guid, (class1_t*) in) || service_base::handle_service_query(out, guid, (class2_t*) in); + } + + service_base * as_service_base() { return class1_t::as_service_base(); } + static const char * debugServiceName() { return "multi inherited service"; } + + // Obscure service_base methods from both so calling myclass->service_query() works like it should + virtual int service_release() throw() = 0; + virtual int service_add_ref() throw() = 0; + virtual bool service_query(service_ptr & p_out, const GUID & p_guid) = 0; + + static bool serviceRequiresMainThreadDestructor() { + return class1_t::serviceRequiresMainThreadDestructor() || class2_t::serviceRequiresMainThreadDestructor(); + } +}; + +//! Template implementing service_query walking the inheritance chain. \n +//! Do not use directly. Each service_impl_* template utilizes it implicitly. +template class implement_service_query : public class_t +{ + typedef class_t base_t; +public: + template implement_service_query( arg_t && ... arg ) : base_t( std::forward(arg) ... ) {} + + bool service_query(service_ptr_t & p_out, const GUID & p_guid) { + return this->handle_service_query( p_out, p_guid, this ); + } +}; + +//! Template implementing reference-counting features of service_base. Intended for dynamic instantiation: "new service_impl_t(param1,param2);"; should not be instantiated otherwise (no local/static/member objects) because it does a "delete this;" when reference count reaches zero. \n +//! Note that there's no more need to use this direclty, see fb2k::service_new<>(). +template +class service_impl_t : public implement_service_query +{ + typedef implement_service_query base_t; +public: + int service_release() throw() { + int ret = (int) --m_counter; + if (ret == 0) { + if (!this->serviceRequiresMainThreadDestructor() || core_api::is_main_thread()) { + PFC_ASSERT_NO_EXCEPTION( delete this ); + } else { + // Pass to release_object_delayed() with zero ref count - a temporary single reference will be created there + service_impl_helper::release_object_delayed(this->as_service_base()); + } + } + return ret; + } + int service_add_ref() throw() {return (int) ++m_counter;} + + template service_impl_t( arg_t && ... arg ) : base_t( std::forward(arg) ... ) {} +private: + pfc::refcounter m_counter; +}; + +//! Alternate version of service_impl_t<> - calls this->service_shutdown() instead of delete this. \n +//! For special cases where selfdestruct on zero refcount is undesired. +template +class service_impl_explicitshutdown_t : public implement_service_query +{ + typedef implement_service_query base_t; +public: + int service_release() throw() { + int ret = --m_counter; + if (ret == 0) { + this->service_shutdown(); + } else { + return ret; + } + } + int service_add_ref() throw() {return ++m_counter;} + + template service_impl_explicitshutdown_t(arg_t && ... arg) : base_t(std::forward(arg) ...) {} +private: + pfc::refcounter m_counter; +}; + +//! Template implementing dummy version of reference-counting features of service_base. Intended for static/local/member instantiation: "static service_impl_single_t myvar(params);". Because reference counting features are disabled (dummy reference counter), code instantiating it is responsible for deleting it as well as ensuring that no references are active when the object gets deleted.\n +template +class service_impl_single_t : public implement_service_query +{ + typedef implement_service_query base_t; +public: + int service_release() throw() {return 1;} + int service_add_ref() throw() {return 1;} + + template service_impl_single_t(arg_t && ... arg) : base_t(std::forward(arg) ...) {} +}; + +namespace fb2k { + //! The new recommended way of spawning service objects, automatically implementing reference counting and service_query on top of your class. \n + //! Usage: auto myObj = fb2k::service_new(args); \n + //! Returned type is a service_ptr_t + template + service_ptr_t service_new(arg_t && ... arg) { + return new service_impl_t< obj_t > ( std::forward (arg) ... ); + } +} \ No newline at end of file diff --git a/foobar2000/SDK/shortcut_actions.h b/foobar2000/SDK/shortcut_actions.h new file mode 100644 index 0000000..5c671dd --- /dev/null +++ b/foobar2000/SDK/shortcut_actions.h @@ -0,0 +1 @@ +#error DEPRECATED diff --git a/foobar2000/SDK/stdafx.cpp b/foobar2000/SDK/stdafx.cpp new file mode 100644 index 0000000..145fc30 --- /dev/null +++ b/foobar2000/SDK/stdafx.cpp @@ -0,0 +1,2 @@ +//cpp used to generate precompiled header +#include "foobar2000.h" \ No newline at end of file diff --git a/foobar2000/SDK/system_time_keeper.h b/foobar2000/SDK/system_time_keeper.h new file mode 100644 index 0000000..84211b1 --- /dev/null +++ b/foobar2000/SDK/system_time_keeper.h @@ -0,0 +1,47 @@ +namespace system_time_periods { + static const t_filetimestamp second = filetimestamp_1second_increment; + static const t_filetimestamp minute = second * 60; + static const t_filetimestamp hour = minute * 60; + static const t_filetimestamp day = hour * 24; + static const t_filetimestamp week = day * 7; +}; +class system_time_callback { +public: + virtual void on_time_changed(t_filetimestamp newVal) = 0; +}; +//! \since 0.9.6 +class system_time_keeper : public service_base { +public: + //! The callback object receives an on_changed() call with the current time inside the register_callback() call. + virtual void register_callback(system_time_callback * callback, t_filetimestamp resolution) = 0; + + virtual void unregister_callback(system_time_callback * callback) = 0; + + FB2K_MAKE_SERVICE_COREAPI(system_time_keeper) +}; + +class system_time_callback_impl : public system_time_callback { +public: + system_time_callback_impl() : m_registered() {} + ~system_time_callback_impl() {stop_timer();} + + void stop_timer() { + if (m_registered) { + system_time_keeper::get()->unregister_callback(this); + m_registered = false; + } + } + //! You get a on_changed() call inside the initialize_timer() call. + void initialize_timer(t_filetimestamp period) { + stop_timer(); + system_time_keeper::get()->register_callback(this, period); + m_registered = true; + } + + void on_time_changed(t_filetimestamp newVal) {} + + PFC_CLASS_NOT_COPYABLE_EX(system_time_callback_impl) +private: + bool m_registered; +}; + diff --git a/foobar2000/SDK/tag_processor.cpp b/foobar2000/SDK/tag_processor.cpp new file mode 100644 index 0000000..d8d4276 --- /dev/null +++ b/foobar2000/SDK/tag_processor.cpp @@ -0,0 +1,180 @@ +#include "foobar2000.h" + +void tag_processor_trailing::write_id3v1(const service_ptr_t & p_file,const file_info & p_info,abort_callback & p_abort) +{ + write(p_file,p_info,flag_id3v1,p_abort); +} + +void tag_processor_trailing::write_apev2(const service_ptr_t & p_file,const file_info & p_info,abort_callback & p_abort) +{ + write(p_file,p_info,flag_apev2,p_abort); +} + +void tag_processor_trailing::write_apev2_id3v1(const service_ptr_t & p_file,const file_info & p_info,abort_callback & p_abort) +{ + write(p_file,p_info,flag_id3v1|flag_apev2,p_abort); +} + + + + +enum { + g_flag_id3v1 = 1<<0, + g_flag_id3v2 = 1<<1, + g_flag_apev2 = 1<<2 +}; + +static void tagtype_list_append(pfc::string_base & p_out,const char * p_name) +{ + if (!p_out.is_empty()) p_out += "|"; + p_out += p_name; +} + +static void g_write_tags_ex(tag_write_callback & p_callback,unsigned p_flags,const service_ptr_t & p_file,const file_info * p_info,abort_callback & p_abort) { + PFC_ASSERT( p_flags == 0 || p_info != 0 ); + + + if (p_flags & (g_flag_id3v1 | g_flag_apev2)) { + switch(p_flags & (g_flag_id3v1 | g_flag_apev2)) { + case g_flag_id3v1 | g_flag_apev2: + tag_processor_trailing::get()->write_apev2_id3v1(p_file,*p_info,p_abort); + break; + case g_flag_id3v1: + tag_processor_trailing::get()->write_id3v1(p_file,*p_info,p_abort); + break; + case g_flag_apev2: + tag_processor_trailing::get()->write_apev2(p_file,*p_info,p_abort); + break; + default: + throw exception_io_data(); + } + } else { + tag_processor_trailing::get()->remove(p_file,p_abort); + } + + if (p_flags & g_flag_id3v2) + { + tag_processor_id3v2::get()->write_ex(p_callback,p_file,*p_info,p_abort); + } + else + { + t_uint64 dummy; + tag_processor_id3v2::g_remove_ex(p_callback,p_file,dummy,p_abort); + } +} + +static void g_write_tags(unsigned p_flags,const service_ptr_t & p_file,const file_info * p_info,abort_callback & p_abort) { + tag_write_callback_dummy cb; + g_write_tags_ex(cb,p_flags,p_file,p_info,p_abort); +} + + +void tag_processor::write_multi(const service_ptr_t & p_file,const file_info & p_info,abort_callback & p_abort,bool p_write_id3v1,bool p_write_id3v2,bool p_write_apev2) { + unsigned flags = 0; + if (p_write_id3v1) flags |= g_flag_id3v1; + if (p_write_id3v2) flags |= g_flag_id3v2; + if (p_write_apev2) flags |= g_flag_apev2; + g_write_tags(flags,p_file,&p_info,p_abort); +} + +void tag_processor::write_multi_ex(tag_write_callback & p_callback,const service_ptr_t & p_file,const file_info & p_info,abort_callback & p_abort,bool p_write_id3v1,bool p_write_id3v2,bool p_write_apev2) { + unsigned flags = 0; + if (p_write_id3v1) flags |= g_flag_id3v1; + if (p_write_id3v2) flags |= g_flag_id3v2; + if (p_write_apev2) flags |= g_flag_apev2; + g_write_tags_ex(p_callback,flags,p_file,&p_info,p_abort); +} + +void tag_processor::write_id3v1(const service_ptr_t & p_file,const file_info & p_info,abort_callback & p_abort) { + g_write_tags(g_flag_id3v1,p_file,&p_info,p_abort); +} + +void tag_processor::write_apev2(const service_ptr_t & p_file,const file_info & p_info,abort_callback & p_abort) { + g_write_tags(g_flag_apev2,p_file,&p_info,p_abort); +} + +void tag_processor::write_apev2_id3v1(const service_ptr_t & p_file,const file_info & p_info,abort_callback & p_abort) { + g_write_tags(g_flag_apev2|g_flag_id3v1,p_file,&p_info,p_abort); +} + +void tag_processor::write_id3v2(const service_ptr_t & p_file,const file_info & p_info,abort_callback & p_abort) { + g_write_tags(g_flag_id3v2,p_file,&p_info,p_abort); +} + +void tag_processor::write_id3v2_id3v1(const service_ptr_t & p_file,const file_info & p_info,abort_callback & p_abort) { + g_write_tags(g_flag_id3v2|g_flag_id3v1,p_file,&p_info,p_abort); +} + +void tag_processor::remove_trailing(const service_ptr_t & p_file,abort_callback & p_abort) { + return tag_processor_trailing::get()->remove(p_file,p_abort); +} + +bool tag_processor::remove_id3v2(const service_ptr_t & p_file,abort_callback & p_abort) { + t_uint64 dummy = 0; + tag_processor_id3v2::g_remove(p_file,dummy,p_abort); + return dummy > 0; +} + +void tag_processor::remove_id3v2_trailing(const service_ptr_t & p_file,abort_callback & p_abort) { + remove_id3v2(p_file,p_abort); + remove_trailing(p_file,p_abort); +} + +void tag_processor::read_trailing(const service_ptr_t & p_file,file_info & p_info,abort_callback & p_abort) { + tag_processor_trailing::get()->read(p_file,p_info,p_abort); +} + +void tag_processor::read_trailing_ex(const service_ptr_t & p_file,file_info & p_info,t_filesize & p_tagoffset,abort_callback & p_abort) { + tag_processor_trailing::get()->read_ex(p_file,p_info,p_tagoffset,p_abort); +} + +void tag_processor::read_id3v2(const service_ptr_t & p_file,file_info & p_info,abort_callback & p_abort) { + tag_processor_id3v2::get()->read(p_file,p_info,p_abort); +} + +void tag_processor::read_id3v2_trailing(const service_ptr_t & p_file,file_info & p_info,abort_callback & p_abort) +{ + file_info_impl id3v2, trailing; + bool have_id3v2 = true, have_trailing = true; + try { + read_id3v2(p_file,id3v2,p_abort); + } catch(exception_io_data) { + have_id3v2 = false; + } + + if (have_id3v2) { + // Disregard empty ID3v2 + if (id3v2.meta_get_count() == 0 && id3v2.get_replaygain().get_value_count() == 0) { + have_id3v2 = false; + } + } + + if (!have_id3v2 || !p_file->is_remote()) try { + read_trailing(p_file,trailing,p_abort); + } catch(exception_io_data) { + have_trailing = false; + } + + if (!have_id3v2 && !have_trailing) throw exception_tag_not_found(); + + if (have_id3v2) { + p_info._set_tag(id3v2); + if (have_trailing) p_info._add_tag(trailing); + } else { + p_info._set_tag(trailing); + } +} + +void tag_processor::skip_id3v2(const service_ptr_t & p_file,t_filesize & p_size_skipped,abort_callback & p_abort) { + tag_processor_id3v2::g_skip(p_file,p_size_skipped,p_abort); +} + +bool tag_processor::is_id3v1_sufficient(const file_info & p_info) +{ + return tag_processor_trailing::get()->is_id3v1_sufficient(p_info); +} + +void tag_processor::truncate_to_id3v1(file_info & p_info) +{ + tag_processor_trailing::get()->truncate_to_id3v1(p_info); +} \ No newline at end of file diff --git a/foobar2000/SDK/tag_processor.h b/foobar2000/SDK/tag_processor.h new file mode 100644 index 0000000..102225b --- /dev/null +++ b/foobar2000/SDK/tag_processor.h @@ -0,0 +1,104 @@ +#pragma once + +PFC_DECLARE_EXCEPTION(exception_tag_not_found,exception_io_data,"Tag not found"); + +//! Callback interface for write-tags-to-temp-file-and-swap scheme, used for ID3v2 tag updates and such where entire file needs to be rewritten. +//! As a speed optimization, file content can be copied to a temporary file in the same directory as the file being updated, and then source file can be swapped for the newly created file with updated tags. +//! This also gives better security against data loss on crash compared to rewriting the file in place and using memory or generic temporary file APIs to store content being rewritten. +class NOVTABLE tag_write_callback { +public: + //! Called only once per operation (or not called at all when operation being performed can be done in-place). + //! Requests a temporary file to be created in the same directory + virtual bool open_temp_file(service_ptr_t & p_out,abort_callback & p_abort) = 0; +protected: + tag_write_callback() {} + ~tag_write_callback() {} +private: + tag_write_callback(const tag_write_callback &) = delete; + void operator=(const tag_write_callback &) = delete; +}; + +class tag_write_callback_dummy : public tag_write_callback { +public: + bool open_temp_file(service_ptr_t & p_out,abort_callback & p_abort) {return false;} +}; + +//! For internal use - call tag_processor namespace methods instead. +class NOVTABLE tag_processor_id3v2 : public service_base +{ +public: + virtual void read(const service_ptr_t & p_file,file_info & p_info,abort_callback & p_abort) = 0; + virtual void write(const service_ptr_t & p_file,const file_info & p_info,abort_callback & p_abort) = 0; + virtual void write_ex(tag_write_callback & p_callback,const service_ptr_t & p_file,const file_info & p_info,abort_callback & p_abort) = 0; + + static bool g_get(service_ptr_t & p_out); + static void g_skip(const service_ptr_t & p_file,t_filesize & p_size_skipped,abort_callback & p_abort); + static void g_skip_at(const service_ptr_t & p_file,t_filesize p_base, t_filesize & p_size_skipped,abort_callback & p_abort); + static t_size g_multiskip(const service_ptr_t & p_file,t_filesize & p_size_skipped,abort_callback & p_abort); + static void g_remove(const service_ptr_t & p_file,t_filesize & p_size_removed,abort_callback & p_abort); + static void g_remove_ex(tag_write_callback & p_callback,const service_ptr_t & p_file,t_filesize & p_size_removed,abort_callback & p_abort); + static uint32_t g_tagsize(const void* pHeader10bytes); + + FB2K_MAKE_SERVICE_COREAPI(tag_processor_id3v2); +}; + +//! For internal use - call tag_processor namespace methods instead. +class NOVTABLE tag_processor_trailing : public service_base +{ +public: + enum { + flag_apev2 = 1, + flag_id3v1 = 2, + }; + + virtual void read(const service_ptr_t & p_file,file_info & p_info,abort_callback & p_abort) = 0; + virtual void write(const service_ptr_t & p_file,const file_info & p_info,unsigned p_flags,abort_callback & p_abort) = 0; + virtual void remove(const service_ptr_t & p_file,abort_callback & p_abort) = 0; + virtual bool is_id3v1_sufficient(const file_info & p_info) = 0; + virtual void truncate_to_id3v1(file_info & p_info) = 0; + virtual void read_ex(const service_ptr_t & p_file,file_info & p_info,t_filesize & p_tagoffset,abort_callback & p_abort) = 0; + + void write_id3v1(const service_ptr_t & p_file,const file_info & p_info,abort_callback & p_abort); + void write_apev2(const service_ptr_t & p_file,const file_info & p_info,abort_callback & p_abort); + void write_apev2_id3v1(const service_ptr_t & p_file,const file_info & p_info,abort_callback & p_abort); + + + FB2K_MAKE_SERVICE_COREAPI(tag_processor_trailing); +}; + +namespace tag_processor { + //! Strips all recognized tags from the file and writes an ID3v1 tag with specified info. + void write_id3v1(const service_ptr_t & p_file,const file_info & p_info,abort_callback & p_abort); + //! Strips all recognized tags from the file and writes an APEv2 tag with specified info. + void write_apev2(const service_ptr_t & p_file,const file_info & p_info,abort_callback & p_abort); + //! Strips all recognized tags from the file and writes ID3v1+APEv2 tags with specified info. + void write_apev2_id3v1(const service_ptr_t & p_file,const file_info & p_info,abort_callback & p_abort); + //! Strips all recognized tags from the file and writes an ID3v2 tag with specified info. + void write_id3v2(const service_ptr_t & p_file,const file_info & p_info,abort_callback & p_abort); + //! Strips all recognized tags from the file and writes ID3v1+ID3v2 tags with specified info. + void write_id3v2_id3v1(const service_ptr_t & p_file,const file_info & p_info,abort_callback & p_abort); + //! Strips all recognized tags from the file and writes new tags with specified info according to parameters. + void write_multi(const service_ptr_t & p_file,const file_info & p_info,abort_callback & p_abort,bool p_write_id3v1,bool p_write_id3v2,bool p_write_apev2); + //! Strips all recognized tags from the file and writes new tags with specified info according to parameters. Extended to allow write-tags-to-temp-file-and-swap scheme. + void write_multi_ex(tag_write_callback & p_callback,const service_ptr_t & p_file,const file_info & p_info,abort_callback & p_abort,bool p_write_id3v1,bool p_write_id3v2,bool p_write_apev2); + //! Removes trailing tags from the file. + void remove_trailing(const service_ptr_t & p_file,abort_callback & p_abort); + //! Removes ID3v2 tags from the file. Returns true when a tag was removed, false when the file was not altered. + bool remove_id3v2(const service_ptr_t & p_file,abort_callback & p_abort); + //! Removes ID3v2 and trailing tags from specified file (not to be confused with trailing ID3v2 which are not yet supported). + void remove_id3v2_trailing(const service_ptr_t & p_file,abort_callback & p_abort); + //! Reads trailing tags from the file. + void read_trailing(const service_ptr_t & p_file,file_info & p_info,abort_callback & p_abort); + //! Reads trailing tags from the file. Extended version, returns offset at which parsed tags start. + void read_trailing_ex(const service_ptr_t & p_file,file_info & p_info,t_filesize & p_tagoffset,abort_callback & p_abort); + //! Reads ID3v2 tags from specified file. + void read_id3v2(const service_ptr_t & p_file,file_info & p_info,abort_callback & p_abort); + //! Reads ID3v2 and trailing tags from specified file (not to be confused with trailing ID3v2 which are not yet supported). + void read_id3v2_trailing(const service_ptr_t & p_file,file_info & p_info,abort_callback & p_abort); + + void skip_id3v2(const service_ptr_t & p_file,t_filesize & p_size_skipped,abort_callback & p_abort); + + bool is_id3v1_sufficient(const file_info & p_info); + void truncate_to_id3v1(file_info & p_info); + +}; diff --git a/foobar2000/SDK/tag_processor_id3v2.cpp b/foobar2000/SDK/tag_processor_id3v2.cpp new file mode 100644 index 0000000..410d1f8 --- /dev/null +++ b/foobar2000/SDK/tag_processor_id3v2.cpp @@ -0,0 +1,112 @@ +#include "foobar2000.h" + +bool tag_processor_id3v2::g_get(service_ptr_t & p_out) +{ + p_out = get(); + return true; +} + +void tag_processor_id3v2::g_remove(const service_ptr_t & p_file,t_uint64 & p_size_removed,abort_callback & p_abort) { + tag_write_callback_dummy cb; + g_remove_ex(cb,p_file,p_size_removed,p_abort); +} + +void tag_processor_id3v2::g_remove_ex(tag_write_callback & p_callback,const service_ptr_t & p_file,t_uint64 & p_size_removed,abort_callback & p_abort) +{ + p_file->ensure_seekable(); + + t_filesize len; + + len = p_file->get_size(p_abort); + + if (len == filesize_invalid) throw exception_io_no_length(); + + p_file->seek(0,p_abort); + + t_uint64 offset; + g_multiskip(p_file,offset,p_abort); + + if (offset>0 && offset temp; + if (p_callback.open_temp_file(temp,p_abort)) { + file::g_transfer_object(p_file,temp,len,p_abort); + } else { + if (len > 16*1024*1024) filesystem::g_open_temp(temp,p_abort); + else filesystem::g_open_tempmem(temp,p_abort); + file::g_transfer_object(p_file,temp,len,p_abort); + p_file->seek(0,p_abort); + p_file->set_eof(p_abort); + temp->seek(0,p_abort); + file::g_transfer_object(temp,p_file,len,p_abort); + } + } + p_size_removed = offset; +} + +t_size tag_processor_id3v2::g_multiskip(const service_ptr_t & p_file,t_filesize & p_size_skipped,abort_callback & p_abort) { + t_filesize offset = 0; + t_size count = 0; + for(;;) { + t_filesize delta; + g_skip_at(p_file, offset, delta, p_abort); + if (delta == 0) break; + offset += delta; + ++count; + } + p_size_skipped = offset; + return count; +} + +uint32_t tag_processor_id3v2::g_tagsize(const void* pHeader10bytes) { + const uint8_t* tmp = (const uint8_t*)pHeader10bytes; + if ( 0 != memcmp(tmp, "ID3", 3) || (tmp[5] & 0x0F) != 0 || ((tmp[6] | tmp[7] | tmp[8] | tmp[9]) & 0x80) != 0 ) { + return 0; + } + + int FooterPresent = tmp[5] & 0x10; + + uint32_t ret; + ret = tmp[6] << 21; + ret += tmp[7] << 14; + ret += tmp[8] << 7; + ret += tmp[9]; + ret += 10; + if (FooterPresent) ret += 10; + return ret; +} + +void tag_processor_id3v2::g_skip(const service_ptr_t & p_file,t_uint64 & p_size_skipped,abort_callback & p_abort) { + g_skip_at(p_file, 0, p_size_skipped, p_abort); +} + +void tag_processor_id3v2::g_skip_at(const service_ptr_t & p_file,t_filesize p_base, t_filesize & p_size_skipped,abort_callback & p_abort) { + + unsigned char tmp[10]; + + p_file->seek ( p_base, p_abort ); + + if (p_file->read( tmp, sizeof(tmp), p_abort) != sizeof(tmp)) { + p_file->seek ( p_base, p_abort ); + p_size_skipped = 0; + return; + } + + uint32_t ret = g_tagsize(tmp); + if (ret == 0) { + p_file->seek(p_base, p_abort); + p_size_skipped = 0; + return; + } + + try { + p_file->seek ( p_base + ret, p_abort ); + } catch(exception_io_seek_out_of_range) { + p_file->seek( p_base, p_abort ); + p_size_skipped = 0; + return; + } + + p_size_skipped = ret; +} diff --git a/foobar2000/SDK/threaded_process.cpp b/foobar2000/SDK/threaded_process.cpp new file mode 100644 index 0000000..a58ea9b --- /dev/null +++ b/foobar2000/SDK/threaded_process.cpp @@ -0,0 +1,118 @@ +#include "foobar2000.h" + +void threaded_process_status::set_progress(t_size p_state,t_size p_max) +{ + set_progress( progress_min + MulDiv_Size(p_state,progress_max-progress_min,p_max) ); +} + +void threaded_process_status::set_progress_secondary(t_size p_state,t_size p_max) +{ + set_progress_secondary( progress_min + MulDiv_Size(p_state,progress_max-progress_min,p_max) ); +} + +void threaded_process_status::set_progress_float(double p_state) +{ + if (p_state < 0.0) set_progress(progress_min); + else if (p_state < 1.0) set_progress( progress_min + (t_size)(p_state * (progress_max - progress_min))); + else set_progress(progress_max); +} + +void threaded_process_status::set_progress_secondary_float(double p_state) +{ + if (p_state < 0.0) set_progress_secondary(progress_min); + else if (p_state < 1.0) set_progress_secondary( progress_min + (t_size)(p_state * (progress_max - progress_min))); + else set_progress_secondary(progress_max); +} + + +bool threaded_process::g_run_modal(service_ptr_t p_callback,unsigned p_flags,HWND p_parent,const char * p_title,t_size p_title_len) +{ + return threaded_process::get()->run_modal(p_callback,p_flags,p_parent,p_title,p_title_len); +} + +bool threaded_process::g_run_modeless(service_ptr_t p_callback,unsigned p_flags,HWND p_parent,const char * p_title,t_size p_title_len) +{ + return threaded_process::get()->run_modeless(p_callback,p_flags,p_parent,p_title,p_title_len); +} + +bool threaded_process::g_query_preventStandby() { + static const GUID guid_preventStandby = { 0x7aafeffb, 0x5f11, 0x483f, { 0xac, 0x65, 0x61, 0xec, 0x9c, 0x70, 0x37, 0x4e } }; + advconfig_entry_checkbox::ptr obj; + if (advconfig_entry::g_find_t(obj, guid_preventStandby)) { + return obj->get_state(); + } else { + return false; + } +} + +enum { + set_items_max_characters = 80 +}; + +void threaded_process_status::set_items(pfc::list_base_const_t const & items) { + const size_t count = items.get_count(); + if (count == 0) return; + if (count == 1) { set_item_path(items[0]); } + pfc::string8 acc; + + for (size_t w = 0; w < count; ++w) { + pfc::string8 name = pfc::string_filename_ext(items[w]); + if (w > 0 && acc.length() + name.length() > set_items_max_characters) { + acc << " and " << (count - w) << " more"; + break; + } + if (w > 0) acc << ", "; + acc << name; + } + + set_item(acc); +} + +void threaded_process_status::set_items(metadb_handle_list_cref items) { + const size_t count = items.get_count(); + if ( count == 0 ) return; + if ( count == 1 ) { set_item_path(items[0]->get_path()); } + pfc::string8 acc; + + for( size_t w = 0; w < count; ++w ) { + pfc::string8 name = pfc::string_filename_ext(items[w]->get_path()); + if ( w > 0 && acc.length() + name.length() > set_items_max_characters) { + acc << " and " << (count-w) << " more"; + break; + } + if (w > 0) acc << ", "; + acc << name; + } + + set_item(acc); +} + + +void threaded_process_callback_lambda::on_init(ctx_t p_ctx) { + if (m_on_init) m_on_init(p_ctx); +} + +void threaded_process_callback_lambda::run(threaded_process_status & p_status, abort_callback & p_abort) { + m_run(p_status, p_abort); +} + +void threaded_process_callback_lambda::on_done(ctx_t p_ctx, bool p_was_aborted) { + if (m_on_done) m_on_done(p_ctx, p_was_aborted); +} + +service_ptr_t threaded_process_callback_lambda::create() { + return new service_impl_t(); +} + +service_ptr_t threaded_process_callback_lambda::create(run_t f) { + auto obj = create(); + obj->m_run = f; + return obj; +} +service_ptr_t threaded_process_callback_lambda::create(on_init_t f1, run_t f2, on_done_t f3) { + auto obj = create(); + obj->m_on_init = f1; + obj->m_run = f2; + obj->m_on_done = f3; + return obj; +} diff --git a/foobar2000/SDK/threaded_process.h b/foobar2000/SDK/threaded_process.h new file mode 100644 index 0000000..d92354a --- /dev/null +++ b/foobar2000/SDK/threaded_process.h @@ -0,0 +1,153 @@ +#pragma once +#include + +//! Callback class passed to your threaded_process client code; allows you to give various visual feedback to the user. +class threaded_process_status { +public: + enum {progress_min = 0, progress_max = 5000}; + + //! Sets the primary progress bar state; scale from progress_min to progress_max. + virtual void set_progress(t_size p_state) {} + //! Sets the secondary progress bar state; scale from progress_min to progress_max. + virtual void set_progress_secondary(t_size p_state) {} + //! Sets the currently progressed item label. When working with files, you should use set_file_path() instead. + virtual void set_item(const char * p_item,t_size p_item_len = ~0) {} + //! Sets the currently progressed item label; treats the label as a file path. + virtual void set_item_path(const char * p_item,t_size p_item_len = ~0) {} + //! Sets the title of the dialog. You normally don't need this function unless you want to override the title you set when initializing the threaded_process. + virtual void set_title(const char * p_title,t_size p_title_len = ~0) {} + //! Should not be used. + virtual void force_update() {} + //! Returns whether the process is paused. + virtual bool is_paused() {return false;} + + //! Checks if process is paused and sleeps if needed; returns false when process should be aborted, true on success. \n + //! You should use poll_pause() instead of calling this directly. + virtual bool process_pause() {return true;} + + //! Automatically sleeps if the process is paused. + void poll_pause() {if (!process_pause()) throw exception_aborted();} + + //! Helper; sets primary progress with a custom scale. + void set_progress(t_size p_state,t_size p_max); + //! Helper; sets secondary progress with a custom scale. + void set_progress_secondary(t_size p_state,t_size p_max); + //! Helper; sets primary progress with a float 0..1 scale. + void set_progress_float(double p_state); + //! Helper; sets secondary progress with a float 0..1 scale. + void set_progress_secondary_float(double p_state); + //! Helper; gracefully reports multiple items being concurrently worked on. + void set_items( metadb_handle_list_cref items ); + void set_items( pfc::list_base_const_t const & paths ); +}; + +//! Fb2k mobile compatibility +class threaded_process_context { +public: + static HWND g_default() { return core_api::get_main_window(); } +}; + +//! Callback class for the threaded_process API. You must implement this to create your own threaded_process client. +class NOVTABLE threaded_process_callback : public service_base { +public: + typedef HWND ctx_t; // fb2k mobile compatibility + + //! Called from the main thread before spawning the worker thread. \n + //! Note that you should not access the window handle passed to on_init() in the worker thread later on. + virtual void on_init(ctx_t p_wnd) {} + //! Called from the worker thread. Do all the hard work here. + virtual void run(threaded_process_status & p_status,abort_callback & p_abort) = 0; + //! Called after the worker thread has finished executing. + virtual void on_done(ctx_t p_wnd,bool p_was_aborted) {} + + //! Safely prevent destruction from worker threads. + static bool serviceRequiresMainThreadDestructor() { return true; } + + FB2K_MAKE_SERVICE_INTERFACE(threaded_process_callback,service_base); +}; + + +//! The threaded_process API allows you to easily put timeconsuming tasks in worker threads, with progress dialog giving nice feedback to the user. \n +//! Thanks to this API you can perform such tasks with no user interface related programming at all. +class NOVTABLE threaded_process : public service_base { +public: + enum { + //! Shows the "abort" button. + flag_show_abort = 1, + //! Obsolete, do not use. + flag_show_minimize = 1 << 1, + //! Shows a progress bar. + flag_show_progress = 1 << 2, + //! Shows dual progress bars; implies flag_show_progress. + flag_show_progress_dual = 1 << 3, + //! Shows the item being currently processed. + flag_show_item = 1 << 4, + //! Shows the "pause" button. + flag_show_pause = 1 << 5, + //! Obsolete, do not use. + flag_high_priority = 1 << 6, + //! Make the dialog hidden by default and show it only if the operation could not be completed after 500ms. Implies flag_no_focus. Relevant only to modeless dialogs. + flag_show_delayed = 1 << 7, + //! Do not focus the dialog by default. + flag_no_focus = 1 << 8, + }; + + //! Runs a synchronous threaded_process operation - the function does not return until the operation has completed, though the app UI is not frozen and the operation is abortable. \n + //! This API is obsolete and should not be used. Please use run_modeless() instead if possible. + //! @param p_callback Interface to your threaded_process client. + //! @param p_flags Flags describing requested dialog functionality. See threaded_process::flag_* constants. + //! @param p_parent Parent window for the progress dialog - typically core_api::get_main_window(). + //! @param p_title Initial title of the dialog. + //! @returns True if the operation has completed normally, false if the user has aborted the operation. In case of a catastrophic failure such as dialog creation failure, exceptions will be thrown. + virtual bool run_modal(service_ptr_t p_callback,unsigned p_flags,HWND p_parent,const char * p_title,t_size p_title_len = ~0) = 0; + //! Runs an asynchronous threaded_process operation. + //! @param p_callback Interface to your threaded_process client. + //! @param p_flags Flags describing requested dialog functionality. See threaded_process::flag_* constants. + //! @param p_parent Parent window for the progress dialog - typically core_api::get_main_window(). + //! @param p_title Initial title of the dialog. + //! @returns True, always; the return value should be ignored. In case of a catastrophic failure such as dialog creation failure, exceptions will be thrown. + virtual bool run_modeless(service_ptr_t p_callback,unsigned p_flags,HWND p_parent,const char * p_title,t_size p_title_len = ~0) = 0; + + + //! Helper invoking run_modal(). + static bool g_run_modal(service_ptr_t p_callback,unsigned p_flags,HWND p_parent,const char * p_title,t_size p_title_len = ~0); + //! Helper invoking run_modeless(). + static bool g_run_modeless(service_ptr_t p_callback,unsigned p_flags,HWND p_parent,const char * p_title,t_size p_title_len = ~0); + + //! Queries user settings; returns whether various timeconsuming tasks should be blocking machine standby. + static bool g_query_preventStandby(); + + FB2K_MAKE_SERVICE_COREAPI(threaded_process); +}; + + +//! Helper - forward threaded_process_callback calls to a service object that for whatever reason cannot publish threaded_process_callback API by itself. +template class threaded_process_callback_redir : public threaded_process_callback { +public: + threaded_process_callback_redir(TTarget * target) : m_target(target) {} + void on_init(HWND p_wnd) {m_target->tpc_on_init(p_wnd);} + void run(threaded_process_status & p_status,abort_callback & p_abort) {m_target->tpc_run(p_status, p_abort);} + void on_done(HWND p_wnd,bool p_was_aborted) {m_target->tpc_on_done(p_wnd, p_was_aborted); } +private: + const service_ptr_t m_target; +}; + +//! Helper - lambda based threaded_process_callback implementation +class threaded_process_callback_lambda : public threaded_process_callback { +public: + typedef std::function on_init_t; + typedef std::function run_t; + typedef std::function on_done_t; + + static service_ptr_t create(); + static service_ptr_t create(run_t f); + static service_ptr_t create(on_init_t f1, run_t f2, on_done_t f3); + + on_init_t m_on_init; + run_t m_run; + on_done_t m_on_done; + + void on_init(ctx_t p_ctx); + void run(threaded_process_status & p_status, abort_callback & p_abort); + void on_done(ctx_t p_ctx, bool p_was_aborted); +}; diff --git a/foobar2000/SDK/titleformat.cpp b/foobar2000/SDK/titleformat.cpp new file mode 100644 index 0000000..584b382 --- /dev/null +++ b/foobar2000/SDK/titleformat.cpp @@ -0,0 +1,187 @@ +#include "foobar2000.h" + + +#define tf_profiler(x) // profiler(x) + +void titleformat_compiler::remove_color_marks(const char * src,pfc::string_base & out)//helper +{ + out.reset(); + while(*src) + { + if (*src==3) + { + src++; + while(*src && *src!=3) src++; + if (*src==3) src++; + } + else out.add_byte(*src++); + } +} + +static bool test_for_bad_char(const char * source,t_size source_char_len,const char * reserved) +{ + return pfc::strstr_ex(reserved,(t_size)(-1),source,source_char_len) != (t_size)(-1); +} + +void titleformat_compiler::remove_forbidden_chars(titleformat_text_out * p_out,const GUID & p_inputtype,const char * p_source,t_size p_source_len,const char * p_reserved_chars) +{ + if (p_reserved_chars == 0 || *p_reserved_chars == 0) + { + p_out->write(p_inputtype,p_source,p_source_len); + } + else + { + p_source_len = pfc::strlen_max(p_source,p_source_len); + t_size index = 0; + t_size good_byte_count = 0; + while(index < p_source_len) + { + t_size delta = pfc::utf8_char_len(p_source + index,p_source_len - index); + if (delta == 0) break; + if (test_for_bad_char(p_source+index,delta,p_reserved_chars)) + { + if (good_byte_count > 0) {p_out->write(p_inputtype,p_source+index-good_byte_count,good_byte_count);good_byte_count=0;} + p_out->write(p_inputtype,"_",1); + } + else + { + good_byte_count += delta; + } + index += delta; + } + if (good_byte_count > 0) {p_out->write(p_inputtype,p_source+index-good_byte_count,good_byte_count);good_byte_count=0;} + } +} + +void titleformat_compiler::remove_forbidden_chars_string_append(pfc::string_receiver & p_out,const char * p_source,t_size p_source_len,const char * p_reserved_chars) +{ + titleformat_text_out_impl_string tfout(p_out); + remove_forbidden_chars(&tfout,pfc::guid_null,p_source,p_source_len,p_reserved_chars); +} + +void titleformat_compiler::remove_forbidden_chars_string(pfc::string_base & p_out,const char * p_source,t_size p_source_len,const char * p_reserved_chars) +{ + p_out.reset(); + remove_forbidden_chars_string_append(p_out,p_source,p_source_len,p_reserved_chars); +} + +bool titleformat_hook_impl_file_info::process_field(titleformat_text_out * p_out,const char * p_name,t_size p_name_length,bool & p_found_flag) { + return m_api->process_field(*m_info,m_location,p_out,p_name,p_name_length,p_found_flag); +} +bool titleformat_hook_impl_file_info::process_function(titleformat_text_out * p_out,const char * p_name,t_size p_name_length,titleformat_hook_function_params * p_params,bool & p_found_flag) { + return m_api->process_function(*m_info,m_location,p_out,p_name,p_name_length,p_params,p_found_flag); +} + +void titleformat_object::run_hook(const playable_location & p_location,const file_info * p_source,titleformat_hook * p_hook,pfc::string_base & p_out,titleformat_text_filter * p_filter) +{ + if (p_hook) + { + titleformat_hook_impl_file_info hook1(p_location, p_source); + titleformat_hook_impl_splitter hook2(p_hook, &hook1); + run(&hook2,p_out,p_filter); + } + else + { + titleformat_hook_impl_file_info hook(p_location, p_source); + run(&hook,p_out,p_filter); + } +} + +void titleformat_object::run_simple(const playable_location & p_location,const file_info * p_source,pfc::string_base & p_out) +{ + titleformat_hook_impl_file_info hook(p_location, p_source); + run(&hook,p_out,NULL); +} + +t_size titleformat_hook_function_params::get_param_uint(t_size index) +{ + const char * str; + t_size str_len; + get_param(index,str,str_len); + return pfc::atoui_ex(str,str_len); +} + + +void titleformat_text_out_impl_filter_chars::write(const GUID & p_inputtype,const char * p_data,t_size p_data_length) +{ + titleformat_compiler::remove_forbidden_chars(m_chain,p_inputtype,p_data,p_data_length,m_restricted_chars); +} + +bool titleformat_hook_impl_splitter::process_field(titleformat_text_out * p_out,const char * p_name,t_size p_name_length,bool & p_found_flag) +{ + p_found_flag = false; + if (m_hook1 && m_hook1->process_field(p_out,p_name,p_name_length,p_found_flag)) return true; + p_found_flag = false; + if (m_hook2 && m_hook2->process_field(p_out,p_name,p_name_length,p_found_flag)) return true; + p_found_flag = false; + return false; +} + +bool titleformat_hook_impl_splitter::process_function(titleformat_text_out * p_out,const char * p_name,t_size p_name_length,titleformat_hook_function_params * p_params,bool & p_found_flag) +{ + p_found_flag = false; + if (m_hook1 && m_hook1->process_function(p_out,p_name,p_name_length,p_params,p_found_flag)) return true; + p_found_flag = false; + if (m_hook2 && m_hook2->process_function(p_out,p_name,p_name_length,p_params,p_found_flag)) return true; + p_found_flag = false; + return false; +} + +void titleformat_text_out::write_int_padded(const GUID & p_inputtype,t_int64 val,t_int64 maxval) +{ + unsigned width = 0; + while(maxval > 0) {maxval/=10;width++;} + write(p_inputtype,pfc::format_int(val,width)); +} + +void titleformat_text_out::write_int(const GUID & p_inputtype,t_int64 val) +{ + write(p_inputtype,pfc::format_int(val)); +} +void titleformat_text_filter_impl_reserved_chars::write(const GUID & p_inputtype,pfc::string_receiver & p_out,const char * p_data,t_size p_data_length) +{ + if (p_inputtype == titleformat_inputtypes::meta) titleformat_compiler::remove_forbidden_chars_string_append(p_out,p_data,p_data_length,m_reserved_chars); + else p_out.add_string(p_data,p_data_length); +} + +void titleformat_compiler::run(titleformat_hook * p_source,pfc::string_base & p_out,const char * p_spec) +{ + service_ptr_t ptr; + if (!compile(ptr,p_spec)) p_out = "[COMPILATION ERROR]"; + else ptr->run(p_source,p_out,NULL); +} + +void titleformat_compiler::compile_safe(service_ptr_t & p_out,const char * p_spec) +{ + if (!compile(p_out,p_spec)) { + compile_force(p_out,"%filename%"); + } +} + + +namespace titleformat_inputtypes { + const GUID meta = { 0xcd839c8e, 0x5c66, 0x4ae1, { 0x8d, 0xad, 0x71, 0x1f, 0x86, 0x0, 0xa, 0xe3 } }; + const GUID unknown = { 0x673aa1cd, 0xa7a8, 0x40c8, { 0xbf, 0x9b, 0x34, 0x37, 0x99, 0x29, 0x16, 0x3b } }; +}; + +void titleformat_text_filter_impl_filename_chars::write(const GUID & p_inputType,pfc::string_receiver & p_out,const char * p_data,t_size p_dataLength) { + if (p_inputType == titleformat_inputtypes::meta) { + //slightly inefficient... + p_out.add_string( pfc::io::path::replaceIllegalNameChars(pfc::string(p_data,p_dataLength)).ptr()); + } else p_out.add_string(p_data,p_dataLength); +} + +void titleformat_compiler::compile_safe_ex(titleformat_object::ptr & p_out,const char * p_spec,const char * p_fallback) { + if (!compile(p_out,p_spec)) compile_force(p_out,p_fallback); +} + + +void titleformat_text_filter_nontext_chars::write(const GUID & p_inputtype,pfc::string_receiver & p_out,const char * p_data,t_size p_data_length) { + for(t_size walk = 0;;) { + t_size base = walk; + while(walk < p_data_length && !isReserved(p_data[walk]) && p_data[walk] != 0) walk++; + p_out.add_string(p_data+base,walk-base); + if (walk >= p_data_length || p_data[walk] == 0) break; + p_out.add_byte('_'); walk++; + } +} diff --git a/foobar2000/SDK/titleformat.h b/foobar2000/SDK/titleformat.h new file mode 100644 index 0000000..4ffda9e --- /dev/null +++ b/foobar2000/SDK/titleformat.h @@ -0,0 +1,243 @@ +#pragma once + +namespace titleformat_inputtypes { + extern const GUID meta, unknown; +}; + +class NOVTABLE titleformat_text_out { +public: + virtual void write(const GUID & p_inputtype,const char * p_data,t_size p_data_length = ~0) = 0; + void write_int(const GUID & p_inputtype,t_int64 val); + void write_int_padded(const GUID & p_inputtype,t_int64 val,t_int64 maxval); +protected: + titleformat_text_out() {} + ~titleformat_text_out() {} +}; + + +class NOVTABLE titleformat_text_filter { +public: + virtual void write(const GUID & p_inputtype,pfc::string_receiver & p_out,const char * p_data,t_size p_data_length) = 0; +protected: + titleformat_text_filter() {} + ~titleformat_text_filter() {} +}; + +class NOVTABLE titleformat_hook_function_params +{ +public: + virtual t_size get_param_count() = 0; + virtual void get_param(t_size index,const char * & p_string,t_size & p_string_len) = 0;//warning: not a null-terminated string + + //helper + t_size get_param_uint(t_size index); +}; + +class NOVTABLE titleformat_hook +{ +public: + virtual bool process_field(titleformat_text_out * p_out,const char * p_name,t_size p_name_length,bool & p_found_flag) = 0; + virtual bool process_function(titleformat_text_out * p_out,const char * p_name,t_size p_name_length,titleformat_hook_function_params * p_params,bool & p_found_flag) = 0; +}; +//! Represents precompiled executable title-formatting script. Use titleformat_compiler to instantiate; do not reimplement. +class NOVTABLE titleformat_object : public service_base +{ +public: + virtual void run(titleformat_hook * p_source,pfc::string_base & p_out,titleformat_text_filter * p_filter)=0; + + void run_hook(const playable_location & p_location,const file_info * p_source,titleformat_hook * p_hook,pfc::string_base & p_out,titleformat_text_filter * p_filter); + void run_simple(const playable_location & p_location,const file_info * p_source,pfc::string_base & p_out); + + FB2K_MAKE_SERVICE_INTERFACE(titleformat_object,service_base); +}; + +//! Standard service for instantiating titleformat_object. Implemented by the core; do not reimplement. +//! To instantiate, use titleformat_compiler::get(). +class NOVTABLE titleformat_compiler : public service_base { + FB2K_MAKE_SERVICE_COREAPI(titleformat_compiler); +public: + //! Returns false in case of a compilation error. + virtual bool compile(titleformat_object::ptr & p_out,const char * p_spec) = 0; + //! Helper; + void run(titleformat_hook * p_source,pfc::string_base & p_out,const char * p_spec); + //! Should never fail, falls back to %filename% in case of failure. + void compile_safe(titleformat_object::ptr & p_out,const char * p_spec); + + //! Falls back to p_fallback in case of failure. + void compile_safe_ex(titleformat_object::ptr & p_out,const char * p_spec,const char * p_fallback = ""); + + //! Throws a bug check exception when script can't be compiled. For use with hardcoded scripts only. + void compile_force(titleformat_object::ptr & p_out,const char * p_spec) {if (!compile(p_out,p_spec)) uBugCheck();} + + + static void remove_color_marks(const char * src,pfc::string_base & out);//helper + static void remove_forbidden_chars(titleformat_text_out * p_out,const GUID & p_inputtype,const char * p_source,t_size p_source_len,const char * p_forbidden_chars); + static void remove_forbidden_chars_string_append(pfc::string_receiver & p_out,const char * p_source,t_size p_source_len,const char * p_forbidden_chars); + static void remove_forbidden_chars_string(pfc::string_base & p_out,const char * p_source,t_size p_source_len,const char * p_forbidden_chars); +}; + + +class titleformat_object_wrapper { +public: + titleformat_object_wrapper(const char * p_script) { + titleformat_compiler::get()->compile_force(m_script,p_script); + } + + operator const service_ptr_t &() const {return m_script;} + +private: + service_ptr_t m_script; +}; + + +//helpers + + +class titleformat_text_out_impl_filter_chars : public titleformat_text_out +{ +public: + inline titleformat_text_out_impl_filter_chars(titleformat_text_out * p_chain,const char * p_restricted_chars) + : m_chain(p_chain), m_restricted_chars(p_restricted_chars) {} + void write(const GUID & p_inputtype,const char * p_data,t_size p_data_length); +private: + titleformat_text_out * m_chain; + const char * m_restricted_chars; +}; + +class titleformat_text_out_impl_string : public titleformat_text_out { +public: + titleformat_text_out_impl_string(pfc::string_receiver & p_string) : m_string(p_string) {} + void write(const GUID & p_inputtype,const char * p_data,t_size p_data_length) {m_string.add_string(p_data,p_data_length);} +private: + pfc::string_receiver & m_string; +}; + +class titleformat_common_methods : public service_base { +public: + virtual bool process_field(const file_info & p_info,const playable_location & p_location,titleformat_text_out * p_out,const char * p_name,t_size p_name_length,bool & p_found_flag) = 0; + virtual bool process_function(const file_info & p_info,const playable_location & p_location,titleformat_text_out * p_out,const char * p_name,t_size p_name_length,titleformat_hook_function_params * p_params,bool & p_found_flag) = 0; + virtual bool remap_meta(const file_info & p_info,t_size & p_index, const char * p_name, t_size p_name_length) = 0; + + FB2K_MAKE_SERVICE_COREAPI(titleformat_common_methods); +}; + +class titleformat_hook_impl_file_info : public titleformat_hook +{ +public: + titleformat_hook_impl_file_info(const playable_location & p_location,const file_info * p_info) : m_location(p_location), m_info(p_info) {}//caller must ensure that referenced file_info object is alive as long as the titleformat_hook_impl_file_info instance + bool process_field(titleformat_text_out * p_out,const char * p_name,t_size p_name_length,bool & p_found_flag); + bool process_function(titleformat_text_out * p_out,const char * p_name,t_size p_name_length,titleformat_hook_function_params * p_params,bool & p_found_flag); +protected: + bool remap_meta(t_size & p_index, const char * p_name, t_size p_name_length) {return m_api->remap_meta(*m_info,p_index,p_name,p_name_length);} + const file_info * m_info; +private: + const playable_location & m_location; + const titleformat_common_methods::ptr m_api = titleformat_common_methods::get(); +}; + +class titleformat_hook_impl_splitter : public titleformat_hook { +public: + inline titleformat_hook_impl_splitter(titleformat_hook * p_hook1,titleformat_hook * p_hook2) : m_hook1(p_hook1), m_hook2(p_hook2) {} + bool process_field(titleformat_text_out * p_out,const char * p_name,t_size p_name_length,bool & p_found_flag); + bool process_function(titleformat_text_out * p_out,const char * p_name,t_size p_name_length,titleformat_hook_function_params * p_params,bool & p_found_flag); +private: + titleformat_hook * m_hook1, * m_hook2; +}; + +class titleformat_text_filter_impl_reserved_chars : public titleformat_text_filter { +public: + titleformat_text_filter_impl_reserved_chars(const char * p_reserved_chars) : m_reserved_chars(p_reserved_chars) {} + virtual void write(const GUID & p_inputtype,pfc::string_receiver & p_out,const char * p_data,t_size p_data_length); +private: + const char * m_reserved_chars; +}; + +class titleformat_text_filter_impl_filename_chars : public titleformat_text_filter { +public: + void write(const GUID & p_inputType,pfc::string_receiver & p_out,const char * p_data,t_size p_dataLength); +}; + +class titleformat_text_filter_nontext_chars : public titleformat_text_filter { +public: + inline static bool isReserved(char c) { return c >= 0 && c < 0x20; } + void write(const GUID & p_inputtype,pfc::string_receiver & p_out,const char * p_data,t_size p_data_length); +}; + + + + + + + +class titleformat_hook_impl_list : public titleformat_hook { +public: + titleformat_hook_impl_list(t_size p_index /* zero-based! */,t_size p_total) : m_index(p_index), m_total(p_total) {} + + bool process_field(titleformat_text_out * p_out,const char * p_name,t_size p_name_length,bool & p_found_flag) { + if ( + pfc::stricmp_ascii_ex(p_name,p_name_length,"list_index",~0) == 0 + ) { + p_out->write_int_padded(titleformat_inputtypes::unknown,m_index+1, m_total); + p_found_flag = true; return true; + } else if ( + pfc::stricmp_ascii_ex(p_name,p_name_length,"list_total",~0) == 0 + ) { + p_out->write_int(titleformat_inputtypes::unknown,m_total); + p_found_flag = true; return true; + } else { + p_found_flag = false; return false; + } + } + + bool process_function(titleformat_text_out * p_out,const char * p_name,t_size p_name_length,titleformat_hook_function_params * p_params,bool & p_found_flag) {return false;} + +private: + t_size m_index, m_total; +}; + +class string_formatter_tf : public pfc::string_base { +public: + string_formatter_tf(titleformat_text_out * out, const GUID & inputType = titleformat_inputtypes::meta) : m_out(out), m_inputType(inputType) {} + + const char * get_ptr() const { + uBugCheck(); + } + void add_string(const char * p_string,t_size p_length) { + m_out->write(m_inputType,p_string,p_length); + } + void set_string(const char * p_string,t_size p_length) { + uBugCheck(); + } + void truncate(t_size len) { + uBugCheck(); + } + t_size get_length() const { + uBugCheck(); + } + char * lock_buffer(t_size p_requested_length) { + uBugCheck(); + } + void unlock_buffer() { + uBugCheck(); + } + +private: + titleformat_text_out * const m_out; + const GUID m_inputType; +}; + + +class titleformat_object_cache { +public: + titleformat_object_cache(const char * pattern) : m_pattern(pattern) {} + operator titleformat_object::ptr() { + PFC_ASSERT(core_api::assert_main_thread()); + if (m_obj.is_empty()) { + titleformat_compiler::get()->compile_force(m_obj, m_pattern); + } + return m_obj; + } +private: + const char * const m_pattern; + titleformat_object::ptr m_obj; +}; diff --git a/foobar2000/SDK/track_property.cpp b/foobar2000/SDK/track_property.cpp new file mode 100644 index 0000000..14de537 --- /dev/null +++ b/foobar2000/SDK/track_property.cpp @@ -0,0 +1,70 @@ +#include "foobar2000.h" + +#include "track_property.h" + +namespace { + class track_property_provider_v3_info_source_impl : public track_property_provider_v3_info_source { + public: + track_property_provider_v3_info_source_impl(trackListRef items) : m_items(items) {} + trackInfoContainer::ptr get_info(size_t index) { + return trackGetInfoRef(m_items, index); + } + private: + trackListRef m_items; + }; + + class track_property_callback_v2_proxy : public track_property_callback_v2 { + public: + track_property_callback_v2_proxy(track_property_callback & callback) : m_callback(callback) {} + void set_property(const char * p_group, double p_sortpriority, const char * p_name, const char * p_value) { m_callback.set_property(p_group, p_sortpriority, p_name, p_value); } + bool is_group_wanted(const char*) { return true; } + + private: + track_property_callback & m_callback; + }; + + +} + +void track_property_provider_v3::enumerate_properties(trackListRef p_tracks, track_property_callback & p_out) { + track_property_provider_v3_info_source_impl src(p_tracks); track_property_callback_v2_proxy cb(p_out); enumerate_properties_v3(p_tracks, src, cb); +} + +void track_property_provider_v3::enumerate_properties_v2(trackListRef p_tracks, track_property_callback_v2 & p_out) { + track_property_provider_v3_info_source_impl src(p_tracks); enumerate_properties_v3(p_tracks, src, p_out); +} + +void track_property_provider_v4::enumerate_properties_v3(trackListRef items, track_property_provider_v3_info_source & info, track_property_callback_v2 & callback) { + this->enumerate_properties_v4(items, info, callback, fb2k::noAbort ); +} + +void track_property_provider::enumerate_properties_helper(trackListRef items, track_property_provider_v3_info_source * info, track_property_callback_v2 & callback, abort_callback & abort) { + + abort.check(); + + track_property_provider_v2::ptr v2; + if ( ! this->cast( v2 ) ) { + // no v2 + PFC_ASSERT(core_api::is_main_thread()); + this->enumerate_properties( items, callback ); return; + } + + track_property_provider_v3::ptr v3; + if ( ! (v3 &= v2 ) ) { + // no v3 + PFC_ASSERT(core_api::is_main_thread()); + v2->enumerate_properties_v2( items, callback ); return; + } + + track_property_provider_v3_info_source_impl infoFallback ( items ); + if ( info == nullptr ) info = & infoFallback; + + track_property_provider_v4::ptr v4; + if (! ( v4 &= v3 ) ) { + // no v4 + PFC_ASSERT( core_api::is_main_thread() ); + v3->enumerate_properties_v3( items, *info, callback ); + } else { + v4->enumerate_properties_v4( items, *info, callback, abort ); + } +} diff --git a/foobar2000/SDK/track_property.h b/foobar2000/SDK/track_property.h new file mode 100644 index 0000000..7cd8658 --- /dev/null +++ b/foobar2000/SDK/track_property.h @@ -0,0 +1,98 @@ +#pragma once + +#include "tracks.h" + +//! Callback interface for track_property_provider::enumerate_properties(). +class NOVTABLE track_property_callback { +public: + //! Sets a property list entry to display. Called by track_property_provider::enumerate_properties() implementation. + //! @param p_group Name of group to put the entry in, case-sensitive. Note that non-standard groups are sorted alphabetically. + //! @param p_sortpriority Sort priority of the property inside its group (smaller value means earlier in the list), pass 0 if you don't care (alphabetic order by name used when more than one item has same priority). + //! @param p_name Name of the property. + //! @param p_value Value of the property. + virtual void set_property(const char * p_group,double p_sortpriority,const char * p_name,const char * p_value) = 0; +protected: + track_property_callback() {} + ~track_property_callback() {} + track_property_callback(track_property_callback const &) {}; + void operator=(track_property_callback const &) {}; +}; + +//! Extended version of track_property_callback +class NOVTABLE track_property_callback_v2 : public track_property_callback { +public: + //! Returns a boolean value indicating whether the specified group is wanted; can be used to suppress expensive processing of information that will not be actually shown. \n + //! See also set_property() p_group parameter. + virtual bool is_group_wanted(const char * p_group) = 0; +protected: + ~track_property_callback_v2() {} +}; + +//! \since 1.3 +class NOVTABLE track_property_provider_v3_info_source { +public: + virtual trackInfoContainer::ptr get_info(size_t index) = 0; + +protected: + track_property_provider_v3_info_source() {} + ~track_property_provider_v3_info_source() {} + track_property_provider_v3_info_source( const track_property_provider_v3_info_source & ) {}; + void operator=( const track_property_provider_v3_info_source & ) {}; +}; + +//! Service for adding custom entries in "Properties" tab of the properties dialog. +class NOVTABLE track_property_provider : public service_base { +public: + //! Enumerates properties of specified track list. + //! @param p_tracks List of tracks to enumerate properties on. + //! @param p_out Callback interface receiving enumerated properties. + virtual void enumerate_properties(trackListRef p_tracks, track_property_callback & p_out) = 0; + //! Returns whether specified tech info filed is processed by our service and should not be displayed among unknown fields. + //! @param p_name Name of tech info field being queried. + //! @returns True if the field is among fields processed by this track_property_provider implementation and should not be displayed among unknown fields, false otherwise. + virtual bool is_our_tech_info(const char * p_name) = 0; + + + //! Helper; calls modern versions of this API where appropriate. + //! @param items List of tracks to enumerate properties on. + //! @param info Callback object to fetch info from. Pass null to use a generic implementation querying the metadb. + //! @param callback Callback interface receiving enumerated properties. + //! @param abort The aborter for this operation. + void enumerate_properties_helper(trackListRef items, track_property_provider_v3_info_source * info, track_property_callback_v2 & callback, abort_callback & abort); + + FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(track_property_provider); +}; + +class NOVTABLE track_property_provider_v2 : public track_property_provider { + FB2K_MAKE_SERVICE_INTERFACE(track_property_provider_v2,track_property_provider) +public: + virtual void enumerate_properties_v2(trackListRef p_tracks, track_property_callback_v2 & p_out) = 0; +}; + + +//! \since 1.3 +class NOVTABLE track_property_provider_v3 : public track_property_provider_v2 { + FB2K_MAKE_SERVICE_INTERFACE(track_property_provider_v3,track_property_provider_v2) +public: + virtual void enumerate_properties_v3(trackListRef items, track_property_provider_v3_info_source & info, track_property_callback_v2 & callback) = 0; + + void enumerate_properties(trackListRef p_tracks, track_property_callback & p_out) override; + void enumerate_properties_v2(trackListRef p_tracks, track_property_callback_v2 & p_out) override; +}; + +template +class track_property_provider_factory_t : public service_factory_single_t {}; + +//! \since 1.5 +//! Adds abortability on top of track_property_provider_v3 interface. \n +//! The previous revisions of this API were only legal to call from the main thread. \n +//! track_property_provider_v4 implementers should make NO ASSUMPTIONS about the thread they are in. \n +//! Implementing track_property_provider_v4 declares your class as safe to call from any thread. \n +//! If called via enumerate_properties_v4() method or off-main-thread, the implementation can assume the info source object to be thread-safe. +class NOVTABLE track_property_provider_v4 : public track_property_provider_v3 { + FB2K_MAKE_SERVICE_INTERFACE(track_property_provider_v4, track_property_provider_v3 ); +public: + void enumerate_properties_v3(trackListRef items, track_property_provider_v3_info_source & info, track_property_callback_v2 & callback) override; + + virtual void enumerate_properties_v4(trackListRef items, track_property_provider_v3_info_source & info, track_property_callback_v2 & callback, abort_callback & abort) = 0; +}; diff --git a/foobar2000/SDK/tracks.h b/foobar2000/SDK/tracks.h new file mode 100644 index 0000000..3fe2c80 --- /dev/null +++ b/foobar2000/SDK/tracks.h @@ -0,0 +1,34 @@ +#pragma once + +// Special header with fb2k mobile metadb<=>trackList interop wrappers + +typedef metadb_handle_ptr trackRef; +typedef metadb_handle_list_cref trackListRef; +typedef metadb_handle_list trackListStore; +typedef metadb_info_container trackInfoContainer; + +inline size_t trackCount(trackListRef l) {return l.get_size();} +inline trackRef trackListGetTrack(trackListRef l, size_t n) { return l[n]; } + +// Returns blank info if no info is known, never null. +inline trackInfoContainer::ptr trackGetInfoRef(trackRef t) { return t->get_info_ref(); } +inline trackInfoContainer::ptr trackGetInfoRef(trackListRef l, size_t n) { return trackGetInfoRef(l[n]); } + +// Returns null info if no info is known, contrary to trackGetInfoRef +inline trackInfoContainer::ptr trackGetInfo(trackRef t) { trackInfoContainer::ptr ret; if(!t->get_info_ref(ret)) ret = nullptr; return ret; } +inline trackInfoContainer::ptr trackGetInfo(trackListRef l, size_t n) { return trackGetInfo(l[n]); } + +// Returns const char* or pfc::string8 depending on which fb2k! +inline const char * trackGetPath(trackListRef l, size_t n) { return l[n]->get_path(); } +inline const char * trackGetPath(trackRef t) { return t->get_path(); } + +// Returns const playable_location& or playable_location_impl depending on which fb2k! +inline playable_location const & trackGetLocation(trackListRef l, size_t n) { return l[n]->get_location(); } +inline playable_location const & trackGetLocation(trackRef t) { return t->get_location(); } + +inline double trackGetLength(trackListRef l, size_t n) { return l[n]->get_length(); } +inline double trackGetLength(trackRef t) { return t->get_length(); } + +inline void trackFormatTitle(trackRef trk, titleformat_hook * hook, pfc::string_base & out, service_ptr_t script, titleformat_text_filter * filter) { + trk->format_title(hook, out, script, filter); +} \ No newline at end of file diff --git a/foobar2000/SDK/ui.cpp b/foobar2000/SDK/ui.cpp new file mode 100644 index 0000000..5991dd1 --- /dev/null +++ b/foobar2000/SDK/ui.cpp @@ -0,0 +1,102 @@ +#include "foobar2000.h" + +bool ui_drop_item_callback::g_on_drop(interface IDataObject * pDataObject) +{ + service_enum_t e; + service_ptr_t ptr; + if (e.first(ptr)) do { + if (ptr->on_drop(pDataObject)) return true; + } while(e.next(ptr)); + return false; +} + +bool ui_drop_item_callback::g_is_accepted_type(interface IDataObject * pDataObject, DWORD * p_effect) +{ + service_enum_t e; + service_ptr_t ptr; + if (e.first(ptr)) do { + if (ptr->is_accepted_type(pDataObject,p_effect)) return true; + } while(e.next(ptr)); + return false; +} + +bool user_interface::g_find(service_ptr_t & p_out,const GUID & p_guid) +{ + service_enum_t e; + service_ptr_t ptr; + if (e.first(ptr)) do { + if (ptr->get_guid() == p_guid) + { + p_out = ptr; + return true; + } + } while(e.next(ptr)); + return false; +} + + +// ui_edit_context.h code + +void ui_edit_context::select_all() { update_selection(pfc::bit_array_true(), pfc::bit_array_true()); } +void ui_edit_context::select_none() { update_selection(pfc::bit_array_true(), pfc::bit_array_false()); } +void ui_edit_context::get_selected_items(metadb_handle_list_ref out) { pfc::bit_array_bittable mask(get_item_count()); get_selection_mask(mask); get_items(out, mask); } +void ui_edit_context::remove_selection() { pfc::bit_array_bittable mask(get_item_count()); get_selection_mask(mask); remove_items(mask); } +void ui_edit_context::crop_selection() { pfc::bit_array_bittable mask(get_item_count()); get_selection_mask(mask); remove_items(pfc::bit_array_not(mask)); } +void ui_edit_context::clear() { remove_items(pfc::bit_array_true()); } +void ui_edit_context::get_all_items(metadb_handle_list_ref out) { get_items(out, pfc::bit_array_true()); } + +void ui_edit_context::get_selection_mask(pfc::bit_array_var & out) { + const t_size count = get_item_count(); for (t_size walk = 0; walk < count; ++walk) out.set(walk, is_item_selected(walk)); +} + +t_size ui_edit_context::get_selection_count(t_size max) { + t_size count = 0; + const t_size total = get_item_count(); + for (t_size walk = 0; walk < total && count < max; ++walk) if (is_item_selected(walk)) ++count; + return count; +} + +void ui_edit_context::sort_by_format(const char * spec, bool onlySelection) { + const t_size count = get_item_count(); + pfc::array_t order; order.set_size(count); + pfc::array_t sel_map; + if (onlySelection) { + sel_map.set_size(count); + t_size sel_count = 0; + for (t_size n = 0; n order_temp; + if (onlySelection) { + get_selected_items(temp); + order_temp.set_size(count); + } else { + get_all_items(temp); + } + + + if (spec != NULL) { + temp.sort_by_format_get_order(onlySelection ? order_temp.get_ptr() : order.get_ptr(), spec, 0); + } else { + auto api = genrand_service::get(); api->seed((unsigned)__rdtsc()); + api->generate_random_order(onlySelection ? order_temp.get_ptr() : order.get_ptr(), temp.get_count()); + } + + if (onlySelection) { + t_size n, ptr; + for (n = 0, ptr = 0; n to register, e.g static user_interface_factory_t g_myclass_factory; +class NOVTABLE user_interface : public service_base { + FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(user_interface); +public: + //!HookProc usage: \n + //! in your windowproc, call HookProc first, and if it returns true, return LRESULT value it passed to you + typedef BOOL (WINAPI * HookProc_t)(HWND wnd,UINT msg,WPARAM wp,LPARAM lp,LRESULT * ret); + + //! Retrieves name (UTF-8 null-terminated string) of the UI module. + virtual const char * get_name()=0; + //! Initializes the UI module - creates the main app window, etc. Failure should be signaled by appropriate exception (std::exception or a derivative). + virtual HWND init(HookProc_t hook)=0; + //! Deinitializes the UI module - destroys the main app window, etc. + virtual void shutdown()=0; + //! Activates main app window. + virtual void activate()=0; + //! Minimizes/hides main app window. + virtual void hide()=0; + //! Returns whether main window is visible / not minimized. Used for activate/hide command. + virtual bool is_visible() = 0; + //! Retrieves GUID of your implementation, to be stored in configuration file etc. + virtual GUID get_guid() = 0; + + //! Overrides statusbar text with specified string. The parameter is a null terminated UTF-8 string. The override is valid until another override_statusbar_text() call or revert_statusbar_text() call. + virtual void override_statusbar_text(const char * p_text) = 0; + //! Disables statusbar text override. + virtual void revert_statusbar_text() = 0; + + //! Shows now-playing item somehow (e.g. system notification area popup). + virtual void show_now_playing() = 0; + + static bool g_find(service_ptr_t & p_out,const GUID & p_guid); +}; + +template +class user_interface_factory : public service_factory_single_t {}; + +//! \since 1.4 +//! Extended version to allow explicit control over certain app features. +class NOVTABLE user_interface_v2 : public user_interface { + FB2K_MAKE_SERVICE_INTERFACE( user_interface_v2, user_interface ); +public: + //! Allows the core to ask the UI module about a specific feature. + virtual bool query_capability( const GUID & cap ) = 0; + + //! Suppress core's shellhook window for intercepting systemwide WM_APPCOMMAND? \n + //! Recommended: false - return true only if your UI does this on its own. + static const GUID cap_suppress_core_shellhook; + //! Suppress coer's integration with with Win10 Universal Volume Control? \n + //! Recommended: false - return true only if your UI is explicitly incompatbile with Win10 UVC. \n + //! Note that cap_suppress_core_shellhook is queried first, as core can't use UVC if this UI does global WM_APPCOMMAND handling on its own. \n + //! Returning true from cap_suppress_core_shellhook implies the same from cap_suppress_core_uvc. + static const GUID cap_suppress_core_uvc; +}; + +//! Interface class allowing you to override UI statusbar text. There may be multiple callers trying to override statusbar text; backend decides which one succeeds so you will not always get what you want. Statusbar text override is automatically cancelled when the object is released.\n +//! Use ui_control::override_status_text_create() to instantiate. +//! Implemented by core. Do not reimplement. +class NOVTABLE ui_status_text_override : public service_base +{ +public: + //! Sets statusbar text to specified UTF-8 null-terminated string. + virtual void override_text(const char * p_message) = 0; + //! Cancels statusbar text override. + virtual void revert_text() = 0; + + FB2K_MAKE_SERVICE_INTERFACE(ui_status_text_override,service_base); +}; + +//! Serivce providing various UI-related commands. Implemented by core; do not reimplement. +//! Instantiation: use ui_control::get() to obtain an instance. +class NOVTABLE ui_control : public service_base { + FB2K_MAKE_SERVICE_COREAPI(ui_control); +public: + //! Returns whether primary UI is visible/unminimized. + virtual bool is_visible()=0; + //! Activates/unminimizes main UI. + virtual void activate()=0; + //! Hides/minimizese main UI. + virtual void hide()=0; + //! Retrieves main GUI icon, to use as window icon etc. Returned handle does not need to be freed. + virtual HICON get_main_icon()=0; + //! Loads main GUI icon, version with specified width/height. Returned handle needs to be freed with DestroyIcon when you are done using it. + virtual HICON load_main_icon(unsigned width,unsigned height) = 0; + + //! Activates preferences dialog and navigates to specified page. See also: preference_page API. \n + //! Since foobar2000 1.5, this can be used to show advanced preferences branches or settings, just pass GUID of the advconfig_entry you wish to show. + virtual void show_preferences(const GUID & p_page) = 0; + + //! Instantiates ui_status_text_override service, that can be used to display status messages. + //! @param p_out receives new ui_status_text_override instance. + //! @returns true on success, false on failure (out of memory / no GUI loaded / etc) + virtual bool override_status_text_create(service_ptr_t & p_out) = 0; +}; + +typedef ui_status_text_override ui_status_host; + +#if FOOBAR2000_TARGET_VERSION >= 80 +//! \since 1.5 +class NOVTABLE ui_control_v2 : public ui_control { + FB2K_MAKE_SERVICE_COREAPI_EXTENSION(ui_control_v2, ui_control) +public: + virtual void register_status_host(HWND wndFor, ui_status_host::ptr obj) = 0; + virtual void unregister_status_host(HWND wndFor) = 0; +}; +#endif // if FOOBAR2000_TARGET_VERSION >= 80 + +//! Service called from the UI when some object is dropped into the UI. Usable for modifying drag&drop behaviors such as adding custom handlers for object types other than supported media files.\n +//! Implement where needed; use ui_drop_item_callback_factory_t<> template to register, e.g. static ui_drop_item_callback_factory_t g_myclass_factory. +class NOVTABLE ui_drop_item_callback : public service_base { +public: + //! Called when an object was dropped; returns true if the object was processed and false if not. + virtual bool on_drop(interface IDataObject * pDataObject) = 0; + //! Tests whether specified object type is supported by this ui_drop_item_callback implementation. Returns true and sets p_effect when it's supported; returns false otherwise. \n + //! See IDropTarget::DragEnter() documentation for more information about p_effect values. + virtual bool is_accepted_type(interface IDataObject * pDataObject, DWORD * p_effect)=0; + + //! Static helper, calls all existing implementations appropriately. See on_drop(). + static bool g_on_drop(interface IDataObject * pDataObject); + //! Static helper, calls all existing implementations appropriately. See is_accepted_type(). + static bool g_is_accepted_type(interface IDataObject * pDataObject, DWORD * p_effect); + + FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(ui_drop_item_callback); +}; + +template +class ui_drop_item_callback_factory_t : public service_factory_single_t {}; + + +class ui_selection_callback; + +//! Write interface and reference counter for the shared selection. +//! The ui_selection_manager stores the selected items as a list. +//! The ui_selection_holder service allows components to modify this list. +//! It also serves as a reference count: the ui_selection_manager clears the stored +//! selection when no component holds a reference to a ui_selection_holder. +//! +//! When a window that uses the shared selection gets the focus, it should acquire +//! a ui_selection_holder from the ui_selection_manager. If it contains selectable items, +//! it should use the appropriate method to store its selected items as the shared selection. +//! If it just wants to preserve the selection - for example so it can display it - it should +//! merely store the acquired ui_selection_holder. +//! +//! When the window loses the focus, it should release its ui_selection_holder. +//! It should not use a set method to clear the selection +class NOVTABLE ui_selection_holder : public service_base { +public: + //! Sets selected items. + virtual void set_selection(metadb_handle_list_cref data) = 0; + //! Sets selected items to playlist selection and enables tracking. + //! When the playlist selection changes, the stored selection is automatically updated. + //! Tracking ends when a set method is called on any ui_selection_holder or when + //! the last reference to this ui_selection_holder is released. + virtual void set_playlist_selection_tracking() = 0; + //! Sets selected items to the contents of the active playlist and enables tracking. + //! When the active playlist or its contents changes, the stored selection is automatically updated. + //! Tracking ends when a set method is called on any ui_selection_holder or when + //! the last reference to this ui_selection_holder is released. + virtual void set_playlist_tracking() = 0; + + //! Sets selected items and type of selection holder. + //! @param type Specifies type of selection. Values same as contextmenu_item caller IDs. + virtual void set_selection_ex(metadb_handle_list_cref data, const GUID & type) = 0; + + FB2K_MAKE_SERVICE_INTERFACE(ui_selection_holder,service_base); +}; + +class NOVTABLE ui_selection_manager : public service_base { + FB2K_MAKE_SERVICE_COREAPI(ui_selection_manager); +public: + //! Retrieves the current selection. + virtual void get_selection(metadb_handle_list_ref p_selection) = 0; + //! Registers a callback. It is recommended to use ui_selection_callback_impl_base class instead of calling this directly. + virtual void register_callback(ui_selection_callback * p_callback) = 0; + //! Unregisters a callback. It is recommended to use ui_selection_callback_impl_base class instead of calling this directly. + virtual void unregister_callback(ui_selection_callback * p_callback) = 0; + + virtual ui_selection_holder::ptr acquire() = 0; + + //! Retrieves type of the active selection holder. Values same as contextmenu_item caller IDs. + virtual GUID get_selection_type() = 0; +}; + +//! \since 1.0 +class NOVTABLE ui_selection_manager_v2 : public ui_selection_manager { + FB2K_MAKE_SERVICE_COREAPI_EXTENSION(ui_selection_manager_v2, ui_selection_manager) +public: + enum { flag_no_now_playing = 1 }; + virtual void get_selection(metadb_handle_list_ref out, t_uint32 flags) = 0; + virtual GUID get_selection_type(t_uint32 flags) = 0; + virtual void register_callback(ui_selection_callback * callback, t_uint32 flags) = 0; +}; + +class ui_selection_callback { +public: + virtual void on_selection_changed(metadb_handle_list_cref p_selection) = 0; +protected: + ui_selection_callback() {} + ~ui_selection_callback() {} +}; + +//! ui_selection_callback implementation helper with autoregistration - do not instantiate statically +class ui_selection_callback_impl_base : public ui_selection_callback { +protected: + ui_selection_callback_impl_base(bool activate = true) : m_active() {ui_selection_callback_activate(activate);} + ~ui_selection_callback_impl_base() {ui_selection_callback_activate(false);} + + void ui_selection_callback_activate(bool state = true) { + if (state != m_active) { + m_active = state; + auto api = ui_selection_manager::get(); + if (state) api->register_callback(this); + else api->unregister_callback(this); + } + } + + //avoid pure virtual function calls in rare cases - provide a dummy implementation + void on_selection_changed(metadb_handle_list_cref p_selection) {} + + PFC_CLASS_NOT_COPYABLE_EX(ui_selection_callback_impl_base); +private: + bool m_active; +}; + +//! \since 1.0 +//! ui_selection_callback implementation helper with autoregistration - do not instantiate statically +template +class ui_selection_callback_impl_base_ex : public ui_selection_callback { +protected: + enum { + ui_selection_flags = flags + }; + ui_selection_callback_impl_base_ex(bool activate = true) : m_active() {ui_selection_callback_activate(activate);} + ~ui_selection_callback_impl_base_ex() {ui_selection_callback_activate(false);} + + void ui_selection_callback_activate(bool state = true) { + if (state != m_active) { + m_active = state; + auto api = ui_selection_manager_v2::get(); + if (state) api->register_callback(this, flags); + else api->unregister_callback(this); + } + } + + //avoid pure virtual function calls in rare cases - provide a dummy implementation + void on_selection_changed(metadb_handle_list_cref p_selection) {} + + PFC_CLASS_NOT_COPYABLE(ui_selection_callback_impl_base_ex, ui_selection_callback_impl_base_ex); +private: + bool m_active; +}; diff --git a/foobar2000/SDK/ui_edit_context.h b/foobar2000/SDK/ui_edit_context.h new file mode 100644 index 0000000..7044d98 --- /dev/null +++ b/foobar2000/SDK/ui_edit_context.h @@ -0,0 +1,109 @@ +#pragma once + +//! A class used to redirect actions coming from the 'edit' menu, typically provided by the UI Element having focus. \n +//! Use ui_edit_context_manager to register and manipulate. +class NOVTABLE ui_edit_context : public service_base { + FB2K_MAKE_SERVICE_INTERFACE(ui_edit_context, service_base) +public: + //! Called by core only. + virtual void initialize() = 0; + //! Called by core only. \n + //! WARNING: you may get other methods called after shutdown() in case someone using ui_edit_context_manager has kept a reference to your service - for an example during an async operation. You should behave sanely in such case - either execute the operation if still possible or fail cleanly. + virtual void shutdown() = 0; + + enum { + flag_removable = 1, + flag_reorderable = 2, + flag_undoable = 4, + flag_redoable = 8, + flag_linearlist = 16, + flag_searchable = 32, + flag_insertable = 64, + }; + + virtual t_uint32 get_flags() = 0; + + bool can_remove() {return (get_flags() & flag_removable) != 0;} + bool test_flags(t_uint32 flags) {return (get_flags() & flags) == flags;} + bool can_remove_mask() {return test_flags(flag_removable | flag_linearlist);} + bool can_reorder() {return test_flags(flag_reorderable);} + bool can_search() {return test_flags(flag_searchable);} + + virtual void select_all(); + virtual void select_none(); + virtual void get_selected_items(metadb_handle_list_ref out); + virtual void remove_selection(); + virtual void crop_selection(); + virtual void clear(); + virtual void get_all_items(metadb_handle_list_ref out); + virtual GUID get_selection_type() = 0; + + // available if flag_linearlist is set + virtual void get_selection_mask(pfc::bit_array_var & out); + + virtual void update_selection(const pfc::bit_array & mask, const pfc::bit_array & newVals) = 0; + virtual t_size get_item_count(t_size max = ~0) = 0; + virtual metadb_handle_ptr get_item(t_size index) = 0; + virtual void get_items(metadb_handle_list_ref out, pfc::bit_array const & mask) = 0; + virtual bool is_item_selected(t_size item) = 0; + virtual void remove_items(pfc::bit_array const & mask) = 0; + virtual void reorder_items(const t_size * order, t_size count) = 0; + virtual t_size get_selection_count(t_size max = ~0); + + virtual void search() = 0; + + virtual void undo_backup() = 0; + virtual void undo_restore() = 0; + virtual void redo_restore() = 0; + + virtual void insert_items(t_size at, metadb_handle_list_cref items, pfc::bit_array const & selection) = 0; + + virtual t_size query_insert_mark() = 0; + + void sort_by_format(const char * spec, bool onlySelection); + + //! Safely prevent destruction from worker threads (some components attempt that). + static bool serviceRequiresMainThreadDestructor() { return true; } +}; + +//! Special case of ui_edit_context operating on a specific playlist (see playlist_manager). \n +//! Use this to let everyone know that your methods operate on a playlist, \n +//! in case some component wishes to provide additional functionality based on that. +class ui_edit_context_playlist : public ui_edit_context { + FB2K_MAKE_SERVICE_INTERFACE(ui_edit_context_playlist, ui_edit_context) +public: + virtual t_size get_playlist_number() = 0; +}; + +//! Entrypoint class for registering and manipulating ui_edit_context objects. \n +//! Implemented by core. +class NOVTABLE ui_edit_context_manager : public service_base { + FB2K_MAKE_SERVICE_COREAPI(ui_edit_context_manager) +public: + //! Sets the current edit context. \n + //! Typically called when a part of your UI receives focus. \n + //! @returns An identifier than can later be passed to unset_context when focus is lost. + virtual t_uint32 set_context(ui_edit_context::ptr context) = 0; + //! Removes an edit context. \n + //! @param id The value returned by matching set_context() call. + virtual void unset_context(t_uint32 id) = 0; + //! Retrieves the current context object. May return null when there's no context. + //! @returns The currently active context object, possibly null if none. + virtual ui_edit_context::ptr get_context() = 0; + //! Creates a context wrapping a specific playlist; see also playlist_manager. + //! @param playlist_number Number of wrapped playlist, per playlist_manager numbering. + //! @returns Newly creted context object. + virtual ui_edit_context::ptr create_playlist_context(t_size playlist_number) = 0; + //! By default, when no context is registered, \n + //! for an example, if the active user interface component is not ui_edit_context-aware, \n + //! a fallback context is automatically set; it follows the active playlist,\n + //! so the edit menu does something meaningful. \n + //! This method is intended to be called during initialiation of an user interface component; \n + //! it tells foobar2000 core to suppress the active playlist fallback-\n + //! but then entire edit menu becomes non-functional if no context is provided. + virtual void disable_autofallback() = 0; + //! Helper, sets the current context to an implemented-by-core context that tracks the active playlist. \n + //! Typically called when a part of your UI receives focus. + //! @returns Identifier value to pass to unset_context() when your focus is gone. + virtual t_uint32 set_context_active_playlist() = 0; +}; diff --git a/foobar2000/SDK/ui_element.cpp b/foobar2000/SDK/ui_element.cpp new file mode 100644 index 0000000..5ae02f2 --- /dev/null +++ b/foobar2000/SDK/ui_element.cpp @@ -0,0 +1,223 @@ +#include "foobar2000.h" + +namespace { + struct sysColorMapping_t { + GUID guid; int idx; + }; + static const sysColorMapping_t sysColorMapping[] = { + { ui_color_text, COLOR_WINDOWTEXT }, + { ui_color_background, COLOR_WINDOW }, + { ui_color_highlight, COLOR_HOTLIGHT }, + {ui_color_selection, COLOR_HIGHLIGHT}, + }; +} +int ui_color_to_sys_color_index(const GUID & p_guid) { + for( unsigned i = 0; i < PFC_TABSIZE( sysColorMapping ); ++ i ) { + if ( p_guid == sysColorMapping[i].guid ) return sysColorMapping[i].idx; + } + return -1; +} +GUID ui_color_from_sys_color_index(int idx) { + for (unsigned i = 0; i < PFC_TABSIZE(sysColorMapping); ++i) { + if (idx == sysColorMapping[i].idx) return sysColorMapping[i].guid; + } + return pfc::guid_null; +} + +namespace { + class ui_element_config_impl : public ui_element_config { + public: + ui_element_config_impl(const GUID & guid) : m_guid(guid) {} + ui_element_config_impl(const GUID & guid, const void * buffer, t_size size) : m_guid(guid) { + m_content.set_data_fromptr(reinterpret_cast(buffer),size); + } + + void * get_data_var() {return m_content.get_ptr();} + void set_data_size(t_size size) {m_content.set_size(size);} + + GUID get_guid() const {return m_guid;} + const void * get_data() const {return m_content.get_ptr();} + t_size get_data_size() const {return m_content.get_size();} + private: + const GUID m_guid; + pfc::array_t m_content; + }; + +} + +service_ptr_t ui_element_config::g_create(const GUID& id, const void * data, t_size size) { + return new service_impl_t(id,data,size); +} + +service_ptr_t ui_element_config::g_create(const GUID & id, stream_reader * in, t_size bytes, abort_callback & abort) { + service_ptr_t data = new service_impl_t(id); + data->set_data_size(bytes); + in->read_object(data->get_data_var(),bytes,abort); + return data; +} + +service_ptr_t ui_element_config::g_create(stream_reader * in, t_size bytes, abort_callback & abort) { + if (bytes < sizeof(GUID)) throw exception_io_data_truncation(); + GUID id; + { stream_reader_formatter<> str(*in,abort); str >> id;} + return g_create(id,in,bytes - sizeof(GUID),abort); +} + +ui_element_config::ptr ui_element_config_parser::subelement(t_size size) { + return ui_element_config::g_create(&m_stream, size, m_abort); +} +ui_element_config::ptr ui_element_config_parser::subelement(const GUID & id, t_size dataSize) { + return ui_element_config::g_create(id, &m_stream, dataSize, m_abort); +} + +service_ptr_t ui_element_config::g_create(const void * data, t_size size) { + stream_reader_memblock_ref stream(data,size); + return g_create(&stream,size,fb2k::noAbort); +} + +bool ui_element_subclass_description(const GUID & id, pfc::string_base & p_out) { + if (id == ui_element_subclass_playlist_renderers) { + p_out = "Playlist Renderers"; return true; + } else if (id == ui_element_subclass_media_library_viewers) { + p_out = "Media Library Viewers"; return true; + } else if (id == ui_element_subclass_selection_information) { + p_out = "Selection Information"; return true; + } else if (id == ui_element_subclass_playback_visualisation) { + p_out = "Playback Visualization"; return true; + } else if (id == ui_element_subclass_playback_information) { + p_out = "Playback Information"; return true; + } else if (id == ui_element_subclass_utility) { + p_out = "Utility"; return true; + } else if (id == ui_element_subclass_containers) { + p_out = "Containers"; return true; + } else if ( id == ui_element_subclass_dsp ) { + p_out = "DSP"; return true; + } else { + return false; + } +} + +bool ui_element::get_element_group(pfc::string_base & p_out) { + return ui_element_subclass_description(get_subclass(),p_out); +} + +t_ui_color ui_element_instance_callback::query_std_color(const GUID & p_what) { +#ifdef _WIN32 + t_ui_color ret; + if (query_color(p_what,ret)) return ret; + int idx = ui_color_to_sys_color_index(p_what); + if (idx < 0) return 0;//should not be triggerable + return GetSysColor(idx); +#else +#error portme +#endif +} +#ifdef _WIN32 +t_ui_color ui_element_instance_callback::getSysColor(int sysColorIndex) { + GUID guid = ui_color_from_sys_color_index( sysColorIndex ); + if ( guid != pfc::guid_null ) return query_std_color(guid); + return GetSysColor(sysColorIndex); +} +#endif + +bool ui_element::g_find(service_ptr_t & out, const GUID & id) { + return service_by_guid(out, id); +} + +bool ui_element::g_get_name(pfc::string_base & p_out,const GUID & p_guid) { + ui_element::ptr ptr; if (!g_find(ptr,p_guid)) return false; + ptr->get_name(p_out); return true; +} + +bool ui_element_instance_callback::is_elem_visible_(service_ptr_t elem) { + ui_element_instance_callback_v2::ptr v2; + if (!this->service_query_t(v2)) { + PFC_ASSERT(!"Should not get here - somebody implemented ui_element_instance_callback but not ui_element_instance_callback_v2."); + return true; + } + return v2->is_elem_visible(elem); +} + +bool ui_element_instance_callback::set_elem_label(ui_element_instance * source, const char * label) { + return notify_(source, ui_element_host_notify_set_elem_label, 0, label, strlen(label)) != 0; +} + +t_uint32 ui_element_instance_callback::get_dialog_texture(ui_element_instance * source) { + return (t_uint32) notify_(source, ui_element_host_notify_get_dialog_texture, 0, NULL, 0); +} + +bool ui_element_instance_callback::is_border_needed(ui_element_instance * source) { + return notify_(source, ui_element_host_notify_is_border_needed, 0, NULL, 0) != 0; +} + +t_size ui_element_instance_callback::notify_(ui_element_instance * source, const GUID & what, t_size param1, const void * param2, t_size param2size) { + ui_element_instance_callback_v3::ptr v3; + if (!this->service_query_t(v3)) { PFC_ASSERT(!"Outdated UI Element host implementation"); return 0; } + return v3->notify(source, what, param1, param2, param2size); +} + + +const ui_element_min_max_info & ui_element_min_max_info::operator|=(const ui_element_min_max_info & p_other) { + m_min_width = pfc::max_t(m_min_width,p_other.m_min_width); + m_min_height = pfc::max_t(m_min_height,p_other.m_min_height); + m_max_width = pfc::min_t(m_max_width,p_other.m_max_width); + m_max_height = pfc::min_t(m_max_height,p_other.m_max_height); + return *this; +} +ui_element_min_max_info ui_element_min_max_info::operator|(const ui_element_min_max_info & p_other) const { + ui_element_min_max_info ret(*this); + ret |= p_other; + return ret; +} + +void ui_element_min_max_info::adjustForWindow(HWND wnd) { + RECT client = {0,0,10,10}; + RECT adjusted = client; + BOOL bMenu = FALSE; + const DWORD style = (DWORD) GetWindowLong( wnd, GWL_STYLE ); + if ( style & WS_POPUP ) { + bMenu = GetMenu( wnd ) != NULL; + } + if (AdjustWindowRectEx( &adjusted, style, bMenu, GetWindowLong(wnd, GWL_EXSTYLE) )) { + int dx = (adjusted.right - adjusted.left) - (client.right - client.left); + int dy = (adjusted.bottom - adjusted.top) - (client.bottom - client.top); + if ( dx < 0 ) dx = 0; + if ( dy < 0 ) dy = 0; + m_min_width += dx; + m_min_height += dy; + if ( m_max_width != ~0 ) m_max_width += dx; + if ( m_max_height != ~0 ) m_max_height += dy; + } +} +//! Retrieves element's minimum/maximum window size. Default implementation will fall back to WM_GETMINMAXINFO. +ui_element_min_max_info ui_element_instance::get_min_max_info() { + ui_element_min_max_info ret; + MINMAXINFO temp = {}; + temp.ptMaxTrackSize.x = 1024*1024;//arbitrary huge number + temp.ptMaxTrackSize.y = 1024*1024; + SendMessage(this->get_wnd(),WM_GETMINMAXINFO,0,(LPARAM)&temp); + if (temp.ptMinTrackSize.x >= 0) ret.m_min_width = temp.ptMinTrackSize.x; + if (temp.ptMaxTrackSize.x > 0) ret.m_max_width = temp.ptMaxTrackSize.x; + if (temp.ptMinTrackSize.y >= 0) ret.m_min_height = temp.ptMinTrackSize.y; + if (temp.ptMaxTrackSize.y > 0) ret.m_max_height = temp.ptMaxTrackSize.y; + return ret; +} + + +namespace { + class ui_element_replace_dialog_notify_impl : public ui_element_replace_dialog_notify { + public: + void on_cancelled() { + reply(pfc::guid_null); + } + void on_ok(const GUID & guid) { + reply(guid); + } + std::function reply; + }; +} +ui_element_replace_dialog_notify::ptr ui_element_replace_dialog_notify::create(std::function reply) { + auto obj = fb2k::service_new(); + obj->reply = reply; + return obj; +} \ No newline at end of file diff --git a/foobar2000/SDK/ui_element.h b/foobar2000/SDK/ui_element.h new file mode 100644 index 0000000..f944fc3 --- /dev/null +++ b/foobar2000/SDK/ui_element.h @@ -0,0 +1,577 @@ +#pragma once + +//! Configuration of a UI element instance. +class NOVTABLE ui_element_config : public service_base { +public: + //! Returns GUID of the UI element this configuration data belongs to. + virtual GUID get_guid() const = 0; + //! Returns raw configuration data pointer. + virtual const void * get_data() const = 0; + //! Returns raw configuration data size, in bytes. + virtual t_size get_data_size() const = 0; + + + //! Helper. + static service_ptr_t g_create(const GUID& id, const void * data, t_size size); + + template static service_ptr_t g_create(const GUID& id, t_source const & data) { + pfc::assert_byte_type(); + return g_create(id, data.get_ptr(), data.get_size()); + } + + static service_ptr_t g_create_empty(const GUID & id = pfc::guid_null) {return g_create(id,NULL,0);} + + static service_ptr_t g_create(const GUID & id, stream_reader * in, t_size bytes, abort_callback & abort); + static service_ptr_t g_create(stream_reader * in, t_size bytes, abort_callback & abort); + static service_ptr_t g_create(const void * data, t_size size); + service_ptr_t clone() const { + return g_create(get_guid(), get_data(), get_data_size()); + } + + FB2K_MAKE_SERVICE_INTERFACE(ui_element_config,service_base); +}; + +//! Helper for reading data from ui_element_config. +class ui_element_config_parser : public stream_reader_formatter<> { +public: + ui_element_config_parser(ui_element_config::ptr in) : m_data(in), _m_stream(in->get_data(),in->get_data_size()), stream_reader_formatter(_m_stream,fb2k::noAbort) {} + + void reset() {_m_stream.reset();} + t_size get_remaining() const {return _m_stream.get_remaining();} + + ui_element_config::ptr subelement(t_size size); + ui_element_config::ptr subelement(const GUID & id, t_size dataSize); +private: + const ui_element_config::ptr m_data; + stream_reader_memblock_ref _m_stream; +}; + +//! Helper creating ui_element_config from your data. +class ui_element_config_builder : public stream_writer_formatter<> { +public: + ui_element_config_builder() : stream_writer_formatter(_m_stream,fb2k::noAbort) {} + ui_element_config::ptr finish(const GUID & id) { + return ui_element_config::g_create(id,_m_stream.m_buffer); + } + void reset() { + _m_stream.m_buffer.set_size(0); + } +private: + stream_writer_buffer_simple _m_stream; +}; + + +FB2K_STREAM_WRITER_OVERLOAD(ui_element_config::ptr) { + stream << value->get_guid(); + t_size size = value->get_data_size(); + stream << pfc::downcast_guarded(size); + stream.write_raw(value->get_data(),size); + return stream; +} +FB2K_STREAM_READER_OVERLOAD(ui_element_config::ptr) { + GUID guid; + t_uint32 size; + stream >> guid >> size; + value = ui_element_config::g_create(guid,&stream.m_stream,size,stream.m_abort); + return stream; +} + + + + +typedef COLORREF t_ui_color; +typedef HFONT t_ui_font; +typedef HICON t_ui_icon; + +static const GUID ui_color_text = { 0x5dd38be7, 0xff8a, 0x416f, { 0x88, 0x2d, 0xa4, 0x8e, 0x31, 0x87, 0x85, 0xb2 } }; +static const GUID ui_color_background = { 0x16fc40c1, 0x1cba, 0x4385, { 0x93, 0x3b, 0xe9, 0x32, 0x7f, 0x6e, 0x35, 0x1f } }; +static const GUID ui_color_highlight = { 0xd2f98042, 0x3e6a, 0x423a, { 0xb8, 0x66, 0x65, 0x1, 0xfe, 0xa9, 0x75, 0x93 } }; +static const GUID ui_color_selection = { 0xebe1a36b, 0x7e0a, 0x469a, { 0x8e, 0xc5, 0xcf, 0x3, 0x12, 0x90, 0x40, 0xb5 } }; + +static const GUID ui_font_default = { 0x9ef02cef, 0xe58a, 0x4f99, { 0x9f, 0xe3, 0x85, 0x39, 0xb, 0xed, 0xc5, 0xe0 } }; +static const GUID ui_font_tabs = { 0x65ffd7ac, 0x71ce, 0x495c, { 0xa5, 0x99, 0x48, 0x5b, 0xbf, 0x7a, 0x4, 0x7b } }; +static const GUID ui_font_lists = { 0xa86198b3, 0xb5d8, 0x40cf, { 0xac, 0x19, 0xf9, 0xda, 0xc, 0xb5, 0xd4, 0x24 } }; +static const GUID ui_font_playlists = { 0xd85b7b7e, 0xbf83, 0x41ed, { 0x88, 0xce, 0x1, 0x99, 0x31, 0x94, 0x3e, 0x2f } }; +static const GUID ui_font_statusbar = { 0xc7fd555b, 0xbd15, 0x4f74, { 0x93, 0xe, 0xba, 0x55, 0x52, 0x9d, 0xd9, 0x71 } }; +static const GUID ui_font_console = { 0xb08c619d, 0xd3d1, 0x4089, { 0x93, 0xb2, 0xd5, 0xb, 0x87, 0x2d, 0x1a, 0x25 } }; + + +//! @returns -1 when the GUID is unknown / unmappable, index that can be passed over to GetSysColor() otherwise. +int ui_color_to_sys_color_index(const GUID & p_guid); +GUID ui_color_from_sys_color_index( int idx ); + +struct ui_element_min_max_info { + ui_element_min_max_info() : m_min_width(0), m_max_width(~0), m_min_height(0), m_max_height(~0) {} + t_uint32 m_min_width, m_max_width, m_min_height, m_max_height; + + const ui_element_min_max_info & operator|=(const ui_element_min_max_info & p_other); + ui_element_min_max_info operator|(const ui_element_min_max_info & p_other) const; + void adjustForWindow(HWND wnd); +}; + +//! Callback class passed by a UI element host to a UI element instance, allowing each UI element instance to communicate with its host. \n +//! Each ui_element_instance_callback implementation must also implement ui_element_instance_callback_v2. +class NOVTABLE ui_element_instance_callback : public service_base { + FB2K_MAKE_SERVICE_INTERFACE(ui_element_instance_callback,service_base); +public: + virtual void on_min_max_info_change() = 0; + //! Deprecated, does nothing. + virtual void on_alt_pressed(bool p_state) = 0; + //! Returns true on success, false when the color is undefined and defaults such as global windows settings should be used. + virtual bool query_color(const GUID & p_what,t_ui_color & p_out) = 0; + //! Tells the host that specified element wants to activate itself as a result of some kind of external user command (eg. menu command). Host should ensure that requesting child element's window is visible.\n + //! @returns True when relevant child element window has been properly made visible and requesting code should proceed to SetFocus their window etc, false when denied. + virtual bool request_activation(service_ptr_t p_item) = 0; + + //! Queries whether "edit mode" is enabled. Most of UI element editing functionality should be locked when it's not. + virtual bool is_edit_mode_enabled() = 0; + + //! Tells the host that the user has requested the element to be replaced with another one. + //! Note: this is generally used only when "edit mode" is enabled, but legal to call when it's not (eg. dummy element calls it regardless of settings when clicked). + virtual void request_replace(service_ptr_t p_item) = 0; + + //! Deprecated - use query_font_ex. Equivalent to query_font_ex(ui_font_default). + t_ui_font query_font() {return query_font_ex(ui_font_default);} + + //! Retrieves an user-configurable font to use for specified kind of display. See ui_font_* constant for possible parameters. + virtual t_ui_font query_font_ex(const GUID & p_what) = 0; + + //! Helper - a wrapper around query_color(), if the color is not user-overridden, returns relevant system color. + t_ui_color query_std_color(const GUID & p_what); +#ifdef _WIN32 + t_ui_color getSysColor( int sysColorIndex ); +#endif + + bool is_elem_visible_(service_ptr_t elem); + + t_size notify_(ui_element_instance * source, const GUID & what, t_size param1, const void * param2, t_size param2size); + bool set_elem_label(ui_element_instance * source, const char * label); + t_uint32 get_dialog_texture(ui_element_instance * source); + bool is_border_needed(ui_element_instance * source); +}; + + +class NOVTABLE ui_element_instance_callback_v2 : public ui_element_instance_callback { + FB2K_MAKE_SERVICE_INTERFACE(ui_element_instance_callback_v2, ui_element_instance_callback); +public: + virtual bool is_elem_visible(service_ptr_t elem) = 0; +}; + +class NOVTABLE ui_element_instance_callback_v3 : public ui_element_instance_callback_v2 { + FB2K_MAKE_SERVICE_INTERFACE(ui_element_instance_callback_v3, ui_element_instance_callback_v2) +public: + //! Returns zero when the notification was not handled, return value depends on the notification otherwise. + virtual t_size notify(ui_element_instance * source, const GUID & what, t_size param1, const void * param2, t_size param2size) = 0; +}; + +typedef service_ptr_t ui_element_instance_callback_ptr; + +//! ui_element_instance_callback implementation helper. +template class ui_element_instance_callback_impl : public ui_element_instance_callback_v3 { +public: + ui_element_instance_callback_impl(t_receiver * p_receiver) : m_receiver(p_receiver) {} + void on_min_max_info_change() { + if (m_receiver != NULL) m_receiver->on_min_max_info_change(); + } + void on_alt_pressed(bool p_state) {} + + bool query_color(const GUID & p_what,t_ui_color & p_out) { + if (m_receiver != NULL) return m_receiver->query_color(p_what,p_out); + else return false; + } + + bool request_activation(service_ptr_t p_item) { + if (m_receiver) return m_receiver->request_activation(p_item); + else return false; + } + + bool is_edit_mode_enabled() { + if (m_receiver) return m_receiver->is_edit_mode_enabled(); + else return false; + } + void request_replace(service_ptr_t p_item) { + if (m_receiver) m_receiver->request_replace(p_item); + } + + t_ui_font query_font_ex(const GUID & p_what) { + if (m_receiver) return m_receiver->query_font_ex(p_what); + else return NULL; + } + + void orphan() {m_receiver = NULL;} + + bool is_elem_visible(service_ptr_t elem) { + if (m_receiver) return m_receiver->is_elem_visible(elem); + else return false; + } + t_size notify(ui_element_instance * source, const GUID & what, t_size param1, const void * param2, t_size param2size) { + if (m_receiver) return m_receiver->host_notify(source, what, param1, param2, param2size); + else return 0; + } +private: + t_receiver * m_receiver; +}; + +//! ui_element_instance_callback implementation helper. +class ui_element_instance_callback_receiver { +public: + virtual void on_min_max_info_change() {} + virtual bool query_color(const GUID & p_what,t_ui_color & p_out) {return false;} + virtual bool request_activation(service_ptr_t p_item) {return false;} + virtual bool is_edit_mode_enabled() {return false;} + virtual void request_replace(service_ptr_t p_item) {} + virtual t_ui_font query_font_ex(const GUID&) {return NULL;} + virtual bool is_elem_visible(service_ptr_t elem) {return true;} + virtual t_size host_notify(ui_element_instance * source, const GUID & what, t_size param1, const void * param2, t_size param2size) {return 0;} + ui_element_instance_callback_ptr ui_element_instance_callback_get_ptr() { + if (m_callback.is_empty()) m_callback = new service_impl_t(this); + return m_callback; + } + void ui_element_instance_callback_release() { + if (m_callback.is_valid()) { + m_callback->orphan(); + m_callback.release(); + } + } +protected: + ~ui_element_instance_callback_receiver() { + ui_element_instance_callback_release(); + } + ui_element_instance_callback_receiver() {} + +private: + typedef ui_element_instance_callback_receiver t_self; + typedef ui_element_instance_callback_impl t_callback; + service_ptr_t m_callback; +}; + +//! Instance of a UI element. +class NOVTABLE ui_element_instance : public service_base { +public: + //! Returns ui_element_instance's window handle.\n + //! UI Element's window must be created when the ui_element_instance object is created. The window may or may not be destroyed by caller before the ui_element_instance itself is destroyed. If caller doesn't destroy the window before ui_element_instance destruction, ui_element_instance destructor should do it. + virtual HWND get_wnd() = 0; + + //! Alters element's current configuration. Specified ui_element_config's GUID must be the same as this element's GUID. + virtual void set_configuration(ui_element_config::ptr data) = 0; + //! Retrieves element's current configuration. Returned object's GUID must be set to your element's GUID so your element can be re-instantiated with stored settings. + virtual ui_element_config::ptr get_configuration() = 0; + + //! Returns GUID of the element. The return value must be the same as your ui_element::get_guid(). + virtual GUID get_guid() = 0; + //! Returns subclass GUID of the element. The return value must be the same as your ui_element::get_guid(). + virtual GUID get_subclass() = 0; + + //! Returns element's focus priority. + virtual double get_focus_priority() {return 0;} + //! Elements that host other elements should pass the call to the child with the highest priority. Override for container elements. + virtual void set_default_focus() {set_default_focus_fallback();} + + //! Overridden by containers only. + virtual bool get_focus_priority_subclass(double & p_out,const GUID & p_subclass) { + if (p_subclass == get_subclass()) {p_out = get_focus_priority(); return true;} + else {return false;} + } + //! Overridden by containers only. + virtual bool set_default_focus_subclass(const GUID & p_subclass) { + if (p_subclass == get_subclass()) {set_default_focus(); return true;} + else {return false;} + } + + //! Retrieves element's minimum/maximum window size. Default implementation will fall back to WM_GETMINMAXINFO. + virtual ui_element_min_max_info get_min_max_info(); + + //! Used by host to notify the element about various events. See ui_element_notify_* GUIDs for possible p_what parameter; meaning of other parameters depends on p_what value. Container classes should dispatch all notifications to their children. + virtual void notify(const GUID & p_what, t_size p_param1, const void * p_param2, t_size p_param2size) {} + + //! @param p_point Context menu point in screen coordinates. Always within out window's rect. + //! @return True to request edit_mode_context_menu_build() call to add our own items to the menu, false if we can't supply a context menu for this point. + virtual bool edit_mode_context_menu_test(const POINT & p_point,bool p_fromkeyboard) {return false;} + virtual void edit_mode_context_menu_build(const POINT & p_point,bool p_fromkeyboard,HMENU p_menu,unsigned p_id_base) {} + virtual void edit_mode_context_menu_command(const POINT & p_point,bool p_fromkeyboard,unsigned p_id,unsigned p_id_base) {} + //! @param p_point Receives the point to spawn context menu over when user has pressed the context menu key; in screen coordinates. + virtual bool edit_mode_context_menu_get_focus_point(POINT & p_point) {return false;} + + virtual bool edit_mode_context_menu_get_description(unsigned p_id,unsigned p_id_base,pfc::string_base & p_out) {return false;} + + + //! Helper. + void set_default_focus_fallback() { + const HWND thisWnd = this->get_wnd(); + if (thisWnd != NULL) ::SetFocus(thisWnd); + } + + FB2K_MAKE_SERVICE_INTERFACE(ui_element_instance,service_base); +}; + +typedef service_ptr_t ui_element_instance_ptr; + + +//! Interface to enumerate and possibly alter children of a container element. Obtained from ui_element::enumerate_children(). +class NOVTABLE ui_element_children_enumerator : public service_base { +public: + //! Retrieves the container's children count. + virtual t_size get_count() = 0; + //! Retrieves the configuration of the child at specified index, 0 <= index < get_count(). + virtual ui_element_config::ptr get_item(t_size p_index) = 0; + + //! Returns whether children count can be altered. For certain containers, children count is fixed and this method returns false. + virtual bool can_set_count() = 0; + //! Alters container's children count (behavior is undefined when can_set_count() returns false). Newly allocated children get default empty element configuration. + virtual void set_count(t_size count) = 0; + //! Alters the selected item. + virtual void set_item(t_size p_index,ui_element_config::ptr cfg) = 0; + //! Creates a new ui_element_config for this container, with our changes (set_count(), set_item()) applied. + virtual ui_element_config::ptr commit() = 0; + + FB2K_MAKE_SERVICE_INTERFACE(ui_element_children_enumerator,service_base); +}; + + +typedef service_ptr_t ui_element_children_enumerator_ptr; + + +//! Entrypoint interface for each UI element implementation. +class NOVTABLE ui_element : public service_base { +public: + //! Retrieves GUID of the element. + virtual GUID get_guid() = 0; + + //! Retrieves subclass GUID of the element. Typically one of ui_element_subclass_* values, but you can create your own custom subclasses. Subclass GUIDs are used for various purposes such as focusing an UI element that provides specific functionality. + virtual GUID get_subclass() = 0; + + //! Retrieves a simple human-readable name of the element. + virtual void get_name(pfc::string_base & p_out) = 0; + + //! Instantiates the element using specified settings. + virtual ui_element_instance_ptr instantiate(HWND p_parent,ui_element_config::ptr cfg,ui_element_instance_callback_ptr p_callback) = 0; + + //! Retrieves default configuration of the element. + virtual ui_element_config::ptr get_default_configuration() = 0; + + //! Implemented by container elements only. Returns NULL for non-container elements. \n + //! Allows caller to parse and edit child element structure of container elements. + virtual ui_element_children_enumerator_ptr enumerate_children(ui_element_config::ptr cfg) = 0; + + + //! In certain cases, an UI element can import settings of another UI element (eg. vertical<=>horizontal splitter, tabs<=>splitters) when user directly replaces one of such elements with another. Overriding this function allows special handling of such cases. \n + //! Implementation hint: when implementing a multi-child container, you probably want to takeover child elements replacing another container element; use enumerate_children() on the element the configuration belongs to to grab those. + //! @returns A new ui_element_config on success, a null pointer when the input data could not be parsed / is in an unknown format. + virtual ui_element_config::ptr import(ui_element_config::ptr cfg) {return NULL;} + + //! Override this to return false when your element is for internal use only and should not be user-addable. + virtual bool is_user_addable() {return true;} + + //! Returns an icon to show in available UI element lists, etc. Returns NULL when there is no icon to display. + virtual t_ui_icon get_icon() {return NULL;} + + //! Retrieves a human-readable description of the element. + virtual bool get_description(pfc::string_base & p_out) {return false;} + + //! Retrieves a human-readable description of the element's function to use for grouping in the element list. The default implementation relies on get_subclass() return value. + virtual bool get_element_group(pfc::string_base & p_out); + + static bool g_find(service_ptr_t & out, const GUID & id); + static bool g_get_name(pfc::string_base & p_out,const GUID & p_guid); + + FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(ui_element); +}; + +//! Extended interface for a UI element implementation. +class NOVTABLE ui_element_v2 : public ui_element { +public: + enum { + //! Indicates that bump() method is supported. + KFlagSupportsBump = 1 << 0, + //! Tells UI backend to auto-generate a menu command activating your element - bumping an existing instance if possible, spawning a popup otherwise. + //! Currently menu commands are generated for ui_element_subclass_playback_visualisation, ui_element_subclass_media_library_viewers and ui_element_subclass_utility subclasses, in relevant menus. + KFlagHavePopupCommand = 1 << 1, + //! Tells backend that your element supports fullscreen mode (typically set only for visualisations). + KFlagHaveFullscreen = 1 << 2, + + KFlagPopupCommandHidden = 1 << 3, + + KFlagsVisualisation = KFlagHavePopupCommand | KFlagHaveFullscreen, + }; + virtual t_uint32 get_flags() = 0; + //! Called only when get_flags() return value has KFlagSupportsBump bit set. + //! Returns true when an existing instance of this element has been "bumped" - brought to user's attention in some way, false when there's no instance to bump or none of existing instances could be bumped for whatever reason. + virtual bool bump() = 0; + + //! Override to use another GUID for our menu command. Relevant only when KFlagHavePopupCommand is set. + virtual GUID get_menu_command_id() {return get_guid();} + + //! Override to use another description for our menu command. Relevant only when KFlagHavePopupCommand is set. + virtual bool get_menu_command_description(pfc::string_base & out) { + pfc::string8 name; get_name(name); + out = PFC_string_formatter() << "Activates " << name << " window."; + return true; + } + + //! Override to use another name for our menu command. Relevant only when KFlagHavePopupCommand is set. + virtual void get_menu_command_name(pfc::string_base & out) {get_name(out);} + //! Relevant only when KFlagHavePopupCommand is set. + //! @param defSize Default window size @ 96DPI. If screen DPI is different, it will be rescaled appropriately. + //! @param title Window title to use. + //! @returns True when implemented, false when not - caller will automatically determine default size and window title then. + virtual bool get_popup_specs(SIZE & defSize, pfc::string_base & title) {return false;} + + + FB2K_MAKE_SERVICE_INTERFACE(ui_element_v2, ui_element) +}; + +class NOVTABLE ui_element_popup_host_callback : public service_base { +public: + virtual void on_resize(t_uint32 width, t_uint32 height) = 0; + virtual void on_close() = 0; + virtual void on_destroy() = 0; + + FB2K_MAKE_SERVICE_INTERFACE(ui_element_popup_host_callback,service_base); +}; + +class NOVTABLE ui_element_popup_host : public service_base { + FB2K_MAKE_SERVICE_INTERFACE(ui_element_popup_host,service_base); +public: + virtual ui_element_instance::ptr get_root_elem() = 0; + virtual HWND get_wnd() = 0; + virtual ui_element_config::ptr get_config() = 0; + virtual void set_config(ui_element_config::ptr cfg) = 0; + //! Sets edit mode on/off. Default: off. + virtual void set_edit_mode(bool state) = 0; +}; + +class NOVTABLE ui_element_popup_host_v2 : public ui_element_popup_host { + FB2K_MAKE_SERVICE_INTERFACE(ui_element_popup_host_v2, ui_element_popup_host); +public: + virtual void set_window_title(const char * title) = 0; + virtual void allow_element_specified_title(bool allow) = 0; +}; + +//! Shared implementation of common UI Element methods. Use ui_element_common_methods::get() to obtain an instance. +class NOVTABLE ui_element_common_methods : public service_base { + FB2K_MAKE_SERVICE_COREAPI(ui_element_common_methods); +public: + virtual void copy(ui_element_config::ptr cfg) = 0; + virtual void cut(ui_element_instance_ptr & p_instance,HWND p_parent,ui_element_instance_callback_ptr p_callback) = 0; + virtual bool paste(ui_element_instance_ptr & p_instance,HWND p_parent,ui_element_instance_callback_ptr p_callback) = 0; + virtual bool is_paste_available() = 0; + virtual bool paste(ui_element_config::ptr & out) = 0; + + virtual bool parse_dataobject_check(pfc::com_ptr_t in, DWORD & dropEffect) = 0; + virtual bool parse_dataobject(pfc::com_ptr_t in,ui_element_config::ptr & out, DWORD & dropEffect) = 0; + + virtual pfc::com_ptr_t create_dataobject(ui_element_config::ptr in) = 0; + + virtual HWND spawn_scratchbox(HWND parent,ui_element_config::ptr cfg) = 0; + + virtual ui_element_popup_host::ptr spawn_host(HWND parent, ui_element_config::ptr cfg, ui_element_popup_host_callback::ptr callback, ui_element::ptr elem = NULL, DWORD style = WS_POPUPWINDOW|WS_CAPTION|WS_THICKFRAME, DWORD styleEx = WS_EX_CONTROLPARENT) = 0; + + void copy(ui_element_instance_ptr p_instance) {copy(p_instance->get_configuration());} + + +}; + +//! Shared implementation of common UI Element methods. Use ui_element_common_methods_v2::get() to obtain an instance. +class NOVTABLE ui_element_common_methods_v2 : public ui_element_common_methods { + FB2K_MAKE_SERVICE_COREAPI_EXTENSION(ui_element_common_methods_v2, ui_element_common_methods); +public: + virtual void spawn_host_simple(HWND parent, ui_element::ptr elem, bool fullScreenMode) = 0; + + void spawn_host_simple(HWND parent, const GUID & elem, bool fullScreenMode) { + spawn_host_simple(parent, service_by_guid(elem), fullScreenMode); + } + + virtual void toggle_fullscreen(ui_element::ptr elem, HWND parent) = 0; + + void toggle_fullscreen(const GUID & elem, HWND parent) { + toggle_fullscreen(service_by_guid(elem), parent); + } +}; + +//! \since 1.4 +//! Callback class for "Replace UI Element" dialog. +class NOVTABLE ui_element_replace_dialog_notify : public service_base { + FB2K_MAKE_SERVICE_INTERFACE(ui_element_replace_dialog_notify, service_base); +public: + virtual void on_cancelled() = 0; + virtual void on_ok( const GUID & guid ) = 0; + + //! Helper; reply is called with new elem GUID on OK and with a null GUID on cancel. + static ui_element_replace_dialog_notify::ptr create( std::function reply ); +}; + +//! \since 1.4 +class NOVTABLE ui_element_common_methods_v3 : public ui_element_common_methods_v2 { +public: + FB2K_MAKE_SERVICE_COREAPI_EXTENSION(ui_element_common_methods_v3, ui_element_common_methods_v2); +public: + //! Creates a "Replace UI Element" or "Add New UI Element" dialog. + //! @param parent Parent *element* window handle, the dialog will be a child of its parent popup window but centered on top of the specified window. + //! @param elemReplacing GUID of element being replaced; specify null to show "Add UI Element" dialog. + //! @param notify Callback object receiving OK/Cancel notifications. + //! @returns Handle to the newly created dialog. You can just destroy this window if you need to abort the dialog programatically. + virtual HWND replace_element_dialog_start(HWND wndElem, const GUID & elemReplacing, ui_element_replace_dialog_notify::ptr notify) = 0; + + //! Highlights the element, creating an overlay window above it. Caller is responsible for destroying the overlay. + virtual HWND highlight_element( HWND wndElem ) = 0; +}; + +//! Dispatched through ui_element_instance::notify() when host changes color settings. Other parameters are not used and should be set to zero. +static const GUID ui_element_notify_colors_changed = { 0xeedda994, 0xe3d2, 0x441a, { 0xbe, 0x47, 0xa1, 0x63, 0x5b, 0x71, 0xab, 0x60 } }; +static const GUID ui_element_notify_font_changed = { 0x7a6964a8, 0xc797, 0x4737, { 0x90, 0x55, 0x7d, 0x84, 0xe7, 0x3d, 0x63, 0x6e } }; +static const GUID ui_element_notify_edit_mode_changed = { 0xf72f00af, 0xec76, 0x4251, { 0xb2, 0x67, 0x89, 0x4c, 0x52, 0x5f, 0x18, 0xc6 } }; + +//! Sent when a portion of the GUI is shown/hidden. First parameter is a bool flag indicating whether your UI Element is now visible. +static const GUID ui_element_notify_visibility_changed = { 0x313c22b9, 0x287a, 0x4804, { 0x8e, 0x6c, 0xff, 0xef, 0x4, 0x10, 0xcd, 0xea } }; + +//! Sent to retrieve areas occupied by elements to show overlay labels. Param1 is ignored, param2 is a pointer to ui_element_notify_get_element_labels_callback, param2size is ignored. +static const GUID ui_element_notify_get_element_labels = { 0x4850a2cb, 0x6cfc, 0x4d74, { 0x9a, 0x6d, 0xc, 0x7b, 0x29, 0x16, 0x5e, 0x69 } }; + + +//! Set to ui_element_instance_callback_v3 to set our element's label. Param1 is ignored, param2 is a pointer to a UTF-8 string containing the new label. Return value is 1 if the label is user-visible, 0 if the host does not support displaying overridden labels. +static const GUID ui_element_host_notify_set_elem_label = { 0x24598cb7, 0x9c5c, 0x488e, { 0xba, 0xff, 0xd, 0x2f, 0x11, 0x93, 0xe4, 0xf2 } }; +static const GUID ui_element_host_notify_get_dialog_texture = { 0xbb98eb99, 0x7b07, 0x42f3, { 0x8c, 0xd1, 0x12, 0xa2, 0xc2, 0x23, 0xb5, 0xbc } }; +static const GUID ui_element_host_notify_is_border_needed = { 0x2974f554, 0x2f31, 0x49c5, { 0xab, 0x4, 0x76, 0x4a, 0xf7, 0x94, 0x7c, 0x4f } }; + + + +class ui_element_notify_get_element_labels_callback { +public: + virtual void set_area_label(const RECT & rc, const char * name) = 0; + virtual void set_visible_element(ui_element_instance::ptr item) = 0; +protected: + ui_element_notify_get_element_labels_callback() {} + ~ui_element_notify_get_element_labels_callback() {} + + PFC_CLASS_NOT_COPYABLE_EX(ui_element_notify_get_element_labels_callback); +}; + + +static const GUID ui_element_subclass_playlist_renderers = { 0x3c4c68a0, 0xfc5, 0x400a, { 0xa3, 0x4a, 0x2e, 0x3a, 0xae, 0x6e, 0x90, 0x76 } }; +static const GUID ui_element_subclass_media_library_viewers = { 0x58455355, 0x289d, 0x459c, { 0x8f, 0x8a, 0xe1, 0x49, 0x6, 0xfc, 0x14, 0x56 } }; +static const GUID ui_element_subclass_containers = { 0x72dc5954, 0x1f26, 0x41be, { 0xae, 0xf2, 0x92, 0x9d, 0x25, 0xb5, 0x8d, 0xcf } }; +static const GUID ui_element_subclass_selection_information = { 0x68084e43, 0x7359, 0x46a5, { 0xb6, 0x84, 0x3c, 0xd7, 0x57, 0xf6, 0xde, 0xfd } }; +static const GUID ui_element_subclass_playback_visualisation = { 0x1f3c62f2, 0x8bb5, 0x4700, { 0x9e, 0x82, 0x8c, 0x48, 0x22, 0xf0, 0x18, 0x35 } }; +static const GUID ui_element_subclass_playback_information = { 0x84859f2d, 0xbb9c, 0x4e70, { 0x9d, 0x4, 0x14, 0x71, 0xb5, 0x63, 0x1f, 0x7f } }; +static const GUID ui_element_subclass_utility = { 0xffa4f4fc, 0xc169, 0x4766, { 0x9c, 0x94, 0xfa, 0xef, 0xae, 0xb2, 0x7e, 0xf } }; +static const GUID ui_element_subclass_dsp = { 0xa6a93251, 0xf0f8, 0x4bed,{ 0xb9, 0x5a, 0xf9, 0xe, 0x7e, 0x4f, 0xf2, 0xd0 } }; + + +bool ui_element_subclass_description(const GUID & id, pfc::string_base & out); + + +#define ReplaceUIElementCommand "Replace UI Element..." +#define ReplaceUIElementDescription "Replaces this UI Element with another one." + +#define CopyUIElementCommand "Copy UI Element" +#define CopyUIElementDescription "Copies this UI Element to Windows Clipboard." + +#define PasteUIElementCommand "Paste UI Element" +#define PasteUIElementDescription "Replaces this UI Element with Windows Clipboard content." + +#define CutUIElementCommand "Cut UI Element" +#define CutUIElementDescription "Copies this UI Element to Windows Clipboard and replaces it with an empty UI Element." + +#define AddNewUIElementCommand "Add New UI Element..." +#define AddNewUIElementDescription "Replaces the selected empty space with a new UI Element." + diff --git a/foobar2000/SDK/ui_element_typable_window_manager.h b/foobar2000/SDK/ui_element_typable_window_manager.h new file mode 100644 index 0000000..0d72cf6 --- /dev/null +++ b/foobar2000/SDK/ui_element_typable_window_manager.h @@ -0,0 +1,14 @@ +#pragma once + +// API obsoleted in 1.5 and should no longer be used by anything +// Core-implemented to keep components that specifically rely on it working + +class NOVTABLE ui_element_typable_window_manager : public service_base { + FB2K_MAKE_SERVICE_COREAPI(ui_element_typable_window_manager); +public: + virtual void add(HWND wnd) = 0; + virtual void remove(HWND wnd) = 0; + virtual bool is_registered(HWND wnd) = 0; +}; + +FOOGUIDDECL const GUID ui_element_typable_window_manager::class_guid = { 0xbaa99ee2, 0xf770, 0x4981,{ 0x9e, 0x50, 0xf3, 0x4c, 0x5c, 0x6d, 0x98, 0x81 } }; diff --git a/foobar2000/SDK/unpack.h b/foobar2000/SDK/unpack.h new file mode 100644 index 0000000..9d5328b --- /dev/null +++ b/foobar2000/SDK/unpack.h @@ -0,0 +1,22 @@ +//! Service providing "unpacker" functionality - processes "packed" file (such as a zip file containing a single media file inside) to allow its contents to be accessed transparently.\n +//! To access existing unpacker implementations, use unpacker::g_open helper function.\n +//! To register your own implementation, use unpacker_factory_t template. +class NOVTABLE unpacker : public service_base { +public: + //! Attempts to open specified file for unpacking, creates interface to virtual file with uncompressed data on success. When examined file doesn't appear to be one of formats supported by this unpacker implementation, throws exception_io_data. + //! @param p_out Receives interface to virtual file with uncompressed data on success. + //! @param p_source Source file to process. + //! @param p_abort abort_callback object signaling user aborting the operation. + virtual void open(service_ptr_t & p_out,const service_ptr_t & p_source,abort_callback & p_abort) = 0; + + //! Static helper querying existing unpacker implementations until one that successfully opens specified file is found. Attempts to open specified file for unpacking, creates interface to virtual file with uncompressed data on success. When examined file doesn't appear to be one of formats supported by registered unpacker implementations, throws exception_io_data. + //! @param p_out Receives interface to virtual file with uncompressed data on success. + //! @param p_source Source file to process. + //! @param p_abort abort_callback object signaling user aborting the operation. + static void g_open(service_ptr_t & p_out,const service_ptr_t & p_source,abort_callback & p_abort); + + FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(unpacker); +}; + +template +class unpacker_factory_t : public service_factory_single_t {}; diff --git a/foobar2000/SDK/utility.cpp b/foobar2000/SDK/utility.cpp new file mode 100644 index 0000000..3f66dca --- /dev/null +++ b/foobar2000/SDK/utility.cpp @@ -0,0 +1,77 @@ +#include "foobar2000.h" +#include "foosort.h" + +namespace { + using namespace fb2k; + + class callOnReleaseImpl : public service_base { + public: + callOnReleaseImpl( std::function f_) : f(f_) {} + std::function f; + + ~callOnReleaseImpl () { + try { + f(); + } catch(...) {} + } + }; +} + +namespace fb2k { + objRef callOnRelease( std::function< void () > f) { + return new service_impl_t< callOnReleaseImpl > (f); + } + objRef callOnReleaseInMainThread( std::function< void () > f) { + return callOnRelease( [f] { + fb2k::inMainThread2( f ); + }); + } +} + +namespace pfc { + /* + Redirect PFC methods to shared.dll + If you're getting linker multiple-definition errors on these, change build configuration of PFC from "Debug" / "Release" to "Debug FB2K" / "Release FB2K" + */ + BOOL winFormatSystemErrorMessageHook(pfc::string_base & p_out, DWORD p_code) { + return uFormatSystemErrorMessage(p_out, p_code); + } + void crashHook() { + uBugCheck(); + } +} + + +// file_lock_manager.h functionality +#include "file_lock_manager.h" +namespace { + class file_lock_interrupt_impl : public file_lock_interrupt { + public: + void interrupt( abort_callback & a ) { f(a); } + std::function f; + }; +} + +file_lock_interrupt::ptr file_lock_interrupt::create( std::function< void (abort_callback&)> f ) { + service_ptr_t i = new service_impl_t(); + i->f = f; + return i; +} + +// file_info_filter.h functionality +#include "file_info_filter.h" +namespace { + class file_info_filter_lambda : public file_info_filter { + public: + bool apply_filter(trackRef p_track,t_filestats p_stats,file_info & p_info) override { + return f(p_track, p_stats, p_info); + } + func_t f; + }; +} + +file_info_filter::ptr file_info_filter::create(func_t f) { + auto o = fb2k::service_new(); + o->f = f; + return o; +} diff --git a/foobar2000/SDK/vis.h b/foobar2000/SDK/vis.h new file mode 100644 index 0000000..e7f74b2 --- /dev/null +++ b/foobar2000/SDK/vis.h @@ -0,0 +1,77 @@ +//! This class provides abstraction for retrieving visualisation data. Instances of visualisation_stream being created/released serve as an indication for visualisation backend to process currently played audio data or shut down when there are no visualisation clients active.\n +//! Use visualisation_manager::create_stream to instantiate. +class NOVTABLE visualisation_stream : public service_base { +public: + //! Retrieves absolute playback time since last playback start or seek. You typically pass value retrieved by this function - optionally with offset added - to other visualisation_stream methods. + virtual bool get_absolute_time(double & p_value) = 0; + + //! Retrieves an audio chunk starting at specified offset (see get_absolute_time()), of specified length. + //! @returns False when requested timestamp is out of available range, true on success. + virtual bool get_chunk_absolute(audio_chunk & p_chunk,double p_offset,double p_requested_length) = 0; + //! Retrieves spectrum for audio data at specified offset (see get_absolute_time()), with specified FFT size. + //! @param p_chunk Receives spectrum data. audio_chunk type is used for consistency (since required functionality is identical to provided by audio_chunk), the data is *not* PCM. Returned sample count is equal to half of FFT size; channels and sample rate are as in audio stream the spectrum was generated from. + //! @param p_offset Timestamp of spectrum to retrieve. See get_absolute_time(). + //! @param p_fft_size FFT size to use for spectrum generation. Must be a power of 2. + //! @returns False when requested timestamp is out of available range, true on success. + virtual bool get_spectrum_absolute(audio_chunk & p_chunk,double p_offset,unsigned p_fft_size) = 0; + + //! Generates fake audio chunk to display when get_chunk_absolute() fails - e.g. shortly after visualisation_stream creation data for currently played audio might not be available yet. + //! Throws std::exception derivatives on failure. + virtual void make_fake_chunk_absolute(audio_chunk & p_chunk,double p_offset,double p_requested_length) = 0; + //! Generates fake spectrum to display when get_spectrum_absolute() fails - e.g. shortly after visualisation_stream creation data for currently played audio might not be available yet. + //! Throws std::exception derivatives on failure. + virtual void make_fake_spectrum_absolute(audio_chunk & p_chunk,double p_offset,unsigned p_fft_size) = 0; + + FB2K_MAKE_SERVICE_INTERFACE(visualisation_stream,service_base); +}; + +//! New in 0.9.5. +class NOVTABLE visualisation_stream_v2 : public visualisation_stream { +public: + virtual void request_backlog(double p_time) = 0; + virtual void set_channel_mode(t_uint32 p_mode) = 0; + + enum { + channel_mode_default = 0, + channel_mode_mono, + channel_mode_frontonly, + channel_mode_backonly, + }; + + FB2K_MAKE_SERVICE_INTERFACE(visualisation_stream_v2,visualisation_stream); +}; + +//! New in 0.9.5.2. +class NOVTABLE visualisation_stream_v3 : public visualisation_stream_v2 { +public: + virtual void chunk_to_spectrum(audio_chunk const & chunk, audio_chunk & spectrum, double centerOffset) = 0; + + FB2K_MAKE_SERVICE_INTERFACE(visualisation_stream_v3,visualisation_stream_v2); +}; + +//! Entrypoint service for visualisation processing; use this to create visualisation_stream objects that can be used to retrieve properties of currently played audio. \n +//! Implemented by core; do not reimplement.\n +//! Use visualisation_manager::get() to obtain an instance, e.g. visualisation_manager::get()->create_stream(mystream,0); +class NOVTABLE visualisation_manager : public service_base { + FB2K_MAKE_SERVICE_COREAPI(visualisation_manager); +public: + //! Creates a visualisation_stream object. See visualisation_stream for more info. + //! @param p_out Receives newly created visualisation_stream instance. + //! @param p_flags Combination of one or more KStreamFlag* values. Currently only KStreamFlagNewFFT is defined. + //! It's recommended that you set p_flags to KStreamFlagNewFFT to get the new FFT behavior (better quality and result normalization), the old behavior for null flags is preserved for compatibility with old components that rely on it. + virtual void create_stream(service_ptr_t & p_out,unsigned p_flags) = 0; + + enum { + //! New FFT behavior for spectrum-generating methods, available in 0.9.5.2 and newer: output normalized to 0..1, Gauss window used instead of rectangluar (better quality / less aliasing). + //! It's recommended to always set this flag. The old behavior is preserved for backwards compatibility. + KStreamFlagNewFFT = 1 << 0, + }; + + + //! Wrapper around non-template create_stream(); retrieves one of newer visualisation_stream_* interfaces rather than base visualisation_stream interface. Throws exception_service_extension_not_found() when running too old foobar2000 version for the requested interface. + template + void create_stream(t_streamptr & out, unsigned flags) { + visualisation_stream::ptr temp; create_stream(temp, flags); + if (!temp->service_query_t(out)) throw exception_service_extension_not_found(); + } +}; diff --git a/foobar2000/foo_input_validator/foo_input_validator.dll b/foobar2000/foo_input_validator/foo_input_validator.dll new file mode 100644 index 0000000000000000000000000000000000000000..ef1989b2b093372bc5aa22cbc7d0ed67e93a69ef GIT binary patch literal 194048 zcmeFae|%KMxd(hUISC6a>_QgZO3)xtqeM$hs!11X0yzmPOE!ev5M>EguxpAILpDJn z{90l;j;FP)-qO~#*lMelR@$Onkzzt98zF}9qmoDhY0bTzG*K5th|1->-)GM5X2Xx( z``-88m(PdnIdkUBJTvpm^J|{xIj(!3mXak&lJQ^HB`Jho`d1+S{`)^RlO&D5X6JZm z$LM#ahD>wcnYzIL(8`PzHQ)Yb&HWE&JaGRbk9@l};~UE|Y66dBJoHFLQE7R`!{1)I z?3Quk#$-mD{?36pQ(As|{k+)!3GLUFw&H$5`?b;EX{BA_{)W;pezV$dC~d~?{==`$ zJAvPp-`+6qE28|%r5nWW^`(2nZ`VBfZTamV#BWx6rYN8P&;x$z>vAJ=E0Q$Vlp;-k z@#pu(>hwxUQ^%UdTO?bOBypp3;ujda4c8ecqbs_`Wr~Dy{EPicGcda}x%ihP6&Np3 zU3x+M8b9l|CrL|D+K1n4Y9fN#qp9A7|B|JpS$JnzlC;FiCRYcRijLBt8e6 z+XSeIg~Qjk)Gk|9i(6(zC4!5_8ihMnD?^f&+)}gj{@VKi-t#D>vHudkcKjy%E0Cmu zTa0Q_F16Kx=NA0Feo?)qTWVI;Jb*`HtQfO21~+>ys#kDJ&9dd+MnxLCL>PM$e%dao zH;bPB|Ns8?GvMDcBgytg_K74L=QWZP9JdIUx<4`{$rer##b2hYB9xvhLZ!R^(_gjCi(CBmuK=bc1k(b*3gewdHkV`zzc&m@zGNXiVrMJe3x9Hw@D}(BaxAJgh;z{DI z=hM-ce?02`YGwyPMfZ8)stA8`i_u)?`!p7#rj=^Wjn|wbt`^|$0;>6YQPZDEqn=5S zTtSf7@J={I+>7BZz!Ms7IAu9Of|cQcgf1pf7mMOuEEHEu@z?IAcj$g0u6SX>I}_=h z<#=KYz%Q<9@t2q4|CI?f)2QZ}c+FK*=j=bkV%hPC4~Xlw;eTcPCucE;40_mp;X_no z<5po5US`tWHshK_*aUsPT%GFQ0#xv_jyKlOT+BX=W|mMh-$66My|%?I)XU}HPw{V! zzQ2jyPp0>`()%Te@B3$5gQDCxWd2^Nk{Q1b4%)mG?2bVzuH}rzjvj{rq}MU=*NnKd(FY~a?N11UM=a9?AHQSq*^?kREyU98RD{=F4qHw zaLQ8vbHRP+{nvk|0otn;W0WlbK(l0eEBq_y(FcPd@sYQ-qUwwIcR`gcRAs+WW&5S8 zG*Ok`7*(1sU4>JXO-7ZD5mmeuyQ$~5He=bT``y&ZThGPrsJ*wg$L{FWxAsQwKo#DY z@gCY;)NPXd8?M1sQ!k;7hYcE2{23diyJdDIJIEsX#A?Yq-_7s*&?KqK5Q_F}_=m3R z>r~IV>oCvjPd+>pTsDMf=h!G*clDlV?(s!*w*@7~m2;p^sog1jX_BqGAyT!N{ihEU zk6o&Vo)1hCU<;eJfkw52LMZ^OXAsjrXfLN%)P@KPm~)z_r}pruC8(v=XRuU&s+P#C zovzGkiP^Zas3jJ0Wjl_G2z!r}SQ21yS$a~o0;nDjFDnDY>6!YZR;+(dN(7}jO_e>K z-b&Wmd5CqWF7wG=)nhxexA5`F-Tj?iCwp-jn3()Fo@_WwbAYD%$(!u9i4D z523fxbhX~5R@r;H&g?Y>8UQ;+F%15Rx68`96+R5L(o3&ZR_44-O%kM&h9R{ipW1MQ zz!WgnVIh;4X`x_hZgm?e4HDZOs4K4~k~rIO8)8{e*#L7Z%C zeEiCFItr9PEMRZBhxN1ffG&zeC>)U3J8Ve5qFSl|4zI>Y)uK#Wj?VV5cMLQZIIJ$n zw4Yp_X~RQ1-c;9Q+UQo`@}f)!Av0b(j{n1J(7h(p(e>d8fek5-H?U-OfO<@02WZn& z(4H9^?GHt=rONw+p(ChsE>6?%`h&9D1SQd!86 z0e*BP?Vx4nB-%pnsmmg&uakYoE5NIQu5&uJ^C(PcMD+}c0oa@oYvT=gnC}_1HeQVj zHi{k8ZHd!(0J!3HDlN(s;`$d ztFFGB<5PVcuWxJtxb^17GuBOS;EK7-eCGxrpVBw|6XxQRn;V;Ito7y%=Sfp+Ct}3Fz7G;lLlQRs5J;K$eyoFZRq85C!m3V z*;}qp+5*no_2GN{L_>=nA9ZU}|MM{9$Om)0*Ue~RtGJTF5{LEzC_&s#2 z7syO$Re?pyUKNn_p>gKvPImPHn&#f5Wbi2rugTeptpRAPPb>$fGFOPvQ#VsOlbhXT z`lLkwk9h_Gq0@7;K8-0I?4Whyd+5>GYTbAkzvwuBp=|w8iyjE^iGPD+@MMd?u=}F4 zmUy^-CfKh&sV^hR))tzYip9Sb+Dutlxi_at@ajQTiG)Y(Cr#UaW~pxC?hq<_W6$5ib3I_ez6hB4 za!i$f7qu19kFYB+AlB{{1^UD&!%76$6C7L=x`zN&p)Ldm-Q`{uSkB%fS;XaM&13fk zu;Kb2fjSIiE3kk_J@qF5x6a)7wsq6XghdDS4pdc9yM4odVRF}P_&b1KuLhQ%eApjc z=9g3#U8%})VjRbn<-^S4#Am?Jn>MkIGd>o62&Qj8+Y~ z$}Qxotg>4I&*`uO0gw_a4HQi*}X;&RJLc@u}dwP^nYBk7vf7+&ZnLh zOO~v8UO-QJU?sRO40i&D&xbZ3Aiv!U4a6*+I0 zk+3)ql~|zf4ciTpWZibcbZDUQc)htscAi?9#M-UfI!rld4*kpdag7;_q!o;$RSmSM zfe!1dt_UF{cvl;jVS@8|)xc3T(8+g>h2=&$N)*h;*#T5a(gPi=p^YV1c)dcdu|Ou$ zl%wE59qbtYHAVuuwyBB4lcQ_~M1Y&wJPS2YY{q!YAX~8mZtyie<%?)lYvY4N!fta0 zjs}hxqS|!O_>Zfl%E_m!+ajhiJrFtcp;x^1IL1h=xi~tU-U>J-e+)L_En^4NK;I=G zvd1B+j6o#Sh(H8f4uTkWV6iY09`M9I0Exz@Hv?o<>7KmbfVdYB^G809gZmNio!!Pz z-NOm!w}*k=&vL4zd(p<^{i@On5LH)qMVTAorx!Ay1tLX!u2?%?bv8Czi!Oq#TMzW| zH75<&deO`X*mw*!noWNAvp8(ujl$Lq>A-+3Ktp1wId21+in21+ZcGy3V8_I&9pDin zyJFR5#o)M&%`>xN%h@htIB9!SS1)GcY^R%MTIuGChx_!1KB>3;7$8eU7ZB0dp6RX* zyghl3bz8Ful1AhZcVqoI@RFmFG*?GlR!Ja-$bm^gK> zfdU#+=eY$ND<*%no5()$SB_H=FRcdU3z2ZRS6lqm`Vp_*CSL8^9~&rcrT$$bUi}*u z*O;T3BVPRi-Bj?G0w}wVKpNC zR#g_Lo`q0w0}DaZTt%v9uJhgcu{BpPWr4GGWy-)fT;PdP#NJf{bJ-`XMOBulfknq53jaUlzd;m;)vq6HAVH!L^e4 z1K4AgmAI7i7uxDlSb;o{5~8DpY8{$}Lvz7oVlFS&Tr%pU@M9KWeLlN`Wd7X}s(JNe zkoZxJj{>4Fmz`|RGK&nCe*~lmd2fV@opT)1AMHP^WE&vPP<^?oFHiN&EGsKRAEUZ9 zy(UJBE-_L*;V)=*$arR^XP6K$_M&f~IVb0sho<-$pt|jiW z`Mj(POXVt1l|?bJ{5oJI?0id3KD-EP%uK3p5iYA!4Y_=g5Rcu*75A{r-`%jNtW47i zuw32B3hMclx6m^L5mj+hpH~3WhUh{lLoDLY#MF?kBQ7*oHt)wqF|;4LJC3{Eq?`{I zwW6qkUa6o8JE=mtm|xPUsOUE+VgYw>pLzJDAEFF%!KcG|WGvArehsc;D+eQ2?Ug(8 z&6mu0Z&)l?@ z{`b%6IxYB35Q1QaaLpmjgrDjMg1fOFIWABLvuP@?#dU1vXxeIbRq&dyTq} zQei9qFKma(O7T*aQF7EMY2~lt0)y}5KgENX38w!%;1Kd_R7Ji|Tej2()GaUPzk>$d z22*B5@IhH(sln?dH{bPFpcjmcm5q9BI&8M#YWd!?|FAyYe70+BYZ-qAk1N$wb*)^r zc#kXv@5hU#;8eUAq$e%He$LD)G*z`g4t`UXoYvYaz3eMFhs%hUk+ur(+nu)u1{PPf zpmY8Yo-Z~G{+4pq1y$IbztX&NS_RB!wr`zJ&NymVpeX`Ce>b|0-CbShJfqA0=DRu2hihX&5%F0RR}Cdl>=kx@k_V{ zuLrV^i_SF}wvACu0sHrF7xuN&ZvL+$7*@57kJ*daXLB=Qi4LR!i8=;kwjk52nqijN zW0qJFpkVunl3{Cu6v7a(Z*dEZwPyOycVZ1t-@?YIbIj~+S)F6qV}X_X<6x_vbNDQ( zI3x8=>#Mkc2^2C%%`~le8qNuBV39M;*90XNW|QhLy8*eqdyvK^7Vm@DbL2tpmbBy!E(NQ~pMoZ{8;ncyyz$wfzFe(rUr_%AXS}dz1pqhb9YOy8O zAqt=okSa}b+rHS*g|WaG2{Yv!4h~wJWpY5K!4W2Z4nh~l_j7{DcCH#p?aO;Eku~(#}@>H zm(^xkXls|)3X=D^`3+Q~R8B6n5z=@C{WI0uupcUMR#UZjpKOwVb)lqDn3^4cmuw@7 znRbG+Sgt9D!lAj$$;Hr4R~BQ%V7jH{`U55*gz~|AoFSEX&@G3 zAe#Vzsor7?#B#wv20AV=5c8!50t@^9VIY6Pti}gIP5hgI1Rt}E+(-*D<@_>BogI5i zEwk*ClW+=VDRA=@m@i}5VMPF5VGV*!rrPV=+>0m7YtDJQ@o+7)c9|Bf(7C_nG_L8) zDoStGVoKt;xB8&LF@kGh0=+`7lcQJ-{ATUw4Rxi(EtpFHV_uo6&9b=%Mr*S&@(ay% zqvr#|cm$}7mSYQ{6`S0=Ek=DxWiL0;k^qs^dP^0N)x~i{`W8Aj5UCuv=q!^>tu|@ILX5g(2n6wwO4m`n8Zi^5ynuGV7rEsBK z3*f^hqGu#LXvK0kHG%G(Day2%Dr?77gDBnb7h*kes&^s!KY$MY7wAuu5l8v(B#OF4sa87rP&Wrl2am>ux4+h_5aG~XR)=Sj4qj#8`4 zi>vN4nu9=KDRaWW(_S~8??xL89fL0oSa4y%1%H_u#ty@vF2fcKp71KYJc1|WG=)t< z;{m}bG^>Rc6{qga(L;A!;9Pbr4s~`AH_B;hfn1UXEdO-AKB-NS+8Tbo+wSqRS>n<^`<&o)J6LNMM`)*PF0@~;`5@os49Z6nZSy+9Wm zIAfe6K8yJZr&3WIH?Wv6-lFA6Sc9Cy)>qH6eP>TO!;mHREryiYR2A#*7nq0P{+3gJ zLXbR6Q$35_&VNr6MXPZRa<@84NXbO(Xx*Ika@|-jKY%J~y*w~>Hn%a?&n~J6kH-pZo<=GlbqNHz(Q$M-l4@N%7A%c&A?-@KWw#>&=w*PaT$&3qeY0c zc$sS5CDW>rYM&W3zt4vn$@W#9{X5v#UF6HL#1Z{XkN(>5iydpjGaFXhtl2l?$HreQ zgi0g4PRy*#0v8i_p0Gkk6kSC>rRf0WcDR@jT~sXWE7lh}lA8aK1WyyMgO#S!6UC0j zqw`QY^d;9e;gN)PNdW9D1)4wiGEf$(%$YE-E|f{K)xuWd1Pf`Ix-_MiOqayJ+Ll0x zyN5cxke>O0@vE=^nq)^Gj_VGpjh9NUY*2q zmccnF(5tPsy;xcK;)|%ktgPJ3W(yPlNcdf5F_CBhmZ?tbtG+(tz+$bL9l;>y!;CS} zt<5v*SHeswWHQr2^L;_LMH)V}#iD4+UTCkq{QboMUU&y+$_}@7kBuLql8ENo%D;oN z1njPZz_cVj1cM*hDfmZpu^+m1BOdQ;!qS7z7LuM%jstnPh&c{qQz2>A)99j=ulYpR z(dkj{$9+^~KI-Pr?86KLy+Idf_GiJ5+@Bep{Z;gg23(MHm{76MB7T7xz#7sWbhiWL z+fBd(azn!PlZ8+dZlm>j2`|%0bVYRmyqH`OKL2%-RGr6$ppRFOFq?!5YLx~4GHR6r z=9sGVa7m`nCaSX3s%*7tMtD>Eg%iz>iv$2YtI?5c(ad7j)L-<;$_MIG=IE z4x9p!VecgM>whvuooucYXVOo$j8I-%z_v1lf!{5!&yxc3txW@yqW6URWK08&nzx&X zO2EIeFdfR1#lx0o+G%6qlnyhLh3?6ZB1rh=lDt}drf}iR+|(2pO|8*nL2MV5^Du)M zn@cjHw-)GdhSVcaXy1ORGUD^jb3B(c=dV*bt}gn<9Y@=xEPFS zyGbpxSNWh*1JOcrXep+^UP1nQ(er*dfI(d%dlg-@^U=VJw|WcV`7CT$C^-ulV0Lfl zeTDv!M^7J)x8RsV?gUCN@j?>wAnX~95H7di9s0fF9-0BT<0Q(CQ3F8+HZL?%uEGYp z364x#X|~>lym`Kk3IdjD$uHdq*PHLs1L5;TbJyb=(s52Z6m?ixJsQ<)s5Vf|QZ-jZ zoc~xr+#*C54#KKuVXa-JuGE-=%gtSda#yh&yS=)r(A-sQj@??j3N2m5mc+(0sPS=z zpCtMk%pxP)X}$vM;otTWZWP!f?uEYJ7P^-vco#0q%7V)xpkNX2&NX+U%-dihgDp5P zrXyngikp2!_W}bVev}Ypb;I;BcpSb`!@ij=bN4tujKV!WGhQVPgWQFP2)j=ADCPtO zy2a>tHg!z>iav<|0|0(}&1Jf-5ZyAYVZ8APhM&g1s>H?(dH~-Hm~-II>0q5&xxV3Z zMAJyt?>tXn!L*{!PX~Q!ZSGHcYXbgFJJ|fMz|vN`)$ds&I2&-(=nUdmg0bw@{WaHw@US2 zHi|N-KeE!AVO26$_-ko>W>{bS!bygxm6NeX>#1l8E|$=R9T&NDF_xqm+|0!dE#dEA zGap{UA2rhw*3uGwhRfh!7L7T0K(E+WESn6Ctf<}0y7-0s9 z9>zP^a1(%72@4MbM;yXJ2Y}F|cDip8z3%t`bO`LcmH!r3I}41`pHnG#dMn>fSFQXi z{D8x7-VP=eon-Fjw>B9Z#t^zMBen@?@5gzB@G)mnTDiEicPUX%ytC}0cS1j<(cXw& zHwZ98SqNeNJl5D`|)2( zQV%4pPI{tgO&BK???th3*n=~T6<9Jl?ZNS|=IS8$a!Y|d-y7DH}#(Z!GH>7i~ueK@j=mO zQpL$p@-W;MV>tA{7^A9W(0ES2i1COsn%z4HOppWPc%_@q86-Y=l!%)+zKWI4K>6;i zVob53DJatFR!|gp9~a?NJrUhNUHG)9&58SmwXG5_`1`%MYpNZ(#3bD8vHz;3muW(X*q* zvp4W8=WUE~(JYX47tW#E;J*^x$D|CN*_-nCwb+K6=gH^`)_G>4z2MID_ZltKixzd?MDe4h>c(M@q9-;cKEtoh`10HH2niSn>$t4bYn^(5e zIUOFuldGxgLq?~oUS8Y_99DSFama!2jonSr9BN=o!hCJUq`11{lTvC|15M%%5&>oh z-qlwztna+|yagY$OQ8o!E{E8Or22yR0XC2HGb3uibvm3%z(mQWh#1IeDro8YkeE+l z7Kz+${>q&&iCH8ZiUn6Yq}myfY}RDjwUz0Rg&l~qVfP5}Hy73h8?IG~MMyDJNit)M zf);Fs7JMJt2&I{U7FX>gw1D=QbB5^b3s55WDcek8<+)m*ts2-7R`xD^fUcqrWNP4C z{z@{a-V-;??-ORr)PmrUUUS+_2ig34H>26r>88LQW!(E@S2kti@n40T3oJyW2UB=o-RzbLbsqh~P4ASZc8+u`E z?5t1ONmfl_G`HLe6lv&VXH~=n=(Tc0FQKFU9GT02=ni!D*V0Ctbf&HEwk z8z4e}k3g)7(A!P)b^!%t#kfjOPz(SmBUIAp~S!#aneYRFhkX&{4(YixqlYv6Q(_EQAI^YleAE;tBGu<)@rppm@}W>Xy$Ti~&)o-DjMN=Ts| zI~Gg|RTt&mkyNanJ&|${4)nVHzF9l8h5tPm$)z<<~w1=GGtsN_MB(Py|#S z71iX`2A~p-lH&yl^hjN6M;8{%A^(VU<_0s1;7oK5JLu(Og^#+p`~bpixN!~UR*-Jr zCe^oD^=&~#ygg16PkK_MWjH8^)8qHX!`s-+Ozbo@N>pH$)yhdulI!KEG!pL z=eERVjRlH~Ziu?k*b`~NF!w<)7apXF9Z1zcCvxLWxM)PlxUHet99XWoHuH6GvS4Iy z9L=-^fJ9Kaohzn;Wl;c$17U!|_G87x&J`eLki?Uzu$4ddcM{08nRttT4FZfIlQ+_R zeJ zTtf*|l`LNL4U%w*Ky^j|pVz;Me&Dl?Y3T7G!mHcoAP;m?V02GPhtQB=Ml3o`^^}zDLREzK)^|dt&uOEt1H` zBp*PnG1YU2|8op$i#@92@L(Sh8_-_a98V%x=Bb9l~N=oCtk+PEC$Ovz)d(H55Ce%e8` zafNt`o~oLk>bO>%{2e_Mjd&d0fn7a5PN5RN##U!=$LI6m17@t99?0dN0I_=%2W#&R zJO^w><(`zy7<_qLG!x;BsCgIIgt4#`eFWz`OHGQ?b#!G)@`2!C6ARdLnv$E~>o0PNG(O$)28j*}0!A-?A1!+QzN?s2PVx&iGnIKTsd z5N^ADTtpaP_|_+1!?v3aM^;}E3t;MZVOik?iwT*m*n%x4Rk6XBHSJ^7w}b6dJ=>zw zgb7kYCm-ISy0&W#n{%38Ir;FmSE$2RPJH&tE2`%y)$_FKc}DeY2`ih0GUbuFmpOo~ zKz&n>A{mGyhyZ`~4|Jl&5AHC*tgmTXNO}C_)4<`Sz*<$wj2#nrHVdz>%@m*u(6C{v z(#}G5yy*m1M`$Z=Ex{WV@$eX&DzxhpN#ShQ16e!?^>({agAKtoj9p1INnoR-!S%8QyTKgR`6P zgE1=7X!&aho!LDT{YH<8*_FaS1pNjwh2mqJnZ`;nF35{~;cW)`U>CUZXq%I7G6ZU~ z*xCchD8-38c5xbZPM*_cv2M%+yQ?4VRI;oazeI*;CAUCTa-Eqs8LHoZWV z5@84DwiBH}91&tAeLOMz)FYpK7>@=?kwUx!MNnr8MQS9S>2Am- zfxm&29wmDq)hJ{Qd6*kx_f>$t@LGfIgPO4R*p};}C9B|FAi=aFm5e7l&m3p%R~^sM zE3hlqzi)~MZRmkK{)gduLuXDRn)fOlwXXfb&quV|V;P6aXF9KHy0&GrQeqGh2u=vL z13yqQ!4inr6_*gP%Y`poO6A#8I0wy4Saz}8SbIJWE`-qu-y5khp}0<)4Fa;T-VOyO6M^Dz`A1ZV?mW{jDlYa&Ig$S3b5}vw(fGh{vM%D zsL11^I$Z6=9;X0Eal^JFYj zb#f(EBd(!{_%{U*F@7FVb^jc^-jGOv{rITO81dBiO7_+71cY*9NXk zl*f{`e>zsoWt)ruiM=s0BnS|cU=VbKf`Yiy@C>rvJ%yk(g2l|blG{H4lrzs)QfnCv9{w~B7<3y^F?)&>8D8vau{y$OO72W@kv9ZIjya7w2aQT-Gyq%(bcX280$nSXi`^G=f3D5fn0oEJ!9-|a*Zox(NZ zA|{DFMun~Xqhmr6Ba63m%VC6WY^t|Gyb83f91T4<=S)u1Kwi;qfKZ`-hC*?^o>6z#;$85&{ZRqXw+y5WZ-sF+$pYJ<67Dr4uN_4)Byhtbohh{{$!u8tis$z1-@a ziKXR$%uY+|7|zOJMOKbO81{NR66@Ln+{d9?@;=Nu`#i0IPEg3I41TvGHTHa&(9kE&Sa! zS}vLcK=DjWG1Ot75*SaD5=+IANTrP3>+b^;6ES{ADW2X%v1hPq{>jwi8SMHHyTo-)5ckzdPj)@o zU}pgb?8BS%+8Kn1U0y8H1MeG%7yyDN?&8BB@~|7DF`#4DhntSqULQ@{v3jZ=XiXed zqN&#|^>iWk32mdj-q<#(ZPyT{??q3Wj$1eVmq6#e{L9m^-;}+0go6}uylOwL*oR3# zH~jf7(hd{$KQv6r-2x5fQv2T?OETjy_CF9HS+S>i9F2$T?by6Z@5*tc`wn7+IOG@@ z*Q3y-h%NRFVu}i22aoOBo5jAR>B_~l5Ya2fK26-WzXF%QbkWuwO0$&Z7r*%V4AxW>NH|-DOwvc>80bdCF}Qk4~sykJ({Mnqs)`9IQs*uNmQwn10=x zZulTsKp0Q+g_wo-30}>wh&{u29LgPyx$VI1hCv6YSmGH&0Xg7S5hx`9McCnR6xz!t zWC15}lzUOc6s9#vkQTo=8|qIa>Rs#>w-n#A@(|Lh#7adJ(fue1Hp~Q$&xoBtmBOIw zz`{_J*9Dg`Lx}DsYV2wV)9<{|54Z z1=poZ)=fVkG3k$p*y<1~GdFqPu*EJ5x+E(Vhqdj11@bbs1Z0aM zG948es@WL-0t~AE7a$j`-jR$@!FGxlOoe*|BA*Ek3i|WPdu z1ye7i5OrAm7Q*WRA&Cg8jceuRs6-447%ZX-PCg8w*i1t4ab-c{;g|?=;xn*2f~$xu zA7r1RJUHD`RtSk%WWWiG^VbqOlp-A+ZElYnZlaA_42U5azCaI)Gqiaby4%eVfubXF z;uH9|7H5=+!Xg&IZu(~Iu+`VA#tg>(ZFm>xkP^eG=vgoo>oGGx?1UI6zs6(YeS~Cx zI&L5tomQmsr;ZBFkAOY*EY;)gpNyrsMBpOoj^f8NVGzS4evGIH9MvJkCcIn&IMv$B zQ?NPwoiAfZ&HbxW2=5kvIqK_RNrGv4G8vbO*uH5WtARO)ETgl|qG|ic=%&np(1bgB z4rdY+y5>|T(}at{Ij94}ND&T@y8#)MpZF|_5zy>@`yF(En#{DHeNc$Y38erV7-uR$*q-U(19b65b0`ykJEl~EvH*L)%h0P zmSm0~2+Y2n%;+MJ5>1U~Ey1A_Yw#JE@V?b*$^ZUKGm>o9#&u-T+prDSe%s9?pdlxh z5!WN`iDVQ2PCXDW$0e2Cgi^m5uf*%q4Lz~piKb)=Le-D6`WO|Nz+X8m7T7lO1ORlQ zTAv>!3C3wS4hICW6G-yiF7ehxG9%YnFf+o09LB_8SmXGiYY7uNF+Z#%gSMJY^O~@) zjw=g?H;J$xQPJ?OF<$8Ly@VI$C8;j)!rQUumv{kiLR;08m*6PTswrD>RJRwV$sXTU zVaDfIU=iT!qXqV|y`VYTDmg5LZovcv>~yx+Pyp&ykjXy)OTIK#r|}dq7CT{e$1ql3 zscCDl1Uwt`Acok#q;*s5!-#!!asbrBKx`i*`w_T}39=v?5XgtB?26%bld8xDa_=G^ z4_K*2A~w`AGQ99Zn-E$_n5-)HMAqTp*B=0K2J9DM_IQ+E0<-@Lbv1_BcK-7Y=;ZLq zkP7-AfC*32R?rxiU|;)fzZIJo&IuT!Kj-*}>DnhDJ{RVmtlgPFIH154BZ2zZj;&8M1FW;u&ZfAk)YF&&O`z{+{?MgQNk}D^M z3x|?9;NY>Hcom;|5JFwogBTo*1)~FY4vTaUkZ@LCm0+z{$6uIHkl9<>ns%lR!N;`#yqkR$D6@PN4a4-wrB-WG*un&r~kv z&v-_T;FSC!6^bQ(k*3boix4JED3UA?mnjYdXN$8`-*c+6MfGh}eJ_ACgq4>ZprvHN z_H9#r+i{psrWHHTdJ5L%CH|KyNEa4$z-!PBgho6}#S5D9ky}%`-Ta?8T0|Lo+*Xbz zco&jkLH!~Kc`B}%>$#XDOMnz&DRsEf`p)%Vi>r|HA*?Fx86%O7Z#aw2?o9>;jZVa% z5MP3k!0m_yLc$KDTyP7Ec9xCJfpINXGD!P$@%`sV`wj3iPdS_(%#p zhaM0-upK{eSkK~zL8Xy44a;x|q1(UJM;xP#pN6xEu@jUwH(v-jj(o=r&%TQdSK7Qx zaU3rk^>*1nbjkH`o`d(p{FbnPp~GBLyHC;yj;D0EJ(6?ak}gWWt*xWWe2x zpWxa&a&MTI9gUr=mdf1xhbx5K9}V=OSlNbj><8qmwKhJ5eY>H-Vgn)tCPFdi6vW4H zxMYF{%D_rInm~_=b9C#UJ4%|l?Lp_~0Y}c62c40?1gifsZG5WF!UILsNsAwJejNB( z&fy209f2I7VA~2kGCiO6~{83D|LBUgjTPUB5;bOc;tY&B&esdXDRf}_)gyTyy?YF5vqWzXfOP|v;RcXS;%Q=Ishu~ANXJBJsui5|w zWf$!R!d$oveF5o%f+z$WxT{b4F` zezwvE!Toh2d_yQ4`0&t2EZoeKke>3>3UK8nJ_84mNbAE~P0o+&#;~G4oKxjL%p3Ta z1#sTSSwpM!m57@AeFi8M_?u9ft8aFHC6JX8SdPVdRTuncT=; zGn&H1vKto&M^uaP*B&l5({qlb&L=&Vp( zo2{;$p{~tU*XAL%M4KlEn=(;9)y=6kN6!+B0XA48nDqkR)b|CKcF*!cK$! zb-a;|9{9{hOj78b<=EiyDv>9$wU0YSx3pfK+T{!0r>BkIrAEmR-0;&!5tCQ!!YY%9GSs-eo z&)~ZV+)MY|NbHSuo37XSoRJOLolqxeBpnBO!}3~1&W&}g-&uL^lcb-{$V zNk0~KhYdD#NOj|b%R0ai+AwFo1Uo^3FNB^0r~-D^(2?ForMET@!xv{B1jH#YKlB|I z4jNJhc!_uvZY)4AL=xQP{^_D){TN0#JJ@6iKHP`nR&H(l59m4m^E!5e^yby5Q;ysU z$BY7GWyLkrx#|>wX*MkICGiRbmt*oIM3$OO2rSL@^7f4*0!uGP=;5_IFUyFY^kbmp zcUEiG>+HN6$)7GD>%qsw!$Ej(-q{e#eQH4S~xLR2a$8GKeflV_lJv7?0%%Gg$P z=whiLf2k4>QM5f`cOXS3kL`^Wv^O%og}w2?x3M>>&Hmn1D49uJp&#L+Oo@;Yga+OY zQxN|$22bmQL+QXEV)?)fTi7yAMLA9UPQ19sA%UaA*9mkN*(QW#$qO@7I)f%IvMSf- z4!8FoFo3A_xnzTKNC^KmmyvfPp8%Ry34sVq2O$Q9GYcpe_o?7P9d~PImLhocOs$nC zf1lx9g$_YeV!Y+AX7u4iKgI zt2aW7szV9~8Hze=c$3WdMZlw5#Cy>L3knv)ArN}X5c8ZPnk1v14aJa~{kM}CiR>JF z8Ct@VTbtqrI0~-7v0??k8;{sL3;%R7eti~78S-9DYW+3Nq58=vFR$QJ(`nS|T3Z!_ zdn40{VVYCPDVM*5zTjMc?0e{p8l`FdGz*Ot+nCoLjW8CaC#e)&J&CTUoa~2DDE8tJ z4%yWPXvQ?k3Qa*$34scFof#wapnvFf81^)Z|J zm_2Oj1_p-<~~f-vxxyrMC_3cmfj#xxD& zAXt;VE1Wsq=?G*~lMus-U!%l4YIN*a>jl?>6TrTm#5~Xbqdc0=NF+V6{-Eh>=U8l<&>CQys*roU4<84po$PFsjv28o6{XuW)2BH=Jrrhgjs8K-Jrhj&H-Rm&@A3C$oFx5BwI zfxhLH2C@NV7IXtXV-Ga5c@Bez$78;K)C`^s!Z)8_q>MwLeTjO3z=*7hYBM{8SQ!M| zfAR_fjf2a2Ay;tH%RtIu|0LPNe?PIyTMY@C0^9fCh^Z)igwsoAu7OF~hBzw3M7hk>vN)DPN@(O8m?2065mam@4b;(vv^Y2qI#&(% z_xZ4@0tN(S6PgvK(-sm}c3NJw&=G%R!y`mW^}h!3EVO_Cu4D-SS0GhVv5dB9ue?b+ zU7)xg)@<>f=GBJ^l(Xf~g!AyzUF2#}WCFX)1 zf|#8|2l3k78R{&vI?JNYO6RZK1FE~{vJ49p7}jcDPyUYoLu>%1GA?y2!9=)YpCtgx z%mMafSAc+@*mzVRw2D~J6$8=-EC)#>?<K-oRmc$#UfnVL$xHR_CSZM*|QP7A`Q@j(Tm zr|BZS)>-&dpH(R;cy4Ao-m_^QqA@mhx0ynSwe&oY8B?M2z{)h9ze^CB12vywtKvHn zF=EpKVpGoc^H;G=5Xx+Gp0As#Rp`!l*Ib1G+qIcDHUBe-jb-xp?^b22w(gws-3BO) zjUUuzvJ-&B-u!-&=svX~xrI%jRC*=B7TNjVHT`&+QH3xJ-+h%t5DYT#D^?<)(x)QT}e1nES z8z?c`hYN+oX`(`wV}y|9C=E;p5xN0_PB?WvvDadQ%R)iKoxwV*Ga&!g3mQbi*NBWH z$VLL%F5k<4OVitd{V|07k&ux@5GhAAsT-Ru6Mc`=mEhzO{o20OPPyHhjofZ`;1u@Y zq5j&bNYZwgCOv%iRB%YHoj{?K18+dCHxj!IOhZBn{=+{agNC}&oS6M>-*6>2Z+U_Q zFpw2YvUU2QE_qDXmq1DohYO_xf(bqw@Kc0l3n3f|oroGndIL~w%0{!5*s7~9Y!Z!E`D3AY@T{u^H>f;AWeA=Y<3^-Wqsf|Ef@Xa`Np85j_VC*Twt z>jWZ-2m=+ig0%={w1CKfWrb5W2|Yi9zxEl{du?8f;K1a72Zp={-zzxb@l(J+tbtun z%@G9HEb(Lzl7DA9BMk*a2bv>&(F(1tMTo;0Md)QSz5KR7j5qE}AZuV6iRAKv;F#ei zU$u%Rr&H8Kd}j%p8|=RcLS5cPG95j?3o@S#2!`Aqn!Hh@Eumli3fgW&%8KE05peL^ zYCsyrwWWHzF*ERMS|sEsWb<8#tQ%`7Y-t{L#NF!c`lL+AtKti6Y6%h?OiIV?^Yn!^ ze7C@Bd}~dglz~EobB|jq(dSp})ywp+=HV+!`lJG&kq|qpO@6TcCjJL3bQ@6oP5P!= z&KY18HORy_+tBNDqdz$8m74QQaOKhHfNRHk%ia8|7(X7l`S($dtg6*AoW?T&CIF{6Hu1kg ze;23-qt1q}O}0O@8lqfa7MmqIQ>0wjXK!b-%+3^r~20|>jg^l^mTKboV8HSnm4Z`sS z!@viXE@T*MKgaV$f?-TC7{(QsVHQnb78&e3u`MEN2#idF2wR|9uGH4a&cD`tKx`y| zQEd2H!MV@=I6MWBocI)+rb}ZAhUoHtHwCa5%Tn#QG9%ySmyoXr42hx)k(2T0AyV!^ zCcPC%TV>UkfWnMhhgwl{A?pC#*sW=eLI8vI7Oq|S1% zRBUt#kW(kXs~j6aWViyQ;x16Zm^}sjWGzrZqz4+VF+HIMa(*mIs3G8ku~`U*qykuw zehNP}&{vS)Li{?^LOW{?7p7-|+Jp-;z&Rx((g_Y`62F>363&{;yfUEM9Ap`B&~G_? zZt9mHv~Yr8&B3Lac@iFh)KIOtY6$|6>4J@-ijY`2;`*?XMZqs_smTg{@#VTH!7pyF zbp*f2uT9(V1qnp8W3^yA3{b(L)cRBi6L|yE&Xgc1athi(yVQ(8`~k~`FE#?efE+AM zHzAntB~$I>;5vr{Ka>hN*n6ImsN(FkBprE|VK#+z{IB#urA+LPP|5}vabP5eYwM&L zv>TfOlcG8=Tprerw*L9dhIUCBwH}g3%k%WzMq+?&BQ00(Ix3JVgV)iA9P!~q`jVs6 z@;rU2k($SalNp)gG5$ z)b1AHvY;2*W3oES{(Kfa9!GzH>MvFi`s^Wmjp~@dm)s4Bqy>jA!`379c0pqQy>9%i z1l{;r2HjZR$Si+UWhlqcgP~os@W0cN-$~Gt*Ze`yqDyPZ=P_eZE&2b8fb1~(xU7JT z^Aice@lB=9^J}gn!bgdJwDfC8{`E-AlE&nns+^|XcTTnJM5Ts)5~u%9l5&48?Mm$S z29Kce z!60!#n!y4HF0nL&Aih`|OLfw{$nVxpGefd%IxeF5-7Y6zT%g1Mi3SoEEuum@ks=Gj zhqpejOCxTO8qC}*>BVyXTDj(yS4q=1Zv^m$B>(VOty{FNuk!SF`xEh#1H#~?-QjBNfshGga4*}%fFk=7e zBJhz_nC50b4qk7E<(FIruZY=XaTY=bQi!tHuOKC3;)YRVv>oKvK!=YygSh#1P-xmh ztt4)K2bXaZM56i)#TyzplG#JK&%F||!^I|u&;YfdPz$jp1|dYl-&7%*$Wl`W)6U>)!oMdYqvIXYY_@pTY!c#k``S=@r0lSOMp&cf#kj(}W*|Q)z6zk@~m;K!M3`Mr^YyqMF3i^XzX51p{0t zL1j%GT%zS(IAqdk%w_5(itr*GlaZ&(n2(r07h$*Z8%)jd{KYgYP=li$sQRR5U~Psg z0OkeZdw<(z%u5V1N+8}s$t{QVH%t!_T2gj5@L!SPk|cU^0-*yBLK2E3oDcgVF^v&x z**CnFBg!QhhobalM7ixU<$0GWcMq3iIz<-j?278qFaXk5XtRLk8tBU>uV5pRHNQGb zgTREFT-@L%Vr-mFD0)8HpS%ctj_?$k-Q7w&5n6nvTExUARXDA4r$9*%?t~*&R;Kh(FgS zK=;72H*>_#4*Y1v8JJcQXHdS1Qc=XwSm=_*A{p7|`P~o?;f~BYql#UAl^d=CRfu#I zs#Q1mz|aiS2HZ_^^<#zepYr!z$lB z!}%-@s4E>@d=m;b7SxF*7r=5Zn$a4BXe8J5+UN!6GxeBu|=)zC)> z&u$MZyj8&{gvURulE|Hn$s^Z`A7T8sPKqWOnP-W5;TQqsh&(sqkA4U1Yh`8BOfrxb zdVwH_-cs%oq?jRf`UGjxbSeh;MK{3z!n0uF&(M+8F_7Y9Ep}v8aoHoQQZu*UB@$Qz z;S0x4={=T&`UyEKwd4iy3`PJUKdjh@yMTomc|I1XK18a*AJ?HqKf-cO^%aO%|2c0_ zo(&pxSXoE{yV?NfDP#M!!lI`voOAY6^FQQduQ~r-3%)Eo3#VIoup%_TP?WibCeoKh zLEOF>w8Z#Sjc^t5oAHG9FhzR#W_pWN-&}mZ#z2G=%`?}iW*jqYRvti**%YSC#K}}( zE*$7FIrx4GYo$zncyH!FDhtfSCAy%5E=FdKz|8dx4@k9F;6rbeG%*tdA`hl%MWh>dp5JqT^PAp$>jrIH+BLmAC_^h6Haf;Jbq70^VT|T8;T{Q+*vU z1tXu>TYC)D1tK$q`x|y2dl^^-3pH2T7=k7tqkFUunDPGgCnR{srSn2z2>lK z7qNg!5O;e-PepPIU6})w8zp4zK8qGLrJ(;){L-B7Fa{ioe^BwQr;Gtl7&+h_-1{?{ zbMjWYV8A=L6OW>E9?JLay~Kdu#z<(uAq;pTZ-OZ(^tB%vR2ixY*myi44cv`gl-%bw z_y}l7^CPG*SXYLc#L6;YCk1iJ2LIY)I1{Lq>1t($?wY}$I0e$(5~r4!!2aA3D2AyB zHaYOk7JSc3I9X$Qf-Z}UeGI6sa^qyG65&Wj)T5Y|pRK`YRo{A5WbhGNkqAv}E2guC zMY#3G_YZK)Ew-ZY+!DL-8yL7|+-hJIL--E-$ek5$Q0%PQSFx*h!R>P3*O)Hh*$>#J z@b5k^+cRi5fPVq+7<^L;k_C?TfwR|OJ!z@!yo*R`qC`O7 zMuJEnzK-<0D64elUpp|4}eXc2oBaujJGJ@*SGU#smz|>1V zM7&@&Ujtuq5KQy{@gpwdcm|mgS-&nzs-1@8GB^ML8vgu`G1k&-k+&Igv8}vX64wMC z&>Kz|Mlw1&lfvWJ?TG22gK}6Mr_h}Az=^Id;#Gk-*hC30*QGhf6|1jZZb|(l3K7M4})M3jB-NLm6-x_@w>xEgT zZHNGEI6w;JLD@=Tc(sW#*73ngdbWjs1sV_i-blYnC@P9m$>8<0i7yiV@V#kz3W5u* z0$R;as%g=0rvaYBC7Fz|=+eHBI=`UKIrQsMyX*+`(_UB`=oU5Zn>$ zsRdCFA5E&eo<4ZqL8Q19V;}*UpTylx`)o)zt^B8if$t9F3D6Qx-{`d;qasbibR-*6 zJrVwEdT|ICju*cnq=5*oN`8dWh*3J1N?ZAh|3PsYNQU=)-23PJ7K)34@1g(YAsXB_ zX>iww!Pzl5v37(0EWo2rT7PX4taRjmC2)%WsS*fH?!%mrm^9|K^3JF3B;Brq1h;&? zX&O2DZX#K&U0o{z5m+kfLovoHG6gY;@3m6ED+*Fj%W(LGLsA)8boL@H)i)nINK{Y7 zx50Fxb|!ighxqs`Jn=gdSeUmWpd_v-%lg^DUD3QAVtpN3U&t#2W%8W^cT?h zjxkbe+Ju7-G7g@jA>iw`x!8|TaH&^K;h4BKJ`Vw##MkisC(`hKZ^9{-$S;=!h!B$R z--by*j&1T?HbeAW83A+AN$Ay&4U~iq{McYfghk*&Vt5!){59*QI}B226Gl^d-^F9C zVID3!lY-v;;+r&wAJ80{Fr6C)L)f3?Y)W(zy%qiyG>1nBBZJE#l7EvJ@gQ}tAIUk6 zUl@PQ^zj)AIELo!;66lQv*-ZHi9&FR^wi z>Q&>zXy_vyo5iTezG{ru?B7nKeRtqy^2qP|Zv!0|V4(Z10ch?b>_$e^&oNOK_xihd zh;~NtJ2M2Z$UX|lViEetcxk+&ADiv;aqGsLv0TwXug9SEz}{%YKiZ(NCaxVE9VCXuNHh( zA`g559_wF}W8V!`66?io)f79FFGQW-FG2zkatxCh9EoZ~NJl56arXbv_BQZQRcGS= zOlFb{Fk}J*j2bm+Y^gzwBvx>uhL;Jb1SdcetORI_cADBQLWbDZXmCiI$@Om8t!`~s z_hcNRNiWN}m{J+n+cU}^LZTGi-J|8moo_pTU zbDr~@=kmtT7N(=Q8mUK3Jsg&2yiaAT`StP3Jt0FGF-#&TB)TCN$4Ij73QO>SWV z-6TW;yTYmFf|-9j^?XofOIdWe8%^kFb&**;t*LBrth%sLimWS+E-#5LpBjwitmIw& z;!DhixY$61@IlA&m9gq&v(Y<_epi?sBCyDWRUHdS?4xuieeG|OCutuw9aH=A524g{ zS4USDqXI2XUe$j}E!qHTUSZ^pVux?TuO+4L$S(DE)7DZXN_F8a<=v8d+O7U@e)uz_a&8t#`#WH+BL-84AdM^o zt7MUTtRk0my~`)v#08_2`={(=xg)c{L}FBPvuNdag=MKueo=r*oJO;quGoWc)Yu=2 zQy-U3Z5lI87GZ6%CR5QNkqdD7&RImny3UwWVh0>ch2P~=-JuH67~r-GNa~$zT>S`T zf}yvmRBC)#07&vpgQPjq!R!g5=+-x#zz)oo#g$?JH!SwK7;*^c4q2|)yV?Ib0j{s0 zVRaeuuFwb8&VSh;u#xqGqd|+XcAwI`wV8J$>9nUnhPp>@~l|Lf>N@cR>DgJwaZ=z<^Z3mkQT5e}8_3#zj~P;IS8LF&zw1_nnp zo5%?!I^!t`x@&8p-4q1jZ&GiXtY+h(;fZ^7;(!b)<3@tv8bYv0U%r@nh7Tn2#d!6e zIRIT->-%`KeUX3?L<`oYdPlTphm5vwm+5@Knoi7BAI70wH1H@bcX9l*mSRw5! zc0kAtOb8ps2s{8~Z@tp-RLWxDLw}MbqsfN>L2Kb3q{>|NESSE$dq(yQbP<{y_!SF= zL}`tjVgzP{2RhGj^C>ha5Fr5&#mumL=Z04$b5E_#40uRpY>Om=*cMrYh)XTvrU8k^ zM&oSHR%>x+V>QmbMl(}XKu#L5aH~p-zl2Nu1KE?I<+3NM@Yvmrq1rRnob1L{QMQHl zHLHP;6Ep>^5&))OL<-HaKLITp9HRj&=y&J?mvK8AY^HXSAmH48Ou6r+FL(>U?%TFT zhk|(wsBGQAqII=oYi0&l{))D19nF^+FEdPa)<<&p5^5Uu{$Q{{b$c{DC#0?I%pTfo zVx0TIQGMyMm@Y1L!#~K(cLYw(2pkP-vv?-1zSg{PQdE5DaP*q(@iGXKNYO zx(R%s7s9wgPdxR8h9rC9rTNDO^fsf2h)nR|)e$&gWtN#wWs6=dDDK#RjuR<4Bdr5C z-#_%P0tiN6H$0<0PW{X+kMrxp|0=+syCaBO%D~7A?>4?KXN!3Qcf|0aMEF31T67$2 zvcm?o-K;jq*P$9V>eU{81VJuoD}iq8oSe`n2h(H5?mB4MR*8bj zQJR>12P;g@U}&Z60Rg?$q_BH&3w(HHE5w`SgGWjxCJXLGI*h?9(%~1rH)=m1TofE9 z+xl&MNVaCZ`cD+ulL}jzLbFW4EF~j%&D9ofbJg3|i6tSbqP8Z%Fmt&7MK)V zEiL{j_b{>F{L*3B&BMv^URC)Al$N^UGK8)#$PkJkc#T)!51iEY{jiV*G--pGy$<|Q zzEk>ah7Zh&oN%rkr+y?jv_Jm7t!0Q$qLvS8o466HuR{D_1)QeNN&8hN=bQ{&W&edi z4`9>zp@_skZP?FaV#ly6gs7L>tXq9hFv=)1=43HkudZ2-GAC*l6SBK2JCC714eKB> zi2K3A*6a&87R04tSZ{Td_ zDyx|&us4FLTUZCOO%Kg%&G+52g@wZDU#r*moqy+%EWSs!yBJl^A(vqAx&f@6UQ|ch z0fBKAvks}XxY|>g81(x#ee5#wd;aKZSezhaAuRT~qkKn62QGsqs(gcIrItX62I9)ik4C?SZ*$o1W5!1*)N~*Mj>$BUCLW^=?tNv^2)wTYuHVtLOe>xXKXq<%h`? zzCx=nKO&D8YxU(vv)$(q1M z0?R2YfpE{YdW^!fA%D^nb`BhD7>zge}pFquLT(L;b^SiL}Y3 zu~#dki#~FpQ7z6Ys*yG!s+p}-4GM6)!By&c3FjiV8HwvX>gAV#5UtU}WgLxE9hir( zX=)Z-5;eP6&XC+cw5?XQdQ`IOfEwBAL7qj?c@?NRspvelUfn-8eQqx9#OJKB7mttG z#_S@TILxL$%>^4?4HNiW-sg)h!?{LJe9*YQIPUY;77-fvs@|}Vp4Xn~Gu zPyDz5qC|VCY#{cCdYA2Lnn!q{p&gQIV$|3@>dxYXD!!m%g#mh;}EDG=7Bb0i7qdSE-zGrdudb- z#mrl^+tF1emH!mR1o!7!w;TsEMLaTS&T~ecM3%9(QUkowLE)7K-JJs+jYpZFb0mrd zJS)(kYztd-KNvl)v+*bs1@=p#X%UzRr+5UKmRUk|-Ix${GRFwVs@yu>#xCsvh_8Yy zgkH1OYP>#8>(!gH)5~=v%xAL6s_s3QUe%&{^-M;#OloKZHfpedL+eFIE%Q54wkw1t z`j%CRHcQOFN%SqV%8>+?@OJbj0xJJ_fDK6`6%P0j`%<98Ha~gdTH6|fsFJ6epFB|Y zzhZte=CtM~)AuD3ySvs3ApA=$fPn8flmyY?D}#N}(1{a~f=+1Fe1j*i;fOgmLo9H% zMJ3ij5!XPAC6v@z1SCZrbUO5~jh66$I3x(`D|cxDRF;ka32+;qm@U&fR!r@<`*Wf+ zPzbJ8yT;mV=DbuK%0LlQ7oAsT#A3M*u(yZ9FpQB->H6=3DIS(^+j7ApwM|picbPql z~wiB2D%dopHMv)!EJ&M; zs^VyMF>K4##p*lEgH)oJY(Y&bi%cu%y4Xy(k7NOXqcm5fUKL-Nc5{U^SEP25lF?kD z>XIxYnmZ%eT%O(B_sNuQ?)%_EHn5;mqDGy7v^xo=Q{Rx9YEI7d`RC(W_P zkt0bQb_T|E*yh+G&$cXK@QE(m88GKUFN);YMG@PS14i5A=dGu!+VW|n)zkCpRc=V- z?=VN#MA7cY$ryJ2b(WhkG@lEM&%2n=VRw~m_GJY1{rmtKika_1e|!yNlEv{_Xxh_r z$nBctw)uzilgiA3z@uX152(iTj658z?5~eFyDj2WQ2+Z(Rt`=!kQ-+^k+*621g;f##9jwP>uyfE9F&#>~shaOK&`bC>B zMLrAb8N*wmnssYu0{Ox?|6jK+yiQc z?LS#ya2+eTyX!$YOMbwWJuT8W7(Dzq)B^~JYgEBWE%8G)^#GEQf_ z3u17`lvL1Hy#EPG)|K&r)DoS#m(<9P;#gpVv_>p8aEKef=$6iDw{G#c}ClL9W-hX8Zc%Si4T1Pk((+z|~W?vKR!dP`?!uGj#Yw z4h)m|iQ~Q}67jM1#^-Hhfx+tgw;D0?C*narO)1%UH8dqFw@p2YbGF@V{?ilg-TsHDyN32FGrbkjZ_J2Q2c^>J zHx?vB@3~y+aZ%3?OSkW3g^8Gr1w+rF{BixYb)kvLnin%b-C1cyFP&x>Xfp{Szhl?X z_U)C9a0u7w-*$&3ew6?w_mds9(HV)g%k&d=52&cT%Z{VrYBt~RfC$Ve@-hC=u95gH z;(sy!OV|gDIWm}UiYJPboXkI|HJ-z$P`yDZ3&=RcRN-D2W#!#4FzQ2HBBOdw{o+m` z4>uxav2LA$oKS8NrodF;RW+asrhj=Twi7TP>b1!Q%2N^6ENP%-`BHfj8d6&Z;d}?nAYBL8av^+kbR17c7kX#T7w_;XA)!dspI3RX zp6DL4VBds9?aaEu(Zf>+gPf*>4t8&w-*B%?+s6I)b`Vcre$qB>6n7Q*Ur--^llJz| z@O3<7xT~7)rgGc+ z7zK=$y9)H{I$kYT5|-JbW?`Ha>B(1w%F-N+0)@9}o|Yfk%i|Q zt!~G1-TSU*Nw6HRMDFq?n)B6Tx(Bczu&_icOc-`u|0ak0uesbqZVNpc!Qe$N0Xk{I z`&f9KNK4fm){78f#l2ns@eFKqVL`WQ4}0`UcyPp*7{kOhsOAesnP*G4rbahXf#h4R zgK;i?p=q+dfo>>)+kh3$i#30XE>P}jbb-pK0<>uPR7Y5JNDciR&@G} z4O}c!z8_9C(1rn}dG()}<Lp&R2w*~DqPM=MNK=p0_*1ypGB)qyG7Vr{rM zN%U=X$G;AHK??&>jp10qZIE`PwMcXQVVVePCmk;L!Xp4Nx^w#V71kw~PpHZzYuq?f zTUa`D_ABUsX^V|w$sfQ0qc-;tNE6+rLB%G3s2_@NKDBR2lq{D};9CHqij9zp3-SdR zOqlWt)Nkc6GFS;|i%7v}^=g&~cTvPZ^a#Ifh)NDOxkah8P91yg><=$|kIRm&Q;DNh zyq1OL0)Bw+QaMwtaJ^@0o<64`XS)b>B%06;C9meKc#1HZ(c)!GRg5*iwiL z&i8oZQSP(irhkvRuo?W+_@dQBBD{y$iQF=XAhUd@zxV9!_FkBh>k15XJX`np9(Vg( z1GD_Q@DM_Hq`;vDEOLDt3L#(^vL10M9&(8!6zD&@4u$O9Dj(Af7@&c{^TWG|uubz> zuHI%ewQBm+qica7?xW#;S1(F0G}wU=q?_(@wFt=_`S{Ehu998q`@N~fI&x5!xe+SF*b}XuG82EX=NZR+k9)5%w)d#Q z^8TLx_LLWxt6741{*TwZ$+$Y|r^q*qd(V*H{Eq6vp0^7->d%yA6+h!n`ezB6kTNxZ z=s*e#%*C93E^^Cm;H4i{<*qdxpJzg;VmM1s-PxXFubj=jTtx@i4m-RnC_!}W7ahxQ z7H_dHQfd#S?xWPH0GWYY1RQs$U+iFnp?{ZCF{qxa1RV8+yBJUv#JKNV5aR+1tOP3_ z(}t85h0zrx9p|e8y`NCh#@TQsW!?4u5?|;Hl50;=dszgBmX(sYruqS#9kQ_KDK*|t z?Q_8}{%+<$%?f7Vg!C}elBL++fB&~O1}#cs(Bd=(E&1y)XxBwP20ca(TDGh$!fnaI@<1E^MK%t*epR7+E_)M~c^s)c5CHr|WbnINvbCm& z=$d)dXI_?*K~#`)3_L-BnJOeJ+akG!%mud+M|@(}Kg298Zh4i^1tPchXxGS}2knSL zwE7umY<%je9o;5_LzTxmj)l8fN>8bJ40Y)^Sc17kKSJFq$FvstWuj~`_pIz%Bx`-n zRifj)hlv*ji)dXT+QF4Z>wIJHVvegFqKsKni3<0Rux?>fnq~=;PoS0x@@^)rf`4-!%USJ%PMaAiv>9E%oLk=FeV6idX@z|?498sU10e2YCO zmAcG2`0vyx^QYqimeV_enYC|YcumF2LyvqBf<|}blU_CTEx@Ng(vyhnOAu~s?3%Lm zC-C*`ST{AaHI+|gjm$OH&IP{;g7z#}CN1tJPaI9Vd>-=SFz^anTup_`&FWkX8zYBs z#9Ab=aRWtKX%4+&{MYjga||%Hp2>iU*MsQkIBtyvP|^tE`8e`PvG2Y>fx7oU;CYyV zBDTbX>hQ1VbxkEhO^>s^5Xuqu?>u1cLDh@)sKlYP0@F##x*GP6HUBp;{}w{2OhVQ$ zy4$>^sFYx@3(`cXH1{{$wI5qOkv5MrJ2i2t}Rf(eKW@DLoMN31gl zI{<-_Cj99S8bqhzSl=awv0aXRfnL~GKhT>-o8a^()=pI|AkUk`4kSzOu{!pcrZ9y} zfh?)w3NBw=vRQoJ`7vt^y5a2Hqwe4=J+hGmRILO9{gT@NIkPd6%@@+TQK3PhHB@oO z80uzvL}^q%K=qw-i|B%rP|r&~FWVl|`^--HWR*brfKi?dFo7SBDcd$%S&T`#SYo0y z+e|mQZhuZl_(lc8zxD7u<__(TBfDhtl*{~$xFtbE;l8O}oew8zH@>+vgF8IUGN9I~ zm&HkAa!)#>L+AjltF3u@Yg!i#S=Q^Q1m1(-Z|=ZNVtS>9F5=2Ca1$E;a3k^J>Z-bd zd*d9p)*YOK;k`FHCqFu;pk90`ux4uLXQ26SPn9d4M`smrjsHrd zy;c%+M(!?_c%yysCMh5XQFuAp2^4@sTvFbRy0%u<{>-g@e71$pqq1V5m|Kd`z83ZG zTV!_lBNc_8uoSP0v}QNWh9|;2Bd-<9K(fXE-sU2rWzbyW))6v9g_P@=dwO^Lj0~kj ztwf6cqIUIwlv?XCa@B>x5$>y`~CM~;s-|YCL(0f+mKU<0C;kpRr zyXfTGUT3BDvW>I?peQV&^CF!4?PT&vl2@td`l$&JIk)V8>Z5P}1GwGD|&^zSNAF8Mq0Qrj25EbXP1 zIxXdrWX(vm)Q_0B@||d3-;Zp_g1ejPkkHRFXL;96sW)zSw^dHRG@RY%p1z`Lw!hZ9 zW>99<@}#+Gy2TCqR8N{^>^Sv}e-p+?h8xXdQf=NOG%TzA&rBAl*XV9Ew{F$EMeRp_ z+Aw9NX-Q=!w7eZnW%bGhKfzXJ%H%=~(+0AA3Kv;Eg&*1iX@pOqlp@gm;7dVjHifG7 zH-Vz0#zEjb;gE~-%h5CrK7#=Hf5GA9S5B*O(ETe*;s!SA0z+DSwzO{#ZPIIbxKIL^ zf&k&>piVF-j5T|2*|-#yh0l zh|BX|X{@RbH?$HxuuZu}ioqMM9S%ULyl89saxLWcEE)salkA2QEW4-#rYK0c=H9+i z)UM6e2YwFks9s#RsMWBtEFzA$P0R zzCzV#FB;)Z$#Yq(6{d(~!n5?h>%=}5MhI;DsbKSVlK5xp$itk&9A6kjKrf@KE%dT@ zQBa@Phx`cMi){(seyQ9d(Yx=uC7-qYJ+Fj|fqvKQv|?FD@sLzv`7tpeZyQfC?;+-0 zM^Bi%jHg(jOdq+86;@C~+@}b%ndUOOL{vgGBcH`NZkk2BOELA+OG!9KXnn+)V!Q^m zQ>IqKi5zCCR;C(BN}8&bsT+BVv{qSBZH?9nv@AXrLpgLKAWSoyCKv%O#l;Pn(IK$H zmx(N|>O+x4)}u@oC>W2y?Qfip{wI~7cPFFjJ^ti zSVpZ5Rq-3?(=$4x?RM&MXL^rOtF9hk#cQD2u-(3uFXj zZF*(8boZ0J4IQ@XPA|(Cb(w5Z41lQYEmDMaDHXTG5dT|Sko| zLKMZ7ZGpb=7N4dz@2IssZFSeYU%K-*)HYs^92<+q5+NynXAS<>PP=acC+XTo4$!<` zzT1Fx^DEk>w><(buppi_5};RqfS7vnIE%oD@?dc6Y!KunkesWU_-0qo2Lo+*!A(-o zy+9ZAQE+2M!OL|)4aX)0u{;@^Dj2gsUCNe=-(s!4x??Z}?qAoBdJ}v}9@#U~PiXW7 zrT~3S)xnge@}UIwSa`*V!^ga3uCQoBgA7EPsR4vSn{baR=Ai0VpOeeror2FMT`Yu2 zLsKE7<@Pg~NHH9Rv`4w{Mt6NIw~7I&S019}+^Iaq=3x&&`w(gtvCnSECtaUOBTPdl zi)rZ3FrkD(U|DP$K?iul$DXE83X;mq_tGOSF3Ffb1viGzfu(;YGrZ7aNx{zl1)CXn zz%_Ids^t`g<|g-2xh}v{g2PlVbZm*xv1wwqeA@$bG(KtDz4GJTwpxC&w=Lx-dA4xo zTDIdJp=30R`vVna?d-6gLDhn#_U`&^n==dH9yD2sB3<8?Y28Xb=?dLgDBZYL{*(z# zJ+GcPPD|kpYjr_%d7y~3&nta|%We1hpo+nIS{`S2dDT0+0T2#)ueyP)7bm_3me=>| zXZu-#@f;yr3Fu>dn9q>^e1tV^BA)8PtdlINB!y zwp;t{<*ga&2v{n&sFHUEe8{1uM?P7F-U(`O)e_5h&2K~?;^Iauz6}aDW8^hL^S6Lt zNAV+iS-i3=^yrjzDc$y9P_N4-`SEVsDnHrVcJsqPjN{19@j#tkj}!lqu^w+Bp%WhO z>6iVAZJQVB>htBtyKP2|uD&aC1yjq8ukdt38#5caOB&*0Cz@ZlqLpz>lr=>SZ6k;D zwmYp}-z&YITjEW28-U=|t>JN4{*^cCZUp&BLEmsdB21KBxMAD0Dyp?-0%>^H3;C?| zQo4lYiD(x-qsd9lZGg~0w#Yq$g+3gt53QCK>O&p;5WNjdJIvX15(S1FNw4c zu|<__-M<+7Q~Y9Jns};}OMSM-y5K`EL0}RPvDM>5eUbCtTzWBKC8n^K79I*Df#5n*F2SPbJvTRdRsnOB9E4Qa;iN3b@ItnrT2b2_X@kx z{B)%S=}HU#2bJzB8@R!)bm-btPd|`HtEb21@h|jrocJ@XSLj?hZjwv;`m2s*d5El% za=kAdvt94YqwRWM9{)PO^RgwlO?%{jq*#+A;+hhyMU!p4B)ipNFF29C0(|d(R|`5K zS2@Ddweie?=`{6OZ9Ef{$09>~|1Xh8@o6l_4}bZO?7af1DI1k)zzayB!rtkxG8D_3 z3K(z-tOr41mU5)%PbY#iItf10*^2>uf%Z0T?A3LN>*VX*JQgO_Xf#r%zFw{tWElw)1G;EtsmoqjpbAWD3M%K#H>TbxjdSOZ`WCy;$z~}0T z$@4JNkFhm+IoQ1zavXuPCFXV~Z;It~1O900XjfebP=rB>__!Jc8VkuLPm+($=Ol^1 z;?uN1ot7tS%M3xv=&SQ*`2JHIX6a|#CXcDQQY?g| zNlQ`6q@_5+q$Op;a;cPFhz$#ha_CZ@$15uT(YvOiRiXuOqxBZUm(N%#)YsukMzsO!`7EdV`x#m`oDZbg%6E4l%`6b?|CDu^a#q+1;!Un`Lc=)VnM~dfcaLC^-}L%+08I z8m!JVNL7i6aEXoHo7e9UKwy{JfMNdtxPYv9AYb)k0e~cjNc4h)Uq>h;NyhiD_2ZQv zgdD~$m*GSvn#J^ zzDB-;-zwkf+G}j@9J^s>^Z2$C*EV}P=R2!5>>aZa-1bN%7nP@b7OvjGZpa8toY9|d$)UcWN-;+ z_!WFo7kJ>7@LMR;Byv4RA8c|5eUHSQ;jwfdCCSK+CB6IKJvg^y)xjkt`2%}jKXh>a zhdZ*rHE#c*zT8U=E-QhGLsqidad4p7)0bPi4Q83ce>6N!`e2vGP5W)4h{E{P7Iq`o9Dh~som1}iO8v9ryCf2K?{d5Do_u8{|jrgBQXTO=DcEm|GEo|9{=%mc@3!js`G;)3iHJC)_UcgN0mpCF~@5x zc5|sRy;&gM^CY%r>*mrEoy3H@&e<_55qX(TR2-oG`o7qGuJW>z%aPD&b+N;k@-jo?_kWPv79tp&W5?ZtYn$_^+e>v z*t1@3`~8Ww-Yc{AcQiUh<#oRpO_X;x-n_BxPPex#d@euZ%feIm$txq~dbcZb{Mt3Y zkLN_Xv*OOG-%3-Z&-;7UVVA|1wV&B0DL|#Sbja0TUyI0=n|H=mkL;KWHN>8keQ&*3 z`=0U8`33ZQKHJ+RC-0H^hW34TPrTFJ-o4g)Y=GoR&d5H;u~*QPUZh8959-ia_Mp~{ z7}RoWP{>;njG(|8&tz*nQ$`t29Q&%cJ8mc$A|oVk5gN{Y#tXhjda`;BWx0kXo$x(!%<1}2 zK=n<+v=Vg+Wr{)1MKGi9UjK$nn$R_`--d4cf|6n|b`fa~dEbBNwVng_>Wb6Y^P6YL z0%-I{l;a^7 zf+6w6W(<*$u#1}DF(w6dVnS$)jH5q!tjm0GnXo5FpM|+1YTjeKZ&o`y^Qt*iPm~TV zj1XX0&f_VZ$5*ma<*d|YtW>Fd;XM8jp404&`f?C-x7}HmSQefXdDj_!N0QL66OH@o z_$%Zh9uu^)mFcQ*Uh=K7Y+2Y@osnC*#71xfbbzDmsF{Ry^$>ZZZ?4B9(V*pgBzBb` z@dBao31A5&JFcy=(9xV-vpjk9u-GQ)&fF45}w^*O7#y!>YhVXTQo7R_m0?Zz5lA$}(W<{2Iz1Nk0nR zBj?xQPd&fvBP$uW3#JPfEO;nbtNYr89t~A-^x22kVh{TPyF7C~@UZqGlejIrkp6+S zCj{lp`nr%USdq=~#IIOe&QCB-regn|b)PY|bG}W)p^{t8)44oVl8w_XdV@zNms!ci z4Pfc!bDxoBjXk9&*e}CQwj1k8ChNrIP+oT- zN=45|A4*TeUFLO;5&gH$@0vkZkG?Z;|L1r%YaZ@%XF1er95tnHAFF4jWL~XjN7bsLxtQ64-RA*%MHl{S9_!Lyf)=* z?;_yIGEx$BkP{Per6BR7`2`my-MCy8nuad(d^zgsK z`JW4)T0IUcz-}k`gQK2$034GoF@W0tZuvfl?_wdmKin}ZJni6qp_IoDKs~21{&%TH zFn$~R#n^+rN! zRescNzGe0pK2Tc5@I<57r?_t4G(V1{lvJY{Cd^o(36`)trmXq zOgI(4!21>V81JDv2+wv{oM})5+sMFC zA6Pd*q`@ff9w5P?&ann!^hNI?F0cS>rmBZclZg3|Ku&UyM|u!`CMSn7!_UqW6if4FZ3L7tv?|Rto4}f(kLRL zz@cnu%+<4@Fgzd8Po1hq2h+$x!|wUa{P*kpV~n~(>$mFydyT%xpwDacgDsY+f|R+%iqcyw^4GUPtZ;2%y!@AI_28x?wCnySjY=CmL^}n~ zvc(%BAG>|uf&C|etFSFhm^UQ&RH(kLpFe>E_GO+3mP;a}n{%FjV2yK*|8@RweJXHm z$L~9Yw~^!esDUmOxn2>270@XL=O`<3z2h7H>>x&pbg~-~lEAAZq1Hh*R6l@SsxP>~ zHQ@i4c!Ab82xHQWkNdcVp+rsHxP0)()?Q)Lf07M5cdGEUKLXTsE-0C_VP|KL?>-9P z7rk9fnwhXd>FhrLiRSS}jyTN|id#BH=Z%P!Q^x4uzxL9N5_@~?1sMYwlj^u-iz2-z^Bn*FHNUg0YI}XrugPT6DgSfQ zpT(AOk0i{J0UqJzp0MGr-gw`4c|&=haRTf+A26sVP|*wO3a#1TN%M`9U~0#f#9IJ< zEyQJjD7rH{;j=LYGrSL!db_?khAS{^5-kB}-|725-Ew8K8I59V%>CZm0A;1C&>9nN zFq+}rY*uVG3%ZP~6@d=xwdA4LZ9={lS2POv4Y2t73x0t6(tJbp9+0-=c5 zZBXGI%mMcjMfVR8+13n3-RrCTeQW;YyS%*5-n{AIW4WiUfL9`PiKX8wLc!(MX)tKF zpwIykS$AL)uR9-~q`#w7WKX!Jl7|5OwSNKe9X@;8YO z-!}H+Mff_0>Aj2_e!h&rgyX926*sm0jB7bt7dUUOcb&MJz!#3S*?3!s&)_-Vv345l zdv;uZj~cuFjRcCp7s;RXI~`CC;~fKdo{lb9oAp~Et>R<#&G)x*VB8vYuXVl0b$d+* z+S0rTcY7E*-HpQ>*qHkp%)6YLemDB)p62x(+hcmq4eCz5tUD=lx)V1Xe5ar1yWX?< znX4I$@v*w6XLlbPsB#^bCbtNoor${jlvw+1Q*& z$E7fDv3YK=SYk!5`e7QIK~E+d2;km98ZfSqN^yTHO{m06es$N_I$g_%>Kmzk-pJ~? zRk#~MZ6_UCH4taHU`S&qvO}CViOw}Hr*t>@3DQioMZ(e|G0Ub;3XsSgx}F7gid+2v z?5a5k8RtFwQC!_7P6VP%xy@B*cZLoyCD*OFAA{s3_|!T&sNnSGCUV{n!qt1c0! zZJJuIKL00;Qb1tk-8O>Kmm#P;Ykj|W>iQxbooX+!>VpX-qjZBa?P^6k!l;=BfFb*> z7w3*8Bym;ASVWnu`p`yuva}=OjOhwA44*6B^g3GRVAQR(Atqo2j;o#i^tguOM)h+r za|S$F<9YOy@k9ovfjR9{Ax%FU^O;hP0H;~kzepnL4RSv8tIsoW_BmkLE9!3akLboQ z9}zaapThU7UpKu-u3u!ZLd5OZ_?|EtO8hT37vt*7-`9L5E{dY{CFwy~XEwEIujYtH zYW**VXBk9S*`ZB^uq)KZn?BHt6(tyuo@Gq(zivF2IYlZqPp>|M0O%5NFY-7mg~CRC z3H8qRzZAYoyv3rb=%yFR@d}Wg2A1sF(1(eSjW(3npg{JSW?+ZEAEgRyY6~ooQ;-`u z`FggjA>Y#-B*x*w57nT-InSW43b;)o2y46M z+pu7pTG?wI)*&mjCWx-kc;2ja3O~q}&+anam&-Tv8y>&%eHY<8qW9`7+y?{<-ozd3nxFkGI7iS(Gi>u;63&^!-{z0ZZ@c{v(Z26SrPLfsndDwf zsdj19E|JrA?Iq#yHoZQN7YcG$K~be;iP`9RS^%>_Ev7^ymet`hZ+40-@}0J8E|GS_ zIn_K-q}uC;IBz-MY#sZQu$e6Ss$QKX1^co(a?QJ)BAI>15{cZzdTBwrJfs&v%Xe6S z{08G(<9FDE8`~rQ?dwr)-{Wl?YP0e?0uJBf_gGI(qZiY0-{buVncf(dC(HNvdMl4h z@_0teLydNxY~SP0ol-?k>NBz)&n3Pm2)Mng=ZNQ&jAK$6$NC=Mb;{>)sm~boj+c6J zMz1F?mC>i`Va$vwHEhOdcXvWxVB#gd$8qRk4|_!_)1*`;6qYA5Qwzr=Go|uOPUV@> z7dS&I8hc8f!c?9!`vPZ4J>yQvb5<(P*?oZ`$wO@G5$jNt%5zR%;9SWw{**lDrt+NE z7a-=z;|udn$y1!lbADgo0?Fe`=2@N^*afLf7aGs@1xg$^*vbSg4P>w%1;k+Dc{>{& zZ6CL-WzKq|h_|omZ4~MbdfC%!#UC!;x8c=qPMIC~C}-_wJMUSEw$%l_SKYHz6vf`2 zjoRi%S>ZE2>^(PpF+?Y#XepTdjeO(`Phu|@_#QvHf;eT{wGhzLYhI6Mh~8}p_Qx|q z!T#=(3y1tKueUtl$jM{8O^susVbsf(HxgIAvoJLQ0hDX6 z>b?2w?&Ze@_!RjhXI=I-*~YN@0{n>i^f%8i60`j;tt}Zb8S&YAE(&BWE?99hVtHz? ze`F%^0MMhx_M2%~nmx@N%u3HepTKa|V(hJ|{Kln6MY*Q~%_t9=i#=&J!J8|&Jbrf)pjIf8gb7#e>0=EBV>FH^3r;fxT&Ruc`=x-g{GkPU~lXww3;h}^x@?65`Wk}BX;c>z!kuk{^U(cC0Kv3H$9OL@( zGxG8d;C%{xU@r99;~L50c3oz9Fvj~cOVB({RP%p*M7Zao7&sc)m;!NYekV>mb$-Qo zxrwL&gX&7E5-N~+*3(K<@R(Ct!r5O;=2^7sV@_FqqtGOTIQ~^uyDe8`mc+zfcp>5! z$Ex)8(GsE|KBJT79ZtO581wT-Xn>rL+cQ)^#*168^Dc!WOOUSdYsc}*lNwKoaNP6I z{3tBlPs|g}`%d?`%;n6({uw7MkvcU(}AY|A=0m*1hga(>)k7& z@M}I8;Xyk!d*BCSf-Pu>2|jWFR+Fp>X9Lw9p?3uaQacLCg4-#?UUI2W_03h*QfU|G z8FkB}HPPu?t+ie=M_aEH?`iNpRwB7pfkAA%2+?s^dPis(>t{$hs}kI0h$bh}zVXfI zdH>%#_ycJ?#jFLGv#fDsr8XxM(Iwf^V0-HM0Os++kP-Hra4RkWeGYvy>)2~O$Ipu# z`os(o9rdm$Vy4hwI1+oV0`oTKE_D65{lmN8hMI}3xH+vlpl1SCoZMLb^Sx^?GWJH^ z$nAOCCI&$k@aX?OoIl_oV1g6j>+xU0xBUysaMzm=n`UlI4U{fIWOUsUO<@Kb+acga;) zyk(;`d^lFEw@<8E^N3>A1$sfNwH9Bjx=6p}9RC6UQ0-Rw$5VKjycNqQk(|4+S}}TD z0T5ySq&n?tAQ~V}ZlS*}za?kQS5b4gMf=3Xi#qt!URDkT1!G*AKTfp`HePqaK$&=SvP2z?0LGN69;*$jdIzj5y$wtDDmT!;5A zqpB=%$oc@kPHq<`#QK)>{KOukr2zl9c+=X&+KED=o|u`O^J~UIhQEaAa6>`q9tt2b zM6KEh?czxk+wB;$3QG~(*A|^!TOdGCPxT1#7_nV_X?6bD^#!6!sY@8O`7W+p#;k%# ztS^Svj6t`jO07;fV@KV-fu5R+@7ywamDH-NqAZC zvU+rQ;c^cv%(zkawV0{6Q5?V-6CiZ3>6bu#Iy6a=No-&Bas4)1<_+tnSB(A(pA(I# zIsVsLP%H5VJS{Ug0896reH6!Jazr)?Cee568@){6Vk;O7X3!(<%G63f8uPW|8G!%| zZgv@ZVmRad!rA`Hw^(q6@tb&s+VVd49H3xPX5zyn+N%S86?u`CWW2rqUE@7*;`&e2 zT)r=oaIdK|`po6t(r3*YH{@BqIq#&PogS4{Wfrtc`}^m7F!05b2l`*@+3)rZoUb3| zoE-R~TR*uXz4-%Q1T6C9RqV4~ke2)}uPrv+B3Hq0yX)D~Kb7vqOemDblz?Eh#a^OR8#a|lE*70`r7JH!zt__6M3;o#OcXa*^^qpCc?;C4}I zLC+glNT6$5Y_^CHrqKWHn&yK8#)){2bsq$m%_x0-{Smmnj=e`3xHbEJxuB%V_si`x zF~cmlht?`gZ->6$f;L3yQ)>`tWq?^>FFL!$zj2C|Sv-r1my}di`~S4=QOU#L#Vu@+ z(1swsxej0Z6HLNhb#TC{zoM2;6{5A)Zx-aMm2xvZwTZm)#BZE31Q|~}=iq?Ut;Kx| z=Boz>A}@$+Xa-GJl!}YS!sKX-9^WtLmaumBFf3`wdiAbXA97HQ(o%9T(hh^EzLAkU zk{SuqB2-r6!831;>?p!NgaDPb-;mxQO3rV!&Rm6e%dhrWX%*4^?LIgQRgeK&pJVnx=fPPXRBe-5 zAw<4q`1@b$dF{P}vkDJn-+^|Bm3h{;CLgRVBuWX_Hp7V$t{7ya0l~uCdJqwqENRmhvH;UNjyr-mY)T#Cx zi4pYTjp$Q}5|HRqX%ZbgmnL_JIn$KGRKl`-QcfAsYX12iPC6ObAqK^eLvpu)SWO*|t>sith#LplGMWBnP!P8C_5e2Cy zoBYB5*BB|FOE|$2=M1>KB0uyh+eCggTR)G1G24G19 zl<~}kj)XM?wevh?HKvcvOd3dZ$sdjku7rL650ge-!qyLNU}+Z{uSCs5ARI0DGRmWk z_;uSI-6B$SZk|b3t29cRsNSJuR1s}oB45!$Gl)@-GP5}c;mv%RT4NyEewfM?2tY#edVH%U&T}C~LZzkkv7c7+U-_`tOG-H_8uO4^gI~^Q<(LlctA`>9GO*dz`RK7uenjFz~Jde@QHhERY z_sh-B{gkxK3>M+YJ9g~%zKuP{_TwL2?Xf;fM_M-ssN5^1Vu23%QBPT4M=AF}vRp^B z^jgZLPpm$@k}RP9acDd4Ot0p50BuA&V+J8!Sj3n9k1VBIeu9c>qI zKG-hXfSi$l!}V8^z{4UW#p{Q{48=>(M75f2E= z!~ze>k5nWTAJpC&hgGzZiteMLYF*JC)Idc7#=4?vYS?I1bf5g-FgSB&a_l*&+bXGg z0;~lYW6hDwmxc-n)2LUSsK=4Lj>vX5#!Hc}7dxXsjQ-$j|3FwPjTK^<7?^6zcN^Qw z_Z@o=&=R02YP+KVo>_7890A(qt0PYfmO&@?YC$k-E-?dbLDLkBq)yh#f!D#SASLw< zuM~{7O95ix8bNQ9gUBo2fCjU&Q#@Vu5Kg&h`II=DTt5Nmrux@8J9E zztFirgqvUg`ZO^kc{Q+^7*jb1vK8m@YbS(WtO@D!8t)gz{jxfwPNGk7k zHSU=dC_tpjc(%JF=CNl|O#z2MO~K5%3SZ=3XqR1@hw@0xZCs^o{PCqp^q<`tz6~X3 zXGoM+h>DTe<2pX+eXSs(Z=JhQqz)&ya(#n3#=ypH_qA6_=|Hh>)3L0cce9-E{#pwr zeR4$F(irCs8T#sy5g4cIo98(316jAjsCfh~N3^Eo$d!x%wO=~O7Ox!9$72)!Cw*LO z_i?Y3PW93Cx^%J6r~wt=l&(IwSe^9;IVZ%EqKAVWy#0_&u5yu#S$JKAEK+#0;dvG2 zWO6|aLGROy-h&V6b?eu*K`f|c9>fwxYmkxH%WYVn)8BB36rQFDS8Xt~Me4BjvQ^SF zJtyVN(!gTF!vQ;&wVU-{h_^gkOIZ8YG}};b)s=!I1yJ7K#pG)zS)n25DdshS8>yv) z`3)?o7TVqII)*w)w$?l%^3yq5pU5m2V{G4x^?|RwYXUQ{%m31vI3;TRa0?c!8>{|d z3dk%RjC=T_Ht=H9EieL0ErY*{o|LAHW2Jl3^yyfe|BI>6`(z}$D`vD-Mq==#mo|uD zf$kUmsiu#vAsQ0}t8~W-Gx}6DYM=f*IipWQycl8WlTlY`dUN2KoLhPVV<*f0IAyPq z>`!=sYdbv&&AkZkMD8-HH>aJY%+9<3*>mayt z^Mg|bS+~D#ewL2frGo_-^RtJS|H=GFnEStNe%`{F;;Hi!%`xZ(7+adzo~{`7DY&O#8!k1?Ajn-TNY(Stic>Rg5%bNO`CL0>vJ<~!>Ufn@ zDHy{Al3hoR|UIA50hyPG@bC=8Tt1-$Se@ zaD+`~5{|+Jf`^I$1i~!!w_1eyQ!>C5J{c}PweT(AZ?xiyd#Q}i zXed-$7P1s2Qgn!An8FUhse(?x>QuHyrD<+tLJ01lzPX)AmHUD?+weVcW0BE=3d)9^ zz70R)pyw8{;fMT&Le%!@OM15G)27dtqEUx?d49%laa%JCJzTU+T9M(V&zI50YmfBE zK5qYXwEag`N85!}9oqdDJDRyD|2S^6MKOFCy}XN;3?$~;@NFh;n>1rXUZw=pJ^_8Ja2oI-qT>_BOC!t@tx4j#I2PA$s)8c= znzB&e04~;dpNXN0WaUBqh*Rm+=@fa4PtiFGLz~m-@+eIo%4KY=+#7crVYm&x_9?72 z(eB!@l5wdd-pF<&qu{<>u zN=;s?iJ(qYXUITavRi%5Egg^=q*?XPEZ{tU>%f|SW2m7+@Uw>XL=x1?tbuvI^jSvG zsVmSUAWmuG$gg0=5Sl?f>(y0G)H72QX)Ph3^$l6Im~3DuT*NHKE_sz}--5Lu;v16Kdo?_D-@ zSo{5lf5`0LvGz_MwSPyMO}l?TlXCR$4>!}ljl=pUZ>pcH z(!Xx2e_Ktn3`k4Q--*MS{hKnPf1R?vdj4kV{z+NgzX?|Vx<{YC#p(WyposF#?0Fde>E%^_wc+OG89LH?>kUl216 zI&)^Uoi_Lgq$Gq-s&jP)=5@3mAtPavqU{_wJis|(J0gIYA5=XQmwwTD^Z35@I60ls z`=#dgM9cYt3W;$`KqVOHOGE?jJG0n1#hw>4a}=yE|JP+up)*Tl*dct5q*~SLrlerl#_Y$ zmX(Bj>Eg}?`s1Fb9;8=R)R%U752|PPN{=6uZ+~9OVlVwUi;W({FGxqW=unUF5PDJ9 zuu*b-Azg#K2NnFrWQG|uu4bk)$a_$INjDLaZ$HXx;`(&KW%ALHE-3Fo^*LQIDBr%A zS#UzS;9U6_<8-zMCGSBsv_}T@m~?k6hnHm_*Dmit^`ibZP0GD1$BH(dYnS(+>eAne z<=ejWH+c`L2lclC`Sy7Fo4f~AoBrmJZ$C>$WtGP42Br{TLmLKZAIBaT2}K2W{o>xwu&iXv}Rcp1){)8c-S!V(G3=UfrtpX z!4_KqvYJRcsV>D$o-x4LO%;vPh@yk66}hTw?#=KR?B@#o>=iv>f>;rYKpjUym?~BF zmrH5EtQIGxq~J63+yyGFNvpDc=2}1Vt#1pgx1jZ-CAmgxGuY2)yb}ZLyNhxE3u?=b zwyvtR)cW#`GFyAMWT!()CWfGdtW9X8B)0 z?Yyos+B&zXxX*o+Lv4D>hNWoZ(v%|e9@IP|y`E^}%9v}_mr@>=;=+v)>I+w-o=ZI`Nd}4 z!#c8DL2_}8nn#oKZqz&+#c(49fY@;O+A${u0OjZf0Oc@_Zdt1TLXip~N6mTBEh}}B zkPzzDnF4^4V1&yw0QzF>QX~TarMgtB0N{g*ttO1{Y8-S}5GeHb%3Jeb^lAB! zOfcJ}D&r*qKoPB|^2Q_pUXujC^!Rj^XzQaI^fqbG+pIzFu~dB)k@1hXv`tQD>#`#f zfzQxFz>&}w93A==sKHM@(VV;y(6@lN>DC7e`WA>}rJE!eBT48RmxjIt(bgq4^j-Sf z6!fi10+}lXeM?7&zSSD~CVndPt@?j~zAwxi1^QYwLT?51W|%cIaPJ4SPO$m(^5Sd% zFX0xYzxvv*7k-w-$+vPmtP?tnntP49jdC*7nU!Y_B8*2tAWjulk-&eK54c;-j2pqmaf&I8~y7X5-uW z_`($TSs5a!7uIGn2yeI8ZZ9Jyo8uKR*$r;QWKAElq&Ud*h-=J7Uc@iv93&6TrdPZf zpKsJPKO>Ev>Krp)yU=VlY918ln8;jU*=hubw_eSIwWJuf$Ig5@I**=?(#sM2*25M% zZbV0Wu-8m=^f6vCIx0RqFOrUm9HsPxWzR9p-x(tkf1h;rVY{=i=Z1Gy^Mw9#XHVt# z2S@Gigv|cBPt#v%^Hckqnppth?8wt|0SU)CBFvd)6^}8ktRd)D&0y)sQxti+BAus9 z=g|q0r@+c{PNX$x`-oa#t8l3kbdcib3LDFE7*ZJvROy;uvp(UnD20s32{oB<_?Llj zXiE+_giO<{pOs0(WYjFfoyvwgbJ-_kII@-pNQ;^c68VNqR`6Z?iQw$e6wZ};be?6| zViOuHZ;qxt@(ukRYujYqQqQBK)Pu(Om#M_4St$p4vYw!|6%;L_Pq^ILwhI%=Nmgfb31r4N9uqY%ADpj%?c2R%ag-ilC1sNTSUP`!Zz z)Olg#=?7@Xh96;U#@clPy-~-H>(QQ14b-EJJ6&2i;|daQxYzt=8nWHk1@*~on2^ufViu9a*KK1KQ z?dRdq`?;DCu|QuYdgzc5o;R*{;cWw^)@pM@e&LAzqAKa8VQW#ci!mI>KL~<5MH8{r2YF zVZ)j_|JWxeXjpr*W@-}RVZo#yQ~LnQE18ORW=ek<>kspmP-Aaf>r+hcPeq0p*vj0m zpvlk84D_$_azO1swNX9x;s2AUA8j@6biGaJi%-+7KlFwi}{lH@_L z>^tJpCUjT?85YLZ=csW5l>9VfZGN?D$ehDujIP^pCzPrsE+UhEzNgmcUpHQz{{cKB z?sBW$@p1f76RdZm%vhXn1g2r$1tn3uP1-~+TI9T$(l+v}b(77gDZ^)2-7+mkOW@96U53b z+%#8$0Jxy5QmNmUEe&B#0PzjQMt z^6mH#b=ZSFLxqyfd_wY*4_M7S`M%xEuXsp~<|mTauRftdM#ENzc|npr8hMBRr<>U$ z-!7)xHXl#k;s0?TH_bw!b@I~R{wC~p^*;Tu%gH(;+KsCZ0%`kiO%=(QnsR~nN#b(Ij2a4yK;r96A zKeV3k0CMLji(-Y4AFXo7<`e*@^5P$AUeKW9DO_VN_i&Qw^c&>(5;syAzJy|PO5__c zaqaU?seh-6M$OPUCRH5|Ke?i*EU5NKy7)9spM2w%-#mJ^)AI>`aARa}HT=OHQ$`MA z{os7IptgI0FBr( z>^a!z+5hf=Mvtf^&kARw2#rh0KsP2w2aAUITYW%FfUKf8%Y61=w&Rv8^uh(`L~UBS z5Nj{uO&i&0qX+4BOdCE}PgD77Jx#Np%Dc<=5iGav|Ksg^;G?Rp#6QVQ!TdmN&FL>5HcZ32<=vzjsHU?XeAUJP#({*wrjU`Yg^muZo9P=TX&7t z(u7bFfdVR(AhD!ww!Lv;8`NS@mic|peQzc+A=qxWpP!$Wyt(ha`|dsW+;h+WGn-jd zVQq#F#UbBi)S{HRn`f*@0Rz9)zy<(7ZStVMc7-?cDd927*A09Bo+k~=rc#PkGHzG* z8{ZO0bOsh!K|jU%!+|B%pEB!@)@)wZW`%nQSGpx|$CiM9SZM0pT$UX3b;&Ccnlul) zIUzW8YR`4usTRuRit&!IgfKI_&Kwg0k5T=&O&X;G`?sgQ5L~c%jPP}0y)7?;yD$RF z;V#B&msZPv3=QDq6d##R@mSs+>-U$JQFyc%LA@P2n-N|j{;W&Xv-~itip{`vW_Y>f zj)MHVe7k%N7a5(;AdSD^K^_k;ckU9cVcwOBrqMH>cTq{V}VM#C_iwJig*0vp_sU(92Qwsa+^GmJLsUsx}m85{Y9#yCrY z7h!%F1ywW8Yz^E2rGS<=`tRYyZvF6DyeMfwb>jdHB-BUaxcV9An;F<(TKfp+hRt$< zTAQ<%D?!mt9k5|t$-!q`am4AXq9dJw)zW0tE;njFZ`9T^z=F$w$DAkeD8gzZL})pV9C+bRykbRgyJL zq~bG685gf494Jy&X#$g?(Ye@Kr~c`hHGUK6oK;zmk=(%W0>FHFgr}$$y;z*bOPr1xF4`GK;bEX|MVR(oBjVA9zZN zYx``MlMU5?#&X?xO77L`2A_UA9>&XbK+KO9r@vc2B+H)qmTvGNZeRC(TijkbLM`w( zB_53y&ly4@&tgn=G$ex{XQmYrx$nOOyH|EPp5pD!;ygXS;)FJ!Gf)pK9GzB!eOsI1 zM29d*O!M111JBA!%KW`vfr&aQSaA4>3qaoonZ5k@IzNoC2hoPKB_*We@ot(!*Jj}J zMj+U!W2USvwKutagniS)*?cg0)oouzLt;_LiAK9goBchylmtj{1D&>F zWQ@Q?T<2RHX8_Gqb58;?&Lm*|<4pilzLMlFygsVUB@m1U>W8f@$mvBrPh@Ic)5#MM>Bf=7Yy?K+EbMw-#Vm;EoJ6Nm@H;l^swg;ICaFn;_5M9nP&k zU@z6c`S!Xj@Ba+X*!Aq~$ga-7;|~5K2H5)Z1NoEK2~UB^3@umH{Ik6i9;Kc=JK@t} zgtHL;kR}k1vJ)On-3gV^=&iC3&QE`{zE5^f>N~nY_Q8_J?R~J^0Dp^xv^WwPGHWYP zOG4`e(==p^?;Hk|Sn$B3$rbN_nwu@l$(SWuq14(6r6ae3)nMN?T2^DMrOW9Ur{u_B zOl*Y>Y${nIz-yD6)c{f;eKwGulB72GohM81`Tl?g^CpYI+9F*f<4hm6e(7Mi1ej-8 zV9rzTJYs`+Q?z{3Xkgw1m^Y08^8hL;)tdgMMQ$uGTkj;v%^PSVWkdZhVSFAVO2T+k z62^7pFHi2Ok!DO%<}W|vmLl;E=2L<7`B==8k+>>{eJZdf;eIWpc?(gTJpwqBJK?-@ zQ>f5c{2tKg%w&s8K6ddIdJWn7k4LqSYpGdU9n^Bb3YXqRPL| zrbgfU7G0l_8Wo%YJ7|udC z9p9FNJn#%iYqaDd547Z}Cj^752&)8%rdsuKHp*ADHFjU)JKVF#oe{<#;4(bZSHwbk z=T=9fPs!d}@D+R4axnTv+qEd}12(m^*n|+oQQpiP@n&F-5b|%p%J_GAH!l$?qC;~n z+^vUE5`c8-a3bsK&SYDYeFeBKinQc2&K$djbiJo0L36dYU(nQ@TFn%Z)?^~}4!NG| zw+Wz6yJIyAwf<+~Ez-%zo#d3mn4w8^IM}Icf+n<BSo7T47Xw$j+!`4a6oU`_!Z( zEXA34moY*~R^-||i}1?PS~;9M@(d^W6*MLJxaC4J450o8fhFluEFA0AFbniqf0XrS zK>jeYPn;2GIOv*zo^)_oZ33vKByjELbVGt2oC7;4E1mmg8%OX(RdnZnFt347Anw-NUA|Adg2MztfW ziiu@BMcIa0zyp>fZ%9eH;h~ z>zzj`h5jm8i$Ir!*xD-E8Ng{$8V_23y5$eO6bnJiR(_T)5W351NFb^UxdjnR z-SMD}sKf$y0d_6iq`s9R?Fa#scl;yXd2!^Ibb~C?YNE3~0=cQNq!~OrXK9jZT+Ub$ zD-`HTF3+2<{f*0G@$pwg%ZYlo!KNN#r)b#MI?w=yBP}a3!ruBJ8|FXYe2T4xYzsqh zN;Va`O6;xL(P^%FR+jHoQf58}L(eS%62HDB%~g0qsqa|r0tkL>rX3%&)oSAu(PisS zSHFt2Ag~Vkd$sx=G1rV`o*7tY37u$ot|INy69uxS(V*M50$lhqI&NG4losDmCY_b9 zQ_oIEVH8-0D6^ZNGCg&k*dkJ8Z|$cuDJIe6^8Q5ii>q#C&zNz9DMf_>^^s64+PJ-=tbs z?yMy*7ZL9Q?~8%At;(0*L3Ip~IU0RJ?u{S5C%=R0H~QYAa_>)UU;91z9aPWjdk@OJ zSBLM(@1Xh?_eS{hqOd+qKcAD7;>ID<*(r2~?M%b^`kHAYRozk#vC}G6Lq){EHmxbO zgza=i1hKTX(mPtU&7|l&Nd<%M2)+cQ{aKs!OYzhNj0D8E%|o35cpTaGgB*1Z_4ZX( zyyjNTTv=HJ@z+2+{}1jL$Fh|U3gsTv|#z%ASoC(j@mcyd6VBfqxRB77C4gfRPAJ&O|tZ@ z^EI<2`F^W-YVR)BIYPM5JGI#k72W2m(M8GnoZ}Yh`kV)%*H9!^m!3VFf~LzTXu6bw zrq@u=v?NjBoA*%y>ul~-Ys?BWKi^0CUtJL`+z$P*86iPGuPc&c5*mtV0@Clgff=z1)R>^fcd;%%16vsNA;H?Nk?F>v%?4g_e{*?dmU!^&b3+3 z>x6AB9{e&fykINGh(JJAko;&T$3S>;1E8)dpTUpsnr1l)u6^Kzx7#&3iM?ujldL9);7uYvaQ1s(6irT>h zih8O4c^ZCBwkg>}_2n%j7l21n|G7a>>wtw?BaUF7;FM(T<3HT(L9L{_yZCmGF_C>|~}>#0-^ zp-91AM*wn=j&er9-FenGri`+3=UTVKgCSdJM_g79_QTQtX^3XJ(#YOE=6-vFNwEn_ z|D32dA)8XyAD+*4y^8JXXS>d2yA~z3D>1>&6H<+g;a;%ds-}qxE(vFvFK=C_yZ9p; zYdopv$ILtw%U&3JMT%?!`^TAAuKLZsQz(d^!gczIwP%1wrP}D!z}VW&D`$^_#GmBc zBz@;i+&*U%NKNE6x2BH*sdp{fkPaykWBVtg-H|M;4_t%%+WIv6km#dNp-PaYze*p} zawul1BChLai${3*L3L1m6ieN!z`>w$@c6Jj)g0htx_gxKrL0nHhJ7Kk-Bx$w;-k`4 z-bQV!QTw7%dy-vs9WI|3QG@zV*AwDC7TEig{&QfP{&R3v2|DQ7!JvH#44HeNOyo%& z7PxW0Pq9_>!Iq}jDz+hQF9y4f@UvoldsbZ`>_hD~6cN+Rurl||eAhgbZlBPFuE}V( z%jKukkmwmDEi1JbTUz-v*AxlvbCib8{l{%;kWl$~K z%&;}6xeV%D@?;(u9#2|+b;dJj?l;0fXs!c`k*3pCZnXyoe2gs9Nex|0a_PuW_L}i# zZ7Z3^^=7^7NbG#Gwh#YehXl%?xer^Mgw6~Of&WXX|D_- zJ(GH(Dp`Ut=^Nb_zgMl+K<*nE3DT=d?HiUaelHom)f}2A1Df!~Pl*KSHChz1wSwO(winVWavDrgDXW>pk;h=;3uM0!1N)KzLz-PmvcQWV5L_* z4}_%_>WGkmUiG+rBW0LTy&8&q)wi;XEk$WyL?Dn98b5O4-d!UFBJSmugzbOLXC87>JwtzBdakcz1dJ`8-nm0gMPLL&u6NGnR_*f@T{Z7VohKc7|5ksFq2WevvCB#wag8eF(tz9VxV-mhu?1A6`pNw0`Dwf?Wr=&!<|@ZK}v&`SG8 z%J{FQDP{bBR~&kK&l$#X(7ur}j&_<-#&J#(htkgj;G96`Gvd!%LrMG@gkJoW_;Vfj zQwIJl0e^~5$DcC+J`HzH?oQ&)uSViduYE6N&cCK9WzJKjga-U&8h!sO=KI0>_I$%G z$aPfm`4GL ztZ2xELU>0m_lMGwtPFx#h6be%DeN7d+;9|p<1vD zepvAOU=(!n!HQ)L z0+97zU$nF?jX-SC3NM$5w|WHAJ|fPe7G@X zQ`z_3t&3hB^?vL1(1zi1Pug{h=+UiL(NjO z8y{!YFD2#cZiGT0RU_N=id3ivrS4!Tf_y1@y5R#~&LdN59#`J%a6p~px~L6psG}*n zI5HG(d@oq>{G$}LQlFz=?vgD%^)5A7T+ctueHPW-{~4Z1Su8HMvd-Vj1%MVg-z)3? z{d|76KFCWQ)bHXIGLPFu!aAgWy+;lq+|-VQC&581s}$-}43u@1`s(Pt8Pq%*U#&h# z6X$kp{F<@V{8i`jTT`d$usW6kmHMe8=#!8oq+^*WWr~X0_|&|T94cHzUE&Bt$?r_U z3OzR)i!vqle38q{ZdXq4(3!}c@6%m38`pSlHnVwtqLc_< zB-M48 z7Z|?b9=?958BgqDQt73Q}cnBLK zHvx|~JF%UZW2044F-aZ|cp6;#LDBN^jS4EC>m1D|ijLs-9v(8nC+F3jXL`MlbX*(x zFN`gbj!Y!*P$k45^dM`zhXANup~~qyf)z?K`D@wyEYlkeNPQ>15z=9pnq?AYvdhu> zqz}Y9m+-5K$W4O|)=CtmRcyY&$W1354Vmiliye-d8d?!fiO(8Li;#tA2U*fc`AP5} zO_W`st2_~bCp3x4L_W)kwMRbdc7TMlSE5+Uco+?Bhh6GQO%%ePElrRy8P#H_hmxAN zk%1+Vn+6Dopr9LyHyINlUhK$3%?WdS;;Tkc^j(U4v9?vv&kwyk&5EhumzrzUwH z_#7AJwYk4Mi~r1*C7oyHj7}=K%{b&dV79-kX6&EQ={(Tt+WTEIx}3XT zh&xC>(LQ50`9EjuaJHLW)Vy!+_c^=#$Gn?8((7VhRxUmbpU#~y)8T#K1Sl@!7&Cvd zr+E@`t|EU-ygvU&nZEwFxVPcnJV(<7k)iyitjJJt__sV3K5XuiB0QH!5uT4l?#*?^ zE{+VjoA6urHI0i5<;tV^;h#nB^<*^um=~J%6Y@WX2VLwcll9sYs(+llzGZQ~`Y(oJ zjt@yZpD)yazpi9^DKsi)sS*^gq55q+N3ht7JQk{Nvwl3yj}7dbu3`R)D>K}-OQHa?+&vR0=zSvwf$D^MO(U_#8UU< zlTR|XP(xuXw}BLBbVDT{>G8i&Z-g3r(W-89JUFRQKE#;D>H!|G#x$MhPn*YVqjHW~ zukUQV@jPpilvgZzY10uR8XsxDI`TW&Ni*AToc^j&GS3JtFjf^CtCq}Ll^0$pJO>_0 zFDxh)9WJ`v0ii}}e=%!F81Wvnh0;D!lyj_!F7!ztLi`?tZ_io-i{6v9S#jAu`EX&nb-lqrKf1imMHy+}ZpWN&j zYl`ueZHE4HM zklI4#%KHZl+8%L`9CgiHVitkyRaOWa+wLvcxb?a1Q6Fj&qq( z-AN?QJ@H_%ztp`Z+d3d-mZBn{fEfZ-CR#btRD|No7C1}YK{+_$Q|`PV)0eT9!lE7s znyx4bJqHbB4COv`3;k;QQLtE!kli!NJz@)*s*eX>qsxC_?KmUkv8iXY{5|zEQY9v9 z)@5u0rkxpg*Fr*%bJ|Pwa%)eAu zYDE0FB)TS_lIeNyjHq(WEiCD-2%mLis0SP2gVyB5r`*a-p2_D=^!h>-c^-?C(C4+V za*#M`taWM!?(h`LR>qI@?&~RRDV8&|nIr1=b+q0lRR1fYG8o8Dqxcy1p=a};{Ja^4 z&gF>0Mb1rO?)I74Mpbt_I2*wtdM`<{dZOWjMpZ8a=t(mpx~4Eh%`r>?Q?;i+wWR~C z2}()VjXx8{P4}M9U)}c|^?W1n#Mp0k2@O%fF{~cKqKdBdp{b@V z)2wv^v@sTMZO{Hr|T2e8zw4m~G=pC^VQOn!5zcG48N+2@GdoJ00- z8@7?9cSAD!B1_j{FoTwO(Y%OwI7rB$In|hGRC+*jiE|`})V1OQ;WWd&8uq%Vafu6- zM_VZ^Cyf$!684dSLV7megJlk?=dy$h4RmHdC@7HqBLC8(E~)7@nhRups&k#F*b2GM zLcwu?)nTc806Tcx_T?{?56JJJ%chly?KX0)Ed~d%#-unGyW-M{3P>(Eaezg+7B9}4 zn#fRQQ}lj`+0z^yb-tM$8MT8MHN+tfL@MSvB4289gfB#585>^<1*m6N+*0at!f4D7 z46LCsv8wP&_O4+u8JeHb-nWvG#U`8AHvzFMA&cb0)jMkypffw_(p>h89R`8sT{dd3wY8DUPPkn4w&Ahq*LwM!RsX)906a8mE#jv&!A< z6?(H+QY=c1%-98DlwjAn6&ur$LXx^k8(P1DN1}oEg6cLj0(-YEoYE1B-c@MraB>`> zkZp@u+n>c{a8@FaW$tYmsUyCTVUc3Q3>8T0)`dAJv8)+N3NW~sQ=o-QJ}=DSS7hBA z_#OAyTXtvZy5w>RA)t!R ziBsXRz=WI;9kEPK?DD~oQ8Uk2xWHNvjmoLZ;dZosUq;j|Li|9EMOepBQwHG{wDv~Y z^Hig3vAM#@Mt0>#Zt8O|)ZP?C^hWwOs46O3nO<|@-Db@?<}{;2R%SL_e*cC@%it-~ z7oHILkcA*iM10wt*tiN3zYNNCt~n-Bc6U5dwk|I3`^=i0ncavsjpu={&i2Y+MSRLi z!QL?-Z)7$3ipCf#a^z8$zkAL2nK-))P)-&Nmu%Z4at>%U)HHKk^gh`flwM^oC?|B| z(#l|bDjQ@jv$6LR`@Zb%8D;C_9P`*+^#2d6Z~RVngB%s+m<;QqIh4DV(H)N8#h#Fj z65H25_1<{A^%nxihg(=#A^+w(36{U1OkI8(h)`?}N$rA`LG}A}QUZu8qtqkupQSz= zElLZ;N@|qOGD>F~rE@yRyr<8!)T7NPE!OTkAT_isBZNfbPM2u*tELmsIxW$-4Sxy+ zV;d$Ue5t9~QPftX;>q>7Zt_2gNc z>PbfpUsv^CeP-H922C18!j4>E$ybX=VC+jEABrwrt=8NF3tuuDQaDQ9A~_aW+f;wR>d?iJH+o0d)29 zx{OkH(GfXnancxbP!6Z?K)u8bRfg2jFEY;aj8$@oMwe1pNf~EHcDh(vm)c2yKy+!I zZmX!EDO)`wO(prvU>-9tRy9Q*+CWF#8>{@1{195pLZ$5Pk^I#J%@>5!YYoFJmafp< zG@+vCl?L}B_3FQ#;`9e1m+#`m;8~<<^p{UNY8lCOiIJEuhl-AOI`k2LCmCkk<~1@X^BT8N<~AB#Mk9q^ zg;mx{6;}n-^(=-!eHG!%7S2B{OZC=0vQ)E#q_JA}xMbY8CBs;lJFWQ|ce{+@0`>a| zJlHbeX!@i@p$9FpN1;sFfWheXL3L#qG9ia%Do0t~r83v*C8+Q-&%)x5YNYddoaVNesUXmKqd9Ulz)VZ0FBA7R*LcmNtoEwVYS@ut?3@hQcqn`&w}DLoAtIB&HrRu68JtmCH|51HXUDDY=3 z7S60{6aUZdN>Se8XAzrePKAZ5MsLoqc(^Y<<$kSs@$Xps=Za;~ zYu#$upH9VNHm;)KJJBcNn06)V|YB$+Lyo7rf(kcCAc;WV{?SXu+P z{#)PT4z9!x12X_6XN9CfvfeF59I1|yW^^BkR*3!3J6DSmq)s%^7Lt>Q6w@4mBbeb* zdfz4PUi++FH9`(zmLNGF$~UhDE+~&Z8?{c~ZK)n{b~`=!iBL__pKxFfMAu+`2`+@- zXx^WBk}ZnE`Xgpqi{hN*yn3?Ao-E5FMLkajIYw0eA4N_|w-=CuV92aGshNVfWHQRY>MO1T(7k{g^dEwuM!}55 z#qJ7!Z90pGhv@H-)B3X{FWu8~=LmQolG*=xjb_b?j+?KujRq?zCK*22kSWlfNLB{u zYWZMgNKY!)Y{4zBveKLz4`HlAC=BIr7fn)SgR|ZZoCH8@XY_ZrL@vUxc@{%*$0ylK zawc6{N9zw{JU7XBEc(upgy9oJbF=S_y5cQ$WuhX0%fAl&K)~bu&tkaOYy|wc5H4CO z%!6i`$B?iEEpSn_#vNTNX2@sDSUm)Rou2u*O>0G!p&Ev4zSfFeZvZ zy*Ieeoz-);F1)&<^#xgzm+zw7=Rh%AK(lchzH~Jq%lV=xFI^F>^UN$IYq`00B?3<5 zx7zixK-6c*9$JsthfM5!`an@z`d~yMTUB-9mpaI99O(yVS+-j5_n#d0f1p zy~77Aoq`Igv+uBI7))Hk6n0p>!)hGWfo)`mMeehCR;X8>#%`*bvKnqg(wM zJa+Dyd}tZD4dZL(s4#=^KCvT{$w8HD>|eOTtXRnrfyGhFPtn`O)SIWaii*(E>=)qc zYAgM{PY-d#<<9JA|3l`?^@aW+v`j<#v1-^4XGvj@+j-88pshe&UkD9y9?e+4JQ7;r z@UJY$#j)1GF3pcFJ*lo_sAx66j8W3;xDf!_*0iFwc_mkyOM~7=+9GY4V(@FeF1{?U zsBKHha-(F0QL<84d^uRPKDs(`^DJ~i%N^kv(e)@4h5l9x?GVMhP?`6jGJI#!yprWj zy&EuKHWk-jCyTlq#gEu?8qiBhdvxh+4%!pZ@Ovm;W7EtGf8f3FCq&5*7z$r#E_C|? zC&TApx|#@e+EubVLw)Sxc>J=qI;~W4Q%{dxMfvoH?vb+&Hif z3;^64)|WZL^9ij3&DD*USciS|Yx2{Yg&gioJ(%-Gpxb)vja9|FO3E_S`JbW+c1ba` zGexujD6PMfuXy=NWB})DZF3wjKZsx%mF?G1uYR(UW6tbi@2fW8Pr2OifTsACPCJ$* zhS~wGGUI?0P^t>l2^7z1>G=`=r5qtv1dHa8jBro1r8lx`wrH$TU*GF8oYAl-u&wi9 zfSE{8plBdcHcdhUu{uoyvHJS(3_f!{KuCz&@n-m^$Jqf5^&vH#DS%fZ=%To2SX~rH zzzL~GZo#v>#Pi~(h@9E!G1g;>HP+`C>vN6udB*yDV|{_KzR*~Y|KDRQ6aIh?>Ob0G zV60zFXRYsQ`cFTRfjmoGK=>BGsd)%PMMri8{^VH95lLQW!~0AD2oO|1K?MLL#ev^md%c$np{EsQyNQo}3+Sa3|Lkr3ADNItPiPLD15opPTMGsJ8DFXJ1A|UwBqJlUbyI&MFKr zH?wkDxXbnQ9 zmb1N2f9M}tJHZmm$ZYX7D#;4LM}^g(AK`uTexV`cnM4Z^>)SH|s-_GXtA+OHlZ|B`*C1 zb19s3xJz36Cco#%I(XG-}B#(G#sYAndOixX|I)E*0L$nNbteu>(iH#_9$R zpd{fy)49T|vGIK7{Mznj34E1a)T=Nfni9C#>J-@_e7cf*1p1wX`*f&lzez_Bya48Q zdyVZgSKOD7OaaV?<}oO$tdLLE#Bd4+DHn@foOg8wYJ`p?ADh8x7x!_Fxn$|(lsWab z*e`wrF~NBKlbe&Q1`789AE_*D#>@pD)Q@;yQ^U*IvJnWx7K_yO)c0?<-zT$)9IFtE zh;lR)iO843KjEw5;T%MvaE^M>OX&Rg#7~4HC*sKd$yJ_Z!FVV)Hc{@3;Q#^IK|Ski z^uZjxr;fhOv6!&7|IbZApo#=<>iD+E|9tfwQPG?b!L&saUjJHGb6$u;{2zcPj=z93 z4W1oq-sUbh)FK{;^2j_-YD=gd9R1D>K+u!MW57Fyo32JK4^!F)J-Vt}aPXhrH0KaFrVsNY4pa}K+U z%qq^_lp9VWypZG$z^r@xosHk-JHKswRCbpp)sdDShfX?90`(qs)xTIc*I`z9(xGHA zOZ055i#o|5IVAl7x0K-U4CB;-0hZ{oA6htd!{=p*Rtuyy3K&tD)U8UeajC0m6Gb6rtqp==HGC*CFk4XUb*KPSg!zh3xsDSqa#Jry zTQ}(vJm9&wOc1a2s^Fct7ph9WuFv~)q_^HR4;01yRXuC6oQEQZ$ROTZ)FzdYdnWIn z88~Q^_f9WIGTq7k+M8B@z$ia(^tQc5pzG*uJ4OEOnQ%9vS5K`^Ck}49xYc|d$|B%> zBWnY97#jT7{x=X;o5h=_wyz(ll@u9}oJpURb#0PKHgO4SuD49!PZp243=KsOwf02+ zgmB?*{k?R3S=(u}&O(h#>B3$QNl-tinz3w1ho&3Q!HnkFI+f5Lz<)+i;;cUB{k;@^SN&zp58Fzoy~br{Nq% z(&!aXcSGQ&LjWnoky_`$C_i}gwgUkF=xtr8V7Brzdp8|t^RyoS?g&teQc#0>JfewN ztBQ{IOU9zVFqGDjLunf^l$}R!gPy;5^tRU1hthOMYs>c-Mn695f9XR%82O=XBR=%( z(c88efv1k%_JdJAlx@v76jHCRO45S`bu7A2Gg+?_7R4Pj52=0Ph^9%JN7yoUUBT9F zi6BdEe62$tW#eDgwgaEYM+Fs*@*UgoFXyi)hI_>>RC`ho0-p6{p`dU#14{8dai--k z8&E$*Nx&tC8mfpkicC@olaItFP@=am~%&byVPop*W2ojRS%e+R6XLf*iqy z!vI&OW%2w%Zh<5kt(+i9PE`dwi4T9)!4T%6928dZN&Y{tIU>IN8+Xd%BQ3#AjM}yY zr+J8OIYaxlGqmp=Zg1TyNb{dJ2-4(YsDsm~#jS|Sor_(M@|f^D&Zf_b@kl4H z^=rpca=ocJ> zX`-u46^q)uZmK>>G+_NQ&0ZK>Lx^M{W%ubASj2>yJ$MUwp;U-54qXvZU zBXFc?x>%@CHqjDkbA?b+JZS&I`>4%>mOZHaIZzfjx^!;~1`xR?*U{|4iK#=U8WKk^ zg-H)D2e;`(AJxWuhAMWnzJ3h%Skv z@{y%{lDVp>P5BY!Y#PgzcQbsj{*;s{s?TSn{pZ>(uOp<_Kg`Gb@k`dvWXj|=NXrSJ zBcHxR-tx&?f3RD2o#dHq@(ekJ^fSxknPPc{c)0k+6L$fVwV&O1;!ggqDK*Q}KIjblCR0ALYM^n_ z+AM!~uz9@PT&4}SIY_w9w_od3Z)nn1RoLU$gil;Sa78sBI;;nZ=J0Ccd>JR$?EC@L+i6 z2#<*CiCV{@fa`lI`^MwQIn!GA{n+N~6a4`H)5V^Vr`r-X%4nzC>NB)&IYawnXK3Gc zhW4&AwD+E&J(r1(HqL@Gv@bYA`^q!4Z#YByBg5_b1Qd5sarrrp4)|l(-q6mWYvC_Z z1vywBQ4emtLb!Si?GsBVd2D#hW`&G|zbggzQ7-yyu}+F2Iz_WES6H8*Cn(MrZo*JRc`43)$4D6}9ylmIg0gw0 ziq;fIoFz@h%fZ1K3X*R9FZA6-n579#vm3PGY4nPVj0Imjw>^AvoHtU@oADs8WlRpLgdNe zRZw~r6sw^8`&b3Nro>qOlk^)tFubl-kA2okev|3r%&_S&pVvYM)={V|;br(N@^$U? z(d|Fr-8@yE(Bpfh=`zO0eHkJPfUBH{;qqqMa?D3`(!(k5ZrnG#G~W9NAknMRqyEgv zI?AeiB!FRKU3N)_oX|S9 zkTSy~U)ZZ(=+iIs3j_3*M(eakJH*$A$5XF={x3&c#p1Op$Gx1`GBi9o4w)iM=pP6Z zWYLs$VJ_kbv`@VTI~J?{O9RZb2iTiFK>hP01}H=El5(;CGWaecDErOV**OwFxgbGy z7C?3+#$*ZfIo`BDJ5#W0i^Zo1`%`?GCUC!%pF-d;iV-u(14~l3^)K1VjXNbp6D?`cA8U+FI$kSGxU?m_mlle56URQdw&r5enSc`e`X{W|!QpYr4`2Q4^yltnHS3ibNTQdy1hg{=kdg`|1D~E)LETXiHe8Yu_<3Z#mY+uR=Bc9}r>V#uSMIAG$)hnbO{i$~_dnZ$~e=#ejw zk>8-FQ;n-Qm3dDhxj5AM>$NPOZr_1tNIDOs8PSoa+jxsYxGxzKIq=8Azzj(aG>MJP zQg6#;Qs1rpNy)&mkT*I|#m|?&Fc6*DtC^9xG)KSlyu71>JF?V|xlYLHWKQ`BZ(v){ zQT>%$vD}(3lTb9vT$B@Ynu|cOrkqXGE-=gFcbUY8oJS$%0rQ&NnAcyCgAdb{9VBdc z*}OUzmyx%N+9cHzPYf{#J>mt`k#-#7WIZR6#6k7R)fFuGQlht|zbti*9v-eN)@aA` z({gJO2N?5GiM+WGX5VAZ!|;t#c;+Xmdf2u;i(Ha;9x zW5;;KEioO$vccJsxE^L&zOR5awPP4fWq0UpY&S%#G2Gj4EoW-b?~xzVUygU*h` zx2D5b5U>Y~@zZ~O+Jhk%AfOCv-r^q6?yBrzjm{<@Oc`JgPnD0r-n59Sf@#wWa zM3+4C)liipek#7&EoH>4)ZLc zx33XMXPZ~&EhK`jD`YlGM6k;&$tzyvZ(o~<=jNikj*3V@fu+Sefu7C>pM9rKt;QB6 z(wO-vb5#xuj88441=ibX+>#rg8(o^CJ}r;H`I^PK2C%Yjq(DH(ILa_YIb%q7&5@KWfqC1+-LGcqV zu-Y3WiicC(r5z5mf5Zch1P(UQ^MDb`YgLF+Ks)ujjIH?b*369Bgiw_85>b&TbseDe9e zZA@l{J{t~ygHJx|$^JfE4RIlBycp#{l+{&c2dW6)@s5n9Yc-o1bZwaDXv(b@&zKZ- zto#(O@JM{pnz81rU))&?WV3!Th$}$V@MBf-2#e`^IaW#p^qTKkJGJ#&!jv>G5dk6x z?;v4>K9Y0z<%T7Y6iKZlD&y+-YYBg#>bUQheLyh1sdD%M9ELc7)I;FrX~InFb25xS zEtX;UHhfx=4mntU@1}2p9F`_TY->eFEFjn4dKGgPnBykd7ZLN@Vx&S&w+x)ht!F|y z1jDtpn~iWT?i*wg3=iT;lA4D@oGfG6;)YUg`8Vi*M!F3T@D_Os&3z4Vls6u1nMjzi zuL;)$uGvly>>b=*QY^T#Sws!@{FcdNEBu`~S3A|_kVVGF!UyFUhp2i6^=?LYW2thI3N*$x!Wpr{LhCJy zJ`$~afbHJ}t<88VrM^;!1eCVFnH5=A;2O6ki%MKXXKgIwP8KP8;?5nByIrE5 zci0)*AvWUK*GJusk#BmEZ%PeAiT&p76B%^s>WYmui#_~}v<;2y&@I#phY0~Ixrp4d z&+xhv&Ju4aEgQledU!k@T&=x{Nu|KJB)@s6mvf`$2TogL*)gUe_nd8! zb?sDy$TpX>yN9vh^hMKx*Qkqj-RX<=d3(`9(P!i%)#iGaxlbY()ibcTD64YWl>FQN zJBcgHbxxFj)qJ+ESdeT0Z)}%$)y-%vEEp#l;ML=XBc0j!$BN?7hXmcLi6uX&-b6=0 zG7x(;)t&rIAsCtk?#viqZIIFQE;00uF!cHv8znr%&~P&}ERBsTX))WWcKwajTpiAG z-<0V){FQpS{>H7kWe5@Q@K>as(cy12@o&c)8|BfCH-NMC7Az$&1vPAZmCZO}OO<<^ zJ3`i$nqzONyW-I~Hu9>U-XWL^u8#j{Jie+{fMn6Vq3AO*mM8g7=K!@5hDM)HYvkie z@QrXj?##A;5q(JDtsPR;L3SF!!s>q6>*?Y{YgQf`@P$vyp^;xn{IR&zlb|JxZw!1< zAMu#t_JyyTHbBVfppM^e?Ye_%=`(l%phI?e$30=w3J1gqR zjg2$C8{0M6>xxz#RDWdGL@7l8trPqh5wQn&KLTWu_GJOSY9i&IoL}N<9*^xGo2uG# zmnIiBvCz0CgbV7zju!Ct%Rk~}DeQltNIk!{R3PZ2qxZ`MjFkliVho9VIIh{|Bk^&K>}xiggG^_?rfD|% z5^WVh>=>-33B7lG8Px$dEXW{z;X>?Fjqg=J+~)+<6Hl}Byb>+L>?h;=5U=E<;*&xh%6z8c@&eBO{l;$04j#T*iY91?mY z5=0{=n!KV{HY~{G>x&{E@O3(w&B=WcUoY{~N#if+q=43(Qbto4Y0sDG`9JjDC&o)r z@kDm!K_4V}$jSm=74*P~bKV|oPdTDk8l=>{OjFGTGDUC0M%0+UGHyl6y2{++95S6% zBD13ksgiJ~VzUeVy(+m?@3sBvJ3qnc0;9k_RU}QJ==E-8(#BBo0whh$iL^-`$2|Y7 zjP%Dc8l5Wu)DgE~2s~^F2)Nd7J|HWDSL|8lYcmgkmQl|bH34O6Y|Q1mm$v0FC&CY4lbR2%fH^;Jh@tZMK~BS0x(kEjh!Vw zR3Vp=7fFOi%>s>hs}r%xe(U++w|-*3^+OT5B-gbtSLXGwIK+Sp-ya*JUSU9H7mDLQ z$1)RP;JXL|mvidtdSO_TRsbvRR{do|M&kFwi8$-~H0Q&IM{T_FDdU8;lEU`GR-T@tFJ13KWj=bF_nbxcS= zwX4STs@+`1=h`v##@yI9;O^AD5FYK32!o#lsE6cn{tucz2IE@dVx64X6ml&itAfuc z%P~m3U6w~d==tkijYngX*PLbUpLvL-b52xW;1eX6vqGOX9n18R^d$V{o_Zx z7lE!=?^(!TkNtdm?5^b4nSsY1^?whK+Qq0hq>Q@tY+0_}xk8|FNW~*k32skltnS7F zRSl>z#0|Bj7rBys)k%JUd(Fu+cVTx4MoTgzux=SCpRjY2+B;UZ40#!9uIhu-0T*Bg;! zd}P{_@&9^w{Oo35!#Mo)*4z}m{doI-XZepszbrjKO1~`hIhylvvx_B<3<*c=njf2B zJ|OKDN8KJYcdH44cu*;`Vc@J%7Io=pogu4qCCUO;$;{LSNj%d~|F!fkH0ylMIzqA?xYFU0 zWGz$&N3u-#NttkY9}|}85}K_SEcUsgmlpbuF_)b8q0yzDXeb`dIp9AmvpZ_FxVoX* zxHJ;EKCo)$2j)1_HOYN5g|C0n6*|wD?6-9V?r|Z2Ip-{dyk|5c_fy$;>@;`cK8v?I=CRqlt@`e3cBV1%dfh6Z zaqw+&aoh4kP&~xu@EJ>_5TlCvB>%|ku$dRi zC6%uB{~5$hIk!Z;*Z%^I{ommUn=p|$rcP&3s&lFDeu49VRQY5y{71KOwlRjlTCyue z>&S5+*-Hr<5E=&SlBf2;Qo*=fWWF0Mb(?R%Ip)snj^-4x`cg|2%TVNP!9bW0-jA;F z2#~q?53HWVXxDm-Y$BPATAxk?m}AuD7jmNuT`CYKpvejeQPl({^L=9sGc}k{ty?r( zi`_w%LcIxuNss1ezF0C&bj;jk{uZJW&B3iliPsnYQjrd~pht`s=}Eid?pJGt<}UU? z2Cwx1OtL6mizg%L6myJg5&qwk52YvN5C-L88Q&jg%lOzVW~13!CY zq7-8QyZ=Dr+0=pXzwEv5ceI+NIZ;Plhs{Zq&S}CJ2@h_ z1}X6&vx&O)ljT7lDOxwg?7l8gDeuL~FQ0o!@0iphytP9Xq?UMY9n+!i5K!w(9Tk;) zEbMM@-#`_ML`9OKHl!=>SKHq29uHsq>Uk(}fR(JqN%+Lv`hT74@YN57Gkl@w3YRgv z!Ck3tXU9U}*1EJ7F%gVtsb%{4nxpmCGKAMJVO3u-pgtMKS@iYsknSuBDXuyhzoNk% zR8e}uHO%!ZFwYSTsce4N)JTHIVOHA(dnl_0HTC^}KH$P)h-vv6kpbYowyVZ(E%r6Z`_2l#mH*HZWJR ziDpL%y7+V#X>Tm9^{v=hYrJOm%tKMnkQ!p-37)m}sLcCK`jInMA2C)C%%Hgzr~9Tx zbNqa7f8Mxtvew3d1- zZPs^q-a2}~0Eg-jR&WHB?MGJMq?Xs*9IsPH5SZ z+F?04Cz@Iz7Tu~(*DiBmu5+Q(A!z+jK)Y+Eg&W#!0}-1-dt`7Tt|V`pohsPMEmFUn z0uD&i$!JEaVL!g>!)*wP@k!g|$&rngk42x#6mV;$yPFMov3>dT6}s~gv67RBw!uoc zLND@2t}1emTl=89Y#(%6dn;ube8WW>mZ_h9J054UaOy5&p+}}Y`79Y9T z0wH_HN~rdrtSHzoN3<1DClux13hJ%tZY?)<0oB*P723KJ}6ni0JSCu^NNJU$* z7)}r>Me~)G40DFIGzh9ipnFK*iZY(7mp7X~q)bcvMwOWgYCG zF0-XCc0mKSJGQU&NDt2NW^E7Vg%(0@Pr^#i4|lJ1`*(To zFN2g3*MMu|-XO)jS@8bLX%amHXHqq`MkQ?*(AFPt;?WVkD_5+FP&S=e0{>>TN(2sY zO8dy=M+BKZkuALFEzo!Y;4wgGw>>bT1(++#mMF+ZV){UDP>fAiy~KyB`mh<=WtqvE zB)CXwHMe+mNd^pka9?emO-U*#d>oZj5Q~VL>}NCu)oR8sWEL(|tOaJB%lR(e=L_Kr zS*LNuu`!^~%IZ^b|E|VA2Sv^LK0nNW7|;sj|ESjznWk6_s_Um*#7KmS;#0MoMvg{Up~3Kd@OL^{`3pm;Fm&!1mvE}IQa}lm6Gc?!6 zU30R9y&sQWlgA3=g{svZV37D=4dH_|5neRRSNTI+5q8R$4==21@`t^!c4%?>bT6#@ zG%u`tHL4d@UYZwH-l$$!vMP9C6(+o}^5BZ`!YUm0!YUli3oB2(z7GJ{URe3KMdhh3 zS}eDy0`bDiSI@~K+AXTUC@Byxtb)_Mu)fMe!){SuCGXrQURe3+mXy!s@j0fP{<%C= zHp=Ht^TL{u_PH5+4xU+9p;RxdQ@>{5sa{wgl5H&23o9RF#0#s?_QDce#0#r%R4**d zS9>w*B``acMNc)FYO|jfD+0^q>!|{t&4)uh{Odi9*$I^vI-a?LZo9jI>mo(U~5R2Zmh|@8ul|X>(K#lUuMnEjS?ZA;sr;|Q>fkYi&ktelcqh!0a2uph zOfqN%IFnO2;1Obs@Y{MQb@otjiy~8V;vkN$cL{f}pHq!Ayw^Zeu@+$}8kE`LEi!=Q zjEeDgZjcv>sv3Hh_~)2cNJSAD1&S1`(o8(1qZfFdz*gRdbIcJ{~{IszDrg&iMn`qHo}^MaCO{1gw~dPLVNWsfO?JYoD4fjl+6T zQ4k8Qh_%(NHWHyPiFQ#KOAdNU$-<2!a?pFO51$S2r+@;9Nr}JUg&5GmRJ3@q>Ydpq zHR-mTVvj%0$7~ngxAe6~CZ3?+c=Gm57{+sREd5RwxySTH7rN*B8poiCxY`HpzMB5R zF|-&~?FX-n7G{Hj=&ZNxDJ%54-+ZURCkjh-BOMbP)n*m8gb6{#@hjAl0)P|4)prGT&Dr)!8gpgVAdJuhNr+qY z3bs}>V?)uv_-ZgjN~;CcDTcVAWxS@};mH~grpl+U*RU|wvial`Ca^2H4rRO|>yT2! zR{-jRL1MW_-%y1c^Di(NnuA4e1JAq~37j-;C(OD>P)GcjahS%7*=<tO{XlD6hlt> zw;d&InQQOmoCLLd1!xFHm^n7TmgYnRm4Nsr(oPQ1qBo~8YF&h%jo}}90d~As{~Kxw65wG5hT!~ zbEIIebLe$t`TO5NlG8&%`E4sk_~}U>S6jK%Gkp;5QfGOPkd%{jV!?Al3Ou*(wBWg) zy8uz%vEZ1)ySH9QCDpd@s}vmy6Xuw}4hG4hSE?_7ZZ$P2u%0*))?Tnyj_>~ster_% zkASML|4F8v3M6cj!$5-a4+H63HaSpnqV7)t(ufWxr7zQi*ZqZMIz1k`!-~fL#5CEk zl3x+GUU&~%>;k>gv+RuU=@qTvIIO{v4Y27Fh6@Iq2}Ph5X~r!Pl>diFmHK*|>cuZ- z{#2x@*4Kr8e3MJcX=uhyJj+og5S6iJG9+Mo*=0OFBqB@y%K)tC6&(gjf|)L(YOhg+ zIhJZ?-Iy7Opv5eMbru4I!HCg`;~XBd%Vc-t6&=wCxp;oSr<1d%V#B9ry`G737~6z9K07{TNj~W!9+9cm zJKUo8tuLM-iojOdbye28>+^V_K1gdwTdfc#L8;Hy=Sy?FZeGFrR`WIW1=74uH!n;! z7u6R^^TWCsy|4Xr9u5qFlY?U+o9y!HeBz>Nxf^xa%O zNm|Gdy-rkpj0=%!g>D6@!{;Vf4Dur-{3j=d7R%?-C7=Vgw)ANdRoNN z-_lUr>Ok5G22tj3vmXmjs;sQ6XEV21#j1&=S!`xk$1j&L*u6$+>6n|e{=m>qD@C#X$U1Sf==Ei~s?2*a$W#(4OSTZO+CO`HUwGDcok*GW$-c!j3 zCM%lj&=JMA(DNq*QcU)6Amy{Lh3huGF}wviShPW+(+F#)$~n zqYmSB9)FdD=AAbRmZ99R7;fo>P6CnjE=SX+qz>qKUQpj+05!4mt2GKMO5wy&n=nd` z^tNA_(%Ydr3eu*HZA7P^u|%A?((42iZJ@Nent{bWR;@RZTAl27lIY=JfTVx*Nvn5y z)55f_71GK|QT`CuK0KMl-$8O7m3SOTO`RpaC)i`^+YgIB;XPC>dl5JRlJWJew8y8c z=I5>~7pm0^fgPs`KIRR%)zK1^XIFx=Y-3~nEMxs_$*MialECDqRZAOo?R+oD@f1m=mqYbspd%`^;j+-+@*@Ze=&IU!>m zYKMNQX^hHe+_7Bs8m^rf=-u`NSFSNGHDrnVaI*J!<^OgJe0HC1O_k5M5m zVsG=Xx{fPV%0K)79O?#ZaJiIc%?N8eDre;Y((eg1h*trgym&ZQJ*4I9+K&In+joFf zadiDJ2#SJwjU^b{Rj~`Apdz+Q2L&XGy@7CP0vB&Vu%cK%M2Qt+L&aW#M$J_*qF6BY z8VgZ0M(+kxEQvMp|DD-gxRB)ad;ib(d<*BEnKS2{In!rnW_Jx81-q;wv`B(sIg#@X z-RDx>sXJ#0ykyT4FTA9ie}G>^fimE9HBOtSL%_uFHAW1>8HB(_JlqbAhXqe9$bCn0 z#k|-#V+kgy??Om%+8UUWrclla?G5MHO>{F2>yR}l^tR|8S_$^NqZ@hTb=`<0GVgc6 ztM$Z#tphhu(c%UE!;_0z93$(a|mozl)0e;3^*TU5y5RiWD4XA-CODX@B~#@FRG+6-PjF%fM&7w7vRBhI^RQ< zm|BPRNH1;lIYf+|j^nfDi>P0tYTPb4!G@dG67x26Bq*uk7Pvb~!`Z!APEebN zP7lW>(vTN<*$tE+s!`0fuV@cuq-I!>|K){Kb|+W-p<;|2MZrHpzQ)xwVId z*q|&7hhdUHzU5s`^reX<1`=6>oHDCEa*pmJaZv6F4yg1VE7N|JhDTKC`piyeP&SVbn5LIbb196neY?~>XmMf5#YorMrPz4CfEKG}zoMaw@6NMB zcW652DSt7^suRJ#!QvYpy}%HemmgKbFbA_zyzm`^ZbTM(jED>rs$m&b){|Bd-son_ z8Ya*nOrvbWh=r91b?2lZ{)lZLVsUm>vk6rN)5vuggJNX z&c)~M$_%xjHm#Hf+~&g!BL#bDbJ!}%6p5wvKZ6-Gl-(>d*TXvGjNueA7N3%MQl|UW zu$Lq#x9Lpfc{HXhO8!`IY#{czc{AzB?!n%h$Y*E_OH=TL)$){^>U<4XKs5Lx!wlcy zC%FyF5y@7pL$L!R7KTuY39~t@qAW+ZX6R2=7=C~-UdCeRAldOFs?T{8Lmt_2gBs6t zD{QFN?bVkom`No;9V?lDwBXGtwUW#5k>#|pvSRnN?jKQ2;Y1BL$C^cP6TIIWnQ+ly z0ug)&j2_{gi`V_+!73JH(9;Z1B5yAze+}b=2Y$;;bPiD01z(Fr-J#wd^KZCjLU02` zN5>q|4>Kwq_JfYHp+#sM=E-H%bR&gmzOINy>nWHCrPVx(dq@`I<-W|qVjHY9q?vqO zW*=ex)&vz6|E#y`iBsE{t563)y$$WL(wLBT+VBUmj2Z(Q+RY6EkZBcSd%=VbfGT%c zn_Z||V59_K!(&We+{{*Q+6Gy6n$1ZooolcG{fr5P=Hn6CGbUzm;SnViF6hxfYmmNsPRYs+QGOM3OL7&sG&i6}&=vuE=kQef}e z)QP;*nZC?_$!|uoh`UWL>H*dnY;betR?VN!saCWxkwEljPJ=J640_?BlZvE)9`0$(uKxj{>t(*9+2`{q^0Gi9y&230rZ zv`zcQ5o*{spn5e#t4VA ze~VX$5(DC!d=3p8=PWOoiaM&{A@1?xP>V74hY9I7J7Z31r#^&gw}sZ9kWB04aJH_= zW!h-ML+qGy;H!2;)sx>=*V%2LY)dY%5CmzNViQsR%$IAwNG^~IWR#9T#>-4-Q^z5% zm$@8VG&4mZ%_^~h;E<><*`qdS9_AS9FLa2i#?INEF|#=jgVN^~Vl0c|0t~-^nQkuT zE<`g)O;4fU<#XtFZ4&(+m`1-x6Yv|haCtUE>6r|z)iN|Uj-k!;kWARZ-4Q@*7SCM+ zl;&ZUI(Hcy>Ey_Q;|z<}gfrpN6d$7kQs-u{&~0SN5Lf~U7<0yB8&+i$ST3dxJ=J5s zaKD7N&yFa2b2r^%8*RKWO<&zbX?@Da<5?J#unmLZ=_Fj3om%0wFcwHO>Lxdm^2BWz!q$U@8TYzN|V$A$!*Vh408!T`>uWnEG0P6ebY@!BoaL_Ar|T07hADGl93 zaxd|swo-BNf8=-!nka~`U$Ty%q(QRsHBRflPFB99^@t?cgO4jZ(8L#?PPBm+`pX*5 z5gfF;Sk^FwF_q#)9}XTR4v79Z+pc46aM;fdrkg%2MFDgw)pVL#@? zn97dO4_>)#Jv^-+`s13oBY%f+x$NiU(|8lN`gfXVfK^Yd^i!GjVpUeuYr2uAQf78j zAxG3SqeUBW`KSOCHW3!;KdS#Mm`~L|%5>y~4x0>jFge*UZ=KGDGI)8ER9&knKsJHH*#P0^t#_O(hJQZ7K!Ew_G=UBtNT~XUv`8 zrmDVy0yn)z2A(lD;Fs(^V_pImRqr$AZ$(#>*@%gG+?k}0@KdKD_|sTXruv${h5vXF zm&Qtz{1JUHEEpbOkfF*_)+PW4qRQF?vR`_Eaao%r_B)6D#2Gy;Y7IeHKmf z{CVi0kLsaS;MYo56MnNd4^GIqA$WDiEed@TYSW;a;CfK)+GqqBnREVY9B8t@P3(iZ z)s8ngYk7PmzXRn~POTwPmw`%hIJt{GNXtBIpU^HPdKibRZ(gvNtbnf-yWk~~*r~=Q zFufzz>I#45?v>xK+_F5Yd;R8BS}Clt1_#bfy?I>i3f?;lKD@xbMsM)OoA~2v-KX(( z&De|^r_+7dM5#oc{HXMH-t9me>-*DeWcM+=ptxxVo9?pu+~en7 zO>BO7QsJ5VF6J;?d^bk>zN!>Yg#v)l+`U!Jq{`U@_%JTJ>i6xo|wdd<2YV6kVmJD-z zHHNeK4|SmIBXPQl>UTq!IA66Pe4Xz^(J55mkTVWzK8@zvXtL;?S) z9bs!e1a!TgBp+iWp$Qh=iJQNk#{V-)Dzvut6}wPh%!*5)FMg&QF<{E z{cCdD!W|{Yu>-?SOfy*uA5pSwY>{%%9~qWPbOX_?K-agQ0?Ajn0j8K+JQ)~wQ@;-)>kIVU^aO!QQfR;=#!qKWq>vxP`~|!h z!~6OEgwsQ*$&4Zvp+R+Ep&ai;b~sIg9ocxM81GT!hcf+EydT32cn@H`Vg@*7GmcE& z0*zNcjVN)9a+$x&crWwUi1!hN3}YcBWo_uAa#2`ZF&NJM#>y9mDft(zC{4&8V14_U z;BU+Liv)gRxyV>b_|ps*N&%1f10jzM|w@=NrtxicS_rW->AIk=TMt_@oWsY%m*~=NacM{-^J-FeGvIuizX;oO6hC zBjYULhcJ5#{6LEBBZZ`}5DUH+Q54Cb#DhZ3v()hVSD`bQM&$9L3K=nOBZ?m=X@1s} zGmT&YGK-vjC1(R(i!mGwd@H;mTK|Ydz9QZ2DM6W{Zu>bPS7W*#@m;onF`6hZUZUs9 zB-azTXpqX3Q3+@?$MA~N*t#|j(Eml;13-{NT0a~*@+M=<^dN-)PU@OL4L z7hLN3j25I6IS2wNJ;AUA-wCSpaik)GDeuEKCgtg9T4nsB{bbP^{#gwYYRaD)2xg0U zVD8PBv-y!28A+Xnsnhcv5RKlKI2wZ^%APWJCB87r`b3s%TG94>%%D>Kc>*M^lf;u3 ztb^j)L6_cfJ}5Upp`2*QwCC~Xnv)%w{JqJ9ckwrY^~;EJC*zd!#caH@=j*Uo?D_qS zljC3JQKGW3j>OlcM5TAeER8o~&S{j?ci&S~_WTWImM>pl#FiqiCgKmw1bcooQ*6%< zArsoCz=ZZNAUFyePYpWdD*21c>sSFso# z@p6`w>%6syFE#_`7~=d9oZ^%a?-b$RV3?$NMce~0jhw$P??3gdHoa8|A?0X>jR0YZ#k1bgZ#GTeyYDI&oJt>PxB}ZFsvz} zMP&?0w2>7icKn&MM5dA_qo$Pu02K>8-exRH#r6&px za3NkBqj)>KrMAh&<7Jq>=x>qaR8bP#Ms23_6Qwf~`t$V-7J@CXY6O(Nw@rooC~T=W}P>01u>fG*J?o(?K*GDa4!G8Ir)=#=f)JC!gnJq z%wpRyXM57plsT7C%gATCWkjW3fhOxYjI@nHrw}Q98aY+WsV8SBbJ~;4Nai#U^-^PkP*SQW4sArGYxp+wXD%=n0IWbqt6kKt{+o?$1x3_|IC-uxTF z=@As!*e2xEl5;R~GU^Wyh?1vyj0iAT-jgb15XGC!L<5Mw72_X6&M%phQKyNbp#g@= zzeu7ZNYtB&;@#9`d^~gZA?HMLqPu<63^u2yP^xdkNwGskdSw;nU=~9(+5q~?vF9PY#4Vk8BHZe$j)5?slgg%sBh#NZdiA01107rzyLO2HVsyRuNq9xKY8 z4s5iy;&*yegasr#rGj}*IWtGhA2HHWzdoXZV?sGc0CC)59CP>{6&#An9D2slnRjLp z?xzTARxsBuXSN}AX^a^|4y)#DURemIn3TtNrKGU9$1&zY{%{4y$;upkh(oxeTVBDm zzB1F7Obv$03XbXJ94ycNpg~KxLG}kzy5BTD+5v?d_YDg7?C(^#IM&VAfFjm!8Qld? zzZ7K6IuQ$gI2?2Xxta7~&UTaG%lyJG$)C-qGk+HUBf}^8=_21AhKu-6#&?}pGXG_M z1;bhVA`w5i4Wlxb!YD^(lvLS(sZuOK^!l7-2yR$47~zGy7u4uWDEtq;YK5f|z!OZN z=mv#xgEfaj48D4&<&em)A|diSmh-dwt6jt&P5eu%@Y6WyhNn{XSrn>6DCJyRB)IDA ziEbn6fS#|vPOJk&)(?%!XP`2}(c5gGr$M8343x@NfK$#mAMq15!-!C#)M1noK7c40 zMEPi-h>Q4sM9C$}?~G!>uZDh_7c2$V8z|&5LTLPai)zZh38%rJgx4aN&DAaVW0)_{ zRBxC{Bw6zVnbW|#Nu;K{ljM~1SF=UN0A4*2R?Al+mM9i~0Urbk#h(o(eI|uq0~VA# z{>9IroF_^@Q1oxf7eQAi$z z{Ki6Xvq6b}O_XayNl&2GJbVXYZNCb{A4LT!5)35DE=IB7Yd;1hi74NKqPO@Kt_(5k z{0vasi86yxig=zGaG5`dp;DhfA)zFdORiCj=N2DKVGUM;;>{@H-5}Xt+5kb`6l6nKFA79n>LLWSOxnOMm8 zqoisnOl>-=6C7-75lVbXe?58RH_HO&G?3OMv5$TfppIpork>ovKe;Nn+>Ql8%8qoD-L zQG6)F*ZJ5(V6)!@T;^Rx{B#>Qwh>25#$n){N#+qz>ViV1961PzY$GUt z;m(zGT!gqdvu zWeKBL@NQoSx!W69Z%-i!Vn_!TVpOlKiRCh}3>DQ~JBA3XvO#fU6f6GeSx};h(p989 zM3mH;?=YOp2V?N1V*QEvi}(`^@8W$J9Sg3^e@mL06!9C33HnUZeFL}7bcdi|CKmEe z3}gKhtop3Y;P@UKM#Inf{IrIScd*fDkCNog@N+2R9@ws9IL()?L+y(|@;UJdl3%CQ z*50SR&IJ5o1p+&q!^prp!CG4nK!=7W)g)5qefQR{bTaen0!*XJo*0{`8iBJ zrCgr;|6_S<`v`lLcoNsX77pHs+m$#5Ksu89t|4vljiR_inJgaWM~I;xM(N6r_)$K7 zR^PI6eHlz2^OL@3G=>^KAjWKff#vm~u7%CxAC#7$6Lw1|$J8 z0XcxvfLnl4fb~IBt~0G&_I-5J2I8*?@I`gMh1mhk#OmDfqhq8UfY-=o*YyO3nb-0crtG z9x~;!0gnN6p0s9bE9bgUU2yg~?0pb*61hhS3%Gm=70jB|3fO&vvfKb3y*gFc?84v*Q1k?e%7VU~UD(LgiHRW~y z;sE0Si$J#q?g(%M$N_Zq1NH7yxvcA?%~*&VdbifFdzZf{^$AfIa{lz(| zHo#SY0*ST2~Gz(sRP__Uk~zmcFt!X1f_X#7rL zVIiDaq=qoMoYR0F!uV8>5ai_e(1{CZ(VQ!|h{F#$LpdFb0cW9cIfdc-lP`q)3FhLU z)s~9^uPrwj?pWXu$fzM}3u+=LA@~rpEi`n+Z#RHFX9u0p_@iqgIMv`%!ta1^<1os$ z$l&P6DM~k0Y$)8S*kQry$lwXlN^VHBQW^I*vfB{SIXrlx5=HU1Pzs|f32C_D$jNvo zPF7&#ZV-+$vUt3O=o5lN<-uxoaH2d`Iawa9j1AXDkSrblp<_Yvc#WL=lY`^A$vqr7 zw}{}_SY@<4M5T+>%3~rmF~Qo92xrD4g~bHNm4}e*kl>iOXr)|@8dHg`3JcRHwenD< zRvDsI!VayVrxRr48YZM6`B;@)WMz@b!w`2U=$$1di1{+h%hk(C6KsqQmB*^I^5EFu z=)@^XQ4NJ5GW>EJRnH13RTvRF<0m>&qwN#ePp(y|MyPWG&k z;5QR#R=|VemFlo))nsC4c`Gy(6e-6^Wid2%VytR%tX!E8qKu=`2JdzgQ(09QqE#z{ zV~|`m(p_O>WGpMI5Ne8KDmNsO<*&OkgjyNW#2gyAHbN;6i$uuiJeh& zF>%3arADsOY2$QSB$07O3bhc$L2B%(>Xmnr2L^}B2P=a^S)qI`9^`qb9QKE5Ljk-_1?k+B-BylN^vDSy0S4ASE!NCZ!ajE>YMk||`rJQx)qWFqP(%S&y9TBQq* z05t^JsZ`s>sa5fjp$OKn;zGu`2^mZZlnGj;8kG;4!jx)dY=}@Ih>YTs3PQ+o#H0(2 zMEX_oJ``1KaE!8_ZMmQwnxJ3;DgYHNS_rhMqC=HxJI+t34GuM~%h2x=8>Z5f*Bh>C z8{q~kZNGPD$MaC)Qt#6&y#x zL=&nHHLY-J{(sk@4CMw*h(agJIztkpu?4YDjEswuVzc9fBA{*>>4`%gYS0zLBBwk8 zUF6U!4^_oNiWr!&T`J;YeoCmA<)O}$bz<*J8OjQZHK6vKKlRA+*kEmBywDspi5jgE zokFlS;%`PF4v*MSTUD4Xla7d_4hsz{SRI{+Frlvtm9x4}9ko=kP+8ch0-gZ1ih2!7 zXe8ocw&Kjb5(yz%c?*;J3OhSHZisGz2HJ!QAdl3@rzq7b=<>&KAPhB;XkFAXqyL5q ztxg@Q47J0Utd50J=ZcDr0aR+=qNFrxQXflwZ3r+;kV0UYogLbEY@{Xv)haTUnM(CA zJTjh4)y07oyi(4F3#q`^THd{|xP)X0J!*Tdm7G$9d`wV==vb0j?V@VMd8pNBMUx|_ z8$^*)l1P{sSmbVMbgAg+DiZX)x~HjyRoGLe~YHjyoWo+JS23qbfN0P)3xPtw;F zwvpbNuu}(2`epzmyIa5xW1YtHng7;CmD*Lu{pVwrs*Ctu@*>f$s1g#yg)-AJV zhpBl#+t}oNT{HV}f}Q8afVR(X+`oMNdFRq~3w~QQX#KBqds#j`RpgrV?rKy&XIFig z(&W>2-O?v%*Tya=Q@NzrW-sbGf9}5HijR#BH=XOYv^U>vW^DHs6E{65t~KLZ&qb#$ zRr?}Cae(8HA`{bUX6DsvSk$a#X;r&UU0J;^>NjZE=u7LyUo~mkta%H0%T}#z+O%!g zzC*`Ooo(&9*mv#b;Ml!KPp4kaz5Dd-*IyAlAtY2879J59H8DCSRuwl%t%9zJ#2^cgdE$vN&T`4W@n^0sohyrulKsl7bNq?dd!Oq)&_&RNSF%bUuZ z$y>?m%NxiGO02zZB@x1_)c)H%`CJ}UevtoZwjPp>OG(%{@Hbl4ed`IC@Om9Ykrmp5OiXA15bu+EXwib>O>=>0aB%ebrKy z%_S7426jGv?fQTpH(gRJYSd))(@A}nZ@l)}ZIkoRWj8e|eoIQb61sBSBgH1G>`<$q z+}{)*-|ZXPul2xAZ$92Qy~)9_z*TdU=2KH;#4A+J1DpGGIwkS&Ll_ z244+jeR>`iFw--Kpso(uGzzYSfh7R7e;QWgzS6!CP*r@z+ zbi|4ovrk3iW7n?9U&n8^>*5$-a{jL?bDBT)J~qCm_R594I_7Cy_>y)9+>(yGbNM;{ ztmzLsHY9G?+s9KsuiL@IVYjblyNvjv#`2|pnL!P&<$HMDbi9)}?#%W!J~<^BYl|Aa zj~IFU>+jlzZ1&5@dC_83=y|&kr;tO*Z9`l(r?>SQoAYG#&_O>~KahENe)DHek#GB! zyw#`iy+{4r+~2t(VNs*4@hZz;?mm08j+rv$CG3Jta0l4ji`{7wxe$jSf6+{G$f^< z!>O!JHPu5ecxV4KRddg>W6AryXYC$6D0Q8`azIF9>3i!XO=^j|e^d97g^^PcZqs2SR<>yLg}xnCbY-0stI zmx2rTmv7wRId|d97H=KW65cl`{>|b@sKEXZsT=DaNN)K9v@d%2t425u={`jh6u{xP+i4L5mk{FwWJ4pZv*r#8$@ zN^%<;xi3bs?7Qc;JFM?Erm!S#SF<|%{MKAPtKWX1{rkWM{aa30aL_&H-0=rvxArcm zKR_d;|mM&`t&luKg-UNvH1R?zI7Ci|DPDbOi9(VU-oVo{)C+oj3R4|I8C5oB(V zJ>NUdf4|k(k9Oa_sMn%yp+jk-cK%zdg$!4B&uN;tr%uM@2+k+_mbKh`kHd^kN!?$y z+U)1{W2zx$MvloX z2YxqVt77wwgJE~39&GMWe~-_i)By9_CKFO^M~t<;_2GNJA6t9vIIa6wPrW}VYS%Zj zJlCFxKf8<@9g@A%ZE)X??L1SSU;H(`X^k~@L33vK6nZIkJPVnWmOHA&wtkk~`%Uz8 zJ9ss5UFP$%ay#d6yJy*JT$UBANuC$v;!EEH|1f2Q|GwrQu01yV@#IjHvgJ_Ig@YC! z`f9RwF7G_>X4iV>%$~U|FSfaort9Xty5)*)n~S^oZdiKu$d}$GcV33|K5%W?L*HW| zQ!icGuy1i<fx92BYxgA*d zc*Vx?J3C+ByLwTl&42oB@9@So>;Dn@y{y4oI()`u@%MZ&Z(+zTUm~(rowC zefxgjbHBIm!r&IX%ka8h8xmT!>)h|Bj4P`+$C9b12Q0~+TxJrG@Xe?*Kiz0;f6*_k z_5stQ=26oeHZ3kzzS`|MuchO#NyDb@I579rpU~K3^aWY{t38W7ZuhA*XtjLNp3t>Z);pzkFpV1d#${7x z*QehO$R>lj$&1(Q3JBuw@7Hb=~>b2htd9Nv;F>Z-EjS>eB_+cn3=m2`-=N= zCkonHZaZ;l%lE^+@Jo4O+H=KMCugMl_jR(Lp&xYMmxU|GrvA1_`LVEWuhpa6ccl2) zjBlw;vQ%64eB(RXGh?A`$6rhnxt1LkUJjU?=#%-*`D^E{Wh=bKZM>Fwc%uLE-CF}X zG%nU8i!W28~Ii0({)a~|>wPXB$T=?UfZFO4uowBI^>}v4~@8xUTwyVCz zuI^%Q^|4(&zW3Y|)3{&InboJBt>#B=a&7C86EiIDbYS@h=ch|@6oOLoV2a^(>wny&8Jv3+wrh@TV-LrlbTZZZ}#3BW7{_8 zjQ!*Kn+9YV1{~O5YuSLap=IrpdghOBq;*>n-DP9Z$7|bWCz?Nd*J^_I_IY#Hc{()u zuqeqkePh&UkBu)bNLw&&UU(18?HhacdD8C2 zS<6#?E8Td|WB#fJibr?VbME;3`VE)zn``dESKHf6-ZQ6Z(WMW=qoe${znI|QsVMv= zea7+ns@mBu`v#2cDRbl7?CbA!yWX*)fh)VzaeEQA!TM9s>gUzYDSWs6)!(|Msb$Za zbptX~FUGlKdF=}DI6MB6i{r+pPGeWOZ|wZZs%ZQC+s*}BDZCd|JgFxR` z0ZxtoT;ZzEYJ2+2y;q`558FmrwN<$-b}`Aw?l3#dHv9RubMd*ZDb?b>R)+8Gdu7jb z?+>Ou+_u#pm(g`fj?3?l7i=E1Dbai1m{%=2=lX5`A<<-BmlX=PUAt@FDGl19|Doi` zR=37Ae3HKp;#&_n^FDi=*-`$t>aL65eX)D}*b5JitoMsZcH8K_GIhp-o)c=G?o_IG zw+IY&o2{_ES1{|)xN{qOFZ9bD>@|D#>34g69+gnQAAkP**%a5suD>NTGM^J;k@TQ! zV{V6mJ5y2`?O$@zak$J~W!-GVfNxJOR;;-*{V&%M_m=-*uO77Ds&S)`gf2;O1&uF6 z-B~bjgSDpPEYr)=H*^Z{ika1K!0z9+Zl6~x{O(R|e1BiF6<#SO(e8b0{8Fa>V3nnQ z+EtM>(cj~{RfAmuQ*YQ7)$BcR&tq@@0h`!l_FHo7qTSjPD`Z)Tpdjq&=!HEUFmPv(mH17f*4p^hD* z{gT@a+WyL=XJMJo-f828Om}|tgUR?A-;Q3X zanld&_qx%SZq+>U&G)+g*>=Fzq$3#*_V3zzx4%``jYc=EvM&rr5*k2TBlNNovb+bB?%2& zT(*un)uqMz=f5koaV7oUf8m!I|FOq+%=+ecyhvSEM&klDOdo!|) z>$+{{J9Mw{?o*exxoekuEpeSau=SRqCSUdY$^O>B$G`e3mb;(*;mWA~K5tAdn%Z1? z?sKR?T4vabm5B?d*61D=d)VvHFE<);54zYL?RP}><`}lfljNQ+>CNJXX`^{wb{RVjmb#4PXgy0Q3g*2Ydj~35PWka8C}% z25bZD0^|Vp1N4AgKpx;Epa5_la2aqNa0^ffC<5RJF1w(TT~+=6+xK$Vo6#o5DY->! zk}rgfc`zV-_oNo{xLUtn*x($#ZEJzz*lNd~t2#95y(!H^wgX_iu4BB`(E-+e@5RY} z0OSJV#+k^@1D9c+k$`xuLy`4!^CQIapHD^xT%J< z&k&Vbt&5{A2wGsK4EMz19hTkMst?;*i^1j@G`Q-p9!z^g;0Z-Kq_rqoo6^Ex7hntM z4(JQ;00ba`^47*{gXL?sY!Q~?qy^C-I!#<;NTf=qkxx)Y1jk3x&IjTNl=cCXSW<_7 zXsoobE%($-!J>3U)^YL)iO|VmFNp24=rqCMN-QGNVx$Jin~(?v0m|U=jTpuNt)e2e zE>_;2c7$l}lgiSV9;^k=fH5@BMjhx=W&yAKNzcjQ* zM(gyp8f*wehDC zo_{o4w3iRrXv$c*pf^!IS*2!MH^P=2!rj?2ubeFg(=LHj$))xqtrdQ?NN5)k-wA7) z{dQyR7jCJ3P1wQ7TEH)@H+I>{$=U#_$`6Lz0DvoC4%S4ke2;Zuz~WQYTg29HH)#F5 z_+`l#2M;^5Xz8I#qo)koIQ8<%3$AD0(R%2%{b*lYAm@t{7j)4n3rZ`Uu!uvxj7P3S zvwYI9^9#hY;t1;ddQRrFrDA<^z%M4URsfTQoQ(DyuE5SS05yQxKcB|Q@bW_03&3l@ zMsRfZQatdt#002&?WfXz)qp7t-4>EccE*otVMl}9v$Qq zcekGJnbViOn-?wh*#6gGmtDIXj&G84ee{VHD~IXQh7B(0(Ku>mr(3aGYOfBdCmR*s zK+!C5)7Zl4Q>LzuyAU=`TXe8>=J1^l*DOlk@J+9bpyhq?+NGua@_6y!51VKI_BeFj zh|!&XeD3-C;T}QTf0#8r;`=$aU3NRzKimF$qutqKEu*tCV!B;#7x(J&r(rq2_zawM z+qX%NGastI$!{g!e{p$V-B0b4oL{bA`{7T|Bl-_7wj`Fl{T^S=F=}*Sp0P1tW}MjD-&`K4x1L4(Qwke^y|92zbu^?oi|u@;BmbPrXMbc z9rIj2&SLbqQCFrn8y*%^I3&b=wcmByQ9iXT8@nHFbW5@F^yXdiyP@0O{nc)>f6?O& z19LVU`?x#kNSn0Q2bZjPxUWm6-3u~%L@#oYbxHoS_Otn~#%^Ebu83H>Hmp-d$Em+B zd(v!wQ2?iVl-jlj->>(Zr@bfUyjmDP>CMo9ZtoxZz4|o1H21XO>GTs_&-J@B?`GM# zWlWI5hCtA3-s@-~JpI{qd^2(?ez7-yi2T*vPnBkg`R+4A_>t)$KORBJ=; zL|tfo@|VjG^UVI-aN+ynptCd5`yD--QFcgnFn9mfozrtResJE?@bQNo=kxSir~Z<- z>2y$!)sv>bS-)-c{v|Cubt}GV)GcMV<*S8S+nibX_LJtW%2+VgE`8w0H#;o?6$j4- zcsyR@|MJ720iAxa@>-L3!L8-=*;7{pxy>Bp$*JFtJ|6#<<-CXijl80l+M5O2*q&1I z16S7@H*8eH`EiY{?PG2=?cR62>1_G9>Z7YSvn**+SoiBKOM5;(GT5R2+Isy`k}f-^ zUtVeBd|{aU*W!k@fq!1_kl1$nsf)cMj(^##)A=m!_kzsp1x+6LLmRjvDTe(-3^Ii4YlXl+k)NM;dk5?OG!uIFhnX1!Q8{6Z+e#M); zmCtHmtfE6xxXL$Y$5f1M*jNnJgs!%gfQ4#(BPgr?SDj47&EXPVno?DLA-kZf>c zLF{aYxciL#Z6yx$2z$Q9r%W`$wlDYb5+{2Jkj9HzKXJ0H=Q!E)it? zmgMLh&}8fjWAeccfC$GlFjx5px-{s~SUX%CASXv^*@>}WO=y@VI5dnf0p(S&TFWLA z!O;_RG4N|6wQN#`S(BQ^f0}n|)xjYXV|6hTlxpms$I$GI_Ix$Mmc54MjU4AF@(2|Q7J+gVIMs4G5y(!e;OJ9mG^KljE}V`&uuHP9 zyuT1jC{#L_!m0^7_7tXLcPLa7pv6v3(_C!?cB)Yugu{j5Dix(EHY_q+M+aRH0PDmV zyEse6#F(A*AVWlrIvkQ?M>TL{LjxhCTsS#L=QD(R0I<@H2hD$ldj*N$7NJtX4mRbd zoB$iE?{uz%nLe$fT}Py}t6eucdZ}T1w+`}d_Vzt(yV-Z`ArHVYP_;ZDcrs2;;$RZO zyWAJ%VuOIg0d!U(8h8?5E+7lA2O!PaX8wZt?_Hch0L%pZ0LTTbxyQ+p0axLcc&~%^ zG1~ka$j`dN$$p3X>)V(!!u<-o2LSs38v(JPKL)G@?>qSKV{S+w>FNkwTMKcf08k33 z4gZbK!FQtL7r1`&IN|AGi~0KwAOE65hTp3JJ9bC!Eg06Qzu&SBjn{tl&TanB^3;fS zCr`}2xS;T>+A6<|-4ey z&bFGs?FSedi(PlzD1{0~ba3w}o?B%{f#~ECoqXfCj2FTg<2XwbSy3$$nLV)L4bGDQ zD)#bST;1F~JjZmI#yR)t*Rem&9sv{Lo_Hi?qLGjzt9Gtqjfhx zSJL%aIo6gS^6$LtXi9lJO7wbYMI?r_NyZ4Nz>!h$fSeFqNhhUUj(CjDWns|3nXMQN zx>a$}L^vkKx<1x-l_!dF|G$%knXr?lA@p%{M%e zGI{H+HWs-nW2 zLfxCqUsz@F!AvEbO(cdX)mXbi)26s^3>R83#9~Da$CsH6LJ!F%JB)|bb)od)g3-|a z&#gfJBQD4yi|Gt5btcHh5Y}L+j?uh8%pxJ_v;<2hjB!t7T=7|7j=Zf}Hk93h0vsI{ zk|-UFMct!}qD;Vf((<+`jtkV3q+rp4-mC;fYcScQv-LRDk7**!pM%3_Zi4cQPNbS9 zwNW%bEmloKkSdx%QFqpbb);g)3j_ZraZ@@l6|Iz(&T>#_*89I?$UoXa1%vBx%)x1VVp{>M8lwLfR(&x#(qkX;;D3Ku1D#~Knl2TObd*2HL2(A7Zi<)- ziw>qGNUmZX!D#IO^Z<=X`FY_|wMqH8TSe{4XL$oun12C^VohWj2+IP{S>8NgI?G!M zES(LewNHEaWpywI17yUQ$dUkZ0QW`Zy&m!_v?j6uKoB5K2VTHs0IkvXpsQEUZnnL; zcJr|9+SSd|)}_0958G}|?ymNZZuFRjJ*BTl_ntj^bnV&G*2T@!0etQbwoY#DU2W~{ z-RvDbojlxKom_~on|)8m9_~GQe2x#g9D8)_;rKbe!9Jdzq<6T>Fb~3EF>K1i(da^c zX>N&e0)25DW^CnmfyVnT!n9QAZ?PXFJau703NW=!!XuoV)9Ojps`!o95{%uk^G##~ zKc?VwQ@~#XS|xF^*6_cDTLJtMc$P^2Yp#jx1;7q|x)&-)s$BL%5at7*bKLo}Ok}qK zRQAt-sq9D3Hj#}7P^RvNy3j(C{IquH0Q+e!6ii12)4 zr5Zue#o#$A;h`|hjh9v~ryI*nWaj`>PTwstkqtyRmD9)!p`3;SOKsE-?v8*5i-mG? z&cr7_R+`9k!0&*!1E|i>UbOcV>{6k}5+13c?4w<`kYIGDG?I;{XKcXs4?MURq0K*$ zqp}WR1CLfei0<5_%Rk6to~+d%pW-9&kQ?2VP$|$OI-I+j7H5bgJh{o9y}>+Sa%8MS zH@Q!jLE`--?maMwQg6!!J$kgS{NYQfZIbW4w1kuhnkIyKgX8g#7p=Ik`zIm& z5G`NmGO|(3RCGCvo$l3WWse?EL1u?2g=d5?>R_{!-O#dWa|CWBFnht;AFUu#n#DW_ z(_zlxN%@WVf}>e+QMJYDnP5wd5)Z0SAY1IEsDuY~Nr(7sge0a+z^X0f@1AWYvULE@ zO(rsFzN4Rl0k{)s5Hy(X1I$O0caRR-nKL*b?B2$bXI_BtMu5NfJO{s}(hYuqK zqmps-;4kB4_it4sq%dhY0DgCIaSTpQ-dB#jb8RrWY2BExkUzAD9IA_nbLNg5sThM@ z^d_=ifUbZWpl4y-V~A4C8bGKnE*cGv<+wzvXaUgc3m6P=<2YYW56%^SSLXKxZ7}Bw z`Vi2DfEEaMAOr%z90;L6Fb9Gm5CUZ5y^AKY<@qMEHGq02O=RzZWqBsDW`IF};ed|c z!9L*U0PS%+_XF4u*bd%0Cro4w0giy7fE$2V@b4p;Qzo))fG3b!13Fu;>4It!s!X7` zTm5Vz>jt28qpyMgW53MmQI&HTRR4sYlXH1ck+(kgOk^&AUVx+6PhN~LdbTJZn4VX< z@wkF_=x-*n3%{Dk+<<2T5&>q9D#}J0k0ksvc!~cAfO}YFjsw4R24Z})Dj2J}&dx&j zhqWSCuFB;k`Z4_5_w)WP_uH30PwDq@`-`i769W2wyNa&uB0kiAiH#5bPf$0F>#~T8 zL|iK31_~C;*@)Ot#Qj9b89IMHbpI0_Xu|Q;@t@nfubG*f2c^Ka4C$= zX^~q2FqR>j{)(w=Srt9m@b4076i$3}8S62Yp|CaJD>TwWC07FfTO(b>BW01XE@L^; z^%1tWM;?=#E)*;01iv@HSRe5THKej_#fTH+&D0|7CB zt$T|g0S5qQ06d_+g(;^2>;dEf?f@KXnsV`gjVe@1OXNUwgK`0w*X&!VaoLa1OegzR{`JDH|2H$o;1Kaf*P4} zBfrG^W&oLhp8)Rx*4Cz+8z2TS6YvdS6W}PIbz@VmFJKU0Bw!j~5nvnOFyIp40pJzj zFF@U|Ot}sK2Y?442oMF(0%ia*0s8>w0X(2u6H~4spdDZ&APuk<@B`oq;8%coQ|JXW z2DAnY07L_305SmifD3^803PrG(6AZa{R0>P7z5A)@XdK{@DTSQ+y1b6J@I8rUw!RM z8&Cb-#gE#DyYrX;j{uJ`VJg*xU^RB4>}^Apnu%IfoDpfvU?scZClXGt(VYpqF`!Lv zySUH^RPk}qp|m#4C15YNKwml)lxK#$2`yd&VG~MQT3jAF*3wcFkroq6v>db#1-<|) z@Fj4iuz58M6#R*7&c?~{z9Te3J9;ZZunldWQNPH}q}tirhlIfZd=ygfyA8(KZ8sO< z*Nm8C0YREbF55bd`^{UfsC(DWg6pCfL7#+w5f%=GkF;5I&2jDHi2X>-tEIVnUvGi zM8jCaWm5h!IU7xacymYNV>UWQkv?V0l+X!O*1KZFw5YWqHa;%Q9(+kmNFSq(V)pDe zrUeRo_>5QuU!cH;u}Gk4*%1IP&&Y3s_%KeDhue%2Wbm0BPEToN#@ZxMI+Akm4lZCj zrI3I}@LN+_g?iJCcs}kmW#hX@qazjA?5tG39EnEn6LZ9XFY@&u8h6>0djw=B<`2d{ z^uMCtM$K8@G3A<8hkt_jnwC;zD$8q)I>g2vyI#0nm~sjW@TeJ$*~I97|4AZJEAyp| zp@J*HSWW(_7lqL{hjt_*-m2loesg$;2Uc-?@J%0*^#wNOk@OKw8b76Q3L^-Dj6xy; zKW&U>>P5ZFs_2v0B)yMdC+S^_bB0HufU^gAS4UGBY z8T>SFqq%a64np~pZOdQ_(BCc!qrBJ)-BP*deTMh45pT)A@KQcVc`VgI>q-CFhWF5G zEJOL|`xzOl8iLLSz?7DzBBnfom*uIkPU6Y>j7~e~kn-aEXLuDNFO}ch&+w8xWXoX4 z7~4c;?T9Z-QFzr$DxXkMCR&V-WFkHzGowm8q-)J*WHLnkRDK2EC7IGHGLlW3Mg3r5 z=^{*a?FOANl>zY*P`hUs-@20O=yhbm3b%?n>Vz<`{q2(CJ7#2CD0qp!u!@YtD=E1G z8KT`5ZF&Gqc`t=ixk0yj~?9NnVQY74#DAgBV{m3n33Ifr(zV z&5~XUP~Zbwq@y!1@ktlas0;|GzoY!j0}t8WOw>67nD~N3e;Z&*k1a5zi7p9K8YY5H zm~0^&VB{yh?(oxdM16s&uk!!~s$@HrUG!B`8D7v|Hf)&C-^i|+%IH%M=P|pabdk+w zy2|MxJ#U1p%ux`!1o`3aM8#7BM!HxR>(cW zF4<4>)y|+3Cc4yy_k*EPSjD)bhd)7C;m?6T9N`McNtao;P*13?DJF~cA6N<}O!-7W zdl^n)EBvx|7#;HUf^2ux)-1eo$~G%$sSiP$)uG@izSPFSK#?S6Yih5dNt zb=i0DQ$3+Hmx}fl{)4>;X7-X!iq|QjBL65XNwgycnCx8+O!j7pcqcHmorAzYmGWQ8 zZ?flMv|tbEh(;X5TlJ#&DX*k5Bur^w@j-^d^;Kjg8A;a-*g-lZKZR4AgwI1(vI#HL zFZ-mTJ4+!>54>+^5P;$%ep#XrpB$KYq{}!y<9H~X!U!B8qX3pJbG$E!^io(v6@3zq zr1wwQFYzw>46l?wB%AjcUT3`5TGCtc8D3wJm-4}SO6BxR_EMQs`m2@|)nVU%k&)6k z8~RDlVqnq{1x#^GHi|dnGdi+G*rT^b=6qBwBmuPykEUIx!wO2tf#=_=2Wa zjz`i(`7lw8OJful>7NQe#gPn5I?_Z8C%b1MOyNsGr#@`8h^bzZpDyaN>7uc46T)dc z*$GU3;*-KD6Y@bPY)qHJY3%$S;e;ui@DU@w6d&PppwpOo6PU*82cnphb2Ov zXLCj1qj2J%hWQZTUf&4(ol#%n!0#sd>HaJl{?qUipCrF#X=T50d_my9yG*EWR7Rnq zZ6t4eQCh%S_6m7K^bf#9t7cWvCbA$)w7e=hB-w`GC0XN(bV~V6e3UmA<*+v)nhr>^0KE4Vc zwM&`<6K|%dTPlaUnU%}&2KkUQKiRXi3fro2O=p0~rmG^pE8<7M zly0*B`hSRr;&=mI!c<2j8H-t!^9X9#CFNDk+Ja0YU`eLWXJp!gmz3CxnB-_4lJFno zh@Wip5aaU`G1*G=q3}~aj0L8AP>MK4#1fyBHmWy|{zH7Eb0%cL#?1$&^ez_t#=3}4 z(n;m-G+W>e0w!7MlJaYVsDtQ4+YUdK&7mrEN(-ffc(l-EJWuPlRj50ZhHYYexBewQ zu(0bK^pmb@Vtl`f@kwbB*;t&E4&p7T67S$`75#!dxpMj>d6J_%`9swImxw7`YC}}s z#25M*9d+vnage^ncvgKCK$6Y+jBHED5-;WB`Ook=ftTVS8_6!ZNS5-!0-s1_gepH$ zItPfdLxG7F1T3YK@I?3}n*%?iYdUx-8nVUa0Ny(akYqDHBfCtDhvLiyFWGXwii~8l z6bE7HqA-FY$S5Qt@U~-u4U`uR=Kklr$Pw+=1Cy>i5mOx_l~Ow)8-qTh>#C^h4lv~b z-9K)F4C!4~MV4q}AGHS>Z^-`^Jd}?mq8{_Qf^2PI1apL`ZZrg@c19QJXlCT6aAVtv zXQ4Q6OgmH27CO)S&-v0tjI$>&+2#sNc`yK&(nfa2K?j9pRnbK>O2-&c2fQqOp`tFy zKB7}PqD4B{PU)d|_KP}g#5vAdJfkc1Cv-2$qydv%D}X5u zx(Kfm{bUz~=ipi3!vNAxHjS7sq%#hfWTcC1W&EOkiB6c}CLsNU=Rj5gEM23n2>SnD zd*2^l*R;gD6G0HYLA61U5^Yu5?%%(EoVIDDq!n#iK}gd4kZ77ja@wX>kV{cS)m4OB z1VIrLMG?0_5N<9}MO;M?6hRPF&)(_DNz!=l{qx@Ez3sD1*N>UC)~s1Gvu2;{ zMQgvsqu(1r-yi=A9{twe-3)6e|o5&9&a3d9`x6>-^<6uhvYO4ka#s7kmTk#K$`bd4-y{bB`j&jF{ zxYeWJ20YxfFdF_?Hv^aE-Wox}Ukg;ZvPRH0_7url+%q;+AYZ5*`T7#@nXf;I4i}qsO2spmZ2=B-5&xgL8c8oy7=S>ItrM9#N{iC$) z_2%~nAmQ(S=$`$c*6p2FNnTsxMZqX-Gtd^zm@E&6cu@}=!rc;q>(7JQ)7*!HR7MZA zrPSroB6>7$D(m&=`e(sI|AOB#;_>tU#ouxepU8epzINof5)O^y25%0v9$e!=s;8&P z%WwAJRzTYSuXuT1FVUI49{mvyYS#|#m&Q#G_1ER$eGN$EKLgVIeg~vE?sr&-#uz}7 zpK*ZH-!XusHzxwpnT!LZInwjL0~de}#WBwEV4(*qJa{1>(Ki9G}~oRzvqzLK$~TcyGB5newT^_Civ4;^$jf)=ge`eD|a#zO1kzh|nxA!K`)!$b3k z10>wBfVBR{0urC50Q&1lGe!B;drx@pBfVBS40}{Sp-;R7D^4kGPM}8Vn|1I)K zDE}Fd)-P%tY!zTUAeH-cjzazfgRgxPX80_{c-qy&Gz*p^M%@0fToY*L&Bx` z$KSYj+;zj?`ZRn1@v*&je^tV;d~c{(>e-He8Qgj`aIe-3eIuB zp|MQ?^yP_gNoR;R?VU&SJlsVdEClq|*oQ=$WPoTA{WYV|TnQR}+s&i2y(+A2*C=iO z;kBi4#ofI3c|C--H0~!nNaGAoaNeko4;p9{diF!>~eoPb{l_}tZD{!auwznM=r-x~wzn}$)^Ug6Err{P0iwun#NqtK}LXf%Y)J>izY zbN1=_V|)y4Y3_bMRGvNxPOC?s+E$O!_C2pHt@#$TC7i|x94hk@`SkSfU1&q&`WpDi z4tj_eG+y7H`}s&12*bMRrqH?(PRgxAX;M?_*XKnf^kubp6x{uf9L!Hz`wgSu9045a ze}V^TZhrrMzg?g~theN-Cqjr-U_Kbg*M>lpusE+FC`7?AA zJskhH1zlluGf+n~a{njkl6(a{9DlrP?+p3O^qV~z+Yxt#@_uakQsjwGWRu!}OY5?0 zgnrj}yj=%K!v^!+jxWdY>(b6Os4VumEkt zss{c^v}x_`^2YXnx9&8K`cZJ2J)Bk#`g(}QxP27dcRZXAz5WO%_V&Gx@kbBmHxGx# zXaa}kkQssN&+Fi$hSH=uO4KL8V&Kwwrpa65wueG|qq+?dbW!9!R7ZR}7W9e7v?gt% zwB^v2a5O-|Kiz|KJval`Oh?Dw0GwnS^G2de;SJ{NlMGOyfU6T&MVh31XG zq5etk){oNmKD0$M<`EClzo~zEe3|PUg~sz9?yFwEgdf+i_j$jMwlr^lUL*s7QEEz32U1>O50n#wj_UbqqHR+)0})> z)3{qk!FkuCPkiker7h8>hv@tK_xUsV?!EKpN3S0`^JeX(Ezu|1`yL&lcd!Sk+<$1S z^!SkU8$Bdj^iZD0Nc99qD);gIcD|hS?PYv4wqw09O#&nyvw%J>+ZZZK14Pxpdt)FcvC!&4V-t zD);BKZJ2Uuk8qQ#6Y1T#pMS3}D!vEF&lBtjtEYVLPlM~}Z)d37r&s**;QF?(`X^EM zpNRTM`ZIqWj2}tAEuuagf5+~q_}`FdJY~OyV9&iFS?C8`b06-QL85y4yM2Nfo;!Yu z`5GQhrgtU?ulr#-zEJ5e?C!8QcqA#xQuLC?f*?86>>F7$wwS_?Z3v8omDf z?;?cb-J^!a6z81_{5rBT^pM=_@bKLN{BIZXv~No9-@_ljulS82KBYv^BYqK2Xn&-j z&2YGB9`44y;1;9J&SCmpjdB{Rzh3@$8c|MtwgZOuxqXB_(3J6Un}I`l|IFd@CVVct zBIx?KV=+d;4S2q6BretYI&Zi--v^GYqjeEMI|C_vK7PMzynZ`J=$ClWgf_$Ru+77b z**;X(eR&=ZFXn-eJfr~n{HNaz(;OqmPi6jClhB^VGtykLJi4pH=(a}CrA{io3(2rg zw;JtL^`Jg{++yI+oSU0)Zx)h|PxLw?@_iBc_=g6!Pl?EAx!}l7|Obu&;EHn9?k=G_GqK@7l9gS~m z5nf$HUARoN3{&T?cmFVT{<@g&4jqeM7aXR}r`a@2osZW&Or4LH*g3R6zb8IhTGpUo}V&Yogc3muI}I7dNe|oXwlryRh;4c{{~9zTXQ9B+JHWMH2W?uM}aM!12!@Z86( z0}k2k?SQ`Ssr?gpZD>z@G>y=fXfm7j(DeIDL>cYfbihbG?E9{Jo2v>uBJDqZ+s8IV zyM77TdrBLTZ$jFH)U`Z{c?}SMJvq!bW(x3?zCB;4aX;rs7g$sl<>eOw<#Bf=_y>4| z?{)MhMZ4efa0~FiOP@qNm4&x^3S|@Uoe;G5pI($@;=3@xu(JB6(FSE*_%?|DG(3ZE zJAqzo7`+yh?Lt{@SXuwG(M$&V5X0!L_$R*e1bUmo+SQ}10qr`&%C?}a4P`sS$~sZj ziL#AhWxG*UkFu<=J`%U$8&c?FxF_}r^pB(>r6DawDnhD6szs_t+Jy8nQYTU`QUdsw zf|Q1oiIj^}f%GBz{0e=&gY*K@!$|c=7b8_56(G$=vXOQpjcGw&&x0pO-vYN5FbQST zkOD{_gm_1L-l;?T3_rGzCdVnuFAUv=*rsZ59I-Bhk|ix}P9T z#9FgRJCI&RYCyUIsRpS4X&c6S7NCxlfiwqc3{syruJfM@(VO2K%{+iqfpjj?T9hXM zPC*ipl91@J0p}on?(rdjcE=z!A+1JvG2|i}DFbN^l8Tgs6oa%AZC*x-L%J2|6r>>1 z>mKbV0aqiRi!=uRegWx2q#uxWBGD6rx_%#Vjlgrsq`!XVXO=I0HqYCu)&dg0GoIQ@>%gE&D9MhhC`~63NUe z#T77Vbh}L<^DQ%*6xA$zz@EN6yAU5i#ivs7n~HtX3-PheA>}VoKeLwO)BR<^%wqZ; z>D&T_d32uzc_meav#ay*NoE)SO?<(WKDQr9;M3?m-xH3gn^9JkeO46Gaf<0z+cJFUNC=ly1-8( zCC+q$AN1+gGw}7y@|6rTiN=vydfr@o$CP0{ie6Y;zS6I|7qs#1;PO>oK~3~ReAIOw zKJ|*riq1eRZJ$NB5bHdAbrlT8H|MKRmhatg6uNgZBM&z|p|lm4!4Q@7^0M<#vH`S< zLM1d>l&qyT-birg0P}as&neGe4r$mxqYSlPfE8rQyk0^@v#ZZw=0z_E9|;vPH|@dl zdOe1jg-_atcYCnA4m5a@Z95;z~|>*a3etI&ZLu@ZaoFX=gM~rdm4S~3=Pxa+3s<@{!XZgUYd1doLW!@_L*vF%EW+iT1Dx6bZIR}@fVMrj# z9L3~7{7UkdRpcxW7O4T1IVXgliSHL@5F14%D^!B(G0JHA^YPW@fB@V%Cd{G$Sj-lRB!$wOKhuxMzX3 zKfYyMS~}1OY`GOJat5?RMzC^0VXzo8@`z-CvCJ)zZM@ADYBR%ab7N$iS>+Y0{012_ z-8S_@+Jv;#K=*!obb9lUw$Nz7!mt+e7A~HZ!HH~0J2Wy2(`MkJH@fA5v>Se-n3jG9 zX?tQ+Jd=|%tGap)_Oz#7EAg{DtR9xBAO|8D`o1%BoL5rlt4n+}-PI$^gdsJ!@oW{o zJcI8gFT*E5q znd9PW_flbhp5A4|i)SrZG(U6hyx9Z%y@uuiNu$3-FLHJH5zI175Q-epY>cr2R*N~3 z!790hUAT{-9OiEaQf;+-7I`@N-_^>)H z9+oPn3B67uEn>fv48P#a_GdPnJV`m=wS(zbQ!h=>}nzSIyba(Lr3Q? zA#OXa!hG{!xn_mhx_1MBPUwDWW-awwMxsMA50;n6BKpJX;&Pqx(xM6)^=fx)d61b{ z)L@3Vi?NuQLG+4qidO$XZGwfHPj~+FxsAf=in~sijt|zs-~_iDVqu^Q^6wGl&PiMR9tWo+UXH3 zis35kt)7h5-s*7!O%RS*c$0`8up+q5FSwT$xF)d__Z5b>xZJbzuKj{V|22lJ8T9%> z%%`+4^l^9QO!5`oCip#h%mNZnFZYYze%|uZU;4^*%#vuIq!qVujqk3|xCj zw;nOAu7?or5ki+!j}h$-qn`1p{ZKJ>IC*08a8pC6rz}B#w(p(IC@8>2!Z><~I$5rT zbpLn}(WRX4&M{xXUkmr|z6kRX7pBAYf|FOk^r94EfUUcjvmFGmlB8RS`$4wUarhkz6GG=i}WpH_3>5P!x z>hbfgb879Ajt`cnmn^Bwt6Vj^dInS$Xt|!vdE~Q%XuK7(s4TR_3kLKlrj02u%ss?| z5v!jMTMci7aL`u~UqgnT&IdE9h==Y;;X?+u9FW5sS{83$v!Dd3@Lk+Y-)4EFXs?Ff z2!eMt$S*3}v-$BBA<~uzuI`>Sw}SlX$j6Jl)Xx7r@a&^UY)z%OOZemEYf(mN;K|Ae z#c*P0hoGNN@bZ4cerIrfTp*Os_V5#t&-MQ8;}DF`4z&v}OBkjsX_&IfVP(tfVHJ`? zw5B;T76*M>7zhk1C-(^&6?c0;`R0V2t$^!h4aVQ&GkEu$1;XM^CFP(xxV@)%!u_^E z{qCU$pP~}hbY-BTDBmSBsGU1L5@>Hw;d2_GF-)CVnzw98L0(S2rWJ{7zLLL0DlFob zXnA>jL7}F|TmhG#&&#SE!H-!pf~5nZADEGu9#KEgXHH3ZPPu#S2W&MAa$(_866ayq zGH5=7@X6VQ!pvDwQjXII9N3}Q!_GISU=^-GDap^l@d&!x=iKmZF-{s>4({8^DKE-# zuQ>d({QwvDoRt(73_%#NFZ%Xxj(_vna5EY#CxiGwxI=mxx&1IrN8mNB(7;W`m`zC3 zhMxn@dO6UwINbRHIefdSMZ6rjwzg9-cmU@7p@VOcdmO`@>9#6D92Re8Uctw&- z)m)w?i3Nph#GL(ow`7T2P_RVBUX!^c0?QRig*>MLh@7Bk`9kEr^YK_YPgC>q^9tl8 z+>$&+Tq23Aj4Q#kq5`Qv6Qq&wq$RQ{N|IWbPnc{$o+7ZKR+P`F`Fv4c!4SMX`~aWJ z%6-i@M5ztS3)=f3HWMcb1XW&=C(C=`x!)cu=`wE+%JBTAK5(<$z$HRCVV4L+@O;o* z_W0~LHHR)kitKX4ITiSq5xFYOy^Lsx&_(F^L4D(zsHMIf4dLVnaz*o8;zk#91}s4Y zS4SLg5xfZ;DJm8^#-J@6|eUL%28Mb|BU^qApH!Mi^|9}49;{fgy!K9Pm>n8xFMfm*m zYgPIke=f=qdSehEsXY&H-H>)sOfM9o7Pl^3I?6#pfGPN{qQiA&V| zADU&XLF9+WFG-_N^2Z;w%xTQ2dm4mVWi)$jXbRM37J4qjn22)t6BYZnj2I?{>0xS^ zFENRQ%qpgeNyk{LFuKKnh0IDuK!2fU(7c(p+R*P;ji|M5^&HISulO%C>;IJ*()`*n zmhky4096F?BA%#>=%`S;(dLLZyczUM`LWC-<5I*>OTdXjhW@60&{G?ok$|>I zQL)Td;0Lu0fAE`H@M|n1MzJ2PTC7qYvkd7x&kc19C5%T z0+uy>w!!C=$x=&>0A?DiS*4N^# zA+37>=q_{VSGv5+M`;kfmJi`8(TGPZU_EGj=h0Y%vEkNsjFlvpW*TIsqO1a?`JhsS zl#lvK)Gv4GmZ1hgps**zw{-9=Wq@yIxb&6+y9~Tb!kUz0=Ak_khKc~f-!P_ZCO#^T z`DU1TW4~u4MP=jn(Q(YT-k7H`^k3u?4LavDF<4t)4rZY5Rp`4AS}7#+{%B}cBq6je zR7R^qJ2MIUf%vdz+{71u+_)hT{2_|8#!0AI0ayu~N>B;nze3R3GqTA26#9L658Bd` z0jxssInRYjSWU#zTzW)>c5@PTKk>Z6{r{iWB(%?Jn9M=@Y&bpgnAc{sJ%(TKXT(Cz z8^UBg!{uL|Cq4P#UOD~?$r8;gawa~0Qi@80@Q znMcsa_y5~{j6OHhsa{r^gwRnH!Y0-vZW=o&2|rpWr@Kw9NsBV)OjRc(;o8}lkk#7$NMT=2V6B3F_p^ls`8@XRJ?}L^Qx9j zTft4j#ouKmMR-%TIJ`HIN=h;j=%}JMy5%0V<3^z-9OvQzvkEU4D30yrQR4pY0`Ei% z5aM2elg)XBRRfjb)wA9A5}0;+;fli2q*D67H7O4-%2$-*t?{I!Ec&wQc+%M zRpF$h=>v4k=_B`IP9HQTbNayKfNoCr`G(A3@%q0r7mvTmje_Q7_hS!X$FoPX6WB@Y zNvy@rX6Lhu*=%+RTgqO@Udmp{-pJm{HnR7#kF!s+&#^DEudx4S-(){vKViRNe`J4U z|I6;f?av*?C2+@alerX*=VZ?0=5q77Ebd%x30KUOa+TaA+-mMB?mF%U?iTJg?tboJ z?s4vE?m6y7?iKDWu9N$O+sXBCKX5;D{ahS>2tSTb_)PvR z{#^cizMK#8f8{UcujcRLU*KQocktc(w|ulPMo1Krg=xYJ;dJ3V;dF@+Vx4%sc&m7a_@MZNc$AbT&6mog`=m`$ zi}bQoB(IQDl`Q2eJFgs^#%10b%HigJ6$W( z4$^gfhJK1(s9&MKuK%pBGwv~(jUSEOW~_CLm11euI_qxh5v#+x%)Y@M>ts6>&gD+4 z^O@7*z_Eb-#>0+dSF+Er0q!zx2N&Sa=3Dp};UFPfs1RPo?8l0)i5=nxVz=l>rPAZl zRQU|~uky9>ZSvFdhw`uTAM&BfeC09aTP0bYsg|o3tN&0RQeRZxR=-zY(%#Z0={9?$aZW$YUE@9aD5*X&p>9g?z!yN7#^i{`K7Z{*+O#|VdGEluGc!iU1ALcA!7 z1>)smgE&??R+=K6A}x|uNiRu#(r!tRPm^=x>oNXuO0sgUvRb)YIYebuU0tL$s-LRk zwQsb5ev|$maP+_W0Ym- zbGP%D12ax%8fGGEvN>!CTgP6Hy>JrbJdJyddxkrQFX1aO=2L~g3V#W zg*+W#BpSyXim}#s+8Ad}FsGQT`JDNOd91bDnrokJUvA%QKWaY>{=NYYpX_`>{LRJR zK>{SQH?d9ZV{8ldXe@UGcRa@;^mrk62e*lv#82mCK8?@gH}c!~5BX2|9{xv2$2jbp zs88@>+Sje2{XK za-1?)qH9qKr3k~U4Vv_;wytw#Gu`&|23yGVaa{|RewprIOP z8$qMb*w-9m9)tPKH5ZsG&DG{L=2O^5zhG4kw2rWJD`1^%m0H!-rPhPi3HAi1(Ang? z@6fSpGv;~#dl-8ZdkXsno5p2ud0aKu&B@Rd`FsVxmT%zyg;~DG@8%B_o)f+h#)=cf zFGNK$rE8?aBjTNLeEWwhRf!Q`DOe{{!;!bjD9_TD`s#X z=I|AdOX!4Sg%bo$Ql#^-V=t6%k{jhM^5=4XPl|7gpNU_Khe{_%DUilfr3~q8sX%%Z*5-5RJ8746fIL<%k~cwLyemg3CnzUD zBUdTcE8CQxl|=AkiCU&!qF$qJQqR;1_4WGa`q`N46~^_(24kBs!92s71TCD0dEILV z$@Xl*Z{RVl@vO$$Y#KX{J%c?5^QvH1u-CF%*jF(x0jqT(cMZ1{Yjv^sPw_o*r#M53 zl@F6S*^(E_7r;u?%2&%9IvpVL?v7h>KzSYwaiOOEBiG&Pz1r4kY+p)7h)o2iZ-q(p$m%H(&!l zfo#Qa0_SkExwE+%?ndrM?!Vk2{7il!e4c#%Y5t%5t9&1yCY&QI7cLR5fTr#ceiGuu zx#E1WQ~XNYM@p0=$kGBSTY6X$lv3qJrC&KfO~Cr3sb^yM+@T(z#cSiWW3&^rRBfSl zmR7Fat$m^0sc+OD(Vx_}>x{8K=65)(j|F)u#2nWdH$tZFHeNK|GP;f5p|1}z4~4$I z-+a{EVs16xHg}jm!0Y*oHO{iFx$t{#ux`it?X-Tx+Re1**;)3B_Urb0cAfK#!*s## z1PjKn99zXEb9ZyC@cw@14#q5(Kr8$UJLo%pqL43KBSuTHn9)+m-M#Wl@&U>R+F9_5 z9@SsgW6Wf$$hzFR5B|o>mSPv!7uYN9pKRR;z-BLYYMtwxdgouxTh7N$pTqRxZ^F>$ zKI{~B4x0|I_Fnc0_9ON)SjW@2df3I6p@)y;=kf*o75v@k{V{QzbTq8lo7ijRYMgeN zwq9%0UeNxd{h&=WvW+_923UnAXvUX}Z{VFBWR5eFVH+HCp?Q|M!F=HUv)P)w7>ip`3-poWLQ>G)ExCD^;PwK?7M@s19cu6 zu~5H7zg=(9_cM#kWoFPk+>-76oTFfoFckcSZ7M(ke-nJ@&-r*^il9KQeisfCEiqp_ zK#G@+mrj$;ld7c~rF$V$N68n<2WWSiL5sJ$?9c2B__R?>J7^rpUcgR)p8t^hjvL2M z;-~QrU&C+WJNOGQ{$x?YxStpM#e?CuZIXVJ&XTW^*U2}^`zX(2uYINTDbeaN>WT1% z49Mz2XttZw+tiI}3%t$)wPUsUS`IAN!`f3?v-ZCBB`nt%JrP=DBc%2heP82T<0|7H z#+}AP#=FK2<16D2V~m+(PQz-Hnm0nHw3x4(yUjRDvSwMiSe09?ChKkMQ%Go>J;DCO z`GNSsMj`4-vYE_^I0r4loGxb9vJbFN;mq?X`v-dzr$FzQLr30+bIV8EZ(JgD%#-|Y z{3Kzi@V4-=aF94&RB*C*L&}tM<$u7M%~Q9jAF4-Sx5es*Lgzi9KdVnPmKepxJH{4R zpF8ab;K9vx7C7r@41p*zpYUrkxwE)7?h0X@c#rrDba9+CK?+D!(i&*67p0G+-=y)f zDQ}QpmakEolxLL+b&Z;!1++44i}p`#qMoClrw8?`VAF30e-1L189y5*m^J2F*uNi4 z5uVjtYoXO=#oDLXr@>#p+`h@)Vn1*HY||d9b~>CLG@gto*KYiUZDwC*cd>_ZnXu^R zbJudWg9Ghc6n5<@{#l$b-h^)2SC}NQLLp@KEnz?LT=9JIHt`gA>DRz-Y?ro4ygUoL zc?CS-7vz5E+XO{Y(v>o0B~BarsGKURC#&aT$E{cIf#&UiXS`6mR{J|v{$=e0*F&C# z^VSYn_x+6oXsG$d1;(XDBUb$r!!)zZD)=Z{&5z7)&51aT+yUM230C+>+q75M_4b=~ z67&P+lpBSZB<^N{CA@>Z2U=IbTt4NNVIOq!Kk!vTpAd}`;X~m2JK`_m5mFK?$290x zQ<@_^A~j1%a;E%|e3WtyZ1PRe6YnV(slQ>g1@NzSX}@c^`t!PD%r!Pb8g=s(aNDs? zv6`(u`w#m-_^u%xFpJHE1-=xg>&MuiVSP>b6K`-6`69?v1K-bQ3%hVyn+iX?NL-Ke z#EsGu(%aH!(pS|9hbXRnS zTA*GEpWrU_2b{u=){fWaXlKGBcn_<2lAfx+pueMk20vSXefi9Y#h$$%R^=7=0D`4j zXIRd_uzYO2{H29iPR-RB^S9U9m zdMM6j27JzJoP1wUzfgaM6*v@D;C|SEkFB<@4bh)xzS*P5hG$@ZM z&C0vVN6Jp+7@T`&si&xgYL$Aw`hnW5o(!IsYZqfLwL^P;1}mn(=RaFtre6a6(2h0O zsqfZP3>g-`%2;8}w2G~V!S6q;eIYYP+tY2yJ{gug$1a4Oy~w`KzR|u99`jcFCHq~R zaK3}>-QPLbIo3IW_SdG6OdW?4J`WGM+4cUP=Z}VVNP#3}3)dnR^S1CG;Y;B_ST#v3 z6<3HCidVzpBuTTROzB+dZ_sk>(p%7T`^n4Xm2wSMtWkbkR+Jga9AyzC><49zS_9AW zTjN*b5cu{cbbX;&VQw@ZhPHp%Y&X9)d(9|otaT)2Q)aESuC&%#H(F0vA6uVW-&xVn z3a8jb_EP(5`y`z6Ux1g~>3r>c?=US<6slm-*jw0_*2ld=qdezm}h zv&0L;Lg_1I10-XrcB=lp@tGNKH-i!oJEGk8h^Mi)u@?6_pD0W~G^zx4b*^}z)G8Iq zH^4G9VlAJ8_t}ex%Ty&>`44R1Zmiu|YNdL!`l$L8?D%+Xx|Ruz9@Of!?OKm^2s~#A zR{cz9_iNnK+D82$_(Os*8{S$KPHS(%@~tsnFyApxwa&G!vHpUy=zRNkc+RQty`q>l z4DUQ{JU@fKh;QeY2p0-h3I~YyiswuBOPi&wQmTBe{4q3JhO$bz0p8C9_>T3^#$7mP zEQJsMp?hXb$2n^qVnCO|JA4#7w;y}pKJ!&`Cr<9an}=BAv3KWU_ZEWd7g=lI|J-3U zT2ETfVt;J2daYk^PELXrI>XMlSJ`VIPj_HH|HuBq{>EmUL!9xB=*)D;CKfuEI#)S2 z;bi%k^R)At^Cmnx{5YA3gAY9pQOlX^I%v zHsLqO+0o*Oq9&dq=87w!+iwtWabtalLE=t^k5eo)N(aL(#UOsfDT*>vNyGj+SILJL zeF5S%YoX`whLwF@=}_K=ztXGx1TAs4dNtN+i~1Z+rSGddRS6z-fmW%l);4IL;G~nF zU#V|}ep8H7jeMg7D|EH-tkG%g1ji0EPe4qk)%-U+=tFQ4O@Xc}gq+sEuU&5~#}2#0 zeg^jVLwmYoJ10AtPN{RR^N7>pv_nombh@2h_Y{P`;RG*^K>T(heB0CE-L7V@Lj>qa zSnL{I8qDguDC*tqo&C+es)6$#L z`_ebkA2?g3%VjtxJ_pbAd-!-KfTMGjv!Sc5R5rj~wt<6V)gaD^&x3E0wi3Q|yY?xf zbjRtOJ`3mgJpCW=lwL)o@ppZikp_S1E1a{AF;#PpS&Vbmb?~wuFrS5;=rX^Ay=Sar zEz??on9K`SkF}qj0G&0%4nhOp22J&<{i!|CS?DZx?t>J4N_#Xu+KrVJu@|xHaUSSk zzh@6X?3#G74$b__z4K3r)gKF;;p>J`4Ne7Q`ifQ;twmaR#qd{|@{6 zPc>PywG6EOMcQ?+5zlHJ?)v`@OECquVve4P87)19-OJnSCKH7Oc@J&3>e=Qra#+zel>m2boD@8Gxd zors)w!Ry(HIAAY4xLvTBj1VKl3UNZbFjhzsOc8(1CdJ?^7Kih|SSbN#vLtvy0VxAs zP?nqxKes?GmP_Rdc=YS#CY-A`BQn)2H`tB#M!U)0g!s@F=*AYi)o!!f5oOtKciKB} zg6M|l+hh0IefBQ9A3HzBiFM+fcxSAW;3PUpPO>xENpVF{=}rdrLKY(5 zxlVyoj9pRT1mUmN;MBL;S>x2fk6w?{-v+0_X>>L^O^6q6cD5ic)q*&A8+K2JvmHBV z2V!#F&Q8S5dvQkG<@6(dj=z@&yBIcxjb-E5cy=tCz$U^oN@gdsDJ+Xqfr=P#7T%8J z!gDJ{^d!huvo&lj_S_o8h1VhOx(U&gX0{bOuLC=82iwIm0sLJ#_&>DudPJ2runl;- zveCud%w|A;WpUYDE`Fd*?@fY;Xx4DGcz3dftAk{&$9Z)#w*@w}1t;e=c;Fq}cEl2Q zaM^q=&acIMDI%Xicn~#wEnhEgK)k$B+$c7Qn-G=WA~uUHVyoCDw&M-Jc0>(!h+TLO zuv6?ogrra0g%cXB^<+e)auJUz#`}Q^DTwo5jkFpkk2+}`V!-v%2B`u1a--CQv)pDx zx0~^vq*ZEz#qN-{OP$gV#8|qeol=j~i}?I5sb6B`7&%sslj9NBN{|!fBsp20EQ_*= zGfZHR9K{-Oh|iCOf1hY18OhiqDTua+h!>ki08z;_Bi+a_GL6Y*3iPK4jbNGqcwlLW z?PZvm=BTTTGfgzJ2J11}ItSQPM9R|H4EX8(nimhOdJptzA5KL5EW^ccv0NM%&yB@9 zutYA2OLpIbv788R+H}{kVqhgVa!p(ZA}yUb$8_o4@Vt8TUcC>|=6-nYF~*;re-`2p zx$yLh5u>Ry*WpxG51rUxHo^mHGB=r<;YT;a6KXZv;0tw_+s#fyYP!sBSm_>kMt$Zk zXcoqbv0|+_D;|DQf|Y0`S;>g-ra+eltW+z_O1Cl)(aC~s%Ed{d7?Jo2yeX-Mm8!+t ztu^p?*TGuVTN|u8dmX&;dV7QYXZJmm8`4W58PDXi5dYpI!#hXNNC|jTk%ad`li^FU zf+(nXLlh8Fg)|{u$PfyIVxbi8xi$%#g)NAlv;EUEl(>1_@ zYl8o_UG9{3%02MocFFNd68r}dXCPAvz=OzDI^gXj*^}*TyVR}*cN(A%+oAn-*)h;= zDbQ>tbXqAiSPisS9W>VlXssq_tQP319%v@$r3&aJ{D30{`X~{4NQ4ebgIf;Zd!J zu4#mx*$f@i3jMMjx`k|JJS;{MG>QnnJ{Rwh)F2&_`wCUJV|vcRAb)9=C(bJLGO~xep#%JmLnXlB#6FYc7C)7F22wvt19*q7jjh%}NXW ziylNiW7IfxteU7Mt0{OZlB#B?6>7D*TCG!?)HXyHI&m`EsrIS;DvP*Dx>l-H!_RKi zT4B9=5%Epbli_b?=vjKMUW{1EdVK@pO>Nk_eb}i<&`asiL)qAa6-G5Y;f;7x+lupG zr_qIIHUmp8LNleA1&Be_;LTPY_GT0IWjprb4(!1`#G}Sy&m~(T_FF3US~hmqYV4*a zSovnF)oMo+e23KykEYM+w_@;Sa;%+bC*uq+;`EY+_;LlLYPDSlOWO>)+6kN5W5>aM zCPKPcSj`Mr$qHD-hArC3_2Q(&@UeV6-rXnjCZsGA)~FhCR)@D)o8f!3 zLe_S|)7XV~j>*`;Y1pqpNZM-1*(U7L9>lZ9Vjrf6s+a}YDn_hfJ#5%UNK+5&Q%IIr zNtIGzpGskwYGH|*V1=3?Q|+)kJ+L?=SMf3n>k`1O&BU$^x)QYkzR4Dx`rG6V$WuRL zDNaemj!jWmSE90&TAZdyj+*g4tzGGc95HIF8jl!y5@M5?hzAs?rSM#9Tsc~=Zh-gK zg!i4zYOC6=ZpS%^(c-ieP1Uk+5?HO(A!e`vGS!5rT&LEBGhCcLR!`CcdKx4vTd&bq z>vej)-T89-AjVaV`1$=lC{pLHyR*YoAI8#3sMyeFEJ4|FAaWhg)3KWkfv@| zp8D}CffQGo(xJ7pp|R=BX)UyK;}Dq&$xy2MW+fXD^ip`sHPGRW@YUMkq3wWo)&tM1 zADYUSsF1!&g%6emnOXx+i!@a;{Hk{NQ{B*4&{p_iFuvI`psy&FY7Wv@k@B_K^_SXR zZ>bw{)(0&X1526$`)P8i_+>yQJfZ@4L)Gwve0ghy@3RB%PQ@O2Yii4>v#QejIn5vM4CEeQ%W zkj^?`z0d$#(hfVa12&`w_M;zB`H-|GLte!}IPs)Y)k^)Ql$^leyd&m*WhZt z?T90g%$jPtn&s-dMo4EHG+i$=U80t(u{fz@`WC#YYlqH@g=dxs$xMX@MmldbPA@G+o6&)od$*B*=-p)KJQH$R zZPuD=;FZ+FBk^@!EOZ@t7HRM!D&Rqcq;d;llU>koq}#|+20U4;^=zdtiM#9soCqen zS}gzxEQJKt!^dxSB@fyR?`-iMBFJ4ly!K>I;&RzyXfe`Z{cH-nvA}>;T<=-MPOb|w z6tat{keCX79bXSgX~p?y7v9OFz{4O(*Z>W+86HJDY#~{}SXWPp&{DZ!TB_BveBIDi zePRsGOk>@(HleRFvDP*4qwB)fyAz)BPI$??B=U<@tanhZc2}GHiY|B-y|8(^WJVba zO~tyNMXI~{#Y%;1`DpDMl#QCpbqb8>(vIl zU)hA{MGJHn*~4zUH|m3o#A^v!qL!phhNP&liK$h0Pem3e&`ew*eGu}y2B)=QpukZWDLbj6K zxAtkSho240S_(M}8a1%4AuZU1-(qZroo#U~Z3lGX4rs-0qX+N!`w&%O%osBc8geXj zWD+d!Wbcd+Iw8>cAj`~!MJC-Dbft7Pz~l5neLgk08(A+YF0YKHVkOj z4xCtfLf#;Kn-9MtCx0-(J)5Su8a8xV$#6ZwAj@>%TU}_Q;;ncZPB63&D&Q&7NwpsP zp#|Eu8+#%dx-|$Zz6GbqI6ejXawDPw@$jf?;P-Vx!=*sN>)_FKBZ8g|>1~F6oeVjx kgB|UcO~_yq>|`QzO+74N3_Ph?Si4RQo&W#m|5y(E5Br?Kr2qf` literal 0 HcmV?d00001 diff --git a/foobar2000/foo_input_validator/readme.txt b/foobar2000/foo_input_validator/readme.txt new file mode 100644 index 0000000..0514b79 --- /dev/null +++ b/foobar2000/foo_input_validator/readme.txt @@ -0,0 +1,32 @@ +Decoder Validator v1.2.1 readme + +1) Usage. +Select a single file handled by the decoder you want to test, select "Utils / Decoder Validator", "Utils / Tag Writer Validator" or "Utils / Fuzzer" from the context menu. +WARNING: Never run Tag Writer Validator on files you don't have backup copies of. Tag Writer Validator will repeatedly rewrite file's tags, with randomly generated info. In case of success (found problems with relevant tag writer implementations), the file will likely be permanently damaged or left with nonsense in its tags. + +2) What Decoder Validator does. +Decoder Validator runs a series of automated decoding tests on the specified file, and reports any abnormal behaviors detected. It has been proven to be a highly useful tool for spotting certain kinds of bugs (especially inaccurate seeking). +Some of the problems it detects are potentially harmful (such as inaccurate seeking on archiving-oriented audio formats) and are hard to detect or reliably reproduce using other means. +Detected programming errors include: +- Different decoding results when decoding the same file again from start. +- Inaccurate seeking, noncompliant seeking-related behaviors. +- File corruption during tag updates. +- Incorrect/inconsistent behaviors of tag writer instance when asked to reread tags after a tag update. +- Crashes on bad data / potential exploits (Fuzzer). Release quality code should never crash, deadlock or leak resources on corrupted files of any kind. + +3) What Decoder Validator does not do. +Decoder Validator does not: +- Entirely ensure that your code is bug-free. +- Check correctness of your decoder's output - only reference data it uses for comparisons is result of first decode pass, if your decoder consistently returns incorrect results, they won't be reported as incorrect. +- Fully replace user testing cycle. +- Test for exception transparency and abortability - abortability test has been excluded for performance reasons (since it was O(n^2)), you don't need automated tests to know whether your decoder implementation relays exceptions properly or not. +- Test for unicode support in tag writers. +- Test whether the output of your tag writer is correct, only whether your own tag reader implementation reads it back and reports the same info as what it was asked to write. +- Test freeform metadata handling - tag writing test is restricted to specific hardcoded fields filled with random data. + +4) But one of official components fails Decoder Validator tests! +This might occur because: +- Limitations of file format being tested break certain features. +- Bugs in third party libraries we have no power over (WMA). +- You have found a bug. +- The file you are trying to decode is corrupted and prevents decoder from returning results that the Validator would accept. diff --git a/foobar2000/foo_sample/IO.cpp b/foobar2000/foo_sample/IO.cpp new file mode 100644 index 0000000..9ed808f --- /dev/null +++ b/foobar2000/foo_sample/IO.cpp @@ -0,0 +1,329 @@ +#include "stdafx.h" +#include +#include + +void RunIOTest() { + try { + abort_callback_dummy noAbort; + auto request = http_client::get()->create_request("GET"); + request->run("https://www.foobar2000.org", noAbort); + } catch (std::exception const & e) { + popup_message::g_show( PFC_string_formatter() << "Network test failure:\n" << e, "Information"); + return; + } + popup_message::g_show(PFC_string_formatter() << "Network test OK", "Information"); +} + +namespace { // anon namespace local classes for good measure + class tpc_copyFiles : public threaded_process_callback { + public: + tpc_copyFiles ( metadb_handle_list_cref items, const char * pathTo ) : m_pathTo(pathTo), m_outFS(filesystem::get(pathTo)) { + m_lstFiles.init_from_list( items ); + } + + void on_init(HWND p_wnd) override { + // Main thread, called before run() gets started + } + void run(threaded_process_status & p_status, abort_callback & p_abort) override { + // Worker thread + for( size_t fileWalk = 0; fileWalk < m_lstFiles.get_size(); ++ fileWalk ) { + + // always do this in every timeconsuming loop + // will throw exception_aborted if the user pushed 'cancel' on us + p_abort.check(); + + const char * inPath = m_lstFiles[fileWalk]; + p_status.set_progress(fileWalk, m_lstFiles.get_size()); + p_status.set_item_path( inPath ); + + try { + workWithFile(inPath, p_abort); + } catch(exception_aborted) { + // User abort, just bail + throw; + } catch(std::exception const & e) { + m_errorLog << "Could not copy: " << file_path_display(inPath) << ", reason: " << e << "\n"; + } + + } + } + + void workWithFile( const char * inPath, abort_callback & abort ) { + FB2K_console_formatter() << "File: " << file_path_display(inPath); + + // Filesystem API for inPath + const filesystem::ptr inFS = filesystem::get( inPath ); + pfc::string8 inFN; + // Extract filename+extension according to this filesystem's rules + // If it's HTTP or so, there might be ?key=value that needs stripping + inFS->extract_filename_ext(inPath, inFN); + + pfc::string8 outPath = m_pathTo; + // Suffix with outFS path separator. On Windows local filesystem this is always a backslash. + outPath.end_with( m_outFS->pathSeparator() ); + outPath += inFN; + + const double openTimeout = 1.0; // wait and keep retrying for up to 1 second in case of a sharing violation error + + // Not strictly needed, but we do it anyway + // Acquire a read lock on the file, so anyone trying to acquire a write lock will just wait till we have finished + auto inLock = file_lock_manager::get()->acquire_read( inPath, abort ); + + auto inFile = inFS->openRead( inPath, abort, openTimeout ); + + // Let's toy around with inFile + { + auto stats = inFile->get_stats(abort); + + if ( stats.m_size != filesize_invalid ) { + FB2K_console_formatter() << "Size: " << pfc::format_file_size_short(stats.m_size); + } + if ( stats.m_timestamp != filetimestamp_invalid ) { + FB2K_console_formatter() << "Last modified: " << format_filetimestamp(stats.m_timestamp); + } + pfc::string8 contentType; + if ( inFile->get_content_type( contentType ) ) { + FB2K_console_formatter() << "Content type: " << contentType; + } + + uint8_t buffer[256]; + size_t got = inFile->read(buffer, sizeof(buffer), abort); + + if ( got > 0 ) { + FB2K_console_formatter() << "Header bytes: " << pfc::format_hexdump( buffer, got ); + } + + if ( inFile->is_remote() ) { + FB2K_console_formatter() << "File is remote"; + } else { + FB2K_console_formatter() << "File is local"; + } + + // For seekable files, reopen() seeks to the beginning. + // For nonseekable stream, reopen() restarts reading the stream. + inFile->reopen(abort); + } + + // This is a glorified strcmp() for file paths. + if ( metadb::path_compare( inPath, outPath ) == 0 ) { + // Same path, go no further. Specifically don't attempt acquiring a writelock because that will never complete, unless user aborted. + FB2K_console_formatter() << "Input and output paths are the same - not copying!"; + return; + } + + // Required to write to files being currently played. + // See file_lock_manager documentation for details. + auto outLock = file_lock_manager::get()->acquire_write( outPath, abort ); + + // WARNING : if a file exists at outPath prior to this, it will be reset to zero bytes (win32 CREATE_ALWAYS semantics) + auto outFile = m_outFS->openWriteNew(outPath, abort, openTimeout); + + + + try { + // Refer to g_transfer_file implementation details in the SDK for lowlevel reading & writing details + file::g_transfer_file(inFile, outFile, abort); + } catch(...) { + + if ( inFile->is_remote() ) { + // Remote file was being downloaded? Suppress deletion of incomplete output + throw; + } + // Failed for some reason + // Release our destination file hadnle + outFile.release(); + + // .. and delete the incomplete file + try { + abort_callback_dummy noAbort; // we might be being aborted, don't let that prevent deletion + m_outFS->remove( outPath, noAbort ); + } catch(...) { + // disregard errors - just report original copy error + } + + throw; // rethrow the original copy error + } + + } + + void on_done(HWND p_wnd, bool p_was_aborted) override { + // All done, main thread again + + if (! p_was_aborted && m_errorLog.length() > 0 ) { + popup_message::g_show(m_errorLog, "Information"); + } + + } + + + + // This is a helper class that generates a sorted list of unique file paths in this metadb_handle_list. + // The metadb_handle_list might contain duplicate tracks or multiple subsongs in the same file. m_lstFiles will list each file only once. + file_list_helper::file_list_from_metadb_handle_list m_lstFiles; + + // Destination path + const pfc::string8 m_pathTo; + // Destination filesystem API. Obtained via filesystem::get() with destination path. + const filesystem::ptr m_outFS; + + // Error log + pfc::string_formatter m_errorLog; + }; +} + +void RunCopyFiles(metadb_handle_list_cref data) { + + // Detect modal dialog wars. + // If another modal dialog is active, bump it instead of allowing our modal dialog (uBrowseForFolder) to run. + // Suppress this if the relevant code is intended to be launched by a modal dialog. + if (!ModalDialogPrologue()) return; + + const HWND wndParent = core_api::get_main_window(); + pfc::string8 copyTo; + // shared.dll method + if (!uBrowseForFolder( wndParent, "Choose destination folder", copyTo )) return; + + // shared.dll methods are win32 API wrappers and return plain paths with no protocol prepended + // Prefix with file:// before passing to fb2k filesystem methods. + // Actually the standard fb2k filesystem implementation recognizes paths even without the prefix, but we enforce it here as a good practice. + pfc::string8 copyTo2 = PFC_string_formatter() << "file://" << copyTo; + + // Create worker object, a threaded_process_callback implementation. + auto worker = fb2k::service_new(data, copyTo2); + const uint32_t flags = threaded_process::flag_show_abort | threaded_process::flag_show_progress | threaded_process::flag_show_item; + // Start the process asynchronously. + threaded_process::get()->run_modeless( worker, flags, wndParent, "Sample Component: Copying Files" ); + + // Our worker is now running. +} + + +namespace { + class processLLtags : public threaded_process_callback { + public: + processLLtags( metadb_handle_list_cref data ) : m_items(data) { + m_hints = metadb_io_v2::get()->create_hint_list(); + } + void on_init(ctx_t p_wnd) override { + // Main thread, called before run() gets started + } + void run(threaded_process_status & p_status, abort_callback & p_abort) override { + // Worker thread + + // Note: + // We should look for references to the same file (such as multiple subsongs) in the track list and update each file only once. + // But for the sake of simplicity we don't do this in a sample component. + for( size_t itemWalk = 0; itemWalk < m_items.get_size(); ++ itemWalk ) { + + // always do this in every timeconsuming loop + // will throw exception_aborted if the user pushed 'cancel' on us + p_abort.check(); + + auto item = m_items[ itemWalk ]; + p_status.set_progress( itemWalk, m_items.get_size() ); + p_status.set_item_path( item->get_path() ); + + try { + workWithTrack(item, p_abort); + } catch(exception_aborted) { + // User abort, just bail + throw; + } catch(std::exception const & e) { + m_errorLog << "Could not update: " << item << ", reason: " << e << "\n"; + } + } + } + void on_done(ctx_t p_wnd, bool p_was_aborted) override { + // All done, main thread again + if ( m_hints.is_valid() ) { + // This is the proper time to finalize the hint list + // All playlists showing these files and such will now be refreshed + m_hints->on_done(); + } + + if (!p_was_aborted && m_errorLog.length() > 0) { + popup_message::g_show(m_errorLog, "Information"); + } + } + + private: + void workWithTrack( metadb_handle_ptr item, abort_callback & abort ) { + + FB2K_console_formatter() << "foo_sample will update tags on: " << item; + + const auto subsong = item->get_subsong_index(); + const auto path = item->get_path(); + + const double openTimeout = 1.0; // wait and keep retrying for up to 1 second in case of a sharing violation error + + // Required to write to files being currently played. + // See file_lock_manager documentation for details. + auto lock = file_lock_manager::get()->acquire_write( path, abort ); + + input_info_writer::ptr writer; + input_entry::g_open_for_info_write_timeout(writer, nullptr, path, abort, openTimeout ); + + { // let's toy around with info that the writer can hand to us + auto stats = writer->get_file_stats( abort ); + if (stats.m_timestamp != filetimestamp_invalid) { + FB2K_console_formatter() << "Last-modified before tag update: " << format_filetimestamp_utc(stats.m_timestamp); + } + } + + file_info_impl info; + writer->get_info( subsong, info, abort ); + + info.meta_set("comment", "foo_sample lowlevel tags write demo was here"); + + // Touchy subject + // Should we let the user abort an incomplete tag write? + // Let's better not + abort_callback_dummy noAbort; + + // This can be called many times for files with multiple subsongs + writer->set_info( subsong, info, noAbort ); + + // This is called once - when we're done set_info()'ing + writer->commit( noAbort ); + + { // let's toy around with info that the writer can hand to us + auto stats = writer->get_file_stats(abort); + if (stats.m_timestamp != filetimestamp_invalid) { + FB2K_console_formatter() << "Last-modified after tag update: " << format_filetimestamp_utc(stats.m_timestamp); + } + } + + // Now send new info to metadb + // If we don't do this, old info may still be shown in playlists etc. + if ( true ) { + // Method #1: feed altered info directly to the hintlist. + // Makes sense here as we updated just one subsong. + auto stats = writer->get_file_stats(abort); + m_hints->add_hint(item, info, stats, true); + } else { + // Method #2: let metadb talk to our writer object (more commonly used). + // The writer is a subclass of input_info_reader and can therefore be legitimately fed to add_hint_reader() + // This will read info from all subsongs in the file and update metadb if appropriate. + m_hints->add_hint_reader(path, writer, abort); + } + } + metadb_hint_list::ptr m_hints; + const metadb_handle_list m_items; + pfc::string_formatter m_errorLog; + }; +} +void RunAlterTagsLL(metadb_handle_list_cref data) { + + const HWND wndParent = core_api::get_main_window(); + + // Our worker object, a threaded_process_callback subclass. + auto worker = fb2k::service_new< processLLtags > ( data ); + + const uint32_t flags = threaded_process::flag_show_abort | threaded_process::flag_show_progress | threaded_process::flag_show_item; + + // Start the worker asynchronously. + threaded_process::get()->run_modeless(worker, flags, wndParent, "Sample Component: Updating Tags"); + + // The worker is now running. + +} diff --git a/foobar2000/foo_sample/PCH.cpp b/foobar2000/foo_sample/PCH.cpp new file mode 100644 index 0000000..d0be5ab --- /dev/null +++ b/foobar2000/foo_sample/PCH.cpp @@ -0,0 +1,3 @@ +#include "stdafx.h" + +// This is a dummy source code file that just generates the precompiled header (PCH) file for use when compiling the rest of the source code, to speed compilation up. \ No newline at end of file diff --git a/foobar2000/foo_sample/contextmenu.cpp b/foobar2000/foo_sample/contextmenu.cpp new file mode 100644 index 0000000..ec5ba49 --- /dev/null +++ b/foobar2000/foo_sample/contextmenu.cpp @@ -0,0 +1,174 @@ +#include "stdafx.h" + + +// Identifier of our context menu group. Substitute with your own when reusing code. +static const GUID guid_mygroup = { 0x572de7f4, 0xcbdf, 0x479a, { 0x97, 0x26, 0xa, 0xb0, 0x97, 0x47, 0x69, 0xe3 } }; + + +// Switch to contextmenu_group_factory to embed your commands in the root menu but separated from other commands. + +//static contextmenu_group_factory g_mygroup(guid_mygroup, contextmenu_groups::root, 0); +static contextmenu_group_popup_factory g_mygroup(guid_mygroup, contextmenu_groups::root, "Sample component", 0); + +static void RunTestCommand(metadb_handle_list_cref data); +void RunCalculatePeak(metadb_handle_list_cref data); //decode.cpp + +void RunCopyFiles(metadb_handle_list_cref data); // IO.cpp +void RunAlterTagsLL(metadb_handle_list_cref data); // IO.cpp + +void RunUIAndThreads( metadb_handle_list_cref data ); // ui_and_threads.cpp + +namespace { // anon namespace local classes for good measure + class myFilter : public file_info_filter { + public: + bool apply_filter(metadb_handle_ptr p_location, t_filestats p_stats, file_info & p_info) { + p_info.meta_set("comment", "foo_sample was here"); + // return true to write changes tags to the file, false to suppress the update + return true; + } + }; +} + +static void RunAlterTags(metadb_handle_list_cref data) { + // Simple alter-file-tags functionality + + const HWND wndParent = core_api::get_main_window(); + + // Filter object that applies our edits to the file tags + auto filter = fb2k::service_new(); + + auto notify = fb2k::makeCompletionNotify( [] (unsigned code) { + // Code values are metadb_io::t_update_info_state enum + FB2K_console_formatter() << "Tag update finished, code: " << code; + } ); + + // Flags + // Indicate that we're aware of fb2k 1.3+ partial info semantics + const uint32_t flags = metadb_io_v2::op_flag_partial_info_aware; + + metadb_io_v2::get()->update_info_async(data, filter, wndParent, flags, notify); +} + +// Simple context menu item class. +class myitem : public contextmenu_item_simple { +public: + enum { + cmd_test1 = 0, + cmd_peak, + cmd_copyFiles, + cmd_alterTags, + cmd_alterTagsLL, + cmd_uiAndThreads, + cmd_total + }; + GUID get_parent() {return guid_mygroup;} + unsigned get_num_items() {return cmd_total;} + void get_item_name(unsigned p_index,pfc::string_base & p_out) { + switch(p_index) { + case cmd_test1: p_out = "Test command"; break; + case cmd_peak: p_out = "Calculate peak"; break; + case cmd_copyFiles: p_out = "Copy files"; break; + case cmd_alterTags: p_out = "Alter tags"; break; + case cmd_alterTagsLL: p_out = "Alter tags (low level)"; break; + case cmd_uiAndThreads: p_out = "UI and threads demo"; break; + default: uBugCheck(); // should never happen unless somebody called us with invalid parameters - bail + } + } + void context_command(unsigned p_index,metadb_handle_list_cref p_data,const GUID& p_caller) { + switch(p_index) { + case cmd_test1: + RunTestCommand(p_data); + break; + case cmd_peak: + RunCalculatePeak(p_data); + break; + case cmd_copyFiles: + RunCopyFiles(p_data); + break; + case cmd_alterTags: + RunAlterTags(p_data); + break; + case cmd_alterTagsLL: + RunAlterTagsLL(p_data); + break; + case cmd_uiAndThreads: + RunUIAndThreads(p_data); + break; + default: + uBugCheck(); + } + } + // Overriding this is not mandatory. We're overriding it just to demonstrate stuff that you can do such as context-sensitive menu item labels. + bool context_get_display(unsigned p_index,metadb_handle_list_cref p_data,pfc::string_base & p_out,unsigned & p_displayflags,const GUID & p_caller) { + switch(p_index) { + case cmd_test1: + if (!__super::context_get_display(p_index, p_data, p_out, p_displayflags, p_caller)) return false; + // Example context sensitive label: append the count of selected items to the label. + p_out << " : " << p_data.get_count() << " item"; + if (p_data.get_count() != 1) p_out << "s"; + p_out << " selected"; + return true; + default: + return __super::context_get_display(p_index, p_data, p_out, p_displayflags, p_caller); + } + } + GUID get_item_guid(unsigned p_index) { + // These GUIDs identify our context menu items. Substitute with your own GUIDs when reusing code. + static const GUID guid_test1 = { 0x4021c80d, 0x9340, 0x423b, { 0xa3, 0xe2, 0x8e, 0x1e, 0xda, 0x87, 0x13, 0x7f } }; + static const GUID guid_peak = { 0xe629b5c3, 0x5af3, 0x4a1e, { 0xa0, 0xcd, 0x2d, 0x5b, 0xff, 0xa6, 0x4, 0x58 } }; + static const GUID guid_copyFiles = { 0x7f8a6569, 0xe46b, 0x4698, { 0xaa, 0x30, 0xc4, 0xc1, 0x44, 0xc9, 0xc8, 0x92 } }; + static const GUID guid_alterTags = { 0xdfb8182b, 0xf8f3, 0x4ce9, { 0xae, 0xf6, 0x8e, 0x4e, 0x51, 0x7c, 0x2d, 0x3 } }; + static const GUID guid_alterTagsLL = { 0x6b43324d, 0x6cb2, 0x42a6, { 0xbf, 0xc, 0xd9, 0x43, 0xfc, 0x83, 0x2f, 0x39 } }; + static const GUID guid_uiAndThreads = { 0x30dace2e, 0xcccf, 0x41d4, { 0x8c, 0x24, 0x57, 0xec, 0xf4, 0xa0, 0xd9, 0xc9 } }; + + switch(p_index) { + case cmd_test1: return guid_test1; + case cmd_peak: return guid_peak; + case cmd_copyFiles: return guid_copyFiles; + case cmd_alterTags: return guid_alterTags; + case cmd_alterTagsLL: return guid_alterTagsLL; + case cmd_uiAndThreads: return guid_uiAndThreads; + default: uBugCheck(); // should never happen unless somebody called us with invalid parameters - bail + } + + } + bool get_item_description(unsigned p_index,pfc::string_base & p_out) { + switch(p_index) { + case cmd_test1: + p_out = "This is a sample command."; + return true; + case cmd_peak: + p_out = "This is a sample command that decodes the selected tracks and reports the peak sample value."; + return true; + case cmd_copyFiles: + p_out = "This is a sample command that copies the selected tracks to another location."; + return true; + case cmd_alterTags: + p_out = "This is a sample command that performs tag manipulation on the files."; + return true; + case cmd_alterTagsLL: + p_out = "This is a sample command that performs low-level manipulation of tags on the files."; + return true; + case cmd_uiAndThreads: + p_out = "This is a smple command that runs UI and Threads demo."; + return true; + default: + uBugCheck(); // should never happen unless somebody called us with invalid parameters - bail + } + } +}; + +static contextmenu_item_factory_t g_myitem_factory; + + +static void RunTestCommand(metadb_handle_list_cref data) { + pfc::string_formatter message; + message << "This is a test command.\n"; + if (data.get_count() > 0) { + message << "Parameters:\n"; + for(t_size walk = 0; walk < data.get_count(); ++walk) { + message << data[walk] << "\n"; + } + } + popup_message::g_show(message, "Blah"); +} diff --git a/foobar2000/foo_sample/decode.cpp b/foobar2000/foo_sample/decode.cpp new file mode 100644 index 0000000..3c280ca --- /dev/null +++ b/foobar2000/foo_sample/decode.cpp @@ -0,0 +1,77 @@ +#include "stdafx.h" +#include + +class calculate_peak_process : public threaded_process_callback { +public: + calculate_peak_process(metadb_handle_list_cref items) : m_items(items), m_peak() {} + void on_init(HWND p_wnd) {} + void run(threaded_process_status & p_status,abort_callback & p_abort) { + try { + const t_uint32 decode_flags = input_flag_no_seeking | input_flag_no_looping; // tell the decoders that we won't seek and that we don't want looping on formats that support looping. + input_helper input; // this object manages lowlevel input_decoder calls for us. + for(t_size walk = 0; walk < m_items.get_size(); ++walk) { + p_abort.check(); // in case the input we're working with fails at doing this + p_status.set_progress(walk, m_items.get_size()); + p_status.set_progress_secondary(0); + p_status.set_item_path( m_items[walk]->get_path() ); + input.open(NULL, m_items[walk], decode_flags, p_abort); + + double length; + { // fetch the track length for proper dual progress display; + file_info_impl info; + // input.open should have preloaded relevant info, no need to query the input itself again. + // Regular get_info() may not retrieve freshly loaded info yet at this point (it will start giving the new info when relevant info change callbacks are dispatched); we need to use get_info_async. + if (m_items[walk]->get_info_async(info)) length = info.get_length(); + else length = 0; + } + + audio_chunk_impl_temporary l_chunk; + double decoded = 0; + while(input.run(l_chunk, p_abort)) { // main decode loop + m_peak = l_chunk.get_peak(m_peak); + if (length > 0) { // don't bother for unknown length tracks + decoded += l_chunk.get_duration(); + if (decoded > length) decoded = length; + p_status.set_progress_secondary_float(decoded / length); + } + p_abort.check(); // in case the input we're working with fails at doing this + } + } + } catch(std::exception const & e) { + m_failMsg = e.what(); + } + } + void on_done(HWND p_wnd,bool p_was_aborted) { + if (!p_was_aborted) { + if (!m_failMsg.is_empty()) { + popup_message::g_complain("Peak scan failure", m_failMsg); + } else { + pfc::string_formatter result; + result << "Value: " << m_peak << "\n\n"; + result << "Scanned items:\n"; + for(t_size walk = 0; walk < m_items.get_size(); ++walk) { + result << m_items[walk] << "\n"; + } + popup_message::g_show(result,"Peak scan result"); + } + } + } +private: + audio_sample m_peak; + pfc::string8 m_failMsg; + const metadb_handle_list m_items; +}; + +void RunCalculatePeak(metadb_handle_list_cref data) { + try { + if (data.get_count() == 0) throw pfc::exception_invalid_params(); + service_ptr_t cb = new service_impl_t(data); + static_api_ptr_t()->run_modeless( + cb, + threaded_process::flag_show_progress_dual | threaded_process::flag_show_item | threaded_process::flag_show_abort, + core_api::get_main_window(), + "Sample component: peak scan"); + } catch(std::exception const & e) { + popup_message::g_complain("Could not start peak scan process", e); + } +} diff --git a/foobar2000/foo_sample/dsp.cpp b/foobar2000/foo_sample/dsp.cpp new file mode 100644 index 0000000..aa68737 --- /dev/null +++ b/foobar2000/foo_sample/dsp.cpp @@ -0,0 +1,154 @@ +#include "stdafx.h" +#include "resource.h" + +static void RunDSPConfigPopup(const dsp_preset & p_data,HWND p_parent,dsp_preset_edit_callback & p_callback); + +class dsp_sample : public dsp_impl_base +{ +public: + dsp_sample(dsp_preset const & in) : m_gain(0) { + parse_preset(m_gain, in); + } + + static GUID g_get_guid() { + //This is our GUID. Generate your own one when reusing this code. + static const GUID guid = { 0x890827b, 0x67df, 0x4c27, { 0xba, 0x1a, 0x4f, 0x95, 0x8d, 0xf, 0xb5, 0xd0 } }; + return guid; + } + + static void g_get_name(pfc::string_base & p_out) { p_out = "Sample DSP";} + + bool on_chunk(audio_chunk * chunk,abort_callback &) { + // Perform any operations on the chunk here. + // The most simple DSPs can just alter the chunk in-place here and skip the following functions. + + + // trivial DSP code: apply our gain to the audio data. + chunk->scale( audio_math::gain_to_scale( m_gain ) ); + + // To retrieve the currently processed track, use get_cur_file(). + // Warning: the track is not always known - it's up to the calling component to provide this data and in some situations we'll be working with data that doesn't originate from an audio file. + // If you rely on get_cur_file(), you should change need_track_change_mark() to return true to get accurate information when advancing between tracks. + + return true; //Return true to keep the chunk or false to drop it from the chain. + } + + void on_endofplayback(abort_callback &) { + // The end of playlist has been reached, we've already received the last decoded audio chunk. + // We need to finish any pending processing and output any buffered data through insert_chunk(). + } + void on_endoftrack(abort_callback &) { + // Should do nothing except for special cases where your DSP performs special operations when changing tracks. + // If this function does anything, you must change need_track_change_mark() to return true. + // If you have pending audio data that you wish to output, you can use insert_chunk() to do so. + } + + void flush() { + // If you have any audio data buffered, you should drop it immediately and reset the DSP to a freshly initialized state. + // Called after a seek etc. + } + + double get_latency() { + // If the DSP buffers some amount of audio data, it should return the duration of buffered data (in seconds) here. + return 0; + } + + bool need_track_change_mark() { + // Return true if you need on_endoftrack() or need to accurately know which track we're currently processing + // WARNING: If you return true, the DSP manager will fire on_endofplayback() at DSPs that are before us in the chain on track change to ensure that we get an accurate mark, so use it only when needed. + return false; + } + static bool g_get_default_preset(dsp_preset & p_out) { + make_preset(0, p_out); + return true; + } + static void g_show_config_popup(const dsp_preset & p_data,HWND p_parent,dsp_preset_edit_callback & p_callback) { + ::RunDSPConfigPopup(p_data, p_parent, p_callback); + } + static bool g_have_config_popup() {return true;} + static void make_preset(float gain, dsp_preset & out) { + dsp_preset_builder builder; builder << gain; builder.finish(g_get_guid(), out); + } + static void parse_preset(float & gain, const dsp_preset & in) { + try { + dsp_preset_parser parser(in); parser >> gain; + } catch(exception_io_data) {gain = 0;} + } +private: + float m_gain; +}; + +// Use dsp_factory_nopreset_t<> instead of dsp_factory_t<> if your DSP does not provide preset/configuration functionality. +static dsp_factory_t g_dsp_sample_factory; + + +class CMyDSPPopup : public CDialogImpl { +public: + CMyDSPPopup(const dsp_preset & initData, dsp_preset_edit_callback & callback) : m_initData(initData), m_callback(callback) {} + + enum { IDD = IDD_DSP }; + + enum { + RangeMin = -20, + RangeMax = 20, + + RangeTotal = RangeMax - RangeMin + }; + + BEGIN_MSG_MAP_EX(CMyDSPPopup) + MSG_WM_INITDIALOG(OnInitDialog) + COMMAND_HANDLER_EX(IDOK, BN_CLICKED, OnButton) + COMMAND_HANDLER_EX(IDCANCEL, BN_CLICKED, OnButton) + MSG_WM_HSCROLL(OnHScroll) + END_MSG_MAP() + +private: + + BOOL OnInitDialog(CWindow, LPARAM) { + m_slider = GetDlgItem(IDC_SLIDER); + m_slider.SetRange(0, RangeTotal); + + { + float val; + dsp_sample::parse_preset(val, m_initData); + m_slider.SetPos( pfc::clip_t( pfc::rint32(val), RangeMin, RangeMax ) - RangeMin ); + RefreshLabel(val); + } + return TRUE; + } + + void OnButton(UINT, int id, CWindow) { + EndDialog(id); + } + + void OnHScroll(UINT nSBCode, UINT nPos, CScrollBar pScrollBar) { + float val; + val = (float) ( m_slider.GetPos() + RangeMin ); + + { + dsp_preset_impl preset; + dsp_sample::make_preset(val, preset); + m_callback.on_preset_changed(preset); + } + RefreshLabel(val); + } + + void RefreshLabel(float val) { + pfc::string_formatter msg; msg << pfc::format_float(val) << " dB"; + ::uSetDlgItemText(*this, IDC_SLIDER_LABEL, msg); + } + + const dsp_preset & m_initData; // modal dialog so we can reference this caller-owned object. + dsp_preset_edit_callback & m_callback; + + CTrackBarCtrl m_slider; +}; + +static void RunDSPConfigPopup(const dsp_preset & p_data,HWND p_parent,dsp_preset_edit_callback & p_callback) { + CMyDSPPopup popup(p_data, p_callback); + if (popup.DoModal(p_parent) != IDOK) { + // If the dialog exited with something else than IDOK,k + // tell host that the editing has been cancelled by sending it old preset data that we got initialized with + p_callback.on_preset_changed(p_data); + } +} diff --git a/foobar2000/foo_sample/foo_sample.rc b/foobar2000/foo_sample/foo_sample.rc new file mode 100644 index 0000000..ab2b95c --- /dev/null +++ b/foobar2000/foo_sample/foo_sample.rc @@ -0,0 +1,227 @@ +// 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 + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Dialog +// + +IDD_MYPREFERENCES DIALOGEX 0, 0, 332, 288 +STYLE DS_SETFONT | WS_CHILD +FONT 8, "Microsoft Sans Serif", 400, 0, 0x0 +BEGIN + RTEXT "Bogo setting 1:",IDC_STATIC,44,60,59,8 + EDITTEXT IDC_BOGO1,104,58,40,12,ES_AUTOHSCROLL | ES_NUMBER + RTEXT "Bogo setting 2:",IDC_STATIC,44,82,59,8 + EDITTEXT IDC_BOGO2,104,80,40,12,ES_AUTOHSCROLL | ES_NUMBER + LTEXT "This is a sample preferences page with meaningless settings.",IDC_STATIC,52,24,204,8 +END + +IDD_PLAYBACK_STATE DIALOGEX 0, 0, 260, 95 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Playback State Demo" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + LTEXT "Pattern:",IDC_STATIC,12,8,28,8 + EDITTEXT IDC_PATTERN,8,16,244,12,ES_AUTOHSCROLL + LTEXT "State:",IDC_STATIC,12,36,21,8 + EDITTEXT IDC_STATE,8,44,244,12,ES_AUTOHSCROLL | ES_READONLY + PUSHBUTTON "Context menu test area - right click me",IDC_CONTEXTMENU,8,60,244,14 + PUSHBUTTON "Play",IDC_PLAY,12,76,36,14 + PUSHBUTTON "Pause",IDC_PAUSE,52,76,36,14 + PUSHBUTTON "Stop",IDC_STOP,92,76,36,14 + PUSHBUTTON "Prev",IDC_PREV,132,76,36,14 + PUSHBUTTON "Next",IDC_NEXT,172,76,36,14 + PUSHBUTTON "Rand",IDC_RAND,212,76,36,14 +END + +IDD_DSP DIALOGEX 0, 0, 284, 83 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Sample DSP Configuration" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + DEFPUSHBUTTON "OK",IDOK,172,64,50,14 + PUSHBUTTON "Cancel",IDCANCEL,226,64,50,14 + CONTROL "",IDC_SLIDER,"msctls_trackbar32",TBS_BOTH | TBS_NOTICKS | WS_TABSTOP,12,12,260,24 + CTEXT "N dB",IDC_SLIDER_LABEL,120,44,44,8 +END + +IDD_UI_ELEMENT DIALOGEX 0, 0, 221, 113 +STYLE DS_SETFONT | DS_FIXEDSYS | DS_CONTROL | WS_CHILD | WS_SYSMENU +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + CONTROL "Lock min width @ 200 units",IDC_LOCK_MIN_WIDTH,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,8,20,104,10 + CONTROL "Lock min height @ 200 units",IDC_LOCK_MIN_HEIGHT,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,8,32,107,10 + LTEXT "Current size: XXX x XXX units / XXX x XXX pixels",IDC_STATIC_SIZE,8,4,168,8 + CONTROL "Lock max width @ 400 units",IDC_LOCK_MAX_WIDTH,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,8,48,106,10 + CONTROL "Lock max height @ 400 units",IDC_LOCK_MAX_HEIGHT,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,8,60,108,10 +END + +IDD_THREADS DIALOGEX 0, 0, 364, 232 +STYLE DS_SETFONT | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME +CAPTION "UI and Threads Demo" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + DEFPUSHBUTTON "Start",IDOK,252,200,50,14 + PUSHBUTTON "Cancel",IDCANCEL,306,200,50,14 + LISTBOX IDC_LIST,4,20,356,176,LBS_NOINTEGRALHEIGHT | WS_VSCROLL | WS_TABSTOP + LTEXT "Header",IDC_HEADER,8,4,348,8 +END + +IDD_LISTCONTROL_DEMO DIALOGEX 0, 0, 311, 177 +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_LIST1,"SysListView32",LVS_ALIGNLEFT | WS_BORDER | WS_TABSTOP,12,12,288,156 +END + + +///////////////////////////////////////////////////////////////////////////// +// +// DESIGNINFO +// + +#ifdef APSTUDIO_INVOKED +GUIDELINES DESIGNINFO +BEGIN + IDD_MYPREFERENCES, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 325 + TOPMARGIN, 7 + BOTTOMMARGIN, 281 + END + + IDD_PLAYBACK_STATE, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 253 + TOPMARGIN, 7 + BOTTOMMARGIN, 88 + END + + IDD_DSP, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 277 + TOPMARGIN, 7 + BOTTOMMARGIN, 76 + END + + IDD_UI_ELEMENT, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 214 + TOPMARGIN, 7 + BOTTOMMARGIN, 106 + END + + IDD_THREADS, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 357 + TOPMARGIN, 7 + BOTTOMMARGIN, 225 + END + + IDD_LISTCONTROL_DEMO, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 304 + TOPMARGIN, 7 + BOTTOMMARGIN, 170 + END +END +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// AFX_DIALOG_LAYOUT +// + +IDD_UI_ELEMENT AFX_DIALOG_LAYOUT +BEGIN + 0 +END + +IDD_THREADS AFX_DIALOG_LAYOUT +BEGIN + 0 +END + +IDD_LISTCONTROL_DEMO AFX_DIALOG_LAYOUT +BEGIN + 0 +END + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDI_SCROLL ICON "..\\..\\libPPUI\\IDI_SCROLL.ico" + +#endif // English (United States) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/foobar2000/foo_sample/foo_sample.sln b/foobar2000/foo_sample/foo_sample.sln new file mode 100644 index 0000000..f797125 --- /dev/null +++ b/foobar2000/foo_sample/foo_sample.sln @@ -0,0 +1,55 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.27130.2027 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "foo_sample", "foo_sample.vcxproj", "{85FBFD09-0099-4FE9-9DB6-78DB6F60F817}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "foobar2000_SDK", "..\SDK\foobar2000_SDK.vcxproj", "{E8091321-D79D-4575-86EF-064EA1A4A20D}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "pfc", "..\..\pfc\pfc.vcxproj", "{EBFFFB4E-261D-44D3-B89C-957B31A0BF9C}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "foobar2000_sdk_helpers", "..\helpers\foobar2000_sdk_helpers.vcxproj", "{EE47764E-A202-4F85-A767-ABDAB4AFF35F}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "foobar2000_component_client", "..\foobar2000_component_client\foobar2000_component_client.vcxproj", "{71AD2674-065B-48F5-B8B0-E1F9D3892081}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libPPUI", "..\..\libPPUI\libPPUI.vcxproj", "{7729EB82-4069-4414-964B-AD399091A03F}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x86 = Debug|x86 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {85FBFD09-0099-4FE9-9DB6-78DB6F60F817}.Debug|x86.ActiveCfg = Debug|Win32 + {85FBFD09-0099-4FE9-9DB6-78DB6F60F817}.Debug|x86.Build.0 = Debug|Win32 + {85FBFD09-0099-4FE9-9DB6-78DB6F60F817}.Release|x86.ActiveCfg = Release|Win32 + {85FBFD09-0099-4FE9-9DB6-78DB6F60F817}.Release|x86.Build.0 = Release|Win32 + {E8091321-D79D-4575-86EF-064EA1A4A20D}.Debug|x86.ActiveCfg = Debug|Win32 + {E8091321-D79D-4575-86EF-064EA1A4A20D}.Debug|x86.Build.0 = Debug|Win32 + {E8091321-D79D-4575-86EF-064EA1A4A20D}.Release|x86.ActiveCfg = Release|Win32 + {E8091321-D79D-4575-86EF-064EA1A4A20D}.Release|x86.Build.0 = Release|Win32 + {EBFFFB4E-261D-44D3-B89C-957B31A0BF9C}.Debug|x86.ActiveCfg = Debug FB2K|Win32 + {EBFFFB4E-261D-44D3-B89C-957B31A0BF9C}.Debug|x86.Build.0 = Debug FB2K|Win32 + {EBFFFB4E-261D-44D3-B89C-957B31A0BF9C}.Release|x86.ActiveCfg = Release FB2K|Win32 + {EBFFFB4E-261D-44D3-B89C-957B31A0BF9C}.Release|x86.Build.0 = Release FB2K|Win32 + {EE47764E-A202-4F85-A767-ABDAB4AFF35F}.Debug|x86.ActiveCfg = Debug|Win32 + {EE47764E-A202-4F85-A767-ABDAB4AFF35F}.Debug|x86.Build.0 = Debug|Win32 + {EE47764E-A202-4F85-A767-ABDAB4AFF35F}.Release|x86.ActiveCfg = Release|Win32 + {EE47764E-A202-4F85-A767-ABDAB4AFF35F}.Release|x86.Build.0 = Release|Win32 + {71AD2674-065B-48F5-B8B0-E1F9D3892081}.Debug|x86.ActiveCfg = Debug|Win32 + {71AD2674-065B-48F5-B8B0-E1F9D3892081}.Debug|x86.Build.0 = Debug|Win32 + {71AD2674-065B-48F5-B8B0-E1F9D3892081}.Release|x86.ActiveCfg = Release|Win32 + {71AD2674-065B-48F5-B8B0-E1F9D3892081}.Release|x86.Build.0 = Release|Win32 + {7729EB82-4069-4414-964B-AD399091A03F}.Debug|x86.ActiveCfg = Debug|Win32 + {7729EB82-4069-4414-964B-AD399091A03F}.Debug|x86.Build.0 = Debug|Win32 + {7729EB82-4069-4414-964B-AD399091A03F}.Release|x86.ActiveCfg = Release|Win32 + {7729EB82-4069-4414-964B-AD399091A03F}.Release|x86.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {C30C2B27-E29B-4792-B392-ED729AF2CF49} + EndGlobalSection +EndGlobal diff --git a/foobar2000/foo_sample/foo_sample.vcxproj b/foobar2000/foo_sample/foo_sample.vcxproj new file mode 100644 index 0000000..c8b3b6f --- /dev/null +++ b/foobar2000/foo_sample/foo_sample.vcxproj @@ -0,0 +1,159 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + + {85FBFD09-0099-4FE9-9DB6-78DB6F60F817} + foo_input_raw + Win32Proj + + + + DynamicLibrary + Unicode + true + v141 + + + DynamicLibrary + Unicode + v141 + + + + + + + + + + + + + <_ProjectFileVersion>10.0.30319.1 + $(SolutionDir)$(Configuration)\ + $(Configuration)\ + true + $(SolutionDir)$(Configuration)\ + $(Configuration)\ + false + + + + Disabled + EnableFastChecks + MultiThreadedDebugDLL + Use + stdafx.h + Level3 + ProgramDatabase + 4715 + ..;../.. + Fast + + + true + Windows + false + + + MachineX86 + ../shared/shared.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + + + MultiThreadedDLL + Use + stdafx.h + Level3 + ProgramDatabase + /d2notypeopt %(AdditionalOptions) + 4715 + false + true + true + ..;../.. + Fast + NDEBUG;_WINDLL;%(PreprocessorDefinitions) + + + true + Windows + true + true + false + + + MachineX86 + ../shared/shared.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + + + + + + + + + + + + + + + Create + Create + + + + + + + + + + + + + + + + + + + + + + + {7729eb82-4069-4414-964b-ad399091a03f} + + + {ebfffb4e-261d-44d3-b89c-957b31a0bf9c} + + + {71ad2674-065b-48f5-b8b0-e1f9d3892081} + + + {ee47764e-a202-4f85-a767-abdab4aff35f} + + + {e8091321-d79d-4575-86ef-064ea1a4a20d} + + + + + + + + + \ No newline at end of file diff --git a/foobar2000/foo_sample/foo_sample.vcxproj.filters b/foobar2000/foo_sample/foo_sample.vcxproj.filters new file mode 100644 index 0000000..3e00fa0 --- /dev/null +++ b/foobar2000/foo_sample/foo_sample.vcxproj.filters @@ -0,0 +1,103 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + Header Files + + + Header Files + + + Header Files + + + + + Resource Files + + + + + + + + Resource Files + + + \ No newline at end of file diff --git a/foobar2000/foo_sample/initquit.cpp b/foobar2000/foo_sample/initquit.cpp new file mode 100644 index 0000000..313baae --- /dev/null +++ b/foobar2000/foo_sample/initquit.cpp @@ -0,0 +1,15 @@ +#include "stdafx.h" + +// Sample initquit implementation. See also: initquit class documentation in relevant header. + +class myinitquit : public initquit { +public: + void on_init() { + console::print("Sample component: on_init()"); + } + void on_quit() { + console::print("Sample component: on_quit()"); + } +}; + +static initquit_factory_t g_myinitquit_factory; diff --git a/foobar2000/foo_sample/input_raw.cpp b/foobar2000/foo_sample/input_raw.cpp new file mode 100644 index 0000000..6c53e42 --- /dev/null +++ b/foobar2000/foo_sample/input_raw.cpp @@ -0,0 +1,103 @@ +#include "stdafx.h" + +enum { + raw_bits_per_sample = 16, + raw_channels = 2, + raw_sample_rate = 44100, + + raw_bytes_per_sample = raw_bits_per_sample / 8, + raw_total_sample_width = raw_bytes_per_sample * raw_channels, +}; + +// Note that input class does *not* implement virtual methods or derive from interface classes. +// Our methods get called over input framework templates. See input_singletrack_impl for descriptions of what each method does. +// input_stubs just provides stub implementations of mundane methods that are irrelevant for most implementations. +class input_raw : public input_stubs { +public: + void open(service_ptr_t p_filehint,const char * p_path,t_input_open_reason p_reason,abort_callback & p_abort) { + if (p_reason == input_open_info_write) throw exception_io_unsupported_format();//our input does not support retagging. + m_file = p_filehint;//p_filehint may be null, hence next line + input_open_file_helper(m_file,p_path,p_reason,p_abort);//if m_file is null, opens file with appropriate privileges for our operation (read/write for writing tags, read-only otherwise). + } + + void get_info(file_info & p_info,abort_callback & p_abort) { + t_filesize size = m_file->get_size(p_abort); + //note that the file size is not always known, for an example, live streams and alike have no defined size and filesize_invalid is returned + if (size != filesize_invalid) { + //file size is known, let's set length + p_info.set_length(audio_math::samples_to_time( size / raw_total_sample_width, raw_sample_rate)); + } + //note that the values below should be based on contents of the file itself, NOT on user-configurable variables for an example. To report info that changes independently from file contents, use get_dynamic_info/get_dynamic_info_track instead. + p_info.info_set_int("samplerate",raw_sample_rate); + p_info.info_set_int("channels",raw_channels); + p_info.info_set_int("bitspersample",raw_bits_per_sample); + + // Indicate whether this is a fixedpoint or floatingpoint stream, when using bps >= 32 + // As 32bit fixedpoint can't be decoded losslessly by fb2k, does not fit in float32 audio_sample. + if ( raw_bits_per_sample >= 32 ) p_info.info_set("bitspersample_extra", "fixed-point"); + + p_info.info_set("encoding","lossless"); + p_info.info_set_bitrate((raw_bits_per_sample * raw_channels * raw_sample_rate + 500 /* rounding for bps to kbps*/ ) / 1000 /* bps to kbps */); + + } + t_filestats get_file_stats(abort_callback & p_abort) {return m_file->get_stats(p_abort);} + + void decode_initialize(unsigned p_flags,abort_callback & p_abort) { + m_file->reopen(p_abort);//equivalent to seek to zero, except it also works on nonseekable streams + } + bool decode_run(audio_chunk & p_chunk,abort_callback & p_abort) { + enum { + deltaread = 1024, + }; + + const size_t deltaReadBytes = deltaread * raw_total_sample_width; + // Prepare buffer + m_buffer.set_size(deltaReadBytes); + // Read bytes + size_t got = m_file->read(m_buffer.get_ptr(), deltaReadBytes,p_abort) / raw_total_sample_width; + + // EOF? + if (got == 0) return false; + + // This converts the data that we've read to the audio_chunk's internal format, audio_sample (float 32-bit). + // audio_sample is the audio data format that all fb2k code works with. + p_chunk.set_data_fixedpoint(m_buffer.get_ptr(), got * raw_total_sample_width,raw_sample_rate,raw_channels,raw_bits_per_sample,audio_chunk::g_guess_channel_config(raw_channels)); + + //processed successfully, no EOF + return true; + } + void decode_seek(double p_seconds,abort_callback & p_abort) { + m_file->ensure_seekable();//throw exceptions if someone called decode_seek() despite of our input having reported itself as nonseekable. + // IMPORTANT: convert time to sample offset with proper rounding! audio_math::time_to_samples does this properly for you. + t_filesize target = audio_math::time_to_samples(p_seconds,raw_sample_rate) * raw_total_sample_width; + + // get_size_ex fails (throws exceptions) if size is not known (where get_size would return filesize_invalid). Should never fail on seekable streams (if it does it's not our problem anymore). + t_filesize max = m_file->get_size_ex(p_abort); + if (target > max) target = max;//clip seek-past-eof attempts to legal range (next decode_run() call will just signal EOF). + + m_file->seek(target,p_abort); + } + bool decode_can_seek() {return m_file->can_seek();} + bool decode_get_dynamic_info(file_info & p_out, double & p_timestamp_delta) {return false;} // deals with dynamic information such as VBR bitrates + bool decode_get_dynamic_info_track(file_info & p_out, double & p_timestamp_delta) {return false;} // deals with dynamic information such as track changes in live streams + void decode_on_idle(abort_callback & p_abort) {m_file->on_idle(p_abort);} + + void retag(const file_info & p_info,abort_callback & p_abort) {throw exception_io_unsupported_format();} + + static bool g_is_our_content_type(const char * p_content_type) {return false;} // match against supported mime types here + static bool g_is_our_path(const char * p_path,const char * p_extension) {return stricmp_utf8(p_extension,"raw") == 0;} + static const char * g_get_name() { return "foo_sample raw input"; } + static const GUID g_get_guid() { + // GUID of the decoder. Replace with your own when reusing code. + static const GUID I_am_foo_sample_and_this_is_my_decoder_GUID = { 0xd9c01c8d, 0x69c5, 0x4eec,{ 0xa2, 0x1c, 0x1d, 0x14, 0xef, 0x65, 0xbf, 0x8b } }; + return I_am_foo_sample_and_this_is_my_decoder_GUID; + } +public: + service_ptr_t m_file; + pfc::array_t m_buffer; +}; + +static input_singletrack_factory_t g_input_raw_factory; + +// Declare .RAW as a supported file type to make it show in "open file" dialog etc. +DECLARE_FILE_TYPE("Raw files","*.RAW"); diff --git a/foobar2000/foo_sample/listcontrol-advanced.cpp b/foobar2000/foo_sample/listcontrol-advanced.cpp new file mode 100644 index 0000000..9b48c09 --- /dev/null +++ b/foobar2000/foo_sample/listcontrol-advanced.cpp @@ -0,0 +1,323 @@ +// Advanced CListControl use demo +// Subclasses a CListControl to use all its features + +#include "stdafx.h" +#include "resource.h" +#include +#include +#include +#include +#include +#include + +namespace { + struct listData_t { + std::string m_key, m_value; + bool m_checkState = false; + }; + static std::vector makeListData() { + std::vector data; + data.resize( 10 ); + for( size_t walk = 0; walk < data.size(); ++ walk ) { + auto & rec = data[walk]; + rec.m_key = (PFC_string_formatter() << "Item #" << (walk+1) ).c_str(); + rec.m_value = "edit me"; + } + return data; + } + + // See CListControlComplete.h for base class description + typedef CListControlComplete CListControlDemoParent; + + class CListControlDemo : public CListControlDemoParent { + typedef CListControlDemoParent parent_t; + public: + BEGIN_MSG_MAP_EX(CListControlDemo) + CHAIN_MSG_MAP( parent_t ); + MSG_WM_CREATE(OnCreate) + MSG_WM_CONTEXTMENU(OnContextMenu) + END_MSG_MAP() + + + // Context menu handler + void OnContextMenu(CWindow wnd, CPoint point) { + // did we get a (-1,-1) point due to context menu key rather than right click? + // GetContextMenuPoint fixes that, returning a proper point at which the menu should be shown + point = this->GetContextMenuPoint(point); + + CMenu menu; + // WIN32_OP_D() : debug build only return value check + // Used to check for obscure errors in debug builds, does nothing (ignores errors) in release build + WIN32_OP_D(menu.CreatePopupMenu()); + + enum { ID_TEST1 = 1, ID_TEST2, ID_SELECTALL, ID_SELECTNONE, ID_INVERTSEL }; + menu.AppendMenu(MF_STRING, ID_TEST1, L"Test 1"); + menu.AppendMenu(MF_STRING, ID_TEST2, L"Test 2"); + menu.AppendMenu(MF_SEPARATOR); + // Note: Ctrl+A handled automatically by CListControl, no need for us to catch it + menu.AppendMenu(MF_STRING, ID_SELECTALL, L"Select all\tCtrl+A"); + menu.AppendMenu(MF_STRING, ID_SELECTNONE, L"Select none"); + menu.AppendMenu(MF_STRING, ID_INVERTSEL, L"Invert selection"); + + int cmd; + { + // Callback object to show menu command descriptions in the status bar. + // it's actually a hidden window, needs a parent HWND, where we feed our control's HWND + CMenuDescriptionMap descriptions(m_hWnd); + + // Set descriptions of all our items + descriptions.Set(ID_TEST1, "This is a test item #1"); + descriptions.Set(ID_TEST2, "This is a test item #2"); + + descriptions.Set(ID_SELECTALL, "Selects all items"); + descriptions.Set(ID_SELECTNONE, "Deselects all items"); + descriptions.Set(ID_INVERTSEL, "Invert selection"); + + cmd = menu.TrackPopupMenuEx(TPM_RIGHTBUTTON | TPM_NONOTIFY | TPM_RETURNCMD, point.x, point.y, descriptions, nullptr); + } + switch(cmd) { + case ID_TEST1: + { + pfc::string_formatter msg; + msg << "Test command #1 triggered.\r\n"; + msg << this->GetSelectedCount() << " items selected."; + // popup_message : non-blocking MessageBox equivalent + popup_message::g_show(msg, "Information"); + } + break; + case ID_TEST2: + { + pfc::string_formatter msg; + msg << "Test command #1 triggered.\r\n"; + msg << "Selected items:\r\n"; + for( size_t walk = 0; walk < GetItemCount(); ++ walk) { + if ( this->IsItemSelected( walk ) ) { + msg << m_data[walk].m_key.c_str() << "\r\n"; + } + } + msg << "End selected items."; + // popup_message : non-blocking MessageBox equivalent + popup_message::g_show(msg, "Information"); + } + break; + case ID_SELECTALL: + this->SelectAll(); // trivial + break; + case ID_SELECTNONE: + this->SelectNone(); // trivial + break; + case ID_INVERTSEL: + { + auto mask = this->GetSelectionMask(); + this->SetSelection( + // Items which we alter - all of them + pfc::bit_array_true(), + // Selection values - NOT'd original selection mask + pfc::bit_array_not(mask) + ); + // Exclusion of footer item from selection handled via CanSelectItem() + } + break; + } + } + + int OnCreate(LPCREATESTRUCT lpCreateStruct) { + InitHeader(); // set up header control with columns + SetWindowText(L"List Control Demo"); // screen reader will see this + return 0; + } + void InitHeader() { + InitializeHeaderCtrl(HDS_FULLDRAG); + + // never hardcode values in pixels, always use screen DPI + auto DPI = m_dpi; + AddColumn( "Check", MulDiv(60, DPI.cx, 96 ) ); + AddColumn( "Name", MulDiv(100, DPI.cx, 96 ) ); + AddColumn( "Value", MulDiv(100, DPI.cx, 96 ) ); + } + + bool CanSelectItem( size_t row ) const override { + // can not select footer + return row != footerRow(); + } + size_t footerRow() const { + return m_data.size(); + } + t_size GetItemCount() const override { + return m_data.size() + 1; // footer + } + void onFooterClicked() { + SelectNone(); + listData_t obj = {}; + obj.m_key = "New item"; + size_t index = m_data.size(); + m_data.push_back( std::move(obj) ); + OnItemsInserted(index, 1, true); + } + void OnSubItemClicked(t_size item, t_size subItem, CPoint pt) override { + if ( item == footerRow() ) { + onFooterClicked(); return; + } + if ( subItem == 2 ) { + TableEdit_Start(item, subItem); return; + } + __super::OnSubItemClicked( item, subItem, pt ); + } + bool AllowScrollbar(bool vertical) const override { + return true; + } + t_size InsertIndexFromPointEx(const CPoint & pt, bool & bInside) const override { + // Drag&drop insertion point hook, for reordering only + auto ret = __super::InsertIndexFromPointEx(pt, bInside); + bInside = false; // never drop *into* an item, only between, as we only allow reorder + if ( ret > m_data.size() ) ret = m_data.size(); // never allow drop beyond footer + return ret; + } + void RequestReorder(size_t const * order, size_t count) override { + // we've been asked to reorder the items, by either drag&drop or cursors+modifiers + // we can either reorder as requested, reorder partially if some of the items aren't moveable, or reject the request + + PFC_ASSERT( count == GetItemCount() ); + + // Footer row cannot be moved + if ( order[footerRow()] != footerRow() ) return; + + pfc::reorder_t( m_data, order, count ); + this->OnItemsReordered( order, count ); + } + void RemoveMask( pfc::bit_array const & mask ) { + if ( mask.get(footerRow() ) ) return; // footer row cannot be removed + auto oldCount = GetItemCount(); + pfc::remove_mask_t( m_data, mask ); + this->OnItemsRemoved( mask, oldCount ); + } + void RequestRemoveSelection() override { + // Delete key etc + RemoveMask(GetSelectionMask()); + } + void ExecuteDefaultAction(t_size index) override { + // double click, enter key, etc + if ( index == footerRow() ) onFooterClicked(); + } + + bool GetSubItemText(t_size item, t_size subItem, pfc::string_base & out) const override { + if ( item == footerRow() ) { + if ( subItem == 0 ) { + out = "+ add new"; + return true; + } + return false; + } + auto & rec = m_data[item]; + switch(subItem) { + case 0: + // pass blank string or return false to create a checkbox only column + out = "check"; + return true; + case 1: + out = rec.m_key.c_str(); + return true; + case 2: + out = rec.m_value.c_str(); + return true; + default: + return false; + } + } + + size_t GetSubItemSpan(size_t row, size_t column) const override { + if ( row == footerRow() && column == 0 ) { + return GetColumnCount(); + } + return 1; + } + cellType_t GetCellType(size_t item, size_t subItem) const override { + // cellType_t is a pointer to a cell class object supplying cell behavior specification & rendering methods + // use PFC_SINGLETON to provide static instances of used cells + if ( item == footerRow() ) { + if ( subItem == 0 ) { + return & PFC_SINGLETON( CListCell_Button ); + } else { + return nullptr; + } + } + switch(subItem) { + case 0: + return & PFC_SINGLETON( CListCell_Checkbox ); + default: + return & PFC_SINGLETON( CListCell_Text ); + } + + } + bool GetCellTypeSupported() const override { + return true; + } + bool GetCellCheckState(size_t item, size_t subItem) const override { + if ( subItem == 0 ) { + auto & rec = m_data[item]; + return rec.m_checkState; + } + return false; + } + void SetCellCheckState(size_t item, size_t subItem, bool value) override { + if ( subItem == 0 ) { + auto & rec = m_data[item]; + if (rec.m_checkState != value) { + rec.m_checkState = value; + __super::SetCellCheckState(item, subItem, value); + } + } + } + + uint32_t QueryDragDropTypes() const override {return dragDrop_reorder;} + + // Inplace edit handlers + // Overrides of CTableEditHelperV2 methods + void TableEdit_SetField(t_size item, t_size subItem, const char * value) override { + if ( subItem == 2 ) { + m_data[item].m_value = value; + ReloadItem( item ); + } + } + bool TableEdit_IsColumnEditable(t_size subItem) const override { + return subItem == 2; + } + private: + std::vector< listData_t > m_data = makeListData(); + }; + + // Straightforward WTL dialog code + class CListControlAdvancedDemoDialog : public CDialogImpl { + public: + enum { IDD = IDD_LISTCONTROL_DEMO }; + + BEGIN_MSG_MAP_EX(CListControlAdvancedDemoDialog) + MSG_WM_INITDIALOG(OnInitDialog) + COMMAND_HANDLER_EX(IDCANCEL, BN_CLICKED, OnCancel) + END_MSG_MAP() + private: + void OnCancel(UINT, int, CWindow) { + DestroyWindow(); + } + + BOOL OnInitDialog(CWindow, LPARAM) { + + // Create replacing existing windows list control + // automatically initialize position, font, etc + m_list.CreateInDialog( *this, IDC_LIST1 ); + + ShowWindow(SW_SHOW); + + return TRUE; // system should set focus + } + + CListControlDemo m_list; + }; +} + +// Called from mainmenu.cpp +void RunListControlAdvancedDemo() { + // automatically creates the dialog with object lifetime management and modeless dialog registration + fb2k::newDialog(); +} + diff --git a/foobar2000/foo_sample/listcontrol-ownerdata.cpp b/foobar2000/foo_sample/listcontrol-ownerdata.cpp new file mode 100644 index 0000000..c328ed7 --- /dev/null +++ b/foobar2000/foo_sample/listcontrol-ownerdata.cpp @@ -0,0 +1,204 @@ +#include "stdafx.h" + +// Owner-data CListControl use demo +// CListControlOwnerData with callbacks + +#include "stdafx.h" +#include "resource.h" +#include +#include +#include +#include +#include + +namespace { + struct listData_t { + std::string m_key, m_value; + }; + static std::vector makeListData() { + std::vector data; + data.resize( 10 ); + for( size_t walk = 0; walk < data.size(); ++ walk ) { + auto & rec = data[walk]; + rec.m_key = (PFC_string_formatter() << "Item #" << (walk+1) ).c_str(); + rec.m_value = "edit me"; + } + return data; + } + + + class CListControlOwnerDataDemoDialog : public CDialogImpl, private IListControlOwnerDataSource { + public: + + // CListControlOwnerData constructor requires ptr to IListControlOwnerDataSource object + CListControlOwnerDataDemoDialog() : m_list(this) {} + + enum { IDD = IDD_LISTCONTROL_DEMO }; + + BEGIN_MSG_MAP_EX(CListControlOwnerDataDemoDialog) + MSG_WM_INITDIALOG(OnInitDialog) + COMMAND_HANDLER_EX(IDCANCEL, BN_CLICKED, OnCancel) + MSG_WM_CONTEXTMENU(OnContextMenu) + END_MSG_MAP() + private: + void OnCancel(UINT, int, CWindow) { + DestroyWindow(); + } + + BOOL OnInitDialog(CWindow, LPARAM) { + + // Create replacing existing windows list control + // automatically initialize position, font, etc + m_list.CreateInDialog( *this, IDC_LIST1 ); + + // never hardcode values in pixels, always use screen DPI + auto DPI = m_list.GetDPI(); + m_list.AddColumn( "Name", MulDiv(100, DPI.cx, 96 ) ); + m_list.AddColumn( "Value", MulDiv(150, DPI.cx, 96 ) ); + + ShowWindow(SW_SHOW); + + return TRUE; // system should set focus + } + + // Context menu handler + void OnContextMenu(CWindow wnd, CPoint point) { + // did we get a (-1,-1) point due to context menu key rather than right click? + // GetContextMenuPoint fixes that, returning a proper point at which the menu should be shown + point = m_list.GetContextMenuPoint(point); + + CMenu menu; + // WIN32_OP_D() : debug build only return value check + // Used to check for obscure errors in debug builds, does nothing (ignores errors) in release build + WIN32_OP_D(menu.CreatePopupMenu()); + + enum { ID_TEST1 = 1, ID_TEST2, ID_SELECTALL, ID_SELECTNONE, ID_INVERTSEL }; + menu.AppendMenu(MF_STRING, ID_TEST1, L"Test 1"); + menu.AppendMenu(MF_STRING, ID_TEST2, L"Test 2"); + menu.AppendMenu(MF_SEPARATOR); + // Note: Ctrl+A handled automatically by CListControl, no need for us to catch it + menu.AppendMenu(MF_STRING, ID_SELECTALL, L"Select all\tCtrl+A"); + menu.AppendMenu(MF_STRING, ID_SELECTNONE, L"Select none"); + menu.AppendMenu(MF_STRING, ID_INVERTSEL, L"Invert selection"); + + int cmd; + { + // Callback object to show menu command descriptions in the status bar. + // it's actually a hidden window, needs a parent HWND, where we feed our control's HWND + CMenuDescriptionMap descriptions(m_hWnd); + + // Set descriptions of all our items + descriptions.Set(ID_TEST1, "This is a test item #1"); + descriptions.Set(ID_TEST2, "This is a test item #2"); + + descriptions.Set(ID_SELECTALL, "Selects all items"); + descriptions.Set(ID_SELECTNONE, "Deselects all items"); + descriptions.Set(ID_INVERTSEL, "Invert selection"); + + cmd = menu.TrackPopupMenuEx(TPM_RIGHTBUTTON | TPM_NONOTIFY | TPM_RETURNCMD, point.x, point.y, descriptions, nullptr); + } + switch(cmd) { + case ID_TEST1: + { + pfc::string_formatter msg; + msg << "Test command #1 triggered.\r\n"; + msg << m_list.GetSelectedCount() << " items selected."; + // popup_message : non-blocking MessageBox equivalent + popup_message::g_show(msg, "Information"); + } + break; + case ID_TEST2: + { + pfc::string_formatter msg; + msg << "Test command #1 triggered.\r\n"; + msg << "Selected items:\r\n"; + for( size_t walk = 0; walk < m_list.GetItemCount(); ++ walk) { + if ( m_list.IsItemSelected( walk ) ) { + msg << m_data[walk].m_key.c_str() << "\r\n"; + } + } + msg << "End selected items."; + // popup_message : non-blocking MessageBox equivalent + popup_message::g_show(msg, "Information"); + } + break; + case ID_SELECTALL: + m_list.SelectAll(); // trivial + break; + case ID_SELECTNONE: + m_list.SelectNone(); // trivial + break; + case ID_INVERTSEL: + { + auto mask = m_list.GetSelectionMask(); + m_list.SetSelection( + // Items which we alter - all of them + pfc::bit_array_true(), + // Selection values - NOT'd original selection mask + pfc::bit_array_not(mask) + ); + // Exclusion of footer item from selection handled via CanSelectItem() + } + break; + } + } + + private: + // IListControlOwnerDataSource methods + + size_t listGetItemCount( ctx_t ctx ) override { + PFC_ASSERT( ctx == &m_list ); // ctx is a pointer to the object calling us + return m_data.size(); + } + pfc::string8 listGetSubItemText( ctx_t, size_t item, size_t subItem ) override { + auto & rec = m_data[item]; + switch(subItem) { + case 0: + return rec.m_key.c_str(); + case 1: + return rec.m_value.c_str(); + default: + return ""; + } + + } + bool listCanReorderItems( ctx_t ) override { + return true; + } + bool listReorderItems( ctx_t, const size_t* order, size_t count) override { + PFC_ASSERT( count == m_data.size() ); + pfc::reorder_t( m_data, order, count ); + return true; + } + bool listRemoveItems( ctx_t, pfc::bit_array const & mask) override { + pfc::remove_mask_t( m_data, mask ); + return true; + } + void listItemAction(ctx_t, size_t item) override { + m_list.TableEdit_Start( item, 1 ); + } + void listSubItemClicked( ctx_t, size_t item, size_t subItem) override { + if ( subItem == 1 ) { + m_list.TableEdit_Start( item, subItem ); + } + } + void listSetEditField(ctx_t ctx, size_t item, size_t subItem, const char * val) override { + if ( subItem == 1 ) { + m_data[item].m_value = val; + } + } + bool listIsColumnEditable( ctx_t, size_t subItem ) override { + return subItem == 1; + } + + + std::vector< listData_t > m_data = makeListData(); + + CListControlOwnerData m_list; + }; +} +// Called from mainmenu.cpp +void RunListControlOwnerDataDemo() { + // automatically creates the dialog with object lifetime management and modeless dialog registration + fb2k::newDialog(); +} diff --git a/foobar2000/foo_sample/listcontrol-simple.cpp b/foobar2000/foo_sample/listcontrol-simple.cpp new file mode 100644 index 0000000..146889e --- /dev/null +++ b/foobar2000/foo_sample/listcontrol-simple.cpp @@ -0,0 +1,163 @@ +#include "stdafx.h" + +// Simple CListControl use demo +// CListControlSimple + +#include "stdafx.h" +#include "resource.h" +#include +#include +#include +#include +#include + +namespace { + struct listData_t { + std::string m_key, m_value; + }; + static std::vector makeListData() { + std::vector data; + data.resize( 10 ); + for( size_t walk = 0; walk < data.size(); ++ walk ) { + auto & rec = data[walk]; + rec.m_key = (PFC_string_formatter() << "Item #" << (walk+1) ).c_str(); + rec.m_value = "sample value"; + } + return data; + } + + + class CListControlSimpleDemoDialog : public CDialogImpl { + public: + + enum { IDD = IDD_LISTCONTROL_DEMO }; + + BEGIN_MSG_MAP_EX(CListControlOwnerDataDemoDialog) + MSG_WM_INITDIALOG(OnInitDialog) + COMMAND_HANDLER_EX(IDCANCEL, BN_CLICKED, OnCancel) + MSG_WM_CONTEXTMENU(OnContextMenu) + END_MSG_MAP() + private: + void OnCancel(UINT, int, CWindow) { + DestroyWindow(); + } + + BOOL OnInitDialog(CWindow, LPARAM) { + + // Create replacing existing windows list control + // automatically initialize position, font, etc + m_list.CreateInDialog( *this, IDC_LIST1 ); + + // never hardcode values in pixels, always use screen DPI + auto DPI = m_list.GetDPI(); + m_list.AddColumn( "Name", MulDiv(100, DPI.cx, 96 ) ); + m_list.AddColumn( "Value", MulDiv(150, DPI.cx, 96 ) ); + + { + auto data = makeListData(); + + m_list.SetItemCount( data.size( ) ); + for( size_t walk = 0; walk < data.size(); ++ walk ) { + auto & rec = data[walk]; + m_list.SetItemText( walk, 0, rec.m_key.c_str() ); + m_list.SetItemText( walk, 1, rec.m_value.c_str() ); + } + } + + ShowWindow(SW_SHOW); + + return TRUE; // system should set focus + } + + // Context menu handler + void OnContextMenu(CWindow wnd, CPoint point) { + // did we get a (-1,-1) point due to context menu key rather than right click? + // GetContextMenuPoint fixes that, returning a proper point at which the menu should be shown + point = m_list.GetContextMenuPoint(point); + + CMenu menu; + // WIN32_OP_D() : debug build only return value check + // Used to check for obscure errors in debug builds, does nothing (ignores errors) in release build + WIN32_OP_D(menu.CreatePopupMenu()); + + enum { ID_TEST1 = 1, ID_TEST2, ID_SELECTALL, ID_SELECTNONE, ID_INVERTSEL }; + menu.AppendMenu(MF_STRING, ID_TEST1, L"Test 1"); + menu.AppendMenu(MF_STRING, ID_TEST2, L"Test 2"); + menu.AppendMenu(MF_SEPARATOR); + // Note: Ctrl+A handled automatically by CListControl, no need for us to catch it + menu.AppendMenu(MF_STRING, ID_SELECTALL, L"Select all\tCtrl+A"); + menu.AppendMenu(MF_STRING, ID_SELECTNONE, L"Select none"); + menu.AppendMenu(MF_STRING, ID_INVERTSEL, L"Invert selection"); + + int cmd; + { + // Callback object to show menu command descriptions in the status bar. + // it's actually a hidden window, needs a parent HWND, where we feed our control's HWND + CMenuDescriptionMap descriptions(m_hWnd); + + // Set descriptions of all our items + descriptions.Set(ID_TEST1, "This is a test item #1"); + descriptions.Set(ID_TEST2, "This is a test item #2"); + + descriptions.Set(ID_SELECTALL, "Selects all items"); + descriptions.Set(ID_SELECTNONE, "Deselects all items"); + descriptions.Set(ID_INVERTSEL, "Invert selection"); + + cmd = menu.TrackPopupMenuEx(TPM_RIGHTBUTTON | TPM_NONOTIFY | TPM_RETURNCMD, point.x, point.y, descriptions, nullptr); + } + switch(cmd) { + case ID_TEST1: + { + pfc::string_formatter msg; + msg << "Test command #1 triggered.\r\n"; + msg << m_list.GetSelectedCount() << " items selected."; + // popup_message : non-blocking MessageBox equivalent + popup_message::g_show(msg, "Information"); + } + break; + case ID_TEST2: + { + pfc::string_formatter msg; + msg << "Test command #1 triggered.\r\n"; + msg << "Selected items:\r\n"; + for( size_t walk = 0; walk < m_list.GetItemCount(); ++ walk) { + if ( m_list.IsItemSelected( walk ) ) { + msg << "#" << (walk+1) << "\r\n"; + } + } + msg << "End selected items."; + // popup_message : non-blocking MessageBox equivalent + popup_message::g_show(msg, "Information"); + } + break; + case ID_SELECTALL: + m_list.SelectAll(); // trivial + break; + case ID_SELECTNONE: + m_list.SelectNone(); // trivial + break; + case ID_INVERTSEL: + { + auto mask = m_list.GetSelectionMask(); + m_list.SetSelection( + // Items which we alter - all of them + pfc::bit_array_true(), + // Selection values - NOT'd original selection mask + pfc::bit_array_not(mask) + ); + // Exclusion of footer item from selection handled via CanSelectItem() + } + break; + } + } + + private: + + CListControlSimple m_list; + }; +} + +// Called from mainmenu.cpp +void RunListControlSimpleDemo() { + fb2k::newDialog(); +} diff --git a/foobar2000/foo_sample/main.cpp b/foobar2000/foo_sample/main.cpp new file mode 100644 index 0000000..1afb97c --- /dev/null +++ b/foobar2000/foo_sample/main.cpp @@ -0,0 +1,11 @@ +#include "stdafx.h" + +// Declaration of your component's version information +// Since foobar2000 v1.0 having at least one of these in your DLL is mandatory to let the troubleshooter tell different versions of your component apart. +// Note that it is possible to declare multiple components within one DLL, but it's strongly recommended to keep only one declaration per DLL. +// As for 1.1, the version numbers are used by the component update finder to find updates; for that to work, you must have ONLY ONE declaration per DLL. If there are multiple declarations, the component is assumed to be outdated and a version number of "0" is assumed, to overwrite the component with whatever is currently on the site assuming that it comes with proper version numbers. +DECLARE_COMPONENT_VERSION("Sample Component","1.0","about message goes here"); + + +// This will prevent users from renaming your component around (important for proper troubleshooter behaviors) or loading multiple instances of it. +VALIDATE_COMPONENT_FILENAME("foo_sample.dll"); diff --git a/foobar2000/foo_sample/mainmenu-dynamic.cpp b/foobar2000/foo_sample/mainmenu-dynamic.cpp new file mode 100644 index 0000000..f6b5362 --- /dev/null +++ b/foobar2000/foo_sample/mainmenu-dynamic.cpp @@ -0,0 +1,120 @@ +#include "stdafx.h" +#include + +namespace { // anon namespace everything, it's not accessible by means other than the service factory + +// The command ID. +// Generate a new GUID when reusing code. +static const GUID guid_menucommand = { 0xab754b0b, 0x204, 0x4471, { 0xb5, 0x29, 0xff, 0x73, 0xae, 0x51, 0x5d, 0xe9 } }; + +// Shared with mainmenu.cpp +static const GUID guid_mainmenu_group_id = { 0x44963e7a, 0x4b2a, 0x4588, { 0xb0, 0x17, 0xa8, 0x69, 0x18, 0xcb, 0x8a, 0xa5 } }; + +class sample_command : public mainmenu_node_command { +public: + sample_command( size_t index ) : m_index(index) { + } + void get_display(pfc::string_base & text, t_uint32 & flags) override { + flags = 0; + text = PFC_string_formatter() << "Test dynamic item #" << m_index; + } + void execute(service_ptr_t callback) override { + popup_message::g_show(PFC_string_formatter() << "Invoked test menu item #" << m_index, "Information"); + } + GUID get_guid() override { + // This method returns our subcommand ID. + // Dynamic commands are identified by a pair of GUIDs: + // - command ID ( see: mainmenu_commands interface ) + // - subcommand ID ( identifier of one of the dynamic items ) + // Subcommand identifiers don't have to be actually globally unique, + // as long as they're unique among the subcommand identifiers for this command ID. + + // In our case, we'll just create a makeshift GUID from a hash of the index. + // This is perfectly okay for production code - as long as your command ID is a proper GUID! + + // Don't ever hash size_t which varies with CPU architecture + // Integer endianness intentionally disregarded + uint32_t hashme = (uint32_t) m_index; + + auto api = hasher_md5::get(); + hasher_md5_state state; + api->initialize( state ); + api->process( state, &hashme, sizeof(hashme) ); + + // fb2k hasher_md5 API even provides a convenient method to return MD5 hashes cast to GUIDs for this. + return api->get_result_guid( state ); + } + bool get_description(pfc::string_base & out) { + out = PFC_string_formatter() << "This is a test menu item #" << m_index << "."; + return true; + } +private: + const size_t m_index; +}; +class sample_group : public mainmenu_node_group { +public: + sample_group() { + m_children.resize(11); + for( size_t walk = 0; walk < m_children.size(); ++ walk ) { + mainmenu_node::ptr node; + // Insert separators for odd items, commands for even + if ( walk % 2 ) { + node = fb2k::service_new(); + } else { + auto cmdIndex = walk/2 + 1; + node = fb2k::service_new( cmdIndex ); + } + m_children[walk] = std::move(node); + } + } + void get_display(pfc::string_base & text, t_uint32 & flags) override { + flags = 0; + text = "Dynamic menu test group"; + } + t_size get_children_count() override { + return m_children.size(); + } + mainmenu_node::ptr get_child(t_size index) override { + PFC_ASSERT( index < m_children.size() ); + return m_children[index]; + } +private: + std::vector m_children; + +}; + +class mainmenu_sample_dynamic : public mainmenu_commands_v2 { +public: + // mainmenu_commands_v2 methods + t_uint32 get_command_count() override { return 1; } + GUID get_command(t_uint32 p_index) override {return guid_menucommand;} + void get_name(t_uint32 p_index,pfc::string_base & p_out) override {p_out = "Dynamic menu test";} + + bool get_description(t_uint32 p_index,pfc::string_base & p_out) override { + // Should not get here much + p_out = "This is a dynamic menu command test."; + return true; + } + GUID get_parent() override {return guid_mainmenu_group_id; } + void execute(t_uint32 p_index,service_ptr_t p_callback) override { + // Should not get here, someone not aware of our dynamic status tried to invoke us? + } + + bool is_command_dynamic(t_uint32 index) override { return true; } + mainmenu_node::ptr dynamic_instantiate(t_uint32 index) override { + return fb2k::service_new(); + } + + bool dynamic_execute(t_uint32 index, const GUID & subID, service_ptr_t callback) override { + // If your component provides a more efficient way to execute the command, + // than doing full dynamic_instantiate() and walking all the dynamic items to find one with the matching identifier, + // please implement it here. + + // ... or just skip implementing this method entirely. + return __super::dynamic_execute( index, subID, callback ); + } +}; + +static service_factory_single_t g_mainmenu_sample_dynamic; + +} // namespace \ No newline at end of file diff --git a/foobar2000/foo_sample/mainmenu.cpp b/foobar2000/foo_sample/mainmenu.cpp new file mode 100644 index 0000000..adfe475 --- /dev/null +++ b/foobar2000/foo_sample/mainmenu.cpp @@ -0,0 +1,120 @@ +#include "stdafx.h" + +#include "playback_stream_capture.h" + +// I am foo_sample and these are *my* GUIDs +// Make your own when reusing code or else +static const GUID guid_mainmenu_group_id = { 0x44963e7a, 0x4b2a, 0x4588, { 0xb0, 0x17, 0xa8, 0x69, 0x18, 0xcb, 0x8a, 0xa5 } }; +static const GUID guid_test = { 0x7c4726df, 0x3b2d, 0x4c7c,{ 0xad, 0xe8, 0x43, 0xd8, 0x46, 0xbe, 0xce, 0xa8 } }; +static const GUID guid_playbackstate = { 0xbd880c51, 0xf0cc, 0x473f,{ 0x9d, 0x14, 0xa6, 0x6e, 0x8c, 0xed, 0x25, 0xae } }; +static const GUID guid_io = { 0xd380c333, 0xa72c, 0x4e1e,{ 0x97, 0xca, 0xed, 0x14, 0xeb, 0x93, 0x76, 0x23 } }; +static const GUID guid_listcontrol_advanced = { 0x27e29db0, 0x3079, 0x4ce0, { 0x8b, 0x4a, 0xa0, 0x78, 0xeb, 0x6, 0x56, 0x86 } }; +static const GUID guid_listcontrol_simple = { 0x34664996, 0x54cd, 0x48eb, { 0xa8, 0x20, 0x8f, 0x45, 0x7d, 0xcc, 0xff, 0xbb } }; +static const GUID guid_listcontrol_ownerdata = { 0xc6d23696, 0x4be5, 0x4daa, { 0xaf, 0xb2, 0x35, 0x14, 0xa, 0x47, 0xd2, 0xf9 } }; +static const GUID guid_playback_stream_capture = { 0x3d0f0f1a, 0x6b5f, 0x42e3, { 0xa4, 0x6d, 0x49, 0x1, 0x3, 0xf0, 0x54, 0xb2 } }; + + +static mainmenu_group_popup_factory g_mainmenu_group(guid_mainmenu_group_id, mainmenu_groups::file, mainmenu_commands::sort_priority_dontcare, "Sample component"); + +void RunPlaybackStateDemo(); //playback_state.cpp +void RunIOTest(); // IO.cpp +void RunListControlSimpleDemo(); // listcontrol-simple.cpp +void RunListControlOwnerDataDemo(); // listcontrol-ownerdata.cpp +void RunListControlAdvancedDemo(); // listcontrol-advanced.cpp + +class mainmenu_commands_sample : public mainmenu_commands { +public: + enum { + cmd_test = 0, + cmd_playbackstate, + cmd_io, + cmd_listcontrol_simple, + cmd_listcontrol_ownerdata, + cmd_listcontrol_advanced, + cmd_playback_stream_capture, + cmd_total + }; + t_uint32 get_command_count() override { + return cmd_total; + } + GUID get_command(t_uint32 p_index) override { + + switch(p_index) { + case cmd_test: return guid_test; + case cmd_playbackstate: return guid_playbackstate; + case cmd_io: return guid_io; + case cmd_listcontrol_simple: return guid_listcontrol_simple; + case cmd_listcontrol_ownerdata: return guid_listcontrol_ownerdata; + case cmd_listcontrol_advanced: return guid_listcontrol_advanced; + case cmd_playback_stream_capture: return guid_playback_stream_capture; + default: uBugCheck(); // should never happen unless somebody called us with invalid parameters - bail + } + } + void get_name(t_uint32 p_index,pfc::string_base & p_out) override { + switch(p_index) { + case cmd_test: p_out = "Test command"; break; + case cmd_playbackstate: p_out = "Playback state demo"; break; + case cmd_io: p_out = "I/O test"; break; + case cmd_listcontrol_simple: p_out = "Simple CListControl demo"; break; + case cmd_listcontrol_ownerdata: p_out = "Owner-data CListControl demo"; break; + case cmd_listcontrol_advanced: p_out = "Advanced CListControl demo"; break; + case cmd_playback_stream_capture: p_out = "Playback stream capture demo"; break; + default: uBugCheck(); // should never happen unless somebody called us with invalid parameters - bail + } + } + bool get_description(t_uint32 p_index,pfc::string_base & p_out) override { + switch(p_index) { + case cmd_test: p_out = "This is a sample menu command."; return true; + case cmd_playbackstate: p_out = "Opens the playback state demo dialog."; return true; + case cmd_io: p_out = "Runs I/O test."; return true; + case cmd_listcontrol_simple: p_out = "Runs Simple CListControl demo."; return true; + case cmd_listcontrol_ownerdata: p_out = "Runs Owner Data CListControl demo."; return true; + case cmd_listcontrol_advanced: p_out = "Runs Advanced CListControl demo."; return true; + case cmd_playback_stream_capture: p_out = "Toggles playback stream capture operation."; return true; + default: return false; + } + } + GUID get_parent() override { + return guid_mainmenu_group_id; + } + void execute(t_uint32 p_index,service_ptr_t p_callback) override { + switch(p_index) { + case cmd_test: + popup_message::g_show("This is a sample menu command.", "Blah"); + break; + case cmd_playbackstate: + RunPlaybackStateDemo(); + break; + case cmd_io: + RunIOTest(); + break; + case cmd_listcontrol_simple: + RunListControlSimpleDemo(); + break; + case cmd_listcontrol_ownerdata: + RunListControlOwnerDataDemo(); + break; + case cmd_listcontrol_advanced: + RunListControlAdvancedDemo(); + break; + case cmd_playback_stream_capture: + ToggleCapture(); + break; + default: + uBugCheck(); // should never happen unless somebody called us with invalid parameters - bail + } + } + bool get_display(t_uint32 p_index,pfc::string_base & p_text,t_uint32 & p_flags) override { + // OPTIONAL method + bool rv = __super::get_display(p_index, p_text, p_flags); + if (rv) switch(p_index) { + case cmd_playback_stream_capture: + // Add checkmark if capture is in progress + if ( IsCaptureRunning() ) p_flags |= flag_checked; + break; + } + return rv; + } +}; + +static mainmenu_commands_factory_t g_mainmenu_commands_sample_factory; diff --git a/foobar2000/foo_sample/playback_state.cpp b/foobar2000/foo_sample/playback_state.cpp new file mode 100644 index 0000000..200c5f0 --- /dev/null +++ b/foobar2000/foo_sample/playback_state.cpp @@ -0,0 +1,155 @@ +#include "stdafx.h" +#include "resource.h" +#include +#include + +class CPlaybackStateDemo : public CDialogImpl, private play_callback_impl_base { +public: + enum {IDD = IDD_PLAYBACK_STATE}; + + BEGIN_MSG_MAP_EX(CPlaybackStateDemo) + MSG_WM_INITDIALOG(OnInitDialog) + COMMAND_HANDLER_EX(IDC_PATTERN, EN_CHANGE, OnPatternChange) + COMMAND_HANDLER_EX(IDCANCEL, BN_CLICKED, OnCancel) + COMMAND_HANDLER_EX(IDC_PLAY, BN_CLICKED, OnPlayClicked) + COMMAND_HANDLER_EX(IDC_PAUSE, BN_CLICKED, OnPauseClicked) + COMMAND_HANDLER_EX(IDC_STOP, BN_CLICKED, OnStopClicked) + COMMAND_HANDLER_EX(IDC_PREV, BN_CLICKED, OnPrevClicked) + COMMAND_HANDLER_EX(IDC_NEXT, BN_CLICKED, OnNextClicked) + COMMAND_HANDLER_EX(IDC_RAND, BN_CLICKED, OnRandClicked) + MSG_WM_CONTEXTMENU(OnContextMenu) + END_MSG_MAP() +private: + + // Playback callback methods. + void on_playback_starting(play_control::t_track_command p_command,bool p_paused) {update();} + void on_playback_new_track(metadb_handle_ptr p_track) {update();} + void on_playback_stop(play_control::t_stop_reason p_reason) {update();} + void on_playback_seek(double p_time) {update();} + void on_playback_pause(bool p_state) {update();} + void on_playback_edited(metadb_handle_ptr p_track) {update();} + void on_playback_dynamic_info(const file_info & p_info) {update();} + void on_playback_dynamic_info_track(const file_info & p_info) {update();} + void on_playback_time(double p_time) {update();} + void on_volume_change(float p_new_val) {} + + void update(); + + void OnPatternChange(UINT, int, CWindow); + void OnCancel(UINT, int, CWindow); + + void OnPlayClicked(UINT, int, CWindow) {m_playback_control->start();} + void OnStopClicked(UINT, int, CWindow) {m_playback_control->stop();} + void OnPauseClicked(UINT, int, CWindow) {m_playback_control->toggle_pause();} + void OnPrevClicked(UINT, int, CWindow) {m_playback_control->start(playback_control::track_command_prev);} + void OnNextClicked(UINT, int, CWindow) {m_playback_control->start(playback_control::track_command_next);} + void OnRandClicked(UINT, int, CWindow) {m_playback_control->start(playback_control::track_command_rand);} + + void OnContextMenu(CWindow wnd, CPoint point); + + BOOL OnInitDialog(CWindow, LPARAM); + + titleformat_object::ptr m_script; + + static_api_ptr_t m_playback_control; +}; + +void CPlaybackStateDemo::OnCancel(UINT, int, CWindow) { + DestroyWindow(); +} + +void CPlaybackStateDemo::OnPatternChange(UINT, int, CWindow) { + m_script.release(); // pattern has changed, force script recompilation + update(); +} + +BOOL CPlaybackStateDemo::OnInitDialog(CWindow, LPARAM) { + update(); + SetDlgItemText(IDC_PATTERN, _T("%codec% | %bitrate% kbps | %samplerate% Hz | %channels% | %playback_time%[ / %length%]$if(%ispaused%, | paused,)")); + ::ShowWindowCentered(*this,GetParent()); // Function declared in SDK helpers. + return TRUE; +} + +void CPlaybackStateDemo::update() { + if (m_script.is_empty()) { + pfc::string8 pattern; + uGetDlgItemText(*this, IDC_PATTERN, pattern); + static_api_ptr_t()->compile_safe_ex(m_script, pattern); + } + pfc::string_formatter state; + if (m_playback_control->playback_format_title(NULL, state, m_script, NULL, playback_control::display_level_all)) { + //Succeeded already. + } else if (m_playback_control->is_playing()) { + //Starting playback but not done opening the first track yet. + state = "Opening..."; + } else { + state = "Stopped."; + } + uSetDlgItemText(*this, IDC_STATE, state); +} + +void CPlaybackStateDemo::OnContextMenu(CWindow wnd, CPoint point) { + try { + if (wnd == GetDlgItem(IDC_CONTEXTMENU)) { + + // handle the context menu key case - center the menu + if (point == CPoint(-1, -1)) { + CRect rc; + WIN32_OP(wnd.GetWindowRect(&rc)); + point = rc.CenterPoint(); + } + + metadb_handle_list items; + + { // note: we would normally just use contextmenu_manager::init_context_now_playing(), but we go the "make the list ourselves" route to demonstrate how to invoke the menu for arbitrary items. + metadb_handle_ptr item; + if (m_playback_control->get_now_playing(item)) items += item; + } + + CMenuDescriptionHybrid menudesc(*this); //this class manages all the voodoo necessary for descriptions of our menu items to show in the status bar. + + static_api_ptr_t api; + CMenu menu; + WIN32_OP(menu.CreatePopupMenu()); + enum { + ID_TESTCMD = 1, + ID_CM_BASE, + }; + menu.AppendMenu(MF_STRING, ID_TESTCMD, _T("Test command")); + menudesc.Set(ID_TESTCMD, "This is a test command."); + menu.AppendMenu(MF_SEPARATOR); + + if (items.get_count() > 0) { + api->init_context(items, 0); + api->win32_build_menu(menu, ID_CM_BASE, ~0); + menudesc.SetCM(api.get_ptr(), ID_CM_BASE, ~0); + } else { + menu.AppendMenu(MF_STRING|MF_GRAYED|MF_DISABLED, (UINT)0, _T("No items selected")); + } + + int cmd = menu.TrackPopupMenu(TPM_RIGHTBUTTON|TPM_NONOTIFY|TPM_RETURNCMD,point.x,point.y,menudesc,0); + if (cmd > 0) { + if (cmd >= ID_CM_BASE) { + api->execute_by_id(cmd - ID_CM_BASE); + } else switch(cmd) { + case ID_TESTCMD: + popup_message::g_show("Blah!", "Test"); + break; + } + } + + } + } catch(std::exception const & e) { + console::complain("Context menu failure", e); //rare + } +} + +void RunPlaybackStateDemo() { + try { + // ImplementModelessTracking registers our dialog to receive dialog messages thru main app loop's IsDialogMessage(). + // CWindowAutoLifetime creates the window in the constructor (taking the parent window as a parameter) and deletes the object when the window has been destroyed (through WTL's OnFinalMessage). + new CWindowAutoLifetime >(core_api::get_main_window()); + } catch(std::exception const & e) { + popup_message::g_complain("Dialog creation failure", e); + } +} diff --git a/foobar2000/foo_sample/playback_stream_capture.cpp b/foobar2000/foo_sample/playback_stream_capture.cpp new file mode 100644 index 0000000..cc74c0b --- /dev/null +++ b/foobar2000/foo_sample/playback_stream_capture.cpp @@ -0,0 +1,121 @@ +#include "stdafx.h" +#include + +namespace { + // private classes in anon namespace + + typedef CWavWriter wav_writer; + typedef wavWriterSetup_t wav_writer_setup; + + static pfc::string8 g_outputPath; + static wav_writer g_wav_writer; + + class playback_stream_capture_callback_impl : public playback_stream_capture_callback { + public: + void on_chunk(const audio_chunk & chunk) override { + PFC_ASSERT(core_api::is_main_thread()); + + try { + // writing files in main thread is not pretty, but good enough for our demo + + auto & abort = fb2k::noAbort; + + if (g_wav_writer.is_open() && g_wav_writer.get_spec() != chunk.get_spec()) { + g_wav_writer.finalize(abort); + } + if (!g_wav_writer.is_open() && ! core_api::is_shutting_down() ) { + wav_writer_setup setup; setup.initialize(chunk, 16, false, false); + + GUID g; CoCreateGuid(&g); + pfc::string_formatter fn; + fn << "capture-" << pfc::print_guid(g) << ".wav"; + pfc::string_formatter path = g_outputPath; + path.add_filename( fn ); // pretty method to add file path components with auto inserted delimiter + g_wav_writer.open(path, setup, abort); + } + g_wav_writer.write(chunk, abort); + } catch(std::exception const & e) { + FB2K_console_formatter() << "Playback stream capture error: " << e; + // FIX ME handle this in a pretty manner, likely inaccessible output folder or out of disk space + } + } + }; + static playback_stream_capture_callback_impl g_callback; + static bool g_active = false; + + static void FlushCapture() { + if (g_active) { + g_wav_writer.finalize(fb2k::noAbort); + } + } + static void StopCapture() { + if ( g_active ) { + g_active = false; + playback_stream_capture::get()->remove_callback(&g_callback); + g_wav_writer.finalize(fb2k::noAbort); + } + } + static void StartCapture() { + PFC_ASSERT( g_outputPath.length() > 0 ); + if (!g_active && !core_api::is_shutting_down()) { + g_active = true; + playback_stream_capture::get()->add_callback(&g_callback); + } + } + + // Forcibly stop capture when fb2k is shutting down + class initquit_psc : public initquit { + public: + void on_quit() override { + PFC_ASSERT( core_api::is_shutting_down() ); + StopCapture(); + } + }; + + // Handle playback stop events to split output WAV files + class play_callback_psc : public play_callback_static { + public: + unsigned get_flags() override { + return flag_on_playback_stop; + } + void on_playback_stop(play_control::t_stop_reason p_reason) override { + // Terminate the current stream + FlushCapture(); + } + void on_playback_starting(play_control::t_track_command p_command,bool p_paused) override {} + void on_playback_new_track(metadb_handle_ptr p_track) override {} + void on_playback_seek(double p_time) override {} + void on_playback_pause(bool p_state) override {} + void on_playback_edited(metadb_handle_ptr p_track) override {} + void on_playback_dynamic_info(const file_info & p_info) override {} + void on_playback_dynamic_info_track(const file_info & p_info) override {} + void on_playback_time(double p_time) override {} + void on_volume_change(float p_new_val) override {} + }; + + // pretty modern macro for service_factory_single_t<> + FB2K_SERVICE_FACTORY( initquit_psc ); + FB2K_SERVICE_FACTORY( play_callback_psc ); +} + +void ToggleCapture() { + // Block modal dialog recursions. + // Folder picker below is a modal dialog, don't ever call it if there's another modal dialog in progress. + // Also prevents this function from recursing into itself if someone manages to hit the menu item while already picking folder. + // This will bump whatever modal dialog already exists, so the user has some idea why this was refused. + if ( !ModalDialogPrologue() ) return; + + if (g_active) { + StopCapture(); + } else { + const HWND wndParent = core_api::get_main_window(); + modal_dialog_scope scope(wndParent); // we can't have a handle to the modal dialog, but parent handle is good enough + if (uBrowseForFolder(wndParent, "Choose output folder", g_outputPath)) { + StartCapture(); + } + } +} + +bool IsCaptureRunning() { + return g_active; +} diff --git a/foobar2000/foo_sample/playback_stream_capture.h b/foobar2000/foo_sample/playback_stream_capture.h new file mode 100644 index 0000000..fcc8bc7 --- /dev/null +++ b/foobar2000/foo_sample/playback_stream_capture.h @@ -0,0 +1,4 @@ +#pragma once + +void ToggleCapture(); +bool IsCaptureRunning(); \ No newline at end of file diff --git a/foobar2000/foo_sample/preferences.cpp b/foobar2000/foo_sample/preferences.cpp new file mode 100644 index 0000000..9aa5296 --- /dev/null +++ b/foobar2000/foo_sample/preferences.cpp @@ -0,0 +1,111 @@ +#include "stdafx.h" +#include "resource.h" +#include + +// Sample preferences interface: two meaningless configuration settings accessible through a preferences page and one accessible through advanced preferences. + + +// These GUIDs identify the variables within our component's configuration file. +static const GUID guid_cfg_bogoSetting1 = { 0xbd5c777, 0x735c, 0x440d, { 0x8c, 0x71, 0x49, 0xb6, 0xac, 0xff, 0xce, 0xb8 } }; +static const GUID guid_cfg_bogoSetting2 = { 0x752f1186, 0x9f61, 0x4f91, { 0xb3, 0xee, 0x2f, 0x25, 0xb1, 0x24, 0x83, 0x5d } }; + +// This GUID identifies our Advanced Preferences branch (replace with your own when reusing code). +static const GUID guid_advconfig_branch = { 0x28564ced, 0x4abf, 0x4f0c, { 0xa4, 0x43, 0x98, 0xda, 0x88, 0xe2, 0xcd, 0x39 } }; +// This GUID identifies our Advanced Preferences setting (replace with your own when reusing code) as well as this setting's storage within our component's configuration file. +static const GUID guid_cfg_bogoSetting3 = { 0xf7008963, 0xed60, 0x4084, { 0xa8, 0x5d, 0xd1, 0xcd, 0xc5, 0x51, 0x22, 0xca } }; + + +enum { + default_cfg_bogoSetting1 = 1337, + default_cfg_bogoSetting2 = 666, + default_cfg_bogoSetting3 = 42, +}; + +static cfg_uint cfg_bogoSetting1(guid_cfg_bogoSetting1, default_cfg_bogoSetting1), cfg_bogoSetting2(guid_cfg_bogoSetting2, default_cfg_bogoSetting2); + +static advconfig_branch_factory g_advconfigBranch("Sample Component", guid_advconfig_branch, advconfig_branch::guid_branch_tools, 0); +static advconfig_integer_factory cfg_bogoSetting3("Bogo setting 3", guid_cfg_bogoSetting3, guid_advconfig_branch, 0, default_cfg_bogoSetting3, 0 /*minimum value*/, 9999 /*maximum value*/); + +class CMyPreferences : public CDialogImpl, public preferences_page_instance { +public: + //Constructor - invoked by preferences_page_impl helpers - don't do Create() in here, preferences_page_impl does this for us + CMyPreferences(preferences_page_callback::ptr callback) : m_callback(callback) {} + + //Note that we don't bother doing anything regarding destruction of our class. + //The host ensures that our dialog is destroyed first, then the last reference to our preferences_page_instance object is released, causing our object to be deleted. + + + //dialog resource ID + enum {IDD = IDD_MYPREFERENCES}; + // preferences_page_instance methods (not all of them - get_wnd() is supplied by preferences_page_impl helpers) + t_uint32 get_state(); + void apply(); + void reset(); + + //WTL message map + BEGIN_MSG_MAP_EX(CMyPreferences) + MSG_WM_INITDIALOG(OnInitDialog) + COMMAND_HANDLER_EX(IDC_BOGO1, EN_CHANGE, OnEditChange) + COMMAND_HANDLER_EX(IDC_BOGO2, EN_CHANGE, OnEditChange) + END_MSG_MAP() +private: + BOOL OnInitDialog(CWindow, LPARAM); + void OnEditChange(UINT, int, CWindow); + bool HasChanged(); + void OnChanged(); + + const preferences_page_callback::ptr m_callback; +}; + +BOOL CMyPreferences::OnInitDialog(CWindow, LPARAM) { + SetDlgItemInt(IDC_BOGO1, cfg_bogoSetting1, FALSE); + SetDlgItemInt(IDC_BOGO2, cfg_bogoSetting2, FALSE); + return FALSE; +} + +void CMyPreferences::OnEditChange(UINT, int, CWindow) { + // not much to do here + OnChanged(); +} + +t_uint32 CMyPreferences::get_state() { + t_uint32 state = preferences_state::resettable; + if (HasChanged()) state |= preferences_state::changed; + return state; +} + +void CMyPreferences::reset() { + SetDlgItemInt(IDC_BOGO1, default_cfg_bogoSetting1, FALSE); + SetDlgItemInt(IDC_BOGO2, default_cfg_bogoSetting2, FALSE); + OnChanged(); +} + +void CMyPreferences::apply() { + cfg_bogoSetting1 = GetDlgItemInt(IDC_BOGO1, NULL, FALSE); + cfg_bogoSetting2 = GetDlgItemInt(IDC_BOGO2, NULL, FALSE); + + OnChanged(); //our dialog content has not changed but the flags have - our currently shown values now match the settings so the apply button can be disabled +} + +bool CMyPreferences::HasChanged() { + //returns whether our dialog content is different from the current configuration (whether the apply button should be enabled or not) + return GetDlgItemInt(IDC_BOGO1, NULL, FALSE) != cfg_bogoSetting1 || GetDlgItemInt(IDC_BOGO2, NULL, FALSE) != cfg_bogoSetting2; +} +void CMyPreferences::OnChanged() { + //tell the host that our state has changed to enable/disable the apply button appropriately. + m_callback->on_state_changed(); +} + +class preferences_page_myimpl : public preferences_page_impl { + // preferences_page_impl<> helper deals with instantiation of our dialog; inherits from preferences_page_v3. +public: + const char * get_name() {return "Sample Component";} + GUID get_guid() { + // This is our GUID. Replace with your own when reusing the code. + static const GUID guid = { 0x7702c93e, 0x24dc, 0x48ed, { 0x8d, 0xb1, 0x3f, 0x27, 0xb3, 0x8c, 0x7c, 0xc9 } }; + return guid; + } + GUID get_parent_guid() {return guid_tools;} +}; + +static preferences_page_factory_t g_preferences_page_myimpl_factory; diff --git a/foobar2000/foo_sample/rating.cpp b/foobar2000/foo_sample/rating.cpp new file mode 100644 index 0000000..c36d778 --- /dev/null +++ b/foobar2000/foo_sample/rating.cpp @@ -0,0 +1,592 @@ +#include "stdafx.h" + + +/* +======================================================================== + Sample implementation of metadb_index_client and a rating database. +======================================================================== + +The rating data is all maintained by metadb backend, we only present and alter it when asked to. +Relevant classes: +metadb_index_client_impl - turns track info into a database key to which our data gets pinned. +init_stage_callback_impl - initializes ourselves at the proper moment of the app lifetime. +initquit_imp - clean up cached objects on app shutdown +metadb_display_field_provider_impl - publishes our %foo_sample_...% fields via title formatting. +contextmenu_rating - context menu command to cycle rating values. +mainmenu_rating - main menu command to show a dump of all present ratings. +track_property_provider_impl - serves info for the Properties dialog +*/ + +namespace { + + // I am foo_sample and these are *my* GUIDs. + // Replace with your own when reusing. + // Always recreate guid_foo_sample_rating_index if your metadb_index_client_impl hashing semantics changed or else you run into inconsistent/nonsensical data. + static const GUID guid_foo_sample_track_rating_index = { 0x88cf3f09, 0x26a8, 0x42ef,{ 0xb7, 0xb8, 0x42, 0x21, 0xb9, 0x62, 0x26, 0x78 } }; + static const GUID guid_foo_sample_album_rating_index = { 0xd94ba576, 0x7fab, 0x4f1b,{ 0xbe, 0x5e, 0x4f, 0x8e, 0x9d, 0x5f, 0x30, 0xf1 } }; + static const GUID guid_foo_sample_rating_contextmenu1 = { 0x5d71c93, 0x5d38, 0x4e63,{ 0x97, 0x66, 0x8f, 0xb7, 0x6d, 0xc7, 0xc5, 0x9e } }; + static const GUID guid_foo_sample_rating_contextmenu2 = { 0xf3972846, 0x7c32, 0x44fa,{ 0xbd, 0xa, 0x68, 0x86, 0x65, 0x69, 0x4b, 0x7d } }; + static const GUID guid_foo_sample_rating_contextmenu3 = { 0x67a6d984, 0xe499, 0x4f86,{ 0xb9, 0xcb, 0x66, 0x8e, 0x59, 0xb8, 0xd0, 0xe6 } }; + static const GUID guid_foo_sample_rating_contextmenu4 = { 0x4541dfa5, 0x7976, 0x43aa,{ 0xb9, 0x73, 0x10, 0xc3, 0x26, 0x55, 0x5a, 0x5c } }; + + static const GUID guid_foo_sample_contextmenu_group = { 0x572de7f4, 0xcbdf, 0x479a,{ 0x97, 0x26, 0xa, 0xb0, 0x97, 0x47, 0x69, 0xe3 } }; + static const GUID guid_foo_sample_rating_mainmenu = { 0x53327baa, 0xbaa4, 0x478e,{ 0x87, 0x24, 0xf7, 0x38, 0x4, 0x15, 0xf7, 0x27 } }; + static const GUID guid_foo_sample_mainmenu_group = { 0x44963e7a, 0x4b2a, 0x4588,{ 0xb0, 0x17, 0xa8, 0x69, 0x18, 0xcb, 0x8a, 0xa5 } }; + + // Patterns by which we pin our data to. + // If multiple songs in the library evaluate to the same string, they will be considered the same by our component, + // and data applied to one will also show up with the rest. + // Always recreate relevant index GUIDs if you change these + static const char strTrackRatingPinTo[] = "%artist% - %title%"; + static const char strAlbumRatingPinTo[] = "%album artist% - %album%"; + + + // Our group in Properties dialog / Details tab, see track_property_provider_impl + static const char strPropertiesGroup[] = "Sample Component"; + + // Retain pinned data for four weeks if there are no matching items in library + static const t_filetimestamp retentionPeriod = system_time_periods::week * 4; + + // A class that turns metadata + location info into hashes to which our data gets pinned by the backend. + class metadb_index_client_impl : public metadb_index_client { + public: + metadb_index_client_impl( const char * pinTo ) { + static_api_ptr_t()->compile_force(m_keyObj, pinTo); + } + + metadb_index_hash transform(const file_info & info, const playable_location & location) { + pfc::string_formatter str; + m_keyObj->run_simple( location, &info, str ); + // Make MD5 hash of the string, then reduce it to 64-bit metadb_index_hash + return static_api_ptr_t()->process_single_string( str ).xorHalve(); + } + private: + titleformat_object::ptr m_keyObj; + }; + + static metadb_index_client_impl * clientByGUID( const GUID & guid ) { + // Static instances, never destroyed (deallocated with the process), created first time we get here + // Using service_impl_single_t, reference counting disabled + // This is somewhat ugly, operating on raw pointers instead of service_ptr, but OK for this purpose + static metadb_index_client_impl * g_clientTrack = new service_impl_single_t(strTrackRatingPinTo); + static metadb_index_client_impl * g_clientAlbum = new service_impl_single_t(strAlbumRatingPinTo); + + PFC_ASSERT( guid == guid_foo_sample_album_rating_index || guid == guid_foo_sample_track_rating_index ); + return (guid == guid_foo_sample_album_rating_index ) ? g_clientAlbum : g_clientTrack; + } + + // Static cached ptr to metadb_index_manager + // Cached because we'll be calling it a lot on per-track basis, let's not pass it everywhere to low level functions + // Obtaining the pointer from core is reasonably efficient - log(n) to the number of known service classes, but not good enough for something potentially called hundreds of times + static metadb_index_manager::ptr g_cachedAPI; + static metadb_index_manager::ptr theAPI() { + auto ret = g_cachedAPI; + if ( ret.is_empty() ) ret = metadb_index_manager::get(); // since fb2k SDK v1.4, core API interfaces have a static get() method + return ret; + } + + // An init_stage_callback to hook ourselves into the metadb + // We need to do this properly early to prevent dispatch_global_refresh() from new fields that we added from hammering playlists etc + class init_stage_callback_impl : public init_stage_callback { + public: + void on_init_stage(t_uint32 stage) { + if (stage == init_stages::before_config_read) { + auto api = metadb_index_manager::get(); + g_cachedAPI = api; + // Important, handle the exceptions here! + // This will fail if the files holding our data are somehow corrupted. + try { + api->add(clientByGUID(guid_foo_sample_track_rating_index), guid_foo_sample_track_rating_index, retentionPeriod); + api->add(clientByGUID(guid_foo_sample_album_rating_index), guid_foo_sample_album_rating_index, retentionPeriod); + } catch (std::exception const & e) { + api->remove(guid_foo_sample_track_rating_index); + api->remove(guid_foo_sample_album_rating_index); + FB2K_console_formatter() << "[foo_sample rating] Critical initialization failure: " << e; + return; + } + api->dispatch_global_refresh(); + } + } + }; + class initquit_impl : public initquit { + public: + void on_quit() { + // Cleanly kill g_cachedAPI before reaching static object destructors or else + g_cachedAPI.release(); + } + }; + + static service_factory_single_t g_init_stage_callback_impl; + static service_factory_single_t g_initquit_impl; + + typedef uint32_t rating_t; + static const rating_t rating_invalid = 0; + static const rating_t rating_max = 5; + + struct record_t { + rating_t m_rating = rating_invalid; + pfc::string8 m_comment; + }; + + static record_t record_get(const GUID & indexID, metadb_index_hash hash) { + mem_block_container_impl temp; // this will receive our BLOB + theAPI()->get_user_data( indexID, hash, temp ); + if ( temp.get_size() > 0 ) { + try { + // Parse the BLOB using stream formatters + stream_reader_formatter_simple_ref< /* using big endian data? nope */ false > reader(temp.get_ptr(), temp.get_size()); + + record_t ret; + reader >> ret.m_rating; // little endian uint32 got + + if ( reader.get_remaining() > 0 ) { + // more data left in the stream? + // note that this is a stream_reader_formatter_simple_ref method, regular stream formatters do not know the size or seek around, only read the stream sequentially + reader >> ret.m_comment; // this reads uint32 prefix indicating string size in bytes, then the actual string in UTF-8 characters } + } // otherwise we leave the string empty + + // if we attempted to read past the EOF, we'd land in the exception_io_data handler below + + return ret; + + } catch (exception_io_data) { + // we get here as a result of stream formatter data error + // fall thru to return a blank record + } + } + return record_t(); + } + + void record_set( const GUID & indexID, metadb_index_hash hash, const record_t & record) { + + stream_writer_formatter_simple< /* using bing endian data? nope */ false > writer; + writer << record.m_rating; + if ( record.m_comment.length() > 0 ) { + // bother with this only if the comment is not blank + writer << record.m_comment; // uint32 size + UTF-8 bytes + } + + theAPI()->set_user_data( indexID, hash, writer.m_buffer.get_ptr(), writer.m_buffer.get_size() ); + } + + static rating_t rating_get( const GUID & indexID, metadb_index_hash hash) { + return record_get(indexID, hash).m_rating; + } + + + // Returns true if the value was actually changed + static bool rating_set( const GUID & indexID, metadb_index_hash hash, rating_t rating) { + bool bChanged = false; + auto rec = record_get(indexID, hash); + if ( rec.m_rating != rating ) { + rec.m_rating = rating; + record_set( indexID, hash, rec); + bChanged = true; + } + return bChanged; + } + + static bool comment_set( const GUID & indexID, metadb_index_hash hash, const char * strComment ) { + auto rec = record_get(indexID, hash ); + bool bChanged = false; + if ( ! rec.m_comment.equals( strComment ) ) { + rec.m_comment = strComment; + record_set(indexID, hash, rec); + bChanged = true; + } + return bChanged; + } + + // Provider of our title formatting fields. + class metadb_display_field_provider_impl : public metadb_display_field_provider { + public: + t_uint32 get_field_count() { + return 6; + } + void get_field_name(t_uint32 index, pfc::string_base & out) { + PFC_ASSERT(index < get_field_count()); + switch(index) { + case 0: + out = "foo_sample_track_rating"; break; + case 1: + out = "foo_sample_album_rating"; break; + case 2: + out = "foo_sample_track_comment"; break; + case 3: + out = "foo_sample_album_comment"; break; + case 4: + out = "foo_sample_track_hash"; break; + case 5: + out = "foo_sample_album_hash"; break; + default: + PFC_ASSERT(!"Should never get here"); + } + } + + bool process_field(t_uint32 index, metadb_handle * handle, titleformat_text_out * out) { + PFC_ASSERT( index < get_field_count() ); + + const GUID whichID = ((index%2) == 1) ? guid_foo_sample_album_rating_index : guid_foo_sample_track_rating_index; + + record_t rec; + metadb_index_hash hash; + if (!clientByGUID(whichID)->hashHandle(handle, hash)) return false; + + if ( index < 4 ) { + rec = record_get(whichID, hash); + } + + + if ( index < 2 ) { + // rating + + if (rec.m_rating == rating_invalid) return false; + + out->write_int(titleformat_inputtypes::meta, rec.m_rating); + + return true; + } else if ( index < 4 ) { + // comment + + if ( rec.m_comment.length() == 0 ) return false; + + out->write( titleformat_inputtypes::meta, rec.m_comment.c_str() ); + + return true; + } else { + out->write(titleformat_inputtypes::meta, pfc::format_hex(hash,16) ); + return true; + } + } + }; + + static service_factory_single_t g_metadb_display_field_provider_impl; + + static void cycleRating( const GUID & whichID, metadb_handle_list_cref tracks) { + + const size_t count = tracks.get_count(); + if (count == 0) return; + + auto client = clientByGUID(whichID); + + rating_t rating = rating_invalid; + + // Sorted/dedup'd set of all hashes of p_data items. + // pfc::avltree_t<> is pfc equivalent of std::set<>. + // We go the avltree_t<> route because more than one track in p_data might produce the same hash value, see metadb_index_client_impl / strPinTo + pfc::avltree_t allHashes; + for (size_t w = 0; w < count; ++w) { + metadb_index_hash hash; + if (client->hashHandle(tracks[w], hash)) { + allHashes += hash; + + // Take original rating to increment from the first selected song + if (w == 0) rating = rating_get(whichID, hash); + } + } + + if (allHashes.get_count() == 0) { + FB2K_console_formatter() << "[foo_sample rating] Could not hash any of the tracks due to unavailable metadata, bailing"; + return; + } + + // Now cycle the rating value + if (rating == rating_invalid) rating = 1; + else if (rating >= rating_max) rating = rating_invalid; + else ++rating; + + // Now set the new rating + pfc::list_t lstChanged; // Linear list of hashes that actually changed + for (auto iter = allHashes.first(); iter.is_valid(); ++iter) { + const metadb_index_hash hash = *iter; + if (rating_set(whichID, hash, rating) ) { // rating_set returns true if the value actually changed, false if old equals new and no change was made + lstChanged += hash; + } + } + + FB2K_console_formatter() << "[foo_sample rating] " << lstChanged.get_count() << " entries updated"; + if (lstChanged.get_count() > 0) { + // This gracefully tells everyone about what just changed, in one pass regardless of how many items got altered + theAPI()->dispatch_refresh(whichID, lstChanged); + } + + } + + static void cycleComment( const GUID & whichID, metadb_handle_list_cref tracks ) { + const size_t count = tracks.get_count(); + if (count == 0) return; + + auto client = clientByGUID(whichID); + + pfc::string8 comment; + + // Sorted/dedup'd set of all hashes of p_data items. + // pfc::avltree_t<> is pfc equivalent of std::set<>. + // We go the avltree_t<> route because more than one track in p_data might produce the same hash value, see metadb_index_client_impl / strPinTo + pfc::avltree_t allHashes; + for (size_t w = 0; w < count; ++w) { + metadb_index_hash hash; + if (client->hashHandle(tracks[w], hash)) { + allHashes += hash; + + // Take original rating to increment from the first selected song + if (w == 0) comment = record_get(whichID, hash).m_comment; + } + } + + if (allHashes.get_count() == 0) { + FB2K_console_formatter() << "[foo_sample rating] Could not hash any of the tracks due to unavailable metadata, bailing"; + return; + } + + // Now cycle the comment value + if ( comment.equals("") ) comment = "foo"; + else if ( comment.equals("foo") ) comment = "bar"; + else comment = ""; + + // Now apply the new comment + pfc::list_t lstChanged; // Linear list of hashes that actually changed + for (auto iter = allHashes.first(); iter.is_valid(); ++iter) { + const metadb_index_hash hash = *iter; + + if ( comment_set(whichID, hash, comment) ) { + lstChanged += hash; + } + } + + FB2K_console_formatter() << "[foo_sample rating] " << lstChanged.get_count() << " entries updated"; + if (lstChanged.get_count() > 0) { + // This gracefully tells everyone about what just changed, in one pass regardless of how many items got altered + theAPI()->dispatch_refresh(whichID, lstChanged); + } + } + + class contextmenu_rating : public contextmenu_item_simple { + public: + GUID get_parent() { + return guid_foo_sample_contextmenu_group; + } + unsigned get_num_items() { + return 4; + } + void get_item_name(unsigned p_index, pfc::string_base & p_out) { + PFC_ASSERT( p_index < get_num_items() ); + switch(p_index) { + case 0: + p_out = "Cycle track rating"; break; + case 1: + p_out = "Cycle album rating"; break; + case 2: + p_out = "Cycle track comment"; break; + case 3: + p_out = "Cycle album comment"; break; + } + + } + void context_command(unsigned p_index, metadb_handle_list_cref p_data, const GUID& p_caller) { + PFC_ASSERT( p_index < get_num_items() ); + + const GUID whichID = ((p_index%2) == 1) ? guid_foo_sample_album_rating_index : guid_foo_sample_track_rating_index; + + if ( p_index < 2 ) { + // rating + cycleRating( whichID, p_data ); + } else { + cycleComment( whichID, p_data ); + + } + + } + GUID get_item_guid(unsigned p_index) { + switch(p_index) { + case 0: return guid_foo_sample_rating_contextmenu1; + case 1: return guid_foo_sample_rating_contextmenu2; + case 2: return guid_foo_sample_rating_contextmenu3; + case 3: return guid_foo_sample_rating_contextmenu4; + default: uBugCheck(); + } + } + bool get_item_description(unsigned p_index, pfc::string_base & p_out) { + PFC_ASSERT( p_index < get_num_items() ); + switch( p_index ) { + case 0: + p_out = "Alters foo_sample's track rating on one or more selected tracks. Use %foo_sample_track_rating% to display the rating."; + return true; + case 1: + p_out = "Alters foo_sample's album rating on one or more selected tracks. Use %foo_sample_album_rating% to display the rating."; + return true; + case 2: + p_out = "Alters foo_sample's track comment on one or more selected tracks. Use %foo_sample_track_comment% to display the comment."; + return true; + case 3: + p_out = "Alters foo_sample's album comment on one or more selected tracks. Use %foo_sample_album_comment% to display the comment."; + return true; + default: + PFC_ASSERT(!"Should not get here"); + return false; + } + } + }; + + static contextmenu_item_factory_t< contextmenu_rating > g_contextmenu_rating; + + static pfc::string_formatter formatRatingDump(const GUID & whichID) { + auto api = theAPI(); + pfc::list_t hashes; + api->get_all_hashes(whichID, hashes); + pfc::string_formatter message; + message << "The database contains " << hashes.get_count() << " hashes.\n"; + for( size_t hashWalk = 0; hashWalk < hashes.get_count(); ++ hashWalk ) { + auto hash = hashes[hashWalk]; + message << pfc::format_hex( hash, 8 ) << " : "; + auto rec = record_get(whichID, hash); + if ( rec.m_rating == rating_invalid ) message << "no rating"; + else message << "rating " << rec.m_rating; + if ( rec.m_comment.length() > 0 ) { + message << ", comment: " << rec.m_comment; + } + + metadb_handle_list tracks; + + // Note that this returns only handles present in the media library + + // Extra work is required if the user has no media library but only playlists, + // have to walk the playlists and match hashes by yourself instead of calling this method + api->get_ML_handles(whichID, hash, tracks); + + + if ( tracks.get_count() == 0 ) message << ", no matching tracks in Media Library\n"; + else { + message << ", " << tracks.get_count() << " matching track(s)\n"; + for( size_t w = 0; w < tracks.get_count(); ++ w ) { + // pfc string formatter operator<< for metadb_handle prints the location + message << tracks[w] << "\n"; + } + } + } + + return message; + } + + class mainmenu_rating : public mainmenu_commands { + public: + t_uint32 get_command_count() { + return 1; + } + GUID get_command(t_uint32 p_index) { + return guid_foo_sample_rating_mainmenu; + } + void get_name(t_uint32 p_index, pfc::string_base & p_out) { + PFC_ASSERT( p_index == 0 ); + p_out = "Dump rating database"; + } + bool get_description(t_uint32 p_index, pfc::string_base & p_out) { + PFC_ASSERT(p_index == 0); + p_out = "Shows a dump of the foo_sample rating database."; return true; + } + GUID get_parent() { + return guid_foo_sample_mainmenu_group; + } + void execute(t_uint32 p_index, service_ptr_t p_callback) { + PFC_ASSERT( p_index == 0 ); + + try { + + pfc::string_formatter dump; + dump << "==== TRACK RATING DUMP ====\n" << formatRatingDump( guid_foo_sample_track_rating_index ) << "\n\n"; + dump << "==== ALBUM RATING DUMP ====\n" << formatRatingDump( guid_foo_sample_album_rating_index ) << "\n\n"; + + popup_message::g_show(dump, "foo_sample rating dump"); + } catch(std::exception const & e) { + // should not really get here + popup_message::g_complain("Rating dump failed", e); + } + } + }; + static service_factory_single_t g_mainmenu_rating; + + + // This class provides our information for the properties dialog + class track_property_provider_impl : public track_property_provider_v2 { + public: + void workThisIndex(GUID const & whichID, const char * whichName, double priorityBase, metadb_handle_list_cref p_tracks, track_property_callback & p_out) { + auto client = clientByGUID( whichID ); + pfc::avltree_t hashes; + const size_t trackCount = p_tracks.get_count(); + for (size_t trackWalk = 0; trackWalk < trackCount; ++trackWalk) { + metadb_index_hash hash; + if (client->hashHandle(p_tracks[trackWalk], hash)) { + hashes += hash; + } + } + + pfc::string8 strAverage = "N/A", strMin = "N/A", strMax = "N/A"; + pfc::string8 strComment; + + { + size_t count = 0; + rating_t minval = rating_invalid; + rating_t maxval = rating_invalid; + uint64_t accum = 0; + bool bFirst = true; + bool bVarComments = false; + for (auto i = hashes.first(); i.is_valid(); ++i) { + auto rec = record_get(whichID, *i); + auto r = rec.m_rating; + if (r != rating_invalid) { + ++count; + accum += r; + + if (minval == rating_invalid || minval > r) minval = r; + if (maxval == rating_invalid || maxval < r) maxval = r; + } + + + if ( bFirst ) { + strComment = rec.m_comment; + } else if ( ! bVarComments ) { + if ( strComment != rec.m_comment ) { + bVarComments = true; + strComment = ""; + } + } + + bFirst = false; + } + + + if (count > 0) { + strMin = pfc::format_uint(minval); + strMax = pfc::format_uint(maxval); + strAverage = pfc::format_float((double)accum / (double)count, 0, 3); + } + } + + p_out.set_property(strPropertiesGroup, priorityBase + 0, PFC_string_formatter() << "Average " << whichName << " Rating", strAverage); + p_out.set_property(strPropertiesGroup, priorityBase + 1, PFC_string_formatter() << "Minimum " << whichName << " Rating", strMin); + p_out.set_property(strPropertiesGroup, priorityBase + 2, PFC_string_formatter() << "Maximum " << whichName << " Rating", strMax); + if ( strComment.length() > 0 ) { + p_out.set_property(strPropertiesGroup, priorityBase + 3, PFC_string_formatter() << whichName << " Comment", strComment); + } + } + void enumerate_properties(metadb_handle_list_cref p_tracks, track_property_callback & p_out) { + workThisIndex( guid_foo_sample_track_rating_index, "Track", 0, p_tracks, p_out ); + workThisIndex( guid_foo_sample_album_rating_index, "Album", 10, p_tracks, p_out); + } + void enumerate_properties_v2(metadb_handle_list_cref p_tracks, track_property_callback_v2 & p_out) { + if ( p_out.is_group_wanted( strPropertiesGroup ) ) { + enumerate_properties( p_tracks, p_out ); + } + } + + bool is_our_tech_info(const char * p_name) { + // If we do stuff with tech infos read from the file itself (see file_info::info_* methods), signal whether this field belongs to us + // We don't do any of this, hence false + return false; + } + + }; + + + static service_factory_single_t g_track_property_provider_impl; +} diff --git a/foobar2000/foo_sample/readme.txt b/foobar2000/foo_sample/readme.txt new file mode 100644 index 0000000..2aadef9 --- /dev/null +++ b/foobar2000/foo_sample/readme.txt @@ -0,0 +1,32 @@ +This component demonstrates: +* main.cpp : + * Declaring your component's version information. +* input_raw.cpp : + * Declaring your own "input" classes for decoding additional audio file formats. + * Calling file system services. +* preferences.cpp : + * Declaring your configuration variables. + * Creating preferences pages using simple WTL dialogs. + * Declaring advanced preferences entries. +* initquit.cpp : + * Sample initialization/shutdown callback service. +* dsp.cpp : + * Sample DSP. +* contextmenu.cpp : + * Sample context menu command. +* decode.cpp : + * Getting PCM data from arbitrary audio files. + * Use of the threaded_process API to easily run time-consuming tasks in worker threads with progress dialogs. +* mainmenu.cpp : + * Sample main menu command +* playback_state.cpp : + * Use of playback callbacks. + * Use of playback control. +* ui_element.cpp : + * Simple UI Element implementation. +* rating.cpp + * Minimal rating+comment implementation using metadb_index_client. + * Present your data via title formatting using metadb_display_field_provider. + * Present your data in the properties dialog using track_property_provider. + * Utility menu items. + * Basic use of stream formatters. \ No newline at end of file diff --git a/foobar2000/foo_sample/resource.h b/foobar2000/foo_sample/resource.h new file mode 100644 index 0000000..50d7220 --- /dev/null +++ b/foobar2000/foo_sample/resource.h @@ -0,0 +1,46 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by foo_sample.rc +// +#define IDD_PLAYBACK_STATE 101 +#define IDD_DSP 102 +#define IDD_UI_ELEMENT 103 +#define IDD_THREADS 105 +#define IDD_LISTCONTROL_DEMO 107 +#define IDI_ICON1 109 +#define IDI_SCROLL 109 +#define IDD_MYPREFERENCES 148 +#define IDC_BOGO1 1001 +#define IDC_BOGO2 1002 +#define IDC_PATTERN 1002 +#define IDC_STATE 1003 +#define IDC_PLAY 1004 +#define IDC_PAUSE 1005 +#define IDC_STOP 1006 +#define IDC_PREV 1007 +#define IDC_NEXT 1008 +#define IDC_RAND 1009 +#define IDC_CONTEXTMENU 1010 +#define IDC_SLIDER1 1012 +#define IDC_SLIDER 1012 +#define IDC_SLIDER_LABEL 1013 +#define IDC_LOCK_MIN_WIDTH 1014 +#define IDC_LOCK_MIN_HEIGHT 1015 +#define IDC_CHECK3 1016 +#define IDC_LOCK_MAX_WIDTH 1016 +#define IDC_LOCK_MAX_HEIGHT 1017 +#define IDC_STATIC_SIZE 1018 +#define IDC_LIST1 1019 +#define IDC_LIST 1019 +#define IDC_HEADER 1020 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 110 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1021 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/foobar2000/foo_sample/stdafx.h b/foobar2000/foo_sample/stdafx.h new file mode 100644 index 0000000..0c227c7 --- /dev/null +++ b/foobar2000/foo_sample/stdafx.h @@ -0,0 +1,2 @@ +#include + diff --git a/foobar2000/foo_sample/ui_and_threads.cpp b/foobar2000/foo_sample/ui_and_threads.cpp new file mode 100644 index 0000000..effc8f9 --- /dev/null +++ b/foobar2000/foo_sample/ui_and_threads.cpp @@ -0,0 +1,286 @@ +#include "stdafx.h" +#include "resource.h" + +// Modern multi threading with C++ +// Or: how I learned to stop worrying and love the lambdas + +#include // shared_ptr +#include +#include +#include +#include + + + +namespace { // anon namespace local classes for good measure + + + class CDemoDialog; // forward declaration + + // This is kept a separate shared_ptr'd struct because it may outlive CDemoDialog instance. + struct sharedData_t { + metadb_handle_list items; + CDemoDialog * owner; // weak reference to the owning dialog; can be only used after checking the validity by other means. + }; + + static const CDialogResizeHelper::Param resizeData[] = { + // Dialog resize handling matrix, defines how the controls scale with the dialog + // L T R B + {IDOK, 1,1,1,1 }, + {IDCANCEL, 1,1,1,1 }, + {IDC_HEADER, 0,0,1,0 }, + {IDC_LIST, 0,0,1,1 }, + + // current position of a control is determined by initial_position + factor * (current_dialog_size - initial_dialog_size) + // where factor is the value from the table above + // applied to all four values - left, top, right, bottom + // 0,0,0,0 means that a control doesn't react to dialog resizing (aligned to top+left, no resize) + // 1,1,1,1 means that the control is aligned to bottom+right but doesn't resize + // 0,0,1,0 means that the control disregards vertical resize (aligned to top) and changes its width with the dialog + }; + + // Minimum/maximum size, in dialog box units; see MSDN MapDialogRect for more info about dialog box units. + // The values can be declared constant here and will be scaled appropriately depending on display DPI. + static const CRect resizeMinMax(150, 100, 1000, 1000); + + class CDemoDialog : public CDialogImpl { + public: + enum { IDD = IDD_THREADS }; + CDemoDialog( metadb_handle_list_cref items ) : m_resizer(resizeData, resizeMinMax) { + m_sharedData = std::make_shared< sharedData_t > (); + m_sharedData->items = items; + m_sharedData->owner = this; + } + + BEGIN_MSG_MAP_EX(CDemoDialog) + CHAIN_MSG_MAP_MEMBER(m_resizer) + MSG_WM_INITDIALOG(OnInitDialog) + COMMAND_HANDLER_EX(IDOK, BN_CLICKED, OnOK) + COMMAND_HANDLER_EX(IDCANCEL, BN_CLICKED, OnCancel) + MSG_WM_CLOSE(OnClose) + MSG_WM_DESTROY(OnDestroy) + MSG_WM_SIZE(OnSize) + END_MSG_MAP() + private: + BOOL OnInitDialog(CWindow, LPARAM) { + uSetDlgItemText(*this, IDC_HEADER, PFC_string_formatter() << "Selected: " << m_sharedData->items.get_size() << " tracks." ); + m_listBox = GetDlgItem(IDC_LIST); + + m_statusBar.Create(*this, NULL, TEXT(""), WS_CHILD | WS_VISIBLE); + + m_statusBar.SetWindowText(L"Ready"); + + ShowWindow(SW_SHOW); + + return TRUE; // system should set focus + } + void OnSize(UINT nType, CSize size) { + // Tell statusbar that we got resized. CDialogResizeHelper can't do this for us. + m_statusBar.SendMessage(WM_SIZE); + } + void OnDestroy() { + cancelTask(); + } + + void OnClose() { + // NOTE if we do not handle WM_CLOSE, WM_COMMAND with IDCANCEL will be invoked, executing our cancel handler. + // We provide our own WM_CLOSE handler to provide a different response to closing the window. + DestroyWindow(); + } + + void OnCancel(UINT, int, CWindow) { + // If a task is active, cancel it + // otherwise destroy the dialog + if (! cancelTask() ) { + DestroyWindow(); + } else { + // Refresh UI + taskCompleted(); + } + } + void OnOK(UINT, int, CWindow) { + startTask(); + } + void startTask() { + cancelTask(); // cancel any running task + + GetDlgItem(IDCANCEL).SetWindowText(L"Cancel"); + m_statusBar.SetWindowText(L"Working..."); + + auto shared = m_sharedData; + auto aborter = std::make_shared(); + m_aborter = aborter; + + // New in fb2k 1.4.5: async_task_manager & splitTask + // Use fb2k::splitTask() for starting detached threads. + // In fb2k < 1.4.5, it will fall back to just starting a detached thread. + // fb2k 1.4.5+ async_task_manager cleanly deals with user exiting foobar2000 while a detached async task is running. + // Shutdown of foobar2000 process will be stalled until your task completes. + // If you use other means to ensure that the thread has finished, such as waiting for the thread to exit in your dialog's destructor, there's no need for this. + + fb2k::splitTask( [aborter, shared] { + // In worker thread! + try { + work( shared, aborter ); + } catch(exception_aborted) { + return; // user abort? + } catch(std::exception const & e) { + // should not really get here + logLineProc( shared, aborter, PFC_string_formatter() << "Critical error: " << e); + } + try { + mainThreadOp( aborter, [shared] { + shared->owner->taskCompleted(); + } ); + } catch(...) {} // mainThreadOp may throw exception_aborted + } ); + } + void taskCompleted() { + m_aborter.reset(); + GetDlgItem(IDCANCEL).SetWindowText(L"Close"); + m_statusBar.SetWindowText(L"Finished, ready"); + } + + static void mainThreadOp(std::shared_ptr aborter, std::function op ) { + aborter->check(); // are we getting aborted? + fb2k::inMainThread( [=] { + if ( aborter->is_set() ) return; // final user abort check + // Past this, we're main thread, the task has not been cancelled by the user and the dialog is still alive + // and any dialog methods can be safely called + op(); + } ); + } + + static void logLineProc(std::shared_ptr shared, std::shared_ptr aborter, const char * line_ ) { + pfc::string8 line( line_ ); // can't hold to the const char* we got passed, we have no guarantees about its lifetime + mainThreadOp( aborter, [shared, line] { + shared->owner->logLine(line); + } ); + } + static void work( std::shared_ptr shared, std::shared_ptr aborter ) { + // clear the log + mainThreadOp(aborter, [shared] { + shared->owner->clearLog(); + } ); + + // A convenience wrapper that calls logLineProc() + auto log = [shared, aborter] ( const char * line ) { + logLineProc(shared, aborter, line); + }; + // Use log(X) instead of logLineProc(shared, aborter, X) + + for( size_t trackWalk = 0; trackWalk < shared->items.get_size(); ++ trackWalk ) { + aborter->check(); + auto track = shared->items[trackWalk]; + log( PFC_string_formatter() << "Track: " << track ); + + try { + const auto path = track->get_path(); + const auto subsong = track->get_subsong_index(); + + // Not strictly needed, but we do it anyway + // Acquire a read lock on the file, so anyone trying to acquire a write lock will just wait till we have finished + auto lock = file_lock_manager::get()->acquire_read(path, *aborter); + + { + input_decoder::ptr dec; + input_entry::g_open_for_decoding(dec, nullptr, path, *aborter); + + file_info_impl info; + dec->get_info( subsong, info, *aborter ); + auto title = info.meta_get("title",0); + if ( title == nullptr ) log("Untitled"); + else log(PFC_string_formatter() << "Title: " << title ); + if ( info.get_length() > 0 ) log(PFC_string_formatter() << "Duration: " << pfc::format_time_ex(info.get_length(),6) ); + auto stats = dec->get_file_stats( *aborter ); + if ( stats.m_size != filesize_invalid ) log( PFC_string_formatter() << "Size: " << pfc::format_file_size_short(stats.m_size) ); + if ( stats.m_timestamp != filetimestamp_invalid ) log( PFC_string_formatter() << "Last modified: " << format_filetimestamp( stats.m_timestamp ) ); + + + dec->initialize( subsong, input_flag_simpledecode, * aborter ); + audio_chunk_impl chunk; + uint64_t numChunks = 0, numSamples = 0; + // duration_counter tool is a strictly accurate audio duration counter retaining all sample counts passed to it, immune to floatingpoint accuracy errors + duration_counter duration; + bool firstChunk = true; + while(dec->run(chunk, *aborter) ) { + if ( firstChunk ) { + auto spec = chunk.get_spec(); + log(PFC_string_formatter() << "Audio sample rate: " << spec.sampleRate ); + log(PFC_string_formatter() << "Audio channels: " << audio_chunk::g_formatChannelMaskDesc( spec.chanMask ) ); + firstChunk = false; + } + ++ numChunks; + duration += chunk; + numSamples += chunk.get_sample_count(); + } + log(PFC_string_formatter() << "Decoded " << numChunks << " chunks"); + log(PFC_string_formatter() << "Exact duration decoded: " << pfc::format_time_ex(duration.query(), 6) << ", " << numSamples << " samples" ); + } + + try { + auto aa = album_art_extractor::g_open( nullptr, path, *aborter ); + + if ( aa->have_entry( album_art_ids::cover_front, *aborter ) ) { + log("Album art: front cover found"); + } + if ( aa->have_entry( album_art_ids::cover_back, *aborter ) ) { + log("Album art: back cover found"); + } + if (aa->have_entry( album_art_ids::artist, *aborter ) ) { + log("Album art: artist picture found"); + } + } catch(exception_album_art_not_found) { + } catch(exception_album_art_unsupported_format) { + } + + } catch(exception_aborted) { + throw; + } catch(std::exception const & e) { + log( PFC_string_formatter() << "Failure: " << e); + } + } + log("All done."); + } + + bool cancelTask() { + bool ret = false; + auto aborter = pfc::replace_null_t(m_aborter); + if (aborter) { + ret = true; + aborter->set(); + logLine("Aborted by user."); + } + return ret; + } + + void logLine( const char * line ) { + m_listBox.AddString( pfc::stringcvt::string_os_from_utf8(line) ); + } + + void clearLog() { + m_listBox.ResetContent(); + } + + + // Worker thread aborter. It's re-created with the thread. If the task is ran more than once, each time it gets a new one. + // A commonly used alternative is to have abort_callback_impl m_aborter, and a blocking cancelTask() that waits for the thread to exit, without all the shared_ptrs and recreation of aborters. + // However that approach will freeze the UI if the worker thread is taking a long time to exit, as well as require some other shared_ptr based means for fb2k::inMainThread() ops to verify that the task is not being aborted / the dialog still exists. + // Therefore we use a shared_ptr'd aborter, which is used both to abort worker threads, and for main thread callbacks to know if the task that sent them is still valid. + std::shared_ptr m_aborter; + + // Data shared with the worker thread. It is created only once per dialog lifetime. + std::shared_ptr< sharedData_t > m_sharedData; + + CListBox m_listBox; + + CStatusBarCtrl m_statusBar; + + CDialogResizeHelper m_resizer; + }; +} + +void RunUIAndThreads(metadb_handle_list_cref data) { + // Equivalent to new CDemoDialog(data), with modeless registration and auto lifetime + fb2k::newDialog( data ); +} diff --git a/foobar2000/foo_sample/ui_element.cpp b/foobar2000/foo_sample/ui_element.cpp new file mode 100644 index 0000000..1190aa8 --- /dev/null +++ b/foobar2000/foo_sample/ui_element.cpp @@ -0,0 +1,82 @@ +#include "stdafx.h" + +#include +#include + + +namespace { + // Anonymous namespace : standard practice in fb2k components + // Nothing outside should have any reason to see these symbols, and we don't want funny results if another cpp has similarly named classes. + // service_factory at the bottom takes care of publishing our class. + + // This is our GUID. Substitute with your own when reusing code. + static const GUID guid_myelem = { 0xb46dc166, 0x88f3, 0x4b45, { 0x9f, 0x77, 0xab, 0x33, 0xf4, 0xc3, 0xf2, 0xe4 } }; + + class CMyElemWindow : public ui_element_instance, public CWindowImpl { + public: + // ATL window class declaration. Replace class name with your own when reusing code. + DECLARE_WND_CLASS_EX(TEXT("{DC2917D5-1288-4434-A28C-F16CFCE13C4B}"),CS_VREDRAW | CS_HREDRAW,(-1)); + + void initialize_window(HWND parent) {WIN32_OP(Create(parent) != NULL);} + + BEGIN_MSG_MAP_EX(CMyElemWindow) + MESSAGE_HANDLER(WM_LBUTTONDOWN,OnLButtonDown); + MSG_WM_ERASEBKGND(OnEraseBkgnd) + MSG_WM_PAINT(OnPaint) + END_MSG_MAP() + + CMyElemWindow(ui_element_config::ptr,ui_element_instance_callback_ptr p_callback); + HWND get_wnd() {return *this;} + void set_configuration(ui_element_config::ptr config) {m_config = config;} + ui_element_config::ptr get_configuration() {return m_config;} + static GUID g_get_guid() { + return guid_myelem; + } + static GUID g_get_subclass() {return ui_element_subclass_utility;} + static void g_get_name(pfc::string_base & out) {out = "Sample UI Element";} + static ui_element_config::ptr g_get_default_configuration() {return ui_element_config::g_create_empty(g_get_guid());} + static const char * g_get_description() {return "This is a sample UI Element.";} + + void notify(const GUID & p_what, t_size p_param1, const void * p_param2, t_size p_param2size); + private: + LRESULT OnLButtonDown(UINT,WPARAM,LPARAM,BOOL&) {m_callback->request_replace(this);return 0;} + void OnPaint(CDCHandle); + BOOL OnEraseBkgnd(CDCHandle); + ui_element_config::ptr m_config; + protected: + // this must be declared as protected for ui_element_impl_withpopup<> to work. + const ui_element_instance_callback_ptr m_callback; + }; + void CMyElemWindow::notify(const GUID & p_what, t_size p_param1, const void * p_param2, t_size p_param2size) { + if (p_what == ui_element_notify_colors_changed || p_what == ui_element_notify_font_changed) { + // we use global colors and fonts - trigger a repaint whenever these change. + Invalidate(); + } + } + CMyElemWindow::CMyElemWindow(ui_element_config::ptr config,ui_element_instance_callback_ptr p_callback) : m_callback(p_callback), m_config(config) { + } + + BOOL CMyElemWindow::OnEraseBkgnd(CDCHandle dc) { + CRect rc; WIN32_OP_D( GetClientRect(&rc) ); + CBrush brush; + WIN32_OP_D( brush.CreateSolidBrush( m_callback->query_std_color(ui_color_background) ) != NULL ); + WIN32_OP_D( dc.FillRect(&rc, brush) ); + return TRUE; + } + void CMyElemWindow::OnPaint(CDCHandle) { + CPaintDC dc(*this); + dc.SetTextColor( m_callback->query_std_color(ui_color_text) ); + dc.SetBkMode(TRANSPARENT); + SelectObjectScope fontScope(dc, (HGDIOBJ) m_callback->query_font_ex(ui_font_default) ); + const UINT format = DT_NOPREFIX | DT_CENTER | DT_VCENTER | DT_SINGLELINE; + CRect rc; + WIN32_OP_D( GetClientRect(&rc) ); + WIN32_OP_D( dc.DrawText(_T("This is a sample element."), -1, &rc, format) > 0 ); + } + + // ui_element_impl_withpopup autogenerates standalone version of our component and proper menu commands. Use ui_element_impl instead if you don't want that. + class ui_element_myimpl : public ui_element_impl_withpopup {}; + + static service_factory_single_t g_ui_element_myimpl_factory; + +} // namespace \ No newline at end of file diff --git a/foobar2000/foo_sample/ui_element_dialog.cpp b/foobar2000/foo_sample/ui_element_dialog.cpp new file mode 100644 index 0000000..4ee3fbb --- /dev/null +++ b/foobar2000/foo_sample/ui_element_dialog.cpp @@ -0,0 +1,178 @@ +#include "stdafx.h" + +#include "resource.h" + +#include +#include // WIN32_OP() +#include // CCheckBox +#include // ui_element_impl + +namespace { + // Anonymous namespace : standard practice in fb2k components + // Nothing outside should have any reason to see these symbols, and we don't want funny results if another cpp has similarly named classes. + // service_factory at the bottom takes care of publishing our class. + + + // I am Sample Component and this is *MY* GUID. + // Replace with your own when reusing code. Component authors with colliding GUIDs will be visited by Urdnot Wrex in person. + static const GUID guid_myelem = { 0x78ca1d7, 0x4e3a, 0x41d5, { 0xa5, 0xef, 0x9d, 0x1a, 0xf7, 0xd5, 0x79, 0xd0 } }; + + enum { + FlagLockMinWidth = 1 << 0, + FlagLockMinHeight = 1 << 1, + FlagLockMaxWidth = 1 << 2, + FlagLockMaxHeight = 1 << 3, + + FlagsDefault = 0 + }; + static const struct { + int btnID; + uint32_t flag; + } flagsAndButtons[] = { + { IDC_LOCK_MIN_WIDTH, FlagLockMinWidth }, + { IDC_LOCK_MIN_HEIGHT, FlagLockMinHeight }, + { IDC_LOCK_MAX_WIDTH, FlagLockMaxWidth }, + { IDC_LOCK_MAX_HEIGHT, FlagLockMaxHeight }, + }; + + class CDialogUIElem : public CDialogImpl, public ui_element_instance { + public: + CDialogUIElem( ui_element_config::ptr cfg, ui_element_instance_callback::ptr cb ) : m_callback(cb), m_flags( parseConfig(cfg) ) {} + + enum { IDD = IDD_UI_ELEMENT }; + + BEGIN_MSG_MAP_EX( CDialogUIElem ) + MSG_WM_INITDIALOG(OnInitDialog) + MSG_WM_SIZE(OnSize) + COMMAND_CODE_HANDLER_EX(BN_CLICKED, OnButtonClicked) + END_MSG_MAP() + + void initialize_window(HWND parent) {WIN32_OP(Create(parent) != NULL);} + HWND get_wnd() { return m_hWnd; } + void set_configuration(ui_element_config::ptr config) { + m_flags = parseConfig( config ); + if ( m_hWnd != NULL ) { + configToUI(); + } + m_callback->on_min_max_info_change(); + } + ui_element_config::ptr get_configuration() {return makeConfig(m_flags);} + static GUID g_get_guid() { + return guid_myelem; + } + static void g_get_name(pfc::string_base & out) {out = "Sample Dialog as UI Element";} + static ui_element_config::ptr g_get_default_configuration() { + return makeConfig( ); + } + static const char * g_get_description() {return "This is a sample UI Element using win32 dialog.";} + static GUID g_get_subclass() {return ui_element_subclass_utility;} + + ui_element_min_max_info get_min_max_info() { + ui_element_min_max_info ret; + + // Note that we play nicely with separate horizontal & vertical DPI. + // Such configurations have not been ever seen in circulation, but nothing stops us from supporting such. + CSize DPI = QueryScreenDPIEx( *this ); + + if ( DPI.cx <= 0 || DPI.cy <= 0 ) { // sanity + DPI = CSize(96, 96); + } + + if ( m_flags & FlagLockMinWidth ) { + ret.m_min_width = MulDiv( 200, DPI.cx, 96 ); + } + if ( m_flags & FlagLockMinHeight ) { + ret.m_min_height = MulDiv( 200, DPI.cy, 96 ); + } + if ( m_flags & FlagLockMaxWidth ) { + ret.m_max_width = MulDiv( 400, DPI.cx, 96 ); + } + if ( m_flags & FlagLockMaxHeight ) { + ret.m_max_height = MulDiv( 400, DPI.cy, 96 ); + } + + // Deal with WS_EX_STATICEDGE and alike that we might have picked from host + ret.adjustForWindow( *this ); + + return ret; + } + private: + static uint32_t parseConfig( ui_element_config::ptr cfg ) { + try { + ::ui_element_config_parser in ( cfg ); + uint32_t flags; in >> flags; + return flags; + } catch(exception_io_data) { + // If we got here, someone's feeding us nonsense, fall back to defaults + return FlagsDefault; + } + } + static ui_element_config::ptr makeConfig(uint32_t flags = FlagsDefault) { + ui_element_config_builder out; + out << flags; + return out.finish( g_get_guid() ); + } + void configToUI() { + for ( unsigned i = 0; i < PFC_TABSIZE( flagsAndButtons ); ++ i ) { + auto rec = flagsAndButtons[i]; + // CCheckBox: WTL-PP class overlaying ToggleCheck(bool) and bool IsChecked() over WTL CButton + CCheckBox cb ( GetDlgItem( rec.btnID ) ); + cb.ToggleCheck( (m_flags & rec.flag ) != 0 ); + } + } + void OnButtonClicked(UINT uNotifyCode, int nID, CWindow wndCtl) { + + uint32_t flagToFlip = 0; + for ( unsigned i = 0; i < PFC_TABSIZE( flagsAndButtons ); ++ i ) { + auto rec = flagsAndButtons[i]; + if ( rec.btnID == nID ) { + flagToFlip = rec.flag; + } + } + if ( flagToFlip != 0 ) { + uint32_t newFlags = m_flags; + CCheckBox cb ( wndCtl ); + if (cb.IsChecked()) { + newFlags |= flagToFlip; + } else { + newFlags &= ~flagToFlip; + } + if ( newFlags != m_flags ) { + m_flags = newFlags; + m_callback->on_min_max_info_change(); + } + } + } + + void OnSize(UINT, CSize s) { + auto DPI = QueryScreenDPIEx(*this); + + pfc::string_formatter msg; + msg << "Current size: "; + if ( DPI.cx > 0 && DPI.cy > 0 ) { + msg << MulDiv( s.cx, 96, DPI.cx ) << "x" << MulDiv( s.cy, 96, DPI.cy ) << " units, "; + } + msg << s.cx << "x" << s.cy << " pixels"; + + uSetDlgItemText( *this, IDC_STATIC_SIZE, msg ); + } + BOOL OnInitDialog(CWindow, LPARAM) { + configToUI(); + { + CRect rc; + // WIN32_OP_D() - Debug build only retval check and assert + // For stuff that practically never fails + WIN32_OP_D( GetClientRect( &rc ) ); + OnSize( 0, rc.Size() ); + } + + return FALSE; + } + const ui_element_instance_callback::ptr m_callback; + uint32_t m_flags; + }; + + + static service_factory_single_t< ui_element_impl< CDialogUIElem > > g_CDialogUIElem_factory; +} + diff --git a/foobar2000/foobar2000_component_client/component_client.cpp b/foobar2000/foobar2000_component_client/component_client.cpp new file mode 100644 index 0000000..b9b5e06 --- /dev/null +++ b/foobar2000/foobar2000_component_client/component_client.cpp @@ -0,0 +1,123 @@ +#include "../SDK/foobar2000.h" +#include "../SDK/component.h" + +static HINSTANCE g_hIns; + +static pfc::string_simple g_name,g_full_path; + +static bool g_services_available = false, g_initialized = false; + + + +namespace core_api +{ + + HINSTANCE get_my_instance() + { + return g_hIns; + } + + HWND get_main_window() + { + PFC_ASSERT( g_foobar2000_api != NULL ); + return g_foobar2000_api->get_main_window(); + } + const char* get_my_file_name() + { + return g_name; + } + + const char* get_my_full_path() + { + return g_full_path; + } + + bool are_services_available() + { + return g_services_available; + } + bool assert_main_thread() + { + return (g_services_available && g_foobar2000_api) ? g_foobar2000_api->assert_main_thread() : true; + } + + void ensure_main_thread() { + if (!is_main_thread()) uBugCheck(); + } + + bool is_main_thread() + { + return (g_services_available && g_foobar2000_api) ? g_foobar2000_api->is_main_thread() : true; + } + const char* get_profile_path() + { + PFC_ASSERT( g_foobar2000_api != NULL ); + return g_foobar2000_api->get_profile_path(); + } + + bool is_shutting_down() + { + return (g_services_available && g_foobar2000_api) ? g_foobar2000_api->is_shutting_down() : g_initialized; + } + bool is_initializing() + { + return (g_services_available && g_foobar2000_api) ? g_foobar2000_api->is_initializing() : !g_initialized; + } + bool is_portable_mode_enabled() { + PFC_ASSERT( g_foobar2000_api != NULL ); + return g_foobar2000_api->is_portable_mode_enabled(); + } + + bool is_quiet_mode_enabled() { + PFC_ASSERT( g_foobar2000_api != NULL ); + return g_foobar2000_api->is_quiet_mode_enabled(); + } +} + +namespace { + class foobar2000_client_impl : public foobar2000_client, private foobar2000_component_globals + { + public: + t_uint32 get_version() {return FOOBAR2000_CLIENT_VERSION;} + pservice_factory_base get_service_list() {return service_factory_base::__internal__list;} + + void get_config(stream_writer * p_stream,abort_callback & p_abort) { + cfg_var::config_write_file(p_stream,p_abort); + } + + void set_config(stream_reader * p_stream,abort_callback & p_abort) { + cfg_var::config_read_file(p_stream,p_abort); + } + + void set_library_path(const char * path,const char * name) { + g_full_path = path; + g_name = name; + } + + void services_init(bool val) { + if (val) g_initialized = true; + g_services_available = val; + } + + bool is_debug() { +#ifdef _DEBUG + return true; +#else + return false; +#endif + } + }; +} + +static foobar2000_client_impl g_client; + +extern "C" +{ + __declspec(dllexport) foobar2000_client * _cdecl foobar2000_get_interface(foobar2000_api * p_api,HINSTANCE hIns) + { + g_hIns = hIns; + g_foobar2000_api = p_api; + + return &g_client; + } +} diff --git a/foobar2000/foobar2000_component_client/foobar2000_component_client.vcxproj b/foobar2000/foobar2000_component_client/foobar2000_component_client.vcxproj new file mode 100644 index 0000000..e0e24c3 --- /dev/null +++ b/foobar2000/foobar2000_component_client/foobar2000_component_client.vcxproj @@ -0,0 +1,102 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + + {71AD2674-065B-48F5-B8B0-E1F9D3892081} + foobar2000_component_client + + + + StaticLibrary + false + Unicode + true + v141 + + + StaticLibrary + false + Unicode + v141 + + + + + + + + + + + + + <_ProjectFileVersion>10.0.30319.1 + $(Configuration)\ + $(Configuration)\ + $(Configuration)\ + $(Configuration)\ + + + + Disabled + EnableFastChecks + + + Level3 + true + ProgramDatabase + MultiThreadedDebugDLL + 4715 + + + _DEBUG;%(PreprocessorDefinitions) + 0x0409 + + + true + + + + + MinSpace + true + false + Fast + false + Level3 + true + ProgramDatabase + MultiThreadedDLL + /d2notypeopt %(AdditionalOptions) + 4715 + true + NDEBUG;_UNICODE;UNICODE;%(PreprocessorDefinitions) + + + NDEBUG;%(PreprocessorDefinitions) + 0x0409 + + + true + + + + + Disabled + EnableFastChecks + MaxSpeed + + + + + + \ No newline at end of file diff --git a/foobar2000/helpers/AutoComplete.cpp b/foobar2000/helpers/AutoComplete.cpp new file mode 100644 index 0000000..0e404d6 --- /dev/null +++ b/foobar2000/helpers/AutoComplete.cpp @@ -0,0 +1,49 @@ +#include "stdafx.h" +#include "AutoComplete.h" +#include // CLSID_AutoComplete +#include "../helpers/COM_utils.h" +#include "../helpers/dropdown_helper.h" +#include + +using PP::CEnumString; + +namespace { + class CACList_History : public CEnumString { + public: + CACList_History(cfg_dropdown_history_mt * var) : m_var(var) { Reset(); } + typedef ImplementCOMRefCounter TImpl; + + HRESULT STDMETHODCALLTYPE Reset() { + /*if (core_api::assert_main_thread())*/ { + ResetStrings(); + pfc::string8 state; m_var->get_state(state); + for (const char * walk = state;;) { + const char * next = strchr(walk, cfg_dropdown_history_mt::separator); + t_size len = (next != NULL) ? next - walk : ~0; + AddStringU(walk, len); + if (next == NULL) break; + walk = next + 1; + } + } + return __super::Reset(); + } + + HRESULT STDMETHODCALLTYPE Clone(IEnumString **ppenum) { + *ppenum = new TImpl(*this); return S_OK; + } + + private: + cfg_dropdown_history_mt * const m_var; + }; +} + +HRESULT InitializeDropdownAC(HWND comboBox, cfg_dropdown_history_mt & var, const char * initVal) { + var.on_init(comboBox, initVal); + COMBOBOXINFO ci = {}; ci.cbSize = sizeof(ci); + if (!GetComboBoxInfo(comboBox, &ci)) { + PFC_ASSERT(!"Should not get here - GetComboBoxInfo fail!"); + return E_FAIL; + } + pfc::com_ptr_t acl = new CACList_History::TImpl(&var); + return InitializeSimpleAC(ci.hwndItem, acl.get_ptr(), ACO_AUTOAPPEND|ACO_AUTOSUGGEST); +} diff --git a/foobar2000/helpers/AutoComplete.h b/foobar2000/helpers/AutoComplete.h new file mode 100644 index 0000000..db7b5cd --- /dev/null +++ b/foobar2000/helpers/AutoComplete.h @@ -0,0 +1,7 @@ +#pragma once + +#include + +class cfg_dropdown_history_mt; + +HRESULT InitializeDropdownAC(HWND comboBox, cfg_dropdown_history_mt & var, const char * initVal); diff --git a/foobar2000/helpers/BumpableElem.h b/foobar2000/helpers/BumpableElem.h new file mode 100644 index 0000000..cb46859 --- /dev/null +++ b/foobar2000/helpers/BumpableElem.h @@ -0,0 +1,73 @@ +#pragma once + +#include +#include "atl-misc.h" + +#include + +template +class ImplementBumpableElem : public TClass { +private: + typedef ImplementBumpableElem TSelf; +public: + template ImplementBumpableElem( arg_t && ... arg ) : TClass(std::forward(arg) ... ) {_init(); } + + BEGIN_MSG_MAP_EX(ImplementBumpableElem) + MSG_WM_DESTROY(OnDestroy) + CHAIN_MSG_MAP(__super) + END_MSG_MAP_HOOK() + + void notify(const GUID & p_what, t_size p_param1, const void * p_param2, t_size p_param2size) { + if (p_what == ui_element_notify_visibility_changed && p_param1 == 0 && m_flash.m_hWnd != NULL) m_flash.Deactivate(); + __super::notify(p_what, p_param1, p_param2, p_param2size); + } + + static bool Bump() { + for(auto walk = instances.cfirst(); walk.is_valid(); ++walk) { + if ((*walk)->_bump()) return true; + } + return false; + } + ~ImplementBumpableElem() throw() { + PFC_ASSERT(core_api::is_main_thread()); + instances -= this; + } +private: + void OnDestroy() throw() { + m_selfDestruct = true; + m_flash.CleanUp(); + SetMsgHandled(FALSE); + } + bool _bump() { + if (m_selfDestruct || this->m_hWnd == NULL) return false; + //PROBLEM: This assumes we're implementing service_base methods at this point. Explodes if called during constructors/destructors. + if (!this->m_callback->request_activation(this)) return false; + m_flash.Activate(*this); + this->set_default_focus(); + return true; + } + void _init() { + m_selfDestruct = false; + PFC_ASSERT(core_api::is_main_thread()); + instances += this; + } + static pfc::avltree_t instances; + bool m_selfDestruct; + CFlashWindow m_flash; +}; + +template +pfc::avltree_t *> ImplementBumpableElem::instances; + + +template class ui_element_impl_withpopup : public ui_element_impl, TInterface> { +public: + t_uint32 get_flags() {return ui_element_v2::KFlagHavePopupCommand | ui_element_v2::KFlagSupportsBump;} + bool bump() {return ImplementBumpableElem::Bump();} +}; + +template class ui_element_impl_visualisation : public ui_element_impl, TInterface> { +public: + t_uint32 get_flags() {return ui_element_v2::KFlagsVisualisation | ui_element_v2::KFlagSupportsBump;} + bool bump() {return ImplementBumpableElem::Bump();} +}; diff --git a/foobar2000/helpers/CDialogResizeHelper.h b/foobar2000/helpers/CDialogResizeHelper.h new file mode 100644 index 0000000..4ce4b5b --- /dev/null +++ b/foobar2000/helpers/CDialogResizeHelper.h @@ -0,0 +1,24 @@ +#pragma once + +#include "WindowPositionUtils.h" + +#include + +template class CDialogResizeHelperTracking : public CDialogResizeHelper { +public: + template CDialogResizeHelperTracking(const TParam (& src)[paramCount],CRect const& minMaxRange, TCfgVar & cfgVar) : CDialogResizeHelper(src, minMaxRange), m_tracker(cfgVar) {} + + BEGIN_MSG_MAP_EX(CDialogResizeHelperST) + CHAIN_MSG_MAP(CDialogResizeHelper) + CHAIN_MSG_MAP_MEMBER(m_tracker) + END_MSG_MAP() + +private: + TTracker m_tracker; +}; + +typedef CDialogResizeHelperTracking CDialogResizeHelperST; +typedef CDialogResizeHelperTracking CDialogResizeHelperPT; +typedef CDialogResizeHelperTracking CDialogResizeHelperST2; + +#define REDRAW_DIALOG_ON_RESIZE() if (uMsg == WM_SIZE) RedrawWindow(NULL, NULL, RDW_INVALIDATE | RDW_ERASE | RDW_ALLCHILDREN); diff --git a/foobar2000/helpers/COM_utils.h b/foobar2000/helpers/COM_utils.h new file mode 100644 index 0000000..7c46ec5 --- /dev/null +++ b/foobar2000/helpers/COM_utils.h @@ -0,0 +1,6 @@ +#pragma once + + +#include + +#define FB2K_COM_CATCH PP_COM_CATCH diff --git a/foobar2000/helpers/CPropVariant.h b/foobar2000/helpers/CPropVariant.h new file mode 100644 index 0000000..4c5f1fd --- /dev/null +++ b/foobar2000/helpers/CPropVariant.h @@ -0,0 +1,3 @@ +#pragma once + +#include \ No newline at end of file diff --git a/foobar2000/helpers/CSingleThreadWrapper.h b/foobar2000/helpers/CSingleThreadWrapper.h new file mode 100644 index 0000000..20894e7 --- /dev/null +++ b/foobar2000/helpers/CSingleThreadWrapper.h @@ -0,0 +1,91 @@ +#pragma once + +#include "ThreadUtils.h" +#include "rethrow.h" + +namespace ThreadUtils { + + // OLD single thread wrapper class used only by old WMA input + // Modern code should use cmdThread which is all kinds of prettier + template + class CSingleThreadWrapper : protected CVerySimpleThread { + private: + protected: + class command { + protected: + command() : m_abort(), m_completionEvent() {} + virtual void executeImpl(TBase &) {} + virtual ~command() {} + public: + void execute(TBase & obj) { + m_rethrow.exec( [this, &obj] {executeImpl(obj); } ); + SetEvent(m_completionEvent); + } + void rethrow() const { + m_rethrow.rethrow(); + } + CRethrow m_rethrow; + HANDLE m_completionEvent; + abort_callback * m_abort; + }; + + typedef std::function func_t; + + class command2 : public command { + public: + command2( func_t f ) : m_func(f) {} + void executeImpl(TBase & obj) { + m_func(obj); + } + private: + + func_t m_func; + }; + + typedef pfc::rcptr_t command_ptr; + + CSingleThreadWrapper() { + m_completionEvent.create(true,false); + this->StartThread(); + //start(); + } + + ~CSingleThreadWrapper() { + m_threadAbort.abort(); + this->WaitTillThreadDone(); + } + + void invokeCommand2( func_t f, abort_callback & abort) { + auto c = pfc::rcnew_t(f); + invokeCommand( c, abort ); + } + void invokeCommand(command_ptr cmd, abort_callback & abort) { + abort.check(); + m_completionEvent.set_state(false); + pfc::vartoggle_t abortToggle(cmd->m_abort, &abort); + pfc::vartoggle_t eventToggle(cmd->m_completionEvent, m_completionEvent.get() ); + m_commands.Add(cmd); + m_completionEvent.wait_for(-1); + //WaitAbortable(m_completionEvent.get(), abort); + cmd->rethrow(); + } + + private: + void ThreadProc() { + TRACK_CALL_TEXT("CSingleThreadWrapper entry"); + try { + TBase instance; + for(;;) { + command_ptr cmd; + if (processMsgs) m_commands.Get_MsgLoop(cmd, m_threadAbort); + else m_commands.Get(cmd, m_threadAbort); + cmd->execute(instance); + } + } catch(...) {} + if (processMsgs) ProcessPendingMessages(); + } + win32_event m_completionEvent; + CObjectQueue m_commands; + abort_callback_impl m_threadAbort; + }; +} \ No newline at end of file diff --git a/foobar2000/helpers/CTableEditHelper-Legacy.cpp b/foobar2000/helpers/CTableEditHelper-Legacy.cpp new file mode 100644 index 0000000..58726de --- /dev/null +++ b/foobar2000/helpers/CTableEditHelper-Legacy.cpp @@ -0,0 +1,80 @@ +#include "stdafx.h" +#include "CTableEditHelper-Legacy.h" +#include + +namespace InPlaceEdit { + void CTableEditHelper::TableEdit_Start(HWND p_listview, unsigned p_item, unsigned p_column, unsigned p_itemcount, unsigned p_columncount, unsigned p_basecolumn, unsigned p_flags) { + if (m_notify.is_valid() || p_columncount == 0 || p_itemcount == 0 || p_item >= p_itemcount || p_column >= p_columncount) return; + m_listview = p_listview; + m_item = p_item; + m_column = p_column; + m_itemcount = p_itemcount; + m_columncount = p_columncount; + m_basecolumn = p_basecolumn; + m_flags = p_flags; + _Start(); + } + + void CTableEditHelper::TableEdit_Abort(bool p_forwardcontent) { + if (m_notify.is_valid()) { + m_notify->orphan(); + m_notify.release(); + + if (p_forwardcontent && (m_flags & KFlagReadOnly) == 0) { + if (m_content.is_valid()) { + pfc::string8 temp(*m_content); + m_content.release(); + TableEdit_SetItemText(m_item, m_column, temp); + } + } else { + m_content.release(); + } + SetFocus(NULL); + TableEdit_Finished(); + } + } + + bool CTableEditHelper::TableEdit_GetItemText(unsigned p_item, unsigned p_column, pfc::string_base & p_out, unsigned & p_linecount) { + listview_helper::get_item_text(m_listview, p_item, p_column + m_basecolumn, p_out); + p_linecount = pfc::is_multiline(p_out) ? 5 : 1; + return true; + } + void CTableEditHelper::TableEdit_SetItemText(unsigned p_item, unsigned p_column, const char * p_text) { + listview_helper::set_item_text(m_listview, p_item, p_column + m_basecolumn, p_text); + } + + void CTableEditHelper::on_task_completion(unsigned p_taskid, unsigned p_state) { + if (p_taskid == KTaskID) { + m_notify.release(); + if (m_content.is_valid()) { + if (p_state & InPlaceEdit::KEditFlagContentChanged) { + TableEdit_SetItemText(m_item, m_column, *m_content); + } + m_content.release(); + } + /*if (InPlaceEdit::TableEditAdvance(m_item,m_column,m_itemcount,m_columncount,p_state))*/ + if (TableEdit_OnEditCompleted(m_item, m_column, p_state) && + InPlaceEdit::TableEditAdvance_ListView(m_listview, m_basecolumn, m_item, m_column, m_itemcount, m_columncount, p_state)) { + _Start(); + } else { + TableEdit_Finished(); + } + } + } + + CTableEditHelper::~CTableEditHelper() { + if (m_notify.is_valid()) { + m_notify->orphan(); + m_notify.release(); + } + } + + void CTableEditHelper::_Start() { + listview_helper::select_single_item(m_listview, m_item); + m_content.new_t(); + unsigned linecount = 1; + if (!TableEdit_GetItemText(m_item, m_column, *m_content, linecount)) return; + m_notify = completion_notify_create(this, KTaskID); + InPlaceEdit::Start_FromListViewEx(m_listview, m_item, m_column + m_basecolumn, linecount, m_flags, m_content, m_notify); + } +} \ No newline at end of file diff --git a/foobar2000/helpers/CTableEditHelper-Legacy.h b/foobar2000/helpers/CTableEditHelper-Legacy.h new file mode 100644 index 0000000..f4dce69 --- /dev/null +++ b/foobar2000/helpers/CTableEditHelper-Legacy.h @@ -0,0 +1,35 @@ +#pragma once +#include "inplace_edit.h" +#include + +namespace InPlaceEdit { + class CTableEditHelper { + public: + void TableEdit_Start(HWND p_listview, unsigned p_item, unsigned p_column, unsigned p_itemcount, unsigned p_columncount, unsigned p_basecolumn, unsigned p_flags = 0); + void TableEdit_Abort(bool p_forwardcontent); + bool TableEdit_IsActive() const {return m_notify.is_valid();} + + virtual bool TableEdit_GetItemText(unsigned p_item, unsigned p_column, pfc::string_base & p_out, unsigned & p_linecount); + virtual void TableEdit_SetItemText(unsigned p_item, unsigned p_column, const char * p_text); + + virtual void TableEdit_Finished() {} + + void on_task_completion(unsigned p_taskid, unsigned p_state); + ~CTableEditHelper(); + protected: + HWND TableEdit_GetListView() const { return m_listview; } + //return false to abort + virtual bool TableEdit_OnEditCompleted(unsigned item, unsigned column, unsigned state) { return true; } + private: + void _Start(); + enum { + KTaskID = 0xc0ffee + }; + HWND m_listview; + unsigned m_item, m_column; + unsigned m_itemcount, m_columncount, m_basecolumn; + unsigned m_flags; + pfc::rcptr_t m_content; + service_ptr_t m_notify; + }; +} \ No newline at end of file diff --git a/foobar2000/helpers/CallForwarder.h b/foobar2000/helpers/CallForwarder.h new file mode 100644 index 0000000..7c261ad --- /dev/null +++ b/foobar2000/helpers/CallForwarder.h @@ -0,0 +1,60 @@ +#pragma once + +namespace CF { + template class _inMainThread : public main_thread_callback { + public: + _inMainThread(obj_t const & obj, const arg_t & arg) : m_obj(obj), m_arg(arg) {} + + void callback_run() { + if (m_obj.IsValid()) callInMainThread::callThis(&*m_obj, m_arg); + } + private: + obj_t m_obj; + arg_t m_arg; + }; + + template class CallForwarder { + private: + CallForwarder() = delete; + protected: + CallForwarder(TWhat * ptr) : m_ptr(pfc::rcnew_t(ptr)) {} + void Orphan() { + *m_ptr = NULL; + } + public: + bool IsValid() const { + PFC_ASSERT( m_ptr.is_valid() ); + return m_ptr.is_valid() && *m_ptr != NULL; + } + bool IsEmpty() const { return !IsValid(); } + + TWhat * operator->() const { + PFC_ASSERT( IsValid() ); + return *m_ptr; + } + + TWhat & operator*() const { + PFC_ASSERT( IsValid() ); + return **m_ptr; + } + + template + void callInMainThread(const arg_t & arg) { + main_thread_callback_add( new service_impl_t<_inMainThread< CallForwarder, arg_t> >(*this, arg) ); + } + private: + const pfc::rcptr_t m_ptr; + }; + + template class CallForwarderMaster : public CallForwarder { + public: + CallForwarderMaster(TWhat * ptr) : CallForwarder(ptr) {PFC_ASSERT(ptr!=NULL);} + ~CallForwarderMaster() { this->Orphan(); } + using CallForwarder::Orphan; + private: + CallForwarderMaster() = delete; + CallForwarderMaster( const CallForwarderMaster & ) = delete; + void operator=( const CallForwarderMaster & ) = delete; + }; + +} \ No newline at end of file diff --git a/foobar2000/helpers/CmdThread.h b/foobar2000/helpers/CmdThread.h new file mode 100644 index 0000000..be15ab5 --- /dev/null +++ b/foobar2000/helpers/CmdThread.h @@ -0,0 +1,132 @@ +#pragma once +#include +#include +#include +#include +#include "rethrow.h" +#include + +namespace ThreadUtils { + + // Serialize access to some resource to a single thread + // Execute blocking/nonabortable methods in with proper abortability (detach on abort and move on) + class cmdThread { + public: + typedef std::function func_t; + typedef pfc::waitQueue queue_t; + typedef std::function funcAbortable_t; + + protected: + std::function makeWorker() { + auto q = m_queue; + auto x = m_atExit; + return [q, x] { + for ( ;; ) { + func_t f; + if (!q->get(f)) break; + try { f(); } catch(...) {} + } + // No guard for atExit access, as nobody is supposed to be still able to call host object methods by the time we get here + for( auto i = x->begin(); i != x->end(); ++ i ) { + auto f = *i; + try { f(); } catch(...) {} + } + }; + }; + std::function makeWorker2( std::function updater, double interval) { + auto q = m_queue; + auto x = m_atExit; + return [=] { + pfc::lores_timer t; t.start(); + for ( ;; ) { + { + bool bWorkReady = false; + double left = interval - t.query(); + if ( left > 0 ) { + if (q->wait_read( left )) bWorkReady = true; + } + + if (!bWorkReady) { + updater(); + t.start(); + continue; + } + } + + func_t f; + if (!q->get(f)) break; + try { f(); } catch(...) {} + } + // No guard for atExit access, as nobody is supposed to be still able to call host object methods by the time we get here + for( auto i = x->begin(); i != x->end(); ++ i ) { + auto f = *i; + try { f(); } catch(...) {} + } + }; + }; + + // For derived classes: create new instance without starting thread, supply thread using by yourself + class noCreate {}; + cmdThread( noCreate ) {} + public: + + cmdThread() { + pfc::splitThread( makeWorker() ); + } + + void atExit( func_t f ) { + m_atExit->push_back(f); + } + ~cmdThread() { + m_queue->set_eof(); + } + void runSynchronously( func_t f ) { runSynchronously_(f, nullptr); } + void runSynchronously_( func_t f, abort_callback * abortOrNull ) { + auto evt = m_eventPool.make(); + evt->set_state(false); + auto rethrow = std::make_shared(); + auto worker2 = [f, rethrow, evt] { + rethrow->exec(f); + evt->set_state( true ); + }; + + add ( worker2 ); + + if ( abortOrNull != nullptr ) { + abortOrNull->waitForEvent( * evt, -1 ); + } else { + evt->wait_for(-1); + } + + m_eventPool.put( evt ); + + rethrow->rethrow(); + } + void runSynchronously( func_t f, abort_callback & abort ) { + runSynchronously_(f, &abort); + } + void runSynchronously2( funcAbortable_t f, abort_callback & abort ) { + auto subAbort = m_abortPool.make(); + subAbort->reset(); + auto worker = [subAbort, f] { + f(*subAbort); + }; + + try { + runSynchronously( worker, abort ); + } catch(...) { + subAbort->set(); throw; + } + + m_abortPool.put( subAbort ); + } + + void add( func_t f ) { m_queue->put( f ); } + private: + pfc::objPool m_eventPool; + pfc::objPool m_abortPool; + std::shared_ptr m_queue = std::make_shared(); + typedef std::list atExit_t; + std::shared_ptr m_atExit = std::make_shared< atExit_t >(); + }; +} diff --git a/foobar2000/helpers/ProcessUtils.h b/foobar2000/helpers/ProcessUtils.h new file mode 100644 index 0000000..bfd70c4 --- /dev/null +++ b/foobar2000/helpers/ProcessUtils.h @@ -0,0 +1,279 @@ +#pragma once + +#ifdef FOOBAR2000_DESKTOP_WINDOWS + +#include + +namespace ProcessUtils { + class PipeIO : public stream_reader, public stream_writer { + public: + PFC_DECLARE_EXCEPTION(timeout, exception_io, "Timeout"); + PipeIO(HANDLE handle, HANDLE hEvent, bool processMessages, DWORD timeOut = INFINITE) : m_handle(handle), m_event(hEvent), m_processMessages(processMessages), m_timeOut(timeOut) { + } + ~PipeIO() { + } + + void write(const void * p_buffer,size_t p_bytes, abort_callback & abort) { + if (p_bytes == 0) return; + OVERLAPPED ol = {}; + ol.hEvent = m_event; + ResetEvent(m_event); + DWORD bytesWritten; + SetLastError(NO_ERROR); + if (WriteFile( m_handle, p_buffer, pfc::downcast_guarded(p_bytes), &bytesWritten, &ol)) { + // succeeded already? + if (bytesWritten != p_bytes) throw exception_io(); + return; + } + + { + const DWORD code = GetLastError(); + if (code != ERROR_IO_PENDING) exception_io_from_win32(code); + } + const HANDLE handles[] = {m_event, abort.get_abort_event()}; + SetLastError(NO_ERROR); + DWORD state = myWait(_countof(handles), handles); + if (state == WAIT_OBJECT_0) { + try { + WIN32_IO_OP( GetOverlappedResult(m_handle,&ol,&bytesWritten,TRUE) ); + } catch(...) { + _cancel(ol); + throw; + } + if (bytesWritten != p_bytes) throw exception_io(); + return; + } + _cancel(ol); + abort.check(); + throw timeout(); + } + size_t read(void * p_buffer,size_t p_bytes, abort_callback & abort) { + uint8_t * ptr = (uint8_t*) p_buffer; + size_t done = 0; + while(done < p_bytes) { + abort.check(); + size_t delta = readPass(ptr + done, p_bytes - done, abort); + if (delta == 0) break; + done += delta; + } + return done; + } + size_t readPass(void * p_buffer,size_t p_bytes, abort_callback & abort) { + if (p_bytes == 0) return 0; + OVERLAPPED ol = {}; + ol.hEvent = m_event; + ResetEvent(m_event); + DWORD bytesDone; + SetLastError(NO_ERROR); + if (ReadFile( m_handle, p_buffer, pfc::downcast_guarded(p_bytes), &bytesDone, &ol)) { + // succeeded already? + return bytesDone; + } + + { + const DWORD code = GetLastError(); + switch(code) { + case ERROR_HANDLE_EOF: + return 0; + case ERROR_IO_PENDING: + break; // continue + default: + exception_io_from_win32(code); + }; + } + + const HANDLE handles[] = {m_event, abort.get_abort_event()}; + SetLastError(NO_ERROR); + DWORD state = myWait(_countof(handles), handles); + if (state == WAIT_OBJECT_0) { + SetLastError(NO_ERROR); + if (!GetOverlappedResult(m_handle,&ol,&bytesDone,TRUE)) { + const DWORD code = GetLastError(); + if (code == ERROR_HANDLE_EOF) bytesDone = 0; + else { + _cancel(ol); + exception_io_from_win32(code); + } + } + return bytesDone; + } + _cancel(ol); + abort.check(); + throw timeout(); + } + private: + DWORD myWait(DWORD count, const HANDLE * handles) { + if (m_processMessages) { + for(;;) { + DWORD state = MsgWaitForMultipleObjects(count, handles, FALSE, m_timeOut, QS_ALLINPUT); + if (state == WAIT_OBJECT_0 + count) { + ProcessPendingMessages(); + } else { + return state; + } + } + } else { + return WaitForMultipleObjects(count, handles, FALSE, m_timeOut); + } + } + void _cancel(OVERLAPPED & ol) { + #if _WIN32_WINNT >= 0x600 + CancelIoEx(m_handle,&ol); + #else + CancelIo(m_handle); + #endif + } + static void ProcessPendingMessages() { + MSG msg = {}; + while(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) DispatchMessage(&msg); + } + + HANDLE m_handle; + HANDLE m_event; + const DWORD m_timeOut; + const bool m_processMessages; + }; + + class SubProcess : public stream_reader, public stream_writer { + public: + PFC_DECLARE_EXCEPTION(failure, std::exception, "Unexpected failure"); + + SubProcess(const char * exePath, DWORD timeOutMS = 60*1000) : ExePath(exePath), hStdIn(), hStdOut(), hProcess(), ProcessMessages(false), TimeOutMS(timeOutMS) { + HANDLE ev; + WIN32_OP( (ev = CreateEvent(NULL, TRUE, FALSE, NULL)) != NULL ); + hEventRead = ev; + WIN32_OP( (ev = CreateEvent(NULL, TRUE, FALSE, NULL)) != NULL ); + hEventWrite = ev; + Restart(); + } + void Restart() { + CleanUp(); + STARTUPINFO si = {}; + try { + si.cb = sizeof(si); + si.dwFlags = STARTF_USESTDHANDLES | STARTF_FORCEOFFFEEDBACK; + //si.wShowWindow = SW_HIDE; + + myCreatePipeOut(si.hStdInput, hStdIn); + myCreatePipeIn(hStdOut, si.hStdOutput); + si.hStdError = GetStdHandle(STD_ERROR_HANDLE); + + PROCESS_INFORMATION pi = {}; + try { + WIN32_OP( CreateProcess(pfc::stringcvt::string_os_from_utf8(ExePath), NULL, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi) ); + } catch(std::exception const & e) { + throw failure(PFC_string_formatter() << "Could not start the worker process - " << e); + } + hProcess = pi.hProcess; _Close(pi.hThread); + } catch(...) { + _Close(si.hStdInput); + _Close(si.hStdOutput); + CleanUp(); throw; + } + _Close(si.hStdInput); + _Close(si.hStdOutput); + } + ~SubProcess() { + CleanUp(); + CloseHandle(hEventRead); + CloseHandle(hEventWrite); + } + + bool IsRunning() const { + return hProcess != NULL; + } + void Detach() { + CleanUp(true); + } + bool ProcessMessages; + DWORD TimeOutMS; + + + void write(const void * p_buffer,size_t p_bytes, abort_callback & abort) { + PipeIO writer(hStdIn, hEventWrite, ProcessMessages, TimeOutMS); + writer.write(p_buffer, p_bytes, abort); + } + size_t read(void * p_buffer,size_t p_bytes, abort_callback & abort) { + PipeIO reader(hStdOut, hEventRead, ProcessMessages, TimeOutMS); + return reader.read(p_buffer, p_bytes, abort); + } + void SetPriority(DWORD val) { + SetPriorityClass(hProcess, val); + } + protected: + HANDLE hStdIn, hStdOut, hProcess, hEventRead, hEventWrite; + const pfc::string8 ExePath; + + void CleanUp(bool bDetach = false) { + _Close(hStdIn); _Close(hStdOut); + if (hProcess != NULL) { + if (!bDetach) { + if (WaitForSingleObject(hProcess, TimeOutMS) != WAIT_OBJECT_0) { + //PFC_ASSERT( !"Should not get here - worker stuck" ); + FB2K_console_formatter() << pfc::string_filename_ext(ExePath) << " unresponsive - terminating"; + TerminateProcess(hProcess, -1); + } + } + _Close(hProcess); + } + } + private: + static void _Close(HANDLE & h) { + if (h != NULL) {CloseHandle(h); h = NULL;} + } + + static void myCreatePipe(HANDLE & in, HANDLE & out) { + SECURITY_ATTRIBUTES Attributes = { sizeof(SECURITY_ATTRIBUTES), 0, true }; + WIN32_OP( CreatePipe( &in, &out, &Attributes, 0 ) ); + } + + static pfc::string_formatter makePipeName() { + GUID id; + CoCreateGuid (&id); + return PFC_string_formatter() << "\\\\.\\pipe\\" << pfc::print_guid(id); + } + + static void myCreatePipeOut(HANDLE & in, HANDLE & out) { + SECURITY_ATTRIBUTES Attributes = { sizeof(SECURITY_ATTRIBUTES), 0, true }; + const pfc::stringcvt::string_os_from_utf8 pipeName( makePipeName() ); + SetLastError(NO_ERROR); + HANDLE pipe = CreateNamedPipe( + pipeName, + FILE_FLAG_FIRST_PIPE_INSTANCE | PIPE_ACCESS_OUTBOUND | FILE_FLAG_OVERLAPPED, + PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT, + 1, + 1024*64, + 1024*64, + NMPWAIT_USE_DEFAULT_WAIT,&Attributes); + if (pipe == INVALID_HANDLE_VALUE) throw exception_win32(GetLastError()); + + in = CreateFile(pipeName,GENERIC_READ,FILE_SHARE_READ|FILE_SHARE_WRITE,&Attributes,OPEN_EXISTING,0,NULL); + DuplicateHandle ( GetCurrentProcess(), pipe, GetCurrentProcess(), &out, 0, FALSE, DUPLICATE_SAME_ACCESS ); + CloseHandle(pipe); + } + + static void myCreatePipeIn(HANDLE & in, HANDLE & out) { + SECURITY_ATTRIBUTES Attributes = { sizeof(SECURITY_ATTRIBUTES), 0, true }; + const pfc::stringcvt::string_os_from_utf8 pipeName( makePipeName() ); + SetLastError(NO_ERROR); + HANDLE pipe = CreateNamedPipe( + pipeName, + FILE_FLAG_FIRST_PIPE_INSTANCE | PIPE_ACCESS_INBOUND | FILE_FLAG_OVERLAPPED, + PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT, + 1, + 1024*64, + 1024*64, + NMPWAIT_USE_DEFAULT_WAIT,&Attributes); + if (pipe == INVALID_HANDLE_VALUE) throw exception_win32(GetLastError()); + + out = CreateFile(pipeName,GENERIC_WRITE,FILE_SHARE_READ|FILE_SHARE_WRITE,&Attributes,OPEN_EXISTING,0,NULL); + DuplicateHandle ( GetCurrentProcess(), pipe, GetCurrentProcess(), &in, 0, FALSE, DUPLICATE_SAME_ACCESS ); + CloseHandle(pipe); + } + + PFC_CLASS_NOT_COPYABLE_EX(SubProcess) + }; +} + +#endif // FOOBAR2000_DESKTOP_WINDOWS + diff --git a/foobar2000/helpers/ProfileCache.h b/foobar2000/helpers/ProfileCache.h new file mode 100644 index 0000000..32cc034 --- /dev/null +++ b/foobar2000/helpers/ProfileCache.h @@ -0,0 +1,39 @@ +#pragma once + +namespace ProfileCache { + inline file::ptr FetchFile(const char * context, const char * name, const char * webURL, t_filetimestamp acceptableAge, abort_callback & abort) { + const double timeoutVal = 5; + + auto path = core_api::pathInProfile( context ); + auto fsLocal = filesystem::get(path); + fsLocal->make_directory(path, abort); + path.add_filename( name ); + + bool fetch = false; + file::ptr fLocal; + + try { + fLocal = fsLocal->openWriteExisting(path, abort, timeoutVal ); + fetch = fLocal->get_timestamp(abort) < filetimestamp_from_system_timer() - acceptableAge; + } catch(exception_io_not_found) { + fLocal = fsLocal->openWriteNew(path, abort, timeoutVal); + fetch = true; + } + if (fetch) { + try { + fLocal->resize(0, abort); + file::ptr fRemote; + filesystem::g_open(fRemote, webURL, filesystem::open_mode_read, abort); + file::g_transfer_file(fRemote, fLocal, abort ); + } catch(exception_io) { + fLocal.release(); + try { + retryOnSharingViolation(timeoutVal, abort, [&] {fsLocal->remove(path, abort);} ); + } catch(...) {} + throw; + } + fLocal->seek(0, abort); + } + return fLocal; + } +}; diff --git a/foobar2000/helpers/StdAfx.cpp b/foobar2000/helpers/StdAfx.cpp new file mode 100644 index 0000000..095ce4d --- /dev/null +++ b/foobar2000/helpers/StdAfx.cpp @@ -0,0 +1,6 @@ +// stdafx.cpp : source file that includes just the standard includes +// foobar2000_sdk_helpers.pch will be the pre-compiled header +// stdafx.obj will contain the pre-compiled type information + +#include "stdafx.h" + diff --git a/foobar2000/helpers/StdAfx.h b/foobar2000/helpers/StdAfx.h new file mode 100644 index 0000000..0d7e085 --- /dev/null +++ b/foobar2000/helpers/StdAfx.h @@ -0,0 +1,20 @@ +// stdafx.h : include file for standard system include files, +// or project specific include files that are used frequently, but +// are changed infrequently +// + +#if !defined(AFX_STDAFX_H__6356EC2B_6DD1_4BE8_935C_87ECBA8697E4__INCLUDED_) +#define AFX_STDAFX_H__6356EC2B_6DD1_4BE8_935C_87ECBA8697E4__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 + +#include "foobar2000+atl.h" + +// TODO: reference additional headers your program requires here + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_STDAFX_H__6356EC2B_6DD1_4BE8_935C_87ECBA8697E4__INCLUDED_) diff --git a/foobar2000/helpers/ThreadUtils.cpp b/foobar2000/helpers/ThreadUtils.cpp new file mode 100644 index 0000000..28651b0 --- /dev/null +++ b/foobar2000/helpers/ThreadUtils.cpp @@ -0,0 +1,139 @@ +#include "StdAfx.h" + +#include "ThreadUtils.h" +#include "rethrow.h" + +#include + +namespace ThreadUtils { + bool CRethrow::exec( std::function f ) throw() { + m_rethrow = nullptr; + bool rv = false; + try { + f(); + rv = true; + } catch( ... ) { + auto e = std::current_exception(); + m_rethrow = [e] { std::rethrow_exception(e); }; + } + + return rv; + } + + void CRethrow::rethrow() const { + if ( m_rethrow ) m_rethrow(); + } +} +#ifdef _WIN32 + +#include "win32_misc.h" + +#ifdef FOOBAR2000_MOBILE +#include +#endif + + +namespace ThreadUtils { + bool WaitAbortable(HANDLE ev, abort_callback & abort, DWORD timeout) { + const HANDLE handles[2] = {ev, abort.get_abort_event()}; + SetLastError(0); + const DWORD status = WaitForMultipleObjects(2, handles, FALSE, timeout); + switch(status) { + case WAIT_TIMEOUT: + PFC_ASSERT( timeout != INFINITE ); + return false; + case WAIT_OBJECT_0: + return true; + case WAIT_OBJECT_0 + 1: + throw exception_aborted(); + case WAIT_FAILED: + WIN32_OP_FAIL(); + default: + uBugCheck(); + } + } +#ifdef FOOBAR2000_DESKTOP_WINDOWS + void ProcessPendingMessagesWithDialog(HWND hDialog) { + MSG msg = {}; + while(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { + if (!IsDialogMessage(hDialog, &msg)) { + DispatchMessage(&msg); + } + } + } + void ProcessPendingMessages() { + MSG msg = {}; + while(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { + DispatchMessage(&msg); + } + } + void WaitAbortable_MsgLoop(HANDLE ev, abort_callback & abort) { + abort.check(); + const HANDLE handles[2] = {ev, abort.get_abort_event()}; + MultiWait_MsgLoop(handles, 2); + abort.check(); + } + t_size MultiWaitAbortable_MsgLoop(const HANDLE * ev, t_size evCount, abort_callback & abort) { + abort.check(); + pfc::array_t handles; handles.set_size(evCount + 1); + handles[0] = abort.get_abort_event(); + pfc::memcpy_t(handles.get_ptr() + 1, ev, evCount); + DWORD status = MultiWait_MsgLoop(handles.get_ptr(), handles.get_count()); + abort.check(); + return (size_t)(status - WAIT_OBJECT_0); + } + + void SleepAbortable_MsgLoop(abort_callback & abort, DWORD timeout) { + HANDLE handles[] = { abort.get_abort_event() }; + MultiWait_MsgLoop(handles, 1, timeout); + abort.check(); + } + + bool WaitAbortable_MsgLoop(HANDLE ev, abort_callback & abort, DWORD timeout) { + abort.check(); + HANDLE handles[2] = { abort.get_abort_event(), ev }; + DWORD status = MultiWait_MsgLoop(handles, 2, timeout); + abort.check(); + return status != WAIT_TIMEOUT; + } + + DWORD MultiWait_MsgLoop(const HANDLE* ev, DWORD evCount) { + for (;; ) { + SetLastError(0); + const DWORD status = MsgWaitForMultipleObjects((DWORD) evCount, ev, FALSE, INFINITE, QS_ALLINPUT); + if (status == WAIT_FAILED) WIN32_OP_FAIL(); + if (status == WAIT_OBJECT_0 + evCount) { + ProcessPendingMessages(); + } else if ( status >= WAIT_OBJECT_0 && status < WAIT_OBJECT_0 + evCount ) { + return status; + } else { + uBugCheck(); + } + } + } + + DWORD MultiWait_MsgLoop(const HANDLE* ev, DWORD evCount, DWORD timeout) { + if (timeout == INFINITE) return MultiWait_MsgLoop(ev, evCount); + const DWORD entry = GetTickCount(); + DWORD now = entry; + for (;;) { + const DWORD done = now - entry; + if (done >= timeout) return WAIT_TIMEOUT; + SetLastError(0); + const DWORD status = MsgWaitForMultipleObjects((DWORD)evCount, ev, FALSE, timeout - done, QS_ALLINPUT); + if (status == WAIT_FAILED) WIN32_OP_FAIL(); + if (status == WAIT_OBJECT_0 + evCount) { + ProcessPendingMessages(); + } else if (status == WAIT_TIMEOUT || (status >= WAIT_OBJECT_0 && status < WAIT_OBJECT_0 + evCount) ) { + return status; + } else { + uBugCheck(); + } + now = GetTickCount(); + } + } + +#endif // FOOBAR2000_DESKTOP_WINDOWS + +} +#endif \ No newline at end of file diff --git a/foobar2000/helpers/ThreadUtils.h b/foobar2000/helpers/ThreadUtils.h new file mode 100644 index 0000000..70afe36 --- /dev/null +++ b/foobar2000/helpers/ThreadUtils.h @@ -0,0 +1,57 @@ +#pragma once +#include "fb2k_threads.h" + +#ifdef _WIN32 +#include + +namespace ThreadUtils { + bool WaitAbortable(HANDLE ev, abort_callback & abort, DWORD timeout = INFINITE); + + void ProcessPendingMessages(); + void ProcessPendingMessagesWithDialog(HWND hDialog); + + void WaitAbortable_MsgLoop(HANDLE ev, abort_callback & abort); + + t_size MultiWaitAbortable_MsgLoop(const HANDLE * ev, t_size evCount, abort_callback & abort); + void SleepAbortable_MsgLoop(abort_callback & abort, DWORD timeout /*must not be INFINITE*/); + bool WaitAbortable_MsgLoop(HANDLE ev, abort_callback & abort, DWORD timeout /*must not be INFINITE*/); + + DWORD MultiWait_MsgLoop(const HANDLE* ev, DWORD evCount, DWORD timeout); + DWORD MultiWait_MsgLoop(const HANDLE* ev, DWORD evCount); + + template + class CObjectQueue { + public: + CObjectQueue() { m_event.create(true,false); } + + template void Add(const TSource & source) { + insync(m_sync); + m_content.add_item(source); + if (m_content.get_count() == 1) m_event.set_state(true); + } + template void Get(TDestination & out, abort_callback & abort) { + WaitAbortable(m_event.get(), abort); + _Get(out); + } + + template void Get_MsgLoop(TDestination & out, abort_callback & abort) { + WaitAbortable_MsgLoop(m_event.get(), abort); + _Get(out); + } + + private: + template void _Get(TDestination & out) { + insync(m_sync); + auto iter = m_content.cfirst(); + FB2K_DYNAMIC_ASSERT( iter.is_valid() ); + out = *iter; + m_content.remove(iter); + if (m_content.get_count() == 0) m_event.set_state(false); + } + win32_event m_event; + critical_section m_sync; + pfc::chain_list_v2_t m_content; + }; + +} +#endif // _WIN32 diff --git a/foobar2000/helpers/VisUtils.cpp b/foobar2000/helpers/VisUtils.cpp new file mode 100644 index 0000000..fad53e9 --- /dev/null +++ b/foobar2000/helpers/VisUtils.cpp @@ -0,0 +1,38 @@ +#include "stdafx.h" + +#include "VisUtils.h" + +namespace VisUtils { + void PrepareFFTChunk(audio_chunk const & source, audio_chunk & out, double centerOffset) { + const t_uint32 channels = source.get_channel_count(); + const t_uint32 sampleRate = source.get_sample_rate(); + FB2K_DYNAMIC_ASSERT( sampleRate > 0 ); + out.set_channels(channels, source.get_channel_config()); + out.set_sample_rate(sampleRate); + const t_size inSize = source.get_sample_count(); + const t_size fftSize = MatchFFTSize(inSize); + out.set_sample_count(fftSize); + out.set_data_size(fftSize * channels); + if (fftSize >= inSize) { //rare case with *REALLY* small input + pfc::memcpy_t( out.get_data(), source.get_data(), inSize * channels ); + pfc::memset_null_t( out.get_data() + inSize * channels, (fftSize - inSize) * channels ); + } else { //inSize > fftSize, we're using a subset of source chunk for the job, pick a subset around centerOffset. + const double baseOffset = pfc::max_t(0, centerOffset - 0.5 * (double)fftSize / (double)sampleRate); + const t_size baseSample = pfc::min_t( (t_size) audio_math::time_to_samples(baseOffset, sampleRate), inSize - fftSize); + pfc::memcpy_t( out.get_data(), source.get_data() + baseSample * channels, fftSize * channels); + } + } + + bool IsValidFFTSize(t_size p_size) { + return p_size >= 2 && (p_size & (p_size - 1)) == 0; + } + + t_size MatchFFTSize(t_size samples) { + if (samples <= 2) return 2; + t_size mask = 1; + while(!IsValidFFTSize(samples)) { + samples &= ~mask; mask <<= 1; + } + return samples; + } +}; diff --git a/foobar2000/helpers/VisUtils.h b/foobar2000/helpers/VisUtils.h new file mode 100644 index 0000000..ba68c1b --- /dev/null +++ b/foobar2000/helpers/VisUtils.h @@ -0,0 +1,10 @@ +#pragma once + +namespace VisUtils { + //! Turns an arbitrary audio_chunk into a valid chunk to run FFT on, with proper sample count etc. + //! @param centerOffset Time offset (in seconds) inside the source chunk to center the output on, in case the FFT window is smaller than input data. + void PrepareFFTChunk(audio_chunk const & source, audio_chunk & out, double centerOffset); + + bool IsValidFFTSize(t_size size); + t_size MatchFFTSize(t_size samples); +}; diff --git a/foobar2000/helpers/VolumeMap.cpp b/foobar2000/helpers/VolumeMap.cpp new file mode 100644 index 0000000..ba68e8d --- /dev/null +++ b/foobar2000/helpers/VolumeMap.cpp @@ -0,0 +1,22 @@ +#include "stdafx.h" +#include "VolumeMap.h" + +static const double powval = 2.0; +static const double silence = -100.0; + +double VolumeMap::SliderToDB2(double slider) { + double v = SliderToDB(slider); + v = floor(v * 2.0 + 0.5) * 0.5; + return v; +} + +double VolumeMap::SliderToDB(double slider) { + if (slider > 0) { + return pfc::max_t(silence,10.0 * log(slider) / log(powval)); + } else { + return silence; + } +} +double VolumeMap::DBToSlider(double volumeDB) { + return pfc::clip_t(pow(powval,volumeDB / 10.0), 0, 1); +} diff --git a/foobar2000/helpers/VolumeMap.h b/foobar2000/helpers/VolumeMap.h new file mode 100644 index 0000000..6b9b1d6 --- /dev/null +++ b/foobar2000/helpers/VolumeMap.h @@ -0,0 +1,7 @@ +#pragma once + +namespace VolumeMap { + double SliderToDB(double slider /*0..1 range*/); + double SliderToDB2(double slider); // rounds to 0.5dB + double DBToSlider(double volumeDB); +} diff --git a/foobar2000/helpers/WindowPositionUtils.h b/foobar2000/helpers/WindowPositionUtils.h new file mode 100644 index 0000000..fa899b8 --- /dev/null +++ b/foobar2000/helpers/WindowPositionUtils.h @@ -0,0 +1,391 @@ +#pragma once + +#include "win32_misc.h" + +static BOOL AdjustWindowRectHelper(CWindow wnd, CRect & rc) { + const DWORD style = wnd.GetWindowLong(GWL_STYLE), exstyle = wnd.GetWindowLong(GWL_EXSTYLE); + return AdjustWindowRectEx(&rc,style,(style & WS_POPUP) ? wnd.GetMenu() != NULL : FALSE, exstyle); +} + +static void AdjustRectToScreenArea(CRect & rc, CRect rcParent) { + HMONITOR monitor = MonitorFromRect(rcParent,MONITOR_DEFAULTTONEAREST); + MONITORINFO mi = {sizeof(MONITORINFO)}; + if (GetMonitorInfo(monitor,&mi)) { + const CRect clip = mi.rcWork; + if (rc.right > clip.right) rc.OffsetRect(clip.right - rc.right, 0); + if (rc.bottom > clip.bottom) rc.OffsetRect(0, clip.bottom - rc.bottom); + if (rc.left < clip.left) rc.OffsetRect(clip.left - rc.left, 0); + if (rc.top < clip.top) rc.OffsetRect(0, clip.top - rc.top); + } +} + +static BOOL GetClientRectAsSC(CWindow wnd, CRect & rc) { + CRect temp; + if (!wnd.GetClientRect(temp)) return FALSE; + if (temp.IsRectNull()) return FALSE; + if (!wnd.ClientToScreen(temp)) return FALSE; + rc = temp; + return TRUE; +} + + +static BOOL CenterWindowGetRect(CWindow wnd, CWindow wndParent, CRect & out) { + CRect parent, child; + if (!wndParent.GetWindowRect(&parent) || !wnd.GetWindowRect(&child)) return FALSE; + { + CPoint origin = parent.CenterPoint(); + origin.Offset( - child.Width() / 2, - child.Height() / 2); + child.OffsetRect( origin - child.TopLeft() ); + } + AdjustRectToScreenArea(child, parent); + out = child; + return TRUE; +} + +static BOOL CenterWindowAbove(CWindow wnd, CWindow wndParent) { + CRect rc; + if (!CenterWindowGetRect(wnd, wndParent, rc)) return FALSE; + return wnd.SetWindowPos(NULL,rc,SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE); +} + +static BOOL ShowWindowCentered(CWindow wnd,CWindow wndParent) { + CRect rc; + if (!CenterWindowGetRect(wnd, wndParent, rc)) return FALSE; + return wnd.SetWindowPos(HWND_TOP,rc,SWP_NOSIZE | SWP_SHOWWINDOW); +} + +class cfgWindowSize : public cfg_var { +public: + cfgWindowSize(const GUID & p_guid) : cfg_var(p_guid) {} + void get_data_raw(stream_writer * p_stream,abort_callback & p_abort) { + stream_writer_formatter<> str(*p_stream,p_abort); str << m_width << m_height; + } + void set_data_raw(stream_reader * p_stream,t_size p_sizehint,abort_callback & p_abort) { + stream_reader_formatter<> str(*p_stream,p_abort); str >> m_width >> m_height; + } + + uint32_t m_width = UINT32_MAX, m_height = UINT32_MAX; +}; + +class cfgWindowSizeTracker { +public: + cfgWindowSizeTracker(cfgWindowSize & p_var) : m_var(p_var) {} + + bool Apply(HWND p_wnd) { + bool retVal = false; + m_applied = false; + if (m_var.m_width != ~0 && m_var.m_height != ~0) { + CRect rect (0,0,m_var.m_width,m_var.m_height); + if (AdjustWindowRectHelper(p_wnd, rect)) { + SetWindowPos(p_wnd,NULL,0,0,rect.right-rect.left,rect.bottom-rect.top,SWP_NOMOVE|SWP_NOACTIVATE|SWP_NOZORDER); + retVal = true; + } + } + m_applied = true; + return retVal; + } + + BOOL ProcessWindowMessage(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT & lResult) { + if (uMsg == WM_SIZE && m_applied) { + if (lParam != 0) { + m_var.m_width = (short)LOWORD(lParam); m_var.m_height = (short)HIWORD(lParam); + } + } + return FALSE; + } +private: + cfgWindowSize & m_var; + bool m_applied = false; +}; + +class cfgDialogSizeTracker : public cfgWindowSizeTracker { +public: + cfgDialogSizeTracker(cfgWindowSize & p_var) : cfgWindowSizeTracker(p_var) {} + BOOL ProcessWindowMessage(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT & lResult) { + if (cfgWindowSizeTracker::ProcessWindowMessage(hWnd, uMsg, wParam, lParam, lResult)) return TRUE; + if (uMsg == WM_INITDIALOG) Apply(hWnd); + return FALSE; + } +}; + +class cfgDialogPositionData { +public: + cfgDialogPositionData() : m_width(sizeInvalid), m_height(sizeInvalid), m_posX(posInvalid), m_posY(posInvalid) {} + + void OverrideDefaultSize(t_uint32 width, t_uint32 height) { + if (m_width == sizeInvalid && m_height == sizeInvalid) { + m_width = width; m_height = height; m_posX = m_posY = posInvalid; + m_dpiX = m_dpiY = 96; + } + } + + void AddWindow(CWindow wnd) { + TryFetchConfig(); + m_windows += wnd; + ApplyConfig(wnd); + } + void RemoveWindow(CWindow wnd) { + if (m_windows.contains(wnd)) { + StoreConfig(wnd); m_windows -= wnd; + } + } + void FetchConfig() {TryFetchConfig();} + +private: + BOOL ApplyConfig(CWindow wnd) { + ApplyDPI(); + CWindow wndParent = wnd.GetParent(); + UINT flags = SWP_NOACTIVATE | SWP_NOZORDER; + CRect rc; + if (!GetClientRectAsSC(wnd,rc)) return FALSE; + if (m_width != sizeInvalid && m_height != sizeInvalid && (wnd.GetWindowLong(GWL_STYLE) & WS_SIZEBOX) != 0) { + rc.right = rc.left + m_width; + rc.bottom = rc.top + m_height; + } else { + flags |= SWP_NOSIZE; + } + if (wndParent != NULL) { + CRect rcParent; + if (GetParentWndRect(wndParent, rcParent)) { + if (m_posX != posInvalid && m_posY != posInvalid) { + rc.MoveToXY( rcParent.TopLeft() + CPoint(m_posX, m_posY) ); + } else { + CPoint center = rcParent.CenterPoint(); + rc.MoveToXY( center.x - rc.Width() / 2, center.y - rc.Height() / 2); + } + } + } + if (!AdjustWindowRectHelper(wnd, rc)) return FALSE; + + DeOverlap(wnd, rc); + + { + CRect rcAdjust(0,0,1,1); + if (wndParent != NULL) { + CRect temp; + if (wndParent.GetWindowRect(temp)) rcAdjust = temp; + } + AdjustRectToScreenArea(rc, rcAdjust); + } + + + return wnd.SetWindowPos(NULL, rc, flags); + } + struct DeOverlapState { + CWindow m_thisWnd; + CPoint m_topLeft; + bool m_match; + }; + static BOOL CALLBACK MyEnumChildProc(HWND wnd, LPARAM param) { + DeOverlapState * state = reinterpret_cast(param); + if (wnd != state->m_thisWnd && IsWindowVisible(wnd) ) { + CRect rc; + if (GetWindowRect(wnd, rc)) { + if (rc.TopLeft() == state->m_topLeft) { + state->m_match = true; return FALSE; + } + } + } + return TRUE; + } + static bool DeOverlapTest(CWindow wnd, CPoint topLeft) { + DeOverlapState state = {}; + state.m_thisWnd = wnd; state.m_topLeft = topLeft; state.m_match = false; + EnumThreadWindows(GetCurrentThreadId(), MyEnumChildProc, reinterpret_cast(&state)); + return state.m_match; + } + static int DeOverlapDelta() { + return pfc::max_t(GetSystemMetrics(SM_CYCAPTION),1); + } + static void DeOverlap(CWindow wnd, CRect & rc) { + const int delta = DeOverlapDelta(); + for(;;) { + if (!DeOverlapTest(wnd, rc.TopLeft())) break; + rc.OffsetRect(delta,delta); + } + } + BOOL StoreConfig(CWindow wnd) { + CRect rc; + if (!GetClientRectAsSC(wnd, rc)) return FALSE; + const CSize DPI = QueryScreenDPIEx(); + m_dpiX = DPI.cx; m_dpiY = DPI.cy; + m_width = rc.Width(); m_height = rc.Height(); + m_posX = m_posY = posInvalid; + CWindow parent = wnd.GetParent(); + if (parent != NULL) { + CRect rcParent; + if (GetParentWndRect(parent, rcParent)) { + m_posX = rc.left - rcParent.left; + m_posY = rc.top - rcParent.top; + } + } + return TRUE; + } + void TryFetchConfig() { + for(auto walk = m_windows.cfirst(); walk.is_valid(); ++walk) { + if (StoreConfig(*walk)) break; + } + } + + void ApplyDPI() { + const CSize screenDPI = QueryScreenDPIEx(); + if (screenDPI.cx == 0 || screenDPI.cy == 0) { + PFC_ASSERT(!"Should not get here - something seriously wrong with the OS"); + return; + } + if (m_dpiX != dpiInvalid && m_dpiX != screenDPI.cx) { + if (m_width != sizeInvalid) m_width = MulDiv(m_width, screenDPI.cx, m_dpiX); + if (m_posX != posInvalid) m_posX = MulDiv(m_posX, screenDPI.cx, m_dpiX); + } + if (m_dpiY != dpiInvalid && m_dpiY != screenDPI.cy) { + if (m_height != sizeInvalid) m_height = MulDiv(m_height, screenDPI.cy, m_dpiY); + if (m_posY != posInvalid) m_posY = MulDiv(m_posY, screenDPI.cy, m_dpiY); + } + m_dpiX = screenDPI.cx; + m_dpiY = screenDPI.cy; + } + CSize GrabDPI() const { + CSize DPI(96,96); + if (m_dpiX != dpiInvalid) DPI.cx = m_dpiX; + if (m_dpiY != dpiInvalid) DPI.cy = m_dpiY; + return DPI; + } + + static BOOL GetParentWndRect(CWindow wndParent, CRect & rc) { + if (!wndParent.IsIconic()) { + return wndParent.GetWindowRect(rc); + } + WINDOWPLACEMENT pl = {sizeof(pl)}; + if (!wndParent.GetWindowPlacement(&pl)) return FALSE; + rc = pl.rcNormalPosition; + return TRUE; + } + + pfc::avltree_t m_windows; +public: + t_uint32 m_width, m_height; + t_int32 m_posX, m_posY; + t_uint32 m_dpiX, m_dpiY; + enum { + posInvalid = 0x80000000, + sizeInvalid = 0xFFFFFFFF, + dpiInvalid = 0, + }; +}; + +FB2K_STREAM_READER_OVERLOAD(cfgDialogPositionData) { + stream >> value.m_width >> value.m_height; + try { + stream >> value.m_posX >> value.m_posY >> value.m_dpiX >> value.m_dpiY; + } catch(exception_io_data) { + value.m_posX = value.m_posY = cfgDialogPositionData::posInvalid; + value.m_dpiX = value.m_dpiY = cfgDialogPositionData::dpiInvalid; + } + return stream; +} +FB2K_STREAM_WRITER_OVERLOAD(cfgDialogPositionData) { + return stream << value.m_width << value.m_height << value.m_posX << value.m_posY << value.m_dpiX << value.m_dpiY; +} + +class cfgDialogPosition : public cfgDialogPositionData, public cfg_var { +public: + cfgDialogPosition(const GUID & id) : cfg_var(id) {} + void get_data_raw(stream_writer * p_stream,abort_callback & p_abort) {FetchConfig(); stream_writer_formatter<> str(*p_stream, p_abort); str << *pfc::implicit_cast(this);} + void set_data_raw(stream_reader * p_stream,t_size p_sizehint,abort_callback & p_abort) {stream_reader_formatter<> str(*p_stream, p_abort); str >> *pfc::implicit_cast(this);} +}; + +class cfgDialogPositionTracker { +public: + cfgDialogPositionTracker(cfgDialogPosition & p_var) : m_var(p_var) {} + ~cfgDialogPositionTracker() {Cleanup();} + + BOOL ProcessWindowMessage(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT & lResult) { + if (uMsg == WM_CREATE || uMsg == WM_INITDIALOG) { + Cleanup(); + m_wnd = hWnd; + m_var.AddWindow(m_wnd); + } else if (uMsg == WM_DESTROY) { + PFC_ASSERT( hWnd == m_wnd ); + Cleanup(); + } + return FALSE; + } + +private: + void Cleanup() { + if (m_wnd != NULL) { + m_var.RemoveWindow(m_wnd); + m_wnd = NULL; + } + } + cfgDialogPosition & m_var; + CWindow m_wnd; +}; + +//! DPI-safe window size var \n +//! Stores size in pixel and original DPI\n +//! Use with cfgWindowSizeTracker2 +class cfgWindowSize2 : public cfg_var { +public: + cfgWindowSize2(const GUID & p_guid) : cfg_var(p_guid) {} + void get_data_raw(stream_writer * p_stream,abort_callback & p_abort) { + stream_writer_formatter<> str(*p_stream,p_abort); str << m_size.cx << m_size.cy << m_dpi.cx << m_dpi.cy; + } + void set_data_raw(stream_reader * p_stream,t_size p_sizehint,abort_callback & p_abort) { + stream_reader_formatter<> str(*p_stream,p_abort); str >> m_size.cx >> m_size.cy >> m_dpi.cx >> m_dpi.cy; + } + + bool is_valid() const { + return m_size.cx > 0 && m_size.cy > 0; + } + + CSize get( CSize forDPI ) const { + if ( forDPI == m_dpi ) return m_size; + + CSize ret; + ret.cx = MulDiv( m_size.cx, forDPI.cx, m_dpi.cx ); + ret.cy = MulDiv( m_size.cy, forDPI.cy, m_dpi.cy ); + return ret; + } + + CSize m_size = CSize(0,0), m_dpi = CSize(0,0); +}; + +//! Forward messages to this class to utilize cfgWindowSize2 +class cfgWindowSizeTracker2 : public CMessageMap { +public: + cfgWindowSizeTracker2( cfgWindowSize2 & var ) : m_var(var) {} + + BEGIN_MSG_MAP_EX(cfgWindowSizeTracker2) + if (uMsg == WM_CREATE || uMsg == WM_INITDIALOG) { + Apply(hWnd); + } + MSG_WM_SIZE( OnSize ) + END_MSG_MAP() + + bool Apply(HWND p_wnd) { + bool retVal = false; + m_applied = false; + if (m_var.is_valid()) { + CRect rect( CPoint(0,0), m_var.get( m_DPI ) ); + if (AdjustWindowRectHelper(p_wnd, rect)) { + SetWindowPos(p_wnd,NULL,0,0,rect.right-rect.left,rect.bottom-rect.top,SWP_NOMOVE|SWP_NOACTIVATE|SWP_NOZORDER); + retVal = true; + } + } + m_applied = true; + return retVal; + } + +private: + void OnSize(UINT nType, CSize size) { + if ( m_applied && size.cx > 0 && size.cy > 0 ) { + m_var.m_size = size; + m_var.m_dpi = m_DPI; + } + SetMsgHandled(FALSE); + } + cfgWindowSize2 & m_var; + bool m_applied = false; + const CSize m_DPI = QueryScreenDPIEx(); +}; \ No newline at end of file diff --git a/foobar2000/helpers/advconfig_impl.h b/foobar2000/helpers/advconfig_impl.h new file mode 100644 index 0000000..0d420c0 --- /dev/null +++ b/foobar2000/helpers/advconfig_impl.h @@ -0,0 +1,3 @@ +#pragma once + +// fb2k mobile compat \ No newline at end of file diff --git a/foobar2000/helpers/advconfig_runtime.h b/foobar2000/helpers/advconfig_runtime.h new file mode 100644 index 0000000..11c7540 --- /dev/null +++ b/foobar2000/helpers/advconfig_runtime.h @@ -0,0 +1,45 @@ +#pragma once + +// Alternate advconfig var implementations that discard their state across app restarts - use for debug options that should not stick + +template +class advconfig_entry_checkbox_runtime_impl : public advconfig_entry_checkbox_v2 { +public: + advconfig_entry_checkbox_runtime_impl(const char * p_name,const GUID & p_guid,const GUID & p_parent,double p_priority,bool p_initialstate) + : m_name(p_name), m_initialstate(p_initialstate), m_state(p_initialstate), m_parent(p_parent), m_priority(p_priority), m_guid(p_guid) {} + + void get_name(pfc::string_base & p_out) {p_out = m_name;} + GUID get_guid() {return m_guid;} + GUID get_parent() {return m_parent;} + void reset() {m_state = m_initialstate;} + bool get_state() {return m_state;} + void set_state(bool p_state) {m_state = p_state;} + bool is_radio() {return p_is_radio;} + double get_sort_priority() {return m_priority;} + bool get_state_() const {return m_state;} + bool get_default_state() {return m_initialstate;} + bool get_default_state_() const {return m_initialstate;} + t_uint32 get_preferences_flags() {return prefFlags;} +private: + pfc::string8 m_name; + const bool m_initialstate; + bool m_state; + const GUID m_parent; + const GUID m_guid; + const double m_priority; +}; + +template +class advconfig_checkbox_factory_runtime_t : public service_factory_single_t > { +public: + advconfig_checkbox_factory_runtime_t(const char * p_name,const GUID & p_guid,const GUID & p_parent,double p_priority,bool p_initialstate) + : service_factory_single_t >(p_name,p_guid,p_parent,p_priority,p_initialstate) {} + + bool get() const {return this->get_static_instance().get_state_();} + void set(bool val) {this->get_static_instance().set_state(val);} + operator bool() const {return get();} + bool operator=(bool val) {set(val); return val;} +}; + +typedef advconfig_checkbox_factory_runtime_t advconfig_checkbox_factory_runtime; +typedef advconfig_checkbox_factory_runtime_t advconfig_radio_factory_runtime; diff --git a/foobar2000/helpers/album_art_helpers.h b/foobar2000/helpers/album_art_helpers.h new file mode 100644 index 0000000..3deaeed --- /dev/null +++ b/foobar2000/helpers/album_art_helpers.h @@ -0,0 +1,2 @@ +#pragma once +// stub, added for compatibility with fb2k mobile source \ No newline at end of file diff --git a/foobar2000/helpers/atl-misc.h b/foobar2000/helpers/atl-misc.h new file mode 100644 index 0000000..ee7c39c --- /dev/null +++ b/foobar2000/helpers/atl-misc.h @@ -0,0 +1,291 @@ +#pragma once + +#include "win32_misc.h" +#include +#include + +class CMenuSelectionReceiver : public CWindowImpl { +public: + CMenuSelectionReceiver(HWND p_parent) { + WIN32_OP( Create(p_parent) != NULL ); + } + ~CMenuSelectionReceiver() { + if (m_hWnd != NULL) DestroyWindow(); + } + typedef CWindowImpl _baseClass; + DECLARE_WND_CLASS_EX(TEXT("{DF0087DB-E765-4283-BBAB-6AB2E8AB64A1}"),0,0); + + BEGIN_MSG_MAP(CMenuSelectionReceiver) + MESSAGE_HANDLER(WM_MENUSELECT,OnMenuSelect) + END_MSG_MAP() +protected: + virtual bool QueryHint(unsigned p_id,pfc::string_base & p_out) { + return false; + } +private: + LRESULT OnMenuSelect(UINT,WPARAM p_wp,LPARAM p_lp,BOOL&) { + if (p_lp != 0) { + if (HIWORD(p_wp) & MF_POPUP) { + m_status.release(); + } else { + pfc::string8 msg; + UINT cmd = LOWORD(p_wp); + if ( cmd == 0 || !QueryHint(cmd,msg)) { + m_status.release(); + } else { + if (m_status.is_empty()) { + if (!static_api_ptr_t()->override_status_text_create(m_status)) m_status.release(); + } + if (m_status.is_valid()) { + m_status->override_text(msg); + } + } + } + } else { + m_status.release(); + } + return 0; + } + + service_ptr_t m_status; + + PFC_CLASS_NOT_COPYABLE(CMenuSelectionReceiver,CMenuSelectionReceiver); +}; + +class CMenuDescriptionMap : public CMenuSelectionReceiver { +public: + CMenuDescriptionMap(HWND p_parent) : CMenuSelectionReceiver(p_parent) {} + void Set(unsigned p_id,const char * p_description) {m_content.set(p_id,p_description);} +protected: + bool QueryHint(unsigned p_id,pfc::string_base & p_out) { + return m_content.query(p_id,p_out); + } +private: + pfc::map_t m_content; +}; + +class CMenuDescriptionHybrid : public CMenuSelectionReceiver { +public: + CMenuDescriptionHybrid(HWND parent) : CMenuSelectionReceiver(parent) {} + void Set(unsigned id, const char * desc) {m_content.set(id, desc);} + + void SetCM(contextmenu_manager::ptr mgr, unsigned base, unsigned max) { + m_cmMgr = mgr; m_cmMgr_base = base; m_cmMgr_max = max; + } +protected: + bool QueryHint(unsigned p_id,pfc::string_base & p_out) { + if (m_cmMgr.is_valid() && p_id >= m_cmMgr_base && p_id < m_cmMgr_max) { + return m_cmMgr->get_description_by_id(p_id - m_cmMgr_base,p_out); + } + return m_content.query(p_id,p_out); + } +private: + pfc::map_t m_content; + contextmenu_manager::ptr m_cmMgr; unsigned m_cmMgr_base, m_cmMgr_max; +}; + +inline pfc::string_base & operator<<(pfc::string_base & p_fmt,const CPoint & p_point) { + return p_fmt << "(" << p_point.x << "," << p_point.y << ")"; +} + +inline pfc::string_base & operator<<(pfc::string_base & p_fmt,const CRect & p_rect) { + return p_fmt << "(" << p_rect.left << "," << p_rect.top << "," << p_rect.right << "," << p_rect.bottom << ")"; +} + +template +class CAddDummyMessageMap : public TClass { +public: + BEGIN_MSG_MAP(CAddDummyMessageMap) + END_MSG_MAP() +}; + +template class CWindowFixSEH : public _parentClass { public: + BOOL ProcessWindowMessage(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT& lResult, DWORD dwMsgMapID = 0) { + __try { + return _parentClass::ProcessWindowMessage(hWnd, uMsg, wParam, lParam, lResult, dwMsgMapID); + } __except(uExceptFilterProc(GetExceptionInformation())) { return FALSE; /* should not get here */ } + } + template CWindowFixSEH( arg_t && ... arg ) : _parentClass( std::forward(arg) ... ) {} +}; + +template +class CWindowAutoLifetime : public CWindowFixSEH { +public: + typedef CWindowFixSEH TBase; + template CWindowAutoLifetime(HWND parent, arg_t && ... arg) : TBase( std::forward(arg) ... ) {Init(parent);} +private: + void Init(HWND parent) {WIN32_OP(this->Create(parent) != NULL);} + void OnFinalMessage(HWND wnd) {PFC_ASSERT_NO_EXCEPTION( TBase::OnFinalMessage(wnd) ); PFC_ASSERT_NO_EXCEPTION(delete this);} +}; + +template +class ImplementModelessTracking : public TClass { +public: + template ImplementModelessTracking(arg_t && ... arg ) : TClass(std::forward(arg) ... ) {} + + BEGIN_MSG_MAP_EX(ImplementModelessTracking) + MSG_WM_INITDIALOG(OnInitDialog) + MSG_WM_DESTROY(OnDestroy) + CHAIN_MSG_MAP(TClass) + END_MSG_MAP_HOOK() +private: + BOOL OnInitDialog(CWindow, LPARAM) {m_modeless.Set( this->m_hWnd ); SetMsgHandled(FALSE); return FALSE; } + void OnDestroy() {m_modeless.Set(NULL); SetMsgHandled(FALSE); } + CModelessDialogEntry m_modeless; +}; + +namespace fb2k { + template dialog_t * newDialogEx( HWND parent, arg_t && ... arg ) { + return new CWindowAutoLifetime > ( parent, std::forward(arg) ... ); + } + template dialog_t * newDialog(arg_t && ... arg) { + return new CWindowAutoLifetime > (core_api::get_main_window(), std::forward(arg) ...); + } +} + +class CMenuSelectionReceiver_UiElement : public CMenuSelectionReceiver { +public: + CMenuSelectionReceiver_UiElement(service_ptr_t p_owner,unsigned p_id_base) : CMenuSelectionReceiver(p_owner->get_wnd()), m_owner(p_owner), m_id_base(p_id_base) {} +protected: + bool QueryHint(unsigned p_id,pfc::string_base & p_out) { + return m_owner->edit_mode_context_menu_get_description(p_id,m_id_base,p_out); + } +private: + const unsigned m_id_base; + const service_ptr_t m_owner; +}; + +static bool window_service_trait_defer_destruction(const service_base *) {return true;} + + +//! Special service_impl_t replacement for service classes that also implement ATL/WTL windows. +template +class window_service_impl_t : public implement_service_query< CWindowFixSEH<_t_base> > { +private: + typedef window_service_impl_t<_t_base> t_self; + typedef implement_service_query< CWindowFixSEH<_t_base> > t_base; +public: + BEGIN_MSG_MAP_EX(window_service_impl_t) + MSG_WM_DESTROY(OnDestroyPassThru) + CHAIN_MSG_MAP(__super) + END_MSG_MAP_HOOK() + + int FB2KAPI service_release() throw() { + int ret = --m_counter; + if (ret == 0) { + if (window_service_trait_defer_destruction(this) && !InterlockedExchange(&m_delayedDestroyInProgress,1)) { + PFC_ASSERT_NO_EXCEPTION( service_impl_helper::release_object_delayed(this); ); + } else if (this->m_hWnd != NULL) { + if (!m_destroyWindowInProgress) { // don't double-destroy in weird scenarios + PFC_ASSERT_NO_EXCEPTION( ::DestroyWindow(this->m_hWnd) ); + } + } else { + PFC_ASSERT_NO_EXCEPTION( delete this ); + } + } + return ret; + } + int FB2KAPI service_add_ref() throw() {return ++m_counter;} + + template + window_service_impl_t( arg_t && ... arg ) : t_base( std::forward(arg) ... ) {}; +private: + void OnDestroyPassThru() { + SetMsgHandled(FALSE); m_destroyWindowInProgress = true; + } + void OnFinalMessage(HWND p_wnd) { + t_base::OnFinalMessage(p_wnd); + service_ptr_t bump(this); + } + volatile bool m_destroyWindowInProgress = false; + volatile LONG m_delayedDestroyInProgress = 0; + pfc::refcounter m_counter; +}; + +namespace fb2k { + template + service_ptr_t service_new_window(arg_t && ... arg) { + return new window_service_impl_t< obj_t > ( std::forward (arg) ... ); + } +} + +static void AppendMenuPopup(HMENU menu, UINT flags, CMenu & popup, const TCHAR * label) { + PFC_ASSERT( flags & MF_POPUP ); + WIN32_OP( CMenuHandle(menu).AppendMenu(flags, popup, label) ); + popup.Detach(); +} + +class CMessageMapDummy : public CMessageMap { +public: + BOOL ProcessWindowMessage(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, + LRESULT& lResult, DWORD dwMsgMapID) {return FALSE;} +}; + + + + + + + +template class preferences_page_instance_impl : public TDialog { +public: + preferences_page_instance_impl(HWND parent, preferences_page_callback::ptr callback) : TDialog(callback) { + WIN32_OP(this->Create(parent) != NULL); + + // complain early if what we created isn't a child window + PFC_ASSERT( (this->GetStyle() & (WS_POPUP|WS_CHILD)) == WS_CHILD ); + } + HWND get_wnd() {return this->m_hWnd;} +}; +static bool window_service_trait_defer_destruction(const preferences_page_instance *) {return false;} +template class preferences_page_impl : public preferences_page_v3 { +public: + preferences_page_instance::ptr instantiate(HWND parent, preferences_page_callback::ptr callback) { + return fb2k::service_new_window >(parent, callback); + } +}; + +class CEmbeddedDialog : public CDialogImpl { +public: + CEmbeddedDialog(CMessageMap * owner, DWORD msgMapID, UINT dialogID) : m_owner(*owner), IDD(dialogID), m_msgMapID(msgMapID) {} + + BEGIN_MSG_MAP(CEmbeddedDialog) + CHAIN_MSG_MAP_ALT_MEMBER(m_owner, m_msgMapID) + END_MSG_MAP() + + const DWORD m_msgMapID; + const UINT IDD; + CMessageMap & m_owner; +}; + + + + +// here because of window_service_impl_t +template class ui_element_impl : public TInterface { +public: + GUID get_guid() { return TImpl::g_get_guid(); } + GUID get_subclass() { return TImpl::g_get_subclass(); } + void get_name(pfc::string_base & out) { TImpl::g_get_name(out); } + ui_element_instance::ptr instantiate(HWND parent, ui_element_config::ptr cfg, ui_element_instance_callback::ptr callback) { + PFC_ASSERT(cfg->get_guid() == get_guid()); + service_nnptr_t item = new window_service_impl_t(cfg, callback); + item->initialize_window(parent); + return item; + } + ui_element_config::ptr get_default_configuration() { return TImpl::g_get_default_configuration(); } + ui_element_children_enumerator_ptr enumerate_children(ui_element_config::ptr cfg) { return NULL; } + bool get_description(pfc::string_base & out) { out = TImpl::g_get_description(); return true; } +private: + class ui_element_instance_impl_helper : public TImpl { + public: + ui_element_instance_impl_helper(ui_element_config::ptr cfg, ui_element_instance_callback::ptr callback) : TImpl(cfg, callback) {} + + GUID get_guid() { return TImpl::g_get_guid(); } + GUID get_subclass() { return TImpl::g_get_subclass(); } + HWND get_wnd() { return *this; } + }; +public: + typedef ui_element_instance_impl_helper TInstance; + static TInstance const & instanceGlobals() { return *reinterpret_cast(NULL); } +}; diff --git a/foobar2000/helpers/bitreader_helper.h b/foobar2000/helpers/bitreader_helper.h new file mode 100644 index 0000000..7e12d98 --- /dev/null +++ b/foobar2000/helpers/bitreader_helper.h @@ -0,0 +1,154 @@ +#pragma once + +namespace bitreader_helper { + + inline static size_t extract_bit(const uint8_t * p_stream,size_t p_offset) { + return (p_stream[p_offset>>3] >> (7-(p_offset&7)))&1; + } + + inline static size_t extract_int(const uint8_t * p_stream,size_t p_base,size_t p_width) { + size_t ret = 0; + size_t offset = p_base; + for(size_t bit=0;bit>3]; + b = (b & ~mask) | ((bit&1) << bshift); + } + inline static void write_int( uint8_t * p_stream, size_t p_base, size_t p_width, size_t p_value) { + size_t offset = p_base; + size_t val = p_value; + for( size_t bit = 0; bit < p_width; ++ bit ) { + write_bit( p_stream, offset++, val >> (p_width - bit - 1)); + } + } + +class bitreader +{ +public: + inline bitreader(const t_uint8 * p_ptr,t_size p_base) + : m_ptr(p_ptr), m_bitptr(p_base) + { + } + + inline void skip(t_size p_bits) + { + m_bitptr += p_bits; + } + + + template + t_ret peek_t(t_size p_bits) const { + size_t ptr = m_bitptr; + t_ret ret = 0; + for(t_size bit=0;bit>3] >> (7-(ptr &7)))&1; + ptr++; + } + return ret; + } + + template + t_ret read_t(t_size p_bits) { + t_ret ret = peek_t(p_bits); + skip(p_bits); + return ret; + } + + size_t peek(size_t bits) const { + return peek_t(bits); + } + + t_size read(t_size p_bits) {return read_t(p_bits);} + + inline t_size get_bitptr() const {return m_bitptr;} + + inline bool read_bit() { + bool state = ( (m_ptr[m_bitptr>>3] >> (7-(m_bitptr&7)))&1 ) != 0; + m_bitptr++; + return state; + } + +private: + + const t_uint8 * m_ptr; + t_size m_bitptr; +}; + +class bitreader_fromfile +{ +public: + inline bitreader_fromfile(service_ptr_t const& p_file) : m_file(p_file), m_buffer_ptr(0) {} + + t_size read(t_size p_bits,abort_callback & p_abort) { + t_size ret = 0; + for(t_size bit=0;bitread_object(&m_buffer,1,p_abort); + + ret <<= 1; + ret |= (m_buffer >> (7-m_buffer_ptr))&1; + m_buffer_ptr = (m_buffer_ptr+1) & 7; + } + return ret; + } + + void skip(t_size p_bits,abort_callback & p_abort) { + for(t_size bit=0;bitread_object(&m_buffer,1,p_abort); + m_buffer_ptr = (m_buffer_ptr+1) & 7; + } + } + + inline void byte_align() {m_buffer_ptr = 0;} + +private: + service_ptr_t m_file; + t_size m_buffer_ptr; + t_uint8 m_buffer; +}; + +class bitreader_limited +{ +public: + inline bitreader_limited(const t_uint8 * p_ptr,t_size p_base,t_size p_remaining) : m_reader(p_ptr,p_base), m_remaining(p_remaining) {} + + inline t_size get_bitptr() const {return m_reader.get_bitptr();} + + inline t_size get_remaining() const {return m_remaining;} + + inline void skip(t_size p_bits) { + if (p_bits > m_remaining) throw exception_io_data_truncation(); + m_remaining -= p_bits; + m_reader.skip(p_bits); + } + + size_t peek(size_t bits) { + if (bits > m_remaining) throw exception_io_data_truncation(); + return m_reader.peek(bits); + } + t_size read(t_size p_bits) + { + if (p_bits > m_remaining) throw exception_io_data_truncation(); + m_remaining -= p_bits; + return m_reader.read(p_bits); + } + +private: + bitreader m_reader; + t_size m_remaining; +}; + +inline static t_size extract_bits(const t_uint8 * p_buffer,t_size p_base,t_size p_count) { + return bitreader(p_buffer,p_base).read(p_count); +} + +} diff --git a/foobar2000/helpers/cfg_guidlist.h b/foobar2000/helpers/cfg_guidlist.h new file mode 100644 index 0000000..9cdff2d --- /dev/null +++ b/foobar2000/helpers/cfg_guidlist.h @@ -0,0 +1,31 @@ +#pragma once + +class cfg_guidlist : public cfg_var, public pfc::list_t +{ +public: + void get_data_raw(stream_writer * p_stream,abort_callback & p_abort) override { + t_uint32 n, m = pfc::downcast_guarded(get_count()); + p_stream->write_lendian_t(m,p_abort); + for(n=0;nwrite_lendian_t(get_item(n),p_abort); + } + void set_data_raw(stream_reader * p_stream,t_size p_sizehint,abort_callback & p_abort) override { + t_uint32 n,count; + p_stream->read_lendian_t(count,p_abort); + m_buffer.set_size(count); + for(n=0;nread_lendian_t(m_buffer[n],p_abort); + } catch(...) {m_buffer.set_size(0); throw;} + } + } + + void sort() {sort_t(pfc::guid_compare);} + + bool have_item_bsearch(const GUID & p_item) { + t_size dummy; + return bsearch_t(pfc::guid_compare,p_item,dummy); + } + +public: + cfg_guidlist(const GUID & p_guid) : cfg_var(p_guid) {} +}; diff --git a/foobar2000/helpers/create_directory_helper.cpp b/foobar2000/helpers/create_directory_helper.cpp new file mode 100644 index 0000000..18ac531 --- /dev/null +++ b/foobar2000/helpers/create_directory_helper.cpp @@ -0,0 +1,194 @@ +#include "StdAfx.h" +#include "create_directory_helper.h" +#include + +namespace create_directory_helper +{ + static void create_path_internal(const char * p_path,t_size p_base,abort_callback & p_abort) { + pfc::string8_fastalloc temp; + for(t_size walk = p_base; p_path[walk]; walk++) { + if (p_path[walk] == '\\') { + temp.set_string(p_path,walk); + try {filesystem::g_create_directory(temp.get_ptr(),p_abort);} catch(exception_io_already_exists) {} + } + } + } + + static bool is_valid_netpath_char(char p_char) { + return pfc::char_is_ascii_alphanumeric(p_char) || p_char == '_' || p_char == '-' || p_char == '.'; + } + + static bool test_localpath(const char * p_path) { + if (pfc::strcmp_partial(p_path,"file://") == 0) p_path += strlen("file://"); + return pfc::char_is_ascii_alpha(p_path[0]) && + p_path[1] == ':' && + p_path[2] == '\\'; + } + static bool test_netpath(const char * p_path) { + if (pfc::strcmp_partial(p_path,"file://") == 0) p_path += strlen("file://"); + if (*p_path != '\\') return false; + p_path++; + if (*p_path != '\\') return false; + p_path++; + if (!is_valid_netpath_char(*p_path)) return false; + p_path++; + while(is_valid_netpath_char(*p_path)) p_path++; + if (*p_path != '\\') return false; + return true; + } + + void create_path(const char * p_path,abort_callback & p_abort) { + if (test_localpath(p_path)) { + t_size walk = 0; + if (pfc::strcmp_partial(p_path,"file://") == 0) walk += strlen("file://"); + create_path_internal(p_path,walk + 3,p_abort); + } else if (test_netpath(p_path)) { + t_size walk = 0; + if (pfc::strcmp_partial(p_path,"file://") == 0) walk += strlen("file://"); + while(p_path[walk] == '\\') walk++; + while(p_path[walk] != 0 && p_path[walk] != '\\') walk++; + while(p_path[walk] == '\\') walk++; + while(p_path[walk] != 0 && p_path[walk] != '\\') walk++; + while(p_path[walk] == '\\') walk++; + create_path_internal(p_path,walk,p_abort); + } else { + pfc::throw_exception_with_message< exception_io > ("Could not create directory structure; unknown path format"); + } + } + +#ifdef _WIN32 + static bool is_bad_dirchar(char c) + { + return c==' ' || c=='.'; + } +#endif + + void make_path(const char * parent,const char * filename,const char * extension,bool allow_new_dirs,pfc::string8 & out,bool really_create_dirs,abort_callback & p_abort) + { + out.reset(); + if (parent && *parent) + { + out = parent; + out.fix_dir_separator('\\'); + } + bool last_char_is_dir_sep = true; + while(*filename) + { +#ifdef WIN32 + if (allow_new_dirs && is_bad_dirchar(*filename)) + { + const char * ptr = filename+1; + while(is_bad_dirchar(*ptr)) ptr++; + if (*ptr!='\\' && *ptr!='/') out.add_string(filename,ptr-filename); + filename = ptr; + if (*filename==0) break; + } +#endif + if (pfc::is_path_bad_char(*filename)) + { + if (allow_new_dirs && (*filename=='\\' || *filename=='/')) + { + if (!last_char_is_dir_sep) + { + if (really_create_dirs) try{filesystem::g_create_directory(out,p_abort);}catch(exception_io_already_exists){} + out.add_char('\\'); + last_char_is_dir_sep = true; + } + } + else + out.add_char('_'); + } + else + { + out.add_byte(*filename); + last_char_is_dir_sep = false; + } + filename++; + } + if (out.length()>0 && out[out.length()-1]=='\\') + { + out.add_string("noname"); + } + if (extension && *extension) + { + out.add_char('.'); + out.add_string(extension); + } + } +} + +pfc::string create_directory_helper::sanitize_formatted_path(pfc::stringp formatted, bool allowWC) { + return sanitize_formatted_path_ex(formatted, allowWC, pfc::io::path::charReplaceDefault); +}; + +pfc::string create_directory_helper::sanitize_formatted_path_ex(pfc::stringp formatted, bool allowWC, charReplace_t replace) { + pfc::string out; + t_size curSegBase = 0; + for (t_size walk = 0; ; ++walk) { + const char c = formatted[walk]; + const bool end = (c == 0); + if (end || pfc::io::path::isSeparator(c)) { + if (curSegBase < walk) { + pfc::string seg(formatted + curSegBase, walk - curSegBase); + out = pfc::io::path::combine(out, pfc::io::path::validateFileName(seg, allowWC, end /*preserve ext*/, replace)); + } + if (end) break; + curSegBase = walk + 1; + } + } + return out; +} + +void create_directory_helper::format_filename_ex(const metadb_handle_ptr & handle, titleformat_hook * p_hook, titleformat_object::ptr spec, const char * suffix, pfc::string_base & out) { + format_filename_ex(handle, p_hook, spec, suffix, out, pfc::io::path::charReplaceDefault); +} + +void create_directory_helper::format_filename_ex(const metadb_handle_ptr & handle,titleformat_hook * p_hook,titleformat_object::ptr spec,const char * suffix, pfc::string_base & out, charReplace_t replace) { + pfc::string_formatter formatted; + titleformat_text_filter_myimpl filter; + filter.m_replace = replace; + handle->format_title(p_hook,formatted,spec,&filter); + formatted << suffix; + out = sanitize_formatted_path_ex(formatted, false, replace).ptr(); +} +void create_directory_helper::format_filename(const metadb_handle_ptr & handle,titleformat_hook * p_hook,titleformat_object::ptr spec,pfc::string_base & out) { + format_filename_ex(handle, p_hook, spec, "", out); +} +void create_directory_helper::format_filename(const metadb_handle_ptr & handle,titleformat_hook * p_hook,const char * spec,pfc::string_base & out) +{ + service_ptr_t script; + if (titleformat_compiler::get()->compile(script,spec)) { + format_filename(handle, p_hook, script, out); + } else { + out.reset(); + } +} + +static bool substSanity(const char * subst) { + if (subst == nullptr) return false; + for (size_t w = 0; subst[w]; ++w) { + if (pfc::io::path::isSeparator(subst[w])) return false; + } + return true; +} + +void create_directory_helper::titleformat_text_filter_myimpl::write(const GUID & p_inputType,pfc::string_receiver & p_out,const char * p_data,t_size p_dataLength) { + if (p_inputType == titleformat_inputtypes::meta) { + pfc::string_formatter temp; + for(t_size walk = 0; walk < p_dataLength; ++walk) { + char c = p_data[walk]; + if (c == 0) break; + const char * subst = nullptr; + if (pfc::io::path::isSeparator(c)) { + if (m_replace) { + const char * proposed = m_replace(c); + if (substSanity(proposed)) subst = proposed; + } + if (subst == nullptr) subst = "-"; + } + if (subst != nullptr) temp.add_string(subst); + else temp.add_byte(c); + } + p_out.add_string(temp); + } else p_out.add_string(p_data,p_dataLength); +} diff --git a/foobar2000/helpers/create_directory_helper.h b/foobar2000/helpers/create_directory_helper.h new file mode 100644 index 0000000..353f69a --- /dev/null +++ b/foobar2000/helpers/create_directory_helper.h @@ -0,0 +1,31 @@ +#pragma once + +#include +#include + +#ifdef FOOBAR2000_MODERN +#include "metadb_compat.h" +#include +#endif + +namespace create_directory_helper { + typedef std::function charReplace_t; + + void create_path(const char * p_path,abort_callback & p_abort); + void make_path(const char * parent,const char * filename,const char * extension,bool allow_new_dirs,pfc::string8 & out,bool b_really_create_dirs,abort_callback & p_dir_create_abort); + void format_filename(const metadb_handle_ptr & handle,titleformat_hook * p_hook,const char * spec,pfc::string_base & out); + void format_filename(const metadb_handle_ptr & handle,titleformat_hook * p_hook,titleformat_object::ptr spec,pfc::string_base & out); + void format_filename_ex(const metadb_handle_ptr & handle,titleformat_hook * p_hook,titleformat_object::ptr spec,const char * suffix, pfc::string_base & out); + void format_filename_ex(const metadb_handle_ptr & handle, titleformat_hook * p_hook, titleformat_object::ptr spec, const char * suffix, pfc::string_base & out, charReplace_t replace); + + + pfc::string sanitize_formatted_path(pfc::stringp str, bool allowWC = false); + pfc::string sanitize_formatted_path_ex(pfc::stringp str, bool allowWC, charReplace_t replace); + + class titleformat_text_filter_myimpl : public titleformat_text_filter { + public: + charReplace_t m_replace; + void write(const GUID & p_inputType,pfc::string_receiver & p_out,const char * p_data,t_size p_dataLength); + }; + +}; diff --git a/foobar2000/helpers/cue_creator.cpp b/foobar2000/helpers/cue_creator.cpp new file mode 100644 index 0000000..7e68c1a --- /dev/null +++ b/foobar2000/helpers/cue_creator.cpp @@ -0,0 +1,202 @@ +#include "stdafx.h" +#include "cue_creator.h" + + +namespace { + + class format_meta + { + public: + format_meta(const file_info & p_source,const char * p_name,bool p_allow_space = true) + { + p_source.meta_format(p_name,m_buffer); + m_buffer.replace_byte('\"','\''); + uReplaceString(m_buffer,pfc::string8(m_buffer),pfc_infinite,"\x0d\x0a",2,"\\",1,false); + if (!p_allow_space) m_buffer.replace_byte(' ','_'); + m_buffer.replace_nontext_chars(); + } + inline operator const char*() const {return m_buffer;} + private: + pfc::string8_fastalloc m_buffer; + }; +} + +static bool is_meta_same_everywhere(const cue_creator::t_entry_list & p_list,const char * p_meta) +{ + pfc::string8_fastalloc reference,temp; + + bool first = true; + for(auto iter = p_list.first(); iter.is_valid(); ++ iter ) { + if ( ! iter->isTrackAudio() ) continue; + + if ( first ) { + first = false; + if (!iter->m_infos.meta_format(p_meta,reference)) return false; + } else { + if (!iter->m_infos.meta_format(p_meta,temp)) return false; + if (strcmp(temp,reference)!=0) return false; + } + } + + return true; +} + +#define g_eol "\r\n" + +namespace cue_creator +{ + void create(pfc::string_formatter & p_out,const t_entry_list & p_data) + { + if (p_data.get_count() == 0) return; + bool album_artist_global = is_meta_same_everywhere(p_data,"album artist"), + artist_global = is_meta_same_everywhere(p_data,"artist"), + album_global = is_meta_same_everywhere(p_data,"album"), + genre_global = is_meta_same_everywhere(p_data,"genre"), + date_global = is_meta_same_everywhere(p_data,"date"), + discid_global = is_meta_same_everywhere(p_data,"discid"), + comment_global = is_meta_same_everywhere(p_data,"comment"), + catalog_global = is_meta_same_everywhere(p_data,"catalog"), + songwriter_global = is_meta_same_everywhere(p_data,"songwriter"); + + { + auto firstTrack = p_data.first(); + while( firstTrack.is_valid() && ! firstTrack->isTrackAudio() ) ++ firstTrack; + if ( firstTrack.is_valid() ) { + if (genre_global) { + p_out << "REM GENRE " << format_meta(firstTrack->m_infos,"genre") << g_eol; + } + if (date_global) { + p_out << "REM DATE " << format_meta(firstTrack->m_infos,"date") << g_eol; + } + if (discid_global) { + p_out << "REM DISCID " << format_meta(firstTrack->m_infos,"discid") << g_eol; + } + if (comment_global) { + p_out << "REM COMMENT " << format_meta(firstTrack->m_infos,"comment") << g_eol; + } + if (catalog_global) { + p_out << "CATALOG " << format_meta(firstTrack->m_infos,"catalog") << g_eol; + } + if (songwriter_global) { + p_out << "SONGWRITER \"" << format_meta(firstTrack->m_infos,"songwriter") << "\"" << g_eol; + } + + if (album_artist_global) + { + p_out << "PERFORMER \"" << format_meta(firstTrack->m_infos,"album artist") << "\"" << g_eol; + artist_global = false; + } + else if (artist_global) + { + p_out << "PERFORMER \"" << format_meta(firstTrack->m_infos,"artist") << "\"" << g_eol; + } + if (album_global) + { + p_out << "TITLE \"" << format_meta(firstTrack->m_infos,"album") << "\"" << g_eol; + } + + { + replaygain_info::t_text_buffer rgbuffer; + replaygain_info rg = firstTrack->m_infos.get_replaygain(); + if (rg.format_album_gain(rgbuffer)) + p_out << "REM REPLAYGAIN_ALBUM_GAIN " << rgbuffer << g_eol; + if (rg.format_album_peak(rgbuffer)) + p_out << "REM REPLAYGAIN_ALBUM_PEAK " << rgbuffer << g_eol; + } + + } + } + + pfc::string8 last_file; + + for(t_entry_list::const_iterator iter = p_data.first();iter.is_valid();++iter) + { + if (strcmp(last_file,iter->m_file) != 0) + { + auto fileType = iter->m_fileType; + if ( fileType.length() == 0 ) fileType = "WAVE"; + p_out << "FILE \"" << iter->m_file << "\" " << fileType << g_eol; + last_file = iter->m_file; + } + + { + auto trackType = iter->m_trackType; + if (trackType.length() == 0) trackType = "AUDIO"; + p_out << " TRACK " << pfc::format_int(iter->m_track_number,2) << " " << trackType << g_eol; + } + + + + if (iter->m_infos.meta_find("title") != pfc_infinite) + p_out << " TITLE \"" << format_meta(iter->m_infos,"title") << "\"" << g_eol; + + if (!artist_global && iter->m_infos.meta_find("artist") != pfc_infinite) + p_out << " PERFORMER \"" << format_meta(iter->m_infos,"artist") << "\"" << g_eol; + + if (!songwriter_global && iter->m_infos.meta_find("songwriter") != pfc_infinite) { + p_out << " SONGWRITER \"" << format_meta(iter->m_infos,"songwriter") << "\"" << g_eol; + } + + if (iter->m_infos.meta_find("isrc") != pfc_infinite) { + p_out << " ISRC " << format_meta(iter->m_infos,"isrc") << g_eol; + } + + if (!date_global && iter->m_infos.meta_find("date") != pfc_infinite) { + p_out << " REM DATE " << format_meta(iter->m_infos,"date") << g_eol; + } + + + + { + replaygain_info::t_text_buffer rgbuffer; + replaygain_info rg = iter->m_infos.get_replaygain(); + if (rg.format_track_gain(rgbuffer)) + p_out << " REM REPLAYGAIN_TRACK_GAIN " << rgbuffer << g_eol; + if (rg.format_track_peak(rgbuffer)) + p_out << " REM REPLAYGAIN_TRACK_PEAK " << rgbuffer << g_eol; + } + + if (!iter->m_flags.is_empty()) { + p_out << " FLAGS " << iter->m_flags << g_eol; + } + + if (iter->m_index_list.m_positions[0] < iter->m_index_list.m_positions[1]) + { + if (iter->m_index_list.m_positions[0] < 0) + p_out << " PREGAP " << cuesheet_format_index_time(iter->m_index_list.m_positions[1] - iter->m_index_list.m_positions[0]) << g_eol; + else + p_out << " INDEX 00 " << cuesheet_format_index_time(iter->m_index_list.m_positions[0]) << g_eol; + } + + p_out << " INDEX 01 " << cuesheet_format_index_time(iter->m_index_list.m_positions[1]) << g_eol; + + for(unsigned n=2;nm_index_list.m_positions[n] > 0;n++) + { + p_out << " INDEX " << pfc::format_uint(n,2) << " " << cuesheet_format_index_time(iter->m_index_list.m_positions[n]) << g_eol; + } + + // p_out << " INDEX 01 " << cuesheet_format_index_time(iter->m_offset) << g_eol; + } + } + + + void t_entry::set_simple_index(double p_time) + { + m_index_list.reset(); + m_index_list.m_positions[0] = m_index_list.m_positions[1] = p_time; + } + void t_entry::set_index01(double index0, double index1) { + PFC_ASSERT( index0 <= index1 ); + m_index_list.reset(); + m_index_list.m_positions[0] = index0; + m_index_list.m_positions[1] = index1; + } + + bool t_entry::isTrackAudio() const { + PFC_ASSERT( m_trackType.length() > 0 ); + return pfc::stringEqualsI_ascii( m_trackType, "AUDIO" ); + } +} + + + diff --git a/foobar2000/helpers/cue_creator.h b/foobar2000/helpers/cue_creator.h new file mode 100644 index 0000000..c0b511c --- /dev/null +++ b/foobar2000/helpers/cue_creator.h @@ -0,0 +1,24 @@ +#pragma once + +#include "cuesheet_index_list.h" + +namespace cue_creator +{ + struct t_entry + { + file_info_impl m_infos; + pfc::string8 m_file, m_fileType, m_flags, m_trackType = "AUDIO"; + unsigned m_track_number; + + bool isTrackAudio() const; + + t_cuesheet_index_list m_index_list; + + void set_simple_index(double p_time); + void set_index01(double index0, double index1); + }; + + typedef pfc::chain_list_v2_t t_entry_list; + + void create(pfc::string_formatter & p_out,const t_entry_list & p_list); +}; \ No newline at end of file diff --git a/foobar2000/helpers/cue_parser.cpp b/foobar2000/helpers/cue_parser.cpp new file mode 100644 index 0000000..152dc47 --- /dev/null +++ b/foobar2000/helpers/cue_parser.cpp @@ -0,0 +1,856 @@ +#include "stdafx.h" +#include "cue_parser.h" + + +#define maximumCueTrackNumber 999 + +namespace { + PFC_DECLARE_EXCEPTION(exception_cue,pfc::exception,"Invalid cuesheet"); + PFC_DECLARE_EXCEPTION(exception_cue_tracktype, exception_cue, "Not an audio track") +} + +static bool is_numeric(char c) {return c>='0' && c<='9';} + + +static bool is_spacing(char c) +{ + return c == ' ' || c == '\t'; +} + +static bool is_linebreak(char c) +{ + return c == '\n' || c == '\r'; +} + +static void validate_file_type(const char * p_type,t_size p_type_length) { + if ( + //standard types + stricmp_utf8_ex(p_type,p_type_length,"WAVE",pfc_infinite) != 0 && + stricmp_utf8_ex(p_type,p_type_length,"MP3",pfc_infinite) != 0 && + stricmp_utf8_ex(p_type,p_type_length,"AIFF",pfc_infinite) != 0 && + //common user-entered types + stricmp_utf8_ex(p_type,p_type_length,"APE",pfc_infinite) != 0 && + stricmp_utf8_ex(p_type,p_type_length,"FLAC",pfc_infinite) != 0 && + stricmp_utf8_ex(p_type,p_type_length,"WV",pfc_infinite) != 0 && + stricmp_utf8_ex(p_type,p_type_length,"WAVPACK",pfc_infinite) != 0 && + // BINARY + stricmp_utf8_ex(p_type,p_type_length,"BINARY",pfc_infinite) != 0 + ) + pfc::throw_exception_with_message< exception_cue >(PFC_string_formatter() << "expected WAVE, MP3 or AIFF, got : \"" << pfc::string_part(p_type,p_type_length) << "\""); +} + +namespace { + + class NOVTABLE cue_parser_callback + { + public: + virtual void on_file(const char * p_file,t_size p_file_length,const char * p_type,t_size p_type_length) = 0; + virtual void on_track(unsigned p_index,const char * p_type,t_size p_type_length) = 0; + virtual void on_pregap(unsigned p_value) = 0; + virtual void on_index(unsigned p_index,unsigned p_value) = 0; + virtual void on_title(const char * p_title,t_size p_title_length) = 0; + virtual void on_performer(const char * p_performer,t_size p_performer_length) = 0; + virtual void on_songwriter(const char * p_songwriter,t_size p_songwriter_length) = 0; + virtual void on_isrc(const char * p_isrc,t_size p_isrc_length) = 0; + virtual void on_catalog(const char * p_catalog,t_size p_catalog_length) = 0; + virtual void on_comment(const char * p_comment,t_size p_comment_length) = 0; + virtual void on_flags(const char * p_flags,t_size p_flags_length) = 0; + }; + + class NOVTABLE cue_parser_callback_meta : public cue_parser_callback + { + public: + virtual void on_file(const char * p_file,t_size p_file_length,const char * p_type,t_size p_type_length) = 0; + virtual void on_track(unsigned p_index,const char * p_type,t_size p_type_length) = 0; + virtual void on_pregap(unsigned p_value) = 0; + virtual void on_index(unsigned p_index,unsigned p_value) = 0; + virtual void on_meta(const char * p_name,t_size p_name_length,const char * p_value,t_size p_value_length) = 0; + protected: + static bool is_known_meta(const char * p_name,t_size p_length) + { + static const char * metas[] = {"genre","date","discid","comment","replaygain_track_gain","replaygain_track_peak","replaygain_album_gain","replaygain_album_peak"}; + for(t_size n=0;n("invalid REM syntax"); + if (ptr > value_base) on_meta(p_comment,name_length,p_comment + value_base,ptr - value_base); + } + else + { + unsigned value_base = ptr; + while(ptr < p_comment_length /*&& !is_spacing(p_comment[ptr])*/) ptr++; + if (ptr > value_base) on_meta(p_comment,name_length,p_comment + value_base,ptr - value_base); + } + } + } + } + void on_title(const char * p_title,t_size p_title_length) + { + on_meta("title",pfc_infinite,p_title,p_title_length); + } + void on_songwriter(const char * p_songwriter,t_size p_songwriter_length) { + on_meta("songwriter",pfc_infinite,p_songwriter,p_songwriter_length); + } + void on_performer(const char * p_performer,t_size p_performer_length) + { + on_meta("artist",pfc_infinite,p_performer,p_performer_length); + } + + void on_isrc(const char * p_isrc,t_size p_isrc_length) + { + on_meta("isrc",pfc_infinite,p_isrc,p_isrc_length); + } + void on_catalog(const char * p_catalog,t_size p_catalog_length) + { + on_meta("catalog",pfc_infinite,p_catalog,p_catalog_length); + } + void on_flags(const char * p_flags,t_size p_flags_length) {} + }; + + + class cue_parser_callback_retrievelist : public cue_parser_callback + { + public: + cue_parser_callback_retrievelist(cue_parser::t_cue_entry_list & p_out) : m_out(p_out), m_track(0), m_pregap(0), m_index0_set(false), m_index1_set(false) + { + } + + void on_file(const char * p_file,t_size p_file_length,const char * p_type,t_size p_type_length) + { + validate_file_type(p_type,p_type_length); + m_file.set_string(p_file,p_file_length); + m_fileType.set_string(p_type, p_type_length); + } + + void on_track(unsigned p_index,const char * p_type,t_size p_type_length) + { + finalize_track(); // finalize previous track + + m_trackIsAudio = stricmp_utf8_ex(p_type,p_type_length,"audio",pfc_infinite) == 0; + if (m_file.is_empty()) pfc::throw_exception_with_message("declaring a track with no file set"); + m_trackfile = m_file; + m_trackFileType = m_fileType; + m_track = p_index; + } + + void on_pregap(unsigned p_value) {m_pregap = (double) p_value / 75.0;} + + void on_index(unsigned p_index,unsigned p_value) + { + if (p_index < t_cuesheet_index_list::count) + { + switch(p_index) + { + case 0: m_index0_set = true; break; + case 1: m_index1_set = true; break; + } + m_index_list.m_positions[p_index] = (double) p_value / 75.0; + } + } + + void on_title(const char * p_title,t_size p_title_length) {} + void on_performer(const char * p_performer,t_size p_performer_length) {} + void on_songwriter(const char * p_songwriter,t_size p_songwriter_length) {} + void on_isrc(const char * p_isrc,t_size p_isrc_length) {} + void on_catalog(const char * p_catalog,t_size p_catalog_length) {} + void on_comment(const char * p_comment,t_size p_comment_length) {} + void on_flags(const char * p_flags,t_size p_flags_length) {} + + void finalize() + { + finalize_track(); // finalize last track + } + + private: + void finalize_track() + { + if ( m_track != 0 && m_trackIsAudio ) { + if (!m_index1_set) pfc::throw_exception_with_message< exception_cue > ("INDEX 01 not set"); + if (!m_index0_set) m_index_list.m_positions[0] = m_index_list.m_positions[1] - m_pregap; + if (!m_index_list.is_valid()) pfc::throw_exception_with_message< exception_cue > ("invalid index list"); + + cue_parser::t_cue_entry_list::iterator iter; + iter = m_out.insert_last(); + if (m_trackfile.is_empty()) pfc::throw_exception_with_message< exception_cue > ("track has no file assigned"); + iter->m_file = m_trackfile; + iter->m_fileType = m_trackFileType; + iter->m_track_number = m_track; + iter->m_indexes = m_index_list; + } + + m_index_list.reset(); + m_index0_set = false; + m_index1_set = false; + m_pregap = 0; + + m_track = 0; m_trackIsAudio = false; + } + + bool m_index0_set,m_index1_set; + t_cuesheet_index_list m_index_list; + double m_pregap; + unsigned m_track; + bool m_trackIsAudio = false; + pfc::string8 m_file,m_fileType,m_trackfile,m_trackFileType; + cue_parser::t_cue_entry_list & m_out; + }; + + class cue_parser_callback_retrieveinfo : public cue_parser_callback_meta + { + public: + cue_parser_callback_retrieveinfo(file_info & p_out,unsigned p_wanted_track) : m_out(p_out), m_wanted_track(p_wanted_track), m_track(0), m_is_va(false), m_index0_set(false), m_index1_set(false), m_pregap(0), m_totaltracks(0) {} + + void on_file(const char * p_file,t_size p_file_length,const char * p_type,t_size p_type_length) {} + + void on_track(unsigned p_index,const char * p_type,t_size p_type_length) + { + if (p_index == 0) pfc::throw_exception_with_message< exception_cue > ("invalid TRACK index"); + if (p_index == m_wanted_track) + { + if (stricmp_utf8_ex(p_type,p_type_length,"audio",pfc_infinite)) throw exception_cue_tracktype(); + } + m_track = p_index; + m_totaltracks++; + } + + void on_pregap(unsigned p_value) {if (m_track == m_wanted_track) m_pregap = (double) p_value / 75.0;} + + void on_index(unsigned p_index,unsigned p_value) + { + if (m_track == m_wanted_track && p_index < t_cuesheet_index_list::count) + { + switch(p_index) + { + case 0: m_index0_set = true; break; + case 1: m_index1_set = true; break; + } + m_indexes.m_positions[p_index] = (double) p_value / 75.0; + } + } + + + void on_meta(const char * p_name,t_size p_name_length,const char * p_value,t_size p_value_length) + { + t_meta_list::iterator iter; + if (m_track == 0) //globals + { + //convert global title to album + if (!stricmp_utf8_ex(p_name,p_name_length,"title",pfc_infinite)) + { + p_name = "album"; + p_name_length = 5; + } + else if (!stricmp_utf8_ex(p_name,p_name_length,"artist",pfc_infinite)) + { + m_album_artist.set_string(p_value,p_value_length); + } + + iter = m_globals.insert_last(); + } + else + { + if (!m_is_va) + { + if (!stricmp_utf8_ex(p_name,p_name_length,"artist",pfc_infinite)) + { + if (!m_album_artist.is_empty()) + { + if (stricmp_utf8_ex(p_value,p_value_length,m_album_artist,m_album_artist.length())) m_is_va = true; + } + } + } + + if (m_track == m_wanted_track) //locals + { + iter = m_locals.insert_last(); + } + } + if (iter.is_valid()) + { + iter->m_name.set_string(p_name,p_name_length); + iter->m_value.set_string(p_value,p_value_length); + } + } + + void finalize() + { + if (!m_index1_set) pfc::throw_exception_with_message< exception_cue > ("INDEX 01 not set"); + if (!m_index0_set) m_indexes.m_positions[0] = m_indexes.m_positions[1] - m_pregap; + m_indexes.to_infos(m_out); + + replaygain_info rg; + rg.reset(); + t_meta_list::const_iterator iter; + + if (m_is_va) + { + //clean up VA mess + + t_meta_list::const_iterator iter_global,iter_local; + + iter_global = find_first_field(m_globals,"artist"); + iter_local = find_first_field(m_locals,"artist"); + if (iter_global.is_valid()) + { + m_out.meta_set("album artist",iter_global->m_value); + if (iter_local.is_valid()) m_out.meta_set("artist",iter_local->m_value); + else m_out.meta_set("artist",iter_global->m_value); + } + else + { + if (iter_local.is_valid()) m_out.meta_set("artist",iter_local->m_value); + } + + + wipe_field(m_globals,"artist"); + wipe_field(m_locals,"artist"); + + } + + for(iter=m_globals.first();iter.is_valid();iter++) + { + if (!rg.set_from_meta(iter->m_name,iter->m_value)) + m_out.meta_set(iter->m_name,iter->m_value); + } + for(iter=m_locals.first();iter.is_valid();iter++) + { + if (!rg.set_from_meta(iter->m_name,iter->m_value)) + m_out.meta_set(iter->m_name,iter->m_value); + } + m_out.meta_set("tracknumber",PFC_string_formatter() << m_wanted_track); + m_out.meta_set("totaltracks", PFC_string_formatter() << m_totaltracks); + m_out.set_replaygain(rg); + + } + private: + struct t_meta_entry { + pfc::string8 m_name,m_value; + }; + typedef pfc::chain_list_v2_t t_meta_list; + + static t_meta_list::const_iterator find_first_field(t_meta_list const & p_list,const char * p_field) + { + t_meta_list::const_iterator iter; + for(iter=p_list.first();iter.is_valid();++iter) + { + if (!stricmp_utf8(p_field,iter->m_name)) return iter; + } + return t_meta_list::const_iterator();//null iterator + } + + static void wipe_field(t_meta_list & p_list,const char * p_field) + { + t_meta_list::iterator iter; + for(iter=p_list.first();iter.is_valid();) + { + if (!stricmp_utf8(p_field,iter->m_name)) + { + t_meta_list::iterator temp = iter; + ++temp; + p_list.remove_single(iter); + iter = temp; + } + else + { + ++iter; + } + } + } + + t_meta_list m_globals,m_locals; + file_info & m_out; + unsigned m_wanted_track, m_track,m_totaltracks; + pfc::string8 m_album_artist; + bool m_is_va; + t_cuesheet_index_list m_indexes; + bool m_index0_set,m_index1_set; + double m_pregap; + }; + +}; + +static pfc::string_part_ref cue_line_argument( const char * base, size_t length ) { + const char * end = base + length; + while(base < end && is_spacing(base[0]) ) ++base; + while(base < end && is_spacing(end[-1]) ) --end; + if ( base + 1 < end ) { + if ( base[0] == '\"' && end[-1] == '\"' ) { + ++base; --end; + } + } + return pfc::string_part(base, end-base); +} + +static void g_parse_cue_line(const char * p_line,t_size p_line_length,cue_parser_callback & p_callback) +{ + t_size ptr = 0; + while(ptr < p_line_length && !is_spacing(p_line[ptr])) ptr++; + if (!stricmp_utf8_ex(p_line,ptr,"file",pfc_infinite)) + { + while(ptr < p_line_length && is_spacing(p_line[ptr])) ptr++; + t_size file_base,file_length, type_base,type_length; + + if (p_line[ptr] == '\"') + { + ptr++; + file_base = ptr; + while(ptr < p_line_length && p_line[ptr] != '\"') ptr++; + if (ptr == p_line_length) pfc::throw_exception_with_message< exception_cue > ("invalid FILE syntax"); + file_length = ptr - file_base; + ptr++; + while(ptr < p_line_length && is_spacing(p_line[ptr])) ptr++; + } + else + { + file_base = ptr; + while(ptr < p_line_length && !is_spacing(p_line[ptr])) ptr++; + file_length = ptr - file_base; + while(ptr < p_line_length && is_spacing(p_line[ptr])) ptr++; + } + + type_base = ptr; + while(ptr < p_line_length && !is_spacing(p_line[ptr])) ptr++; + type_length = ptr - type_base; + while(ptr < p_line_length && is_spacing(p_line[ptr])) ptr++; + + if (ptr != p_line_length || file_length == 0 || type_length == 0) pfc::throw_exception_with_message< exception_cue > ("invalid FILE syntax"); + + p_callback.on_file(p_line + file_base, file_length, p_line + type_base, type_length); + } + else if (!stricmp_utf8_ex(p_line,ptr,"track",pfc_infinite)) + { + while(ptr < p_line_length && is_spacing(p_line[ptr])) ptr++; + + t_size track_base = ptr, track_length; + while(ptr < p_line_length && !is_spacing(p_line[ptr])) + { + if (!is_numeric(p_line[ptr])) pfc::throw_exception_with_message< exception_cue > ("invalid TRACK syntax"); + ptr++; + } + track_length = ptr - track_base; + while(ptr < p_line_length && is_spacing(p_line[ptr])) ptr++; + + t_size type_base = ptr, type_length; + while(ptr < p_line_length && !is_spacing(p_line[ptr])) ptr++; + type_length = ptr - type_base; + + while(ptr < p_line_length && is_spacing(p_line[ptr])) ptr++; + if (ptr != p_line_length || type_length == 0) pfc::throw_exception_with_message< exception_cue > ("invalid TRACK syntax"); + unsigned track = pfc::atoui_ex(p_line+track_base,track_length); + if (track < 1 || track > maximumCueTrackNumber) pfc::throw_exception_with_message< exception_cue > ("invalid track number"); + + p_callback.on_track(track,p_line + type_base, type_length); + } + else if (!stricmp_utf8_ex(p_line,ptr,"index",pfc_infinite)) + { + while(ptr < p_line_length && is_spacing(p_line[ptr])) ptr++; + + t_size index_base,index_length, time_base,time_length; + index_base = ptr; + while(ptr < p_line_length && !is_spacing(p_line[ptr])) + { + if (!is_numeric(p_line[ptr])) pfc::throw_exception_with_message< exception_cue > ("invalid INDEX syntax" ); + ptr++; + } + index_length = ptr - index_base; + + while(ptr < p_line_length && is_spacing(p_line[ptr])) ptr++; + time_base = ptr; + while(ptr < p_line_length && !is_spacing(p_line[ptr])) + { + if (!is_numeric(p_line[ptr]) && p_line[ptr] != ':') + pfc::throw_exception_with_message< exception_cue > ("invalid INDEX syntax"); + ptr++; + } + time_length = ptr - time_base; + + while(ptr < p_line_length && is_spacing(p_line[ptr])) ptr++; + + if (ptr != p_line_length || index_length == 0 || time_length == 0) + pfc::throw_exception_with_message< exception_cue > ("invalid INDEX syntax"); + + unsigned index = pfc::atoui_ex(p_line+index_base,index_length); + if (index > maximumCueTrackNumber) pfc::throw_exception_with_message< exception_cue > ("invalid INDEX syntax"); + unsigned time = cuesheet_parse_index_time_ticks_e(p_line + time_base,time_length); + + p_callback.on_index(index,time); + } + else if (!stricmp_utf8_ex(p_line,ptr,"pregap",pfc_infinite)) + { + while(ptr < p_line_length && is_spacing(p_line[ptr])) ptr++; + + t_size time_base, time_length; + time_base = ptr; + while(ptr < p_line_length && !is_spacing(p_line[ptr])) + { + if (!is_numeric(p_line[ptr]) && p_line[ptr] != ':') + pfc::throw_exception_with_message< exception_cue > ("invalid PREGAP syntax"); + ptr++; + } + time_length = ptr - time_base; + + while(ptr < p_line_length && is_spacing(p_line[ptr])) ptr++; + + if (ptr != p_line_length || time_length == 0) + pfc::throw_exception_with_message< exception_cue > ("invalid PREGAP syntax"); + + unsigned time = cuesheet_parse_index_time_ticks_e(p_line + time_base,time_length); + + p_callback.on_pregap(time); + } + else if (!stricmp_utf8_ex(p_line,ptr,"title",pfc_infinite)) + { + auto arg = cue_line_argument(p_line+ptr, p_line_length-ptr); + if ( arg.m_len > 0 ) p_callback.on_title( arg.m_ptr, arg.m_len ); + } + else if (!stricmp_utf8_ex(p_line,ptr,"performer",pfc_infinite)) + { + auto arg = cue_line_argument(p_line + ptr, p_line_length - ptr); + if (arg.m_len > 0) p_callback.on_performer(arg.m_ptr, arg.m_len); + } + else if (!stricmp_utf8_ex(p_line,ptr,"songwriter",pfc_infinite)) + { + auto arg = cue_line_argument(p_line + ptr, p_line_length - ptr); + if (arg.m_len > 0) p_callback.on_songwriter(arg.m_ptr, arg.m_len); + } + else if (!stricmp_utf8_ex(p_line,ptr,"isrc",pfc_infinite)) + { + while(ptr < p_line_length && is_spacing(p_line[ptr])) ptr++; + t_size length = p_line_length - ptr; + if (length == 0) pfc::throw_exception_with_message< exception_cue > ("invalid ISRC syntax"); + p_callback.on_isrc(p_line+ptr,length); + } + else if (!stricmp_utf8_ex(p_line,ptr,"catalog",pfc_infinite)) + { + while(ptr < p_line_length && is_spacing(p_line[ptr])) ptr++; + t_size length = p_line_length - ptr; + if (length == 0) pfc::throw_exception_with_message< exception_cue > ("invalid CATALOG syntax"); + p_callback.on_catalog(p_line+ptr,length); + } + else if (!stricmp_utf8_ex(p_line,ptr,"flags",pfc_infinite)) + { + while(ptr < p_line_length && is_spacing(p_line[ptr])) ptr++; + if (ptr < p_line_length) + p_callback.on_flags(p_line + ptr, p_line_length - ptr); + } + else if (!stricmp_utf8_ex(p_line,ptr,"rem",pfc_infinite)) + { + while(ptr < p_line_length && is_spacing(p_line[ptr])) ptr++; + if (ptr < p_line_length) + p_callback.on_comment(p_line + ptr, p_line_length - ptr); + } + else if (!stricmp_utf8_ex(p_line,ptr,"postgap",pfc_infinite)) { + pfc::throw_exception_with_message< exception_cue > ("POSTGAP is not supported"); + } else if (!stricmp_utf8_ex(p_line,ptr,"cdtextfile",pfc_infinite)) { + //do nothing + } + else pfc::throw_exception_with_message< exception_cue > ("unknown cuesheet item"); +} + +static void g_parse_cue(const char * p_cuesheet,cue_parser_callback & p_callback) +{ + const char * parseptr = p_cuesheet; + t_size lineidx = 1; + while(*parseptr) + { + while(is_spacing(*parseptr)) parseptr++; + if (*parseptr) + { + t_size length = 0; + while(parseptr[length] && !is_linebreak(parseptr[length])) length++; + if (length > 0) { + try { + g_parse_cue_line(parseptr,length,p_callback); + } catch(exception_cue const & e) {//rethrow with line info + pfc::throw_exception_with_message< exception_cue > (PFC_string_formatter() << e.what() << " (line " << (unsigned)lineidx << ")"); + } + } + parseptr += length; + while(is_linebreak(*parseptr)) { + if (*parseptr == '\n') lineidx++; + parseptr++; + } + } + } +} + +void cue_parser::parse(const char *p_cuesheet,t_cue_entry_list & p_out) { + try { + cue_parser_callback_retrievelist callback(p_out); + g_parse_cue(p_cuesheet,callback); + callback.finalize(); + } catch(exception_cue const & e) { + pfc::throw_exception_with_message(PFC_string_formatter() << "Error parsing cuesheet: " << e.what()); + } +} +void cue_parser::parse_info(const char * p_cuesheet,file_info & p_info,unsigned p_index) { + try { + cue_parser_callback_retrieveinfo callback(p_info,p_index); + g_parse_cue(p_cuesheet,callback); + callback.finalize(); + } catch(exception_cue const & e) { + pfc::throw_exception_with_message< exception_bad_cuesheet > (PFC_string_formatter() << "Error parsing cuesheet: " << e.what()); + } +} + +namespace { + + class cue_parser_callback_retrievecount : public cue_parser_callback + { + public: + cue_parser_callback_retrievecount() : m_count(0) {} + unsigned get_count() const {return m_count;} + void on_file(const char * p_file,t_size p_file_length,const char * p_type,t_size p_type_length) {} + void on_track(unsigned p_index,const char * p_type,t_size p_type_length) {m_count++;} + void on_pregap(unsigned p_value) {} + void on_index(unsigned p_index,unsigned p_value) {} + void on_title(const char * p_title,t_size p_title_length) {} + void on_performer(const char * p_performer,t_size p_performer_length) {} + void on_isrc(const char * p_isrc,t_size p_isrc_length) {} + void on_catalog(const char * p_catalog,t_size p_catalog_length) {} + void on_comment(const char * p_comment,t_size p_comment_length) {} + void on_flags(const char * p_flags,t_size p_flags_length) {} + private: + unsigned m_count; + }; + + class cue_parser_callback_retrievecreatorentries : public cue_parser_callback + { + public: + cue_parser_callback_retrievecreatorentries(cue_creator::t_entry_list & p_out) : m_out(p_out), m_track(0), m_pregap(0), m_index0_set(false), m_index1_set(false) {} + + void on_file(const char * p_file,t_size p_file_length,const char * p_type,t_size p_type_length) { + validate_file_type(p_type,p_type_length); + m_file.set_string(p_file,p_file_length); + m_fileType.set_string(p_type, p_type_length); + } + + void on_track(unsigned p_index,const char * p_type,t_size p_type_length) + { + finalize_track(); + + m_trackType.set_string( p_type, p_type_length ); + + //if (p_index != m_track + 1) throw exception_cue("cuesheet tracks out of order",0); + + if (m_file.is_empty()) pfc::throw_exception_with_message< exception_cue > ("declaring a track with no file set"); + m_trackfile = m_file; + m_trackFileType = m_fileType; + m_track = p_index; + } + + void on_pregap(unsigned p_value) + { + m_pregap = (double) p_value / 75.0; + } + + void on_index(unsigned p_index,unsigned p_value) + { + if (p_index < t_cuesheet_index_list::count) + { + switch(p_index) + { + case 0: m_index0_set = true; break; + case 1: m_index1_set = true; break; + } + m_indexes.m_positions[p_index] = (double) p_value / 75.0; + } + } + void on_title(const char * p_title,t_size p_title_length) {} + void on_performer(const char * p_performer,t_size p_performer_length) {} + void on_songwriter(const char * p_performer,t_size p_performer_length) {} + void on_isrc(const char * p_isrc,t_size p_isrc_length) {} + void on_catalog(const char * p_catalog,t_size p_catalog_length) {} + void on_comment(const char * p_comment,t_size p_comment_length) {} + void finalize() + { + finalize_track(); + } + void on_flags(const char * p_flags,t_size p_flags_length) { + m_flags.set_string(p_flags,p_flags_length); + } + private: + void finalize_track() + { + if ( m_track != 0 ) { + if (m_track < 1 || m_track > maximumCueTrackNumber) pfc::throw_exception_with_message< exception_cue > ("track number out of range"); + if (!m_index1_set) pfc::throw_exception_with_message< exception_cue > ("INDEX 01 not set"); + if (!m_index0_set) m_indexes.m_positions[0] = m_indexes.m_positions[1] - m_pregap; + if (!m_indexes.is_valid()) pfc::throw_exception_with_message< exception_cue > ("invalid index list"); + + cue_creator::t_entry_list::iterator iter; + iter = m_out.insert_last(); + iter->m_track_number = m_track; + iter->m_file = m_trackfile; + iter->m_fileType = m_trackFileType; + iter->m_index_list = m_indexes; + iter->m_flags = m_flags; + iter->m_trackType = m_trackType; + } + m_pregap = 0; + m_indexes.reset(); + m_index0_set = m_index1_set = false; + m_flags.reset(); + m_trackType.reset(); + } + + bool m_index0_set,m_index1_set; + double m_pregap; + unsigned m_track; + bool m_trackIsAudio = false; + cue_creator::t_entry_list & m_out; + pfc::string8 m_file, m_fileType,m_trackfile, m_trackFileType, m_flags, m_trackType; + t_cuesheet_index_list m_indexes; + }; +} + +void cue_parser::parse_full(const char * p_cuesheet,cue_creator::t_entry_list & p_out) { + try { + { + cue_parser_callback_retrievecreatorentries callback(p_out); + g_parse_cue(p_cuesheet,callback); + callback.finalize(); + } + + { + cue_creator::t_entry_list::iterator iter; + for(iter=p_out.first();iter.is_valid();++iter) { + if ( iter->isTrackAudio() ) { + cue_parser_callback_retrieveinfo callback(iter->m_infos,iter->m_track_number); + g_parse_cue(p_cuesheet,callback); + callback.finalize(); + } + } + } + } catch(exception_cue const & e) { + pfc::throw_exception_with_message< exception_bad_cuesheet > (PFC_string_formatter() << "Error parsing cuesheet: " << e.what()); + } +} + +namespace file_info_record_helper { + namespace { + class __file_info_record__info__enumerator { + public: + __file_info_record__info__enumerator(file_info & p_out) : m_out(p_out) {} + void operator() (const char * p_name, const char * p_value) { m_out.__info_add_unsafe(p_name, p_value); } + private: + file_info & m_out; + }; + + class __file_info_record__meta__enumerator { + public: + __file_info_record__meta__enumerator(file_info & p_out) : m_out(p_out) {} + template void operator() (const char * p_name, const t_value & p_value) { + t_size index = ~0; + for (typename t_value::const_iterator iter = p_value.first(); iter.is_valid(); ++iter) { + if (index == ~0) index = m_out.__meta_add_unsafe(p_name, *iter); + else m_out.meta_add_value(index, *iter); + } + } + private: + file_info & m_out; + }; + } + + void file_info_record::from_info(const file_info & p_info) { + reset(); + m_length = p_info.get_length(); + m_replaygain = p_info.get_replaygain(); + from_info_overwrite_meta(p_info); + from_info_overwrite_info(p_info); + } + void file_info_record::to_info(file_info & p_info) const { + p_info.reset(); + p_info.set_length(m_length); + p_info.set_replaygain(m_replaygain); + + { + __file_info_record__info__enumerator e(p_info); + m_info.enumerate(e); + } + { + __file_info_record__meta__enumerator e(p_info); + m_meta.enumerate(e); + } + } + + void file_info_record::reset() { + m_meta.remove_all(); m_info.remove_all(); + m_length = 0; + m_replaygain = replaygain_info_invalid; + } + + void file_info_record::from_info_overwrite_info(const file_info & p_info) { + for (t_size infowalk = 0, infocount = p_info.info_get_count(); infowalk < infocount; ++infowalk) { + m_info.set(p_info.info_enum_name(infowalk), p_info.info_enum_value(infowalk)); + } + } + void file_info_record::from_info_overwrite_meta(const file_info & p_info) { + for (t_size metawalk = 0, metacount = p_info.meta_get_count(); metawalk < metacount; ++metawalk) { + const t_size valuecount = p_info.meta_enum_value_count(metawalk); + if (valuecount > 0) { + t_meta_value & entry = m_meta.find_or_add(p_info.meta_enum_name(metawalk)); + entry.remove_all(); + for (t_size valuewalk = 0; valuewalk < valuecount; ++valuewalk) { + entry.add_item(p_info.meta_enum_value(metawalk, valuewalk)); + } + } + } + } + + void file_info_record::from_info_overwrite_rg(const file_info & p_info) { + m_replaygain = replaygain_info::g_merge(m_replaygain, p_info.get_replaygain()); + } + + void file_info_record::merge_overwrite(const file_info & p_info) { + from_info_overwrite_info(p_info); + from_info_overwrite_meta(p_info); + from_info_overwrite_rg(p_info); + } + + void file_info_record::transfer_meta_entry(const char * p_name, const file_info & p_info, t_size p_index) { + const t_size count = p_info.meta_enum_value_count(p_index); + if (count == 0) { + m_meta.remove(p_name); + } else { + t_meta_value & val = m_meta.find_or_add(p_name); + val.remove_all(); + for (t_size walk = 0; walk < count; ++walk) { + val.add_item(p_info.meta_enum_value(p_index, walk)); + } + } + } + + void file_info_record::meta_set(const char * p_name, const char * p_value) { + m_meta.find_or_add(p_name).set_single(p_value); + } + + const file_info_record::t_meta_value * file_info_record::meta_query_ptr(const char * p_name) const { + return m_meta.query_ptr(p_name); + } + + + void file_info_record::from_info_set_meta(const file_info & p_info) { + m_meta.remove_all(); + from_info_overwrite_meta(p_info); + } + +} \ No newline at end of file diff --git a/foobar2000/helpers/cue_parser.h b/foobar2000/helpers/cue_parser.h new file mode 100644 index 0000000..29028c3 --- /dev/null +++ b/foobar2000/helpers/cue_parser.h @@ -0,0 +1,360 @@ +#pragma once + +#include "cue_creator.h" + +//HINT: for info on how to generate an embedded cuesheet enabled input, see the end of this header. + + + +namespace file_info_record_helper { + + class file_info_record { + public: + typedef pfc::chain_list_v2_t t_meta_value; + typedef pfc::map_t t_meta_map; + typedef pfc::map_t t_info_map; + + file_info_record() : m_replaygain(replaygain_info_invalid), m_length(0) {} + + replaygain_info get_replaygain() const {return m_replaygain;} + void set_replaygain(const replaygain_info & p_replaygain) {m_replaygain = p_replaygain;} + double get_length() const {return m_length;} + void set_length(double p_length) {m_length = p_length;} + + void reset(); + void from_info_overwrite_info(const file_info & p_info); + void from_info_overwrite_meta(const file_info & p_info); + void from_info_overwrite_rg(const file_info & p_info); + + template + void overwrite_meta(const t_source & p_meta) { + m_meta.overwrite(p_meta); + } + template + void overwrite_info(const t_source & p_info) { + m_info.overwrite(p_info); + } + + void merge_overwrite(const file_info & p_info); + void transfer_meta_entry(const char * p_name,const file_info & p_info,t_size p_index); + + void meta_set(const char * p_name,const char * p_value); + const t_meta_value * meta_query_ptr(const char * p_name) const; + + void from_info_set_meta(const file_info & p_info); + + void from_info(const file_info & p_info); + void to_info(file_info & p_info) const; + + template void enumerate_meta(t_callback & p_callback) const {m_meta.enumerate(p_callback);} + template void enumerate_meta(t_callback & p_callback) {m_meta.enumerate(p_callback);} + + //private: + t_meta_map m_meta; + t_info_map m_info; + replaygain_info m_replaygain; + double m_length; + }; + +}//namespace file_info_record_helper + + +namespace cue_parser +{ + struct cue_entry { + pfc::string8 m_file, m_fileType; + unsigned m_track_number; + t_cuesheet_index_list m_indexes; + bool isFileBinary() const {return pfc::stringEqualsI_ascii(m_fileType, "BINARY");} + }; + + typedef pfc::chain_list_v2_t t_cue_entry_list; + + + PFC_DECLARE_EXCEPTION(exception_bad_cuesheet,exception_io_data,"Invalid cuesheet"); + + //! Throws exception_bad_cuesheet on failure. + void parse(const char *p_cuesheet,t_cue_entry_list & p_out); + //! Throws exception_bad_cuesheet on failure. + void parse_info(const char *p_cuesheet,file_info & p_info,unsigned p_index); + //! Throws exception_bad_cuesheet on failure. + void parse_full(const char * p_cuesheet,cue_creator::t_entry_list & p_out); + + + + struct track_record { + file_info_record_helper::file_info_record m_info; + pfc::string8 m_file,m_flags; + t_cuesheet_index_list m_index_list; + }; + + typedef pfc::map_t track_record_list; + + class embeddedcue_metadata_manager { + public: + void get_tag(file_info & p_info) const; + void set_tag(file_info const & p_info); + + void get_track_info(unsigned p_track,file_info & p_info) const; + void set_track_info(unsigned p_track,file_info const & p_info); + void query_track_offsets(unsigned p_track,double & p_begin,double & p_length) const; + bool have_cuesheet() const; + unsigned remap_trackno(unsigned p_index) const; + t_size get_cue_track_count() const; + private: + track_record_list m_content; + }; + + + + + + template + class input_wrapper_cue_t : public input_forward_static_methods { + public: + typedef input_info_writer_v2 interface_info_writer_t; // remove_tags supplied + void open(service_ptr_t p_filehint,const char * p_path,t_input_open_reason p_reason,abort_callback & p_abort) { + m_remote = filesystem::g_is_recognized_and_remote( p_path ); + if (m_remote && p_reason == input_open_info_write) throw exception_io_object_is_remote(); + m_impl.open( p_filehint, p_path, p_reason, p_abort ); + if (!m_remote) { + file_info_impl info; + m_impl.get_info(info, p_abort); + m_meta.set_tag(info); + } + } + + t_uint32 get_subsong_count() { + return this->expose_cuesheet() ? (uint32_t) m_meta.get_cue_track_count() : 1; + } + + t_uint32 get_subsong(t_uint32 p_index) { + return this->expose_cuesheet() ? m_meta.remap_trackno(p_index) : 0; + } + + void get_info(t_uint32 p_subsong,file_info & p_info,abort_callback & p_abort) { + if (m_remote) { + PFC_ASSERT(p_subsong == 0); + m_impl.get_info(p_info, p_abort); + } else if (p_subsong == 0) { + m_meta.get_tag(p_info); + } else { + m_meta.get_track_info(p_subsong,p_info); + } + } + + t_filestats get_file_stats(abort_callback & p_abort) {return m_impl.get_file_stats(p_abort);} + + void decode_initialize(t_uint32 p_subsong,unsigned p_flags,abort_callback & p_abort) { + if (p_subsong == 0) { + m_impl.decode_initialize(p_flags, p_abort); + m_decodeFrom = 0; m_decodeLength = -1; m_decodePos = 0; + } else { + double start, length; + _query_track_offsets(p_subsong,start,length); + unsigned flags2 = p_flags; + if (start > 0) flags2 &= ~input_flag_no_seeking; + flags2 &= ~input_flag_allow_inaccurate_seeking; + m_impl.decode_initialize(flags2, p_abort); + m_impl.decode_seek(start, p_abort); + m_decodeFrom = start; m_decodeLength = length; m_decodePos = 0; + } + } + + bool decode_run(audio_chunk & p_chunk,abort_callback & p_abort) { + return _run(p_chunk, NULL, p_abort); + } + + void decode_seek(double p_seconds,abort_callback & p_abort) { + if (this->m_decodeLength >= 0 && p_seconds > m_decodeLength) p_seconds = m_decodeLength; + m_impl.decode_seek(m_decodeFrom + p_seconds,p_abort); + m_decodePos = p_seconds; + } + + bool decode_can_seek() {return m_impl.decode_can_seek();} + + bool decode_run_raw(audio_chunk & p_chunk, mem_block_container & p_raw, abort_callback & p_abort) { + return _run(p_chunk, &p_raw, p_abort); + } + void set_logger(event_logger::ptr ptr) { + m_impl.set_logger(ptr); + } + bool flush_on_pause() { + return m_impl.flush_on_pause(); + } + void set_pause(bool) {} // obsolete + size_t extended_param(const GUID & type, size_t arg1, void * arg2, size_t arg2size) { + return m_impl.extended_param(type, arg1, arg2, arg2size); + } + + bool decode_get_dynamic_info(file_info & p_out, double & p_timestamp_delta) { + return m_impl.decode_get_dynamic_info(p_out, p_timestamp_delta); + } + + bool decode_get_dynamic_info_track(file_info & p_out, double & p_timestamp_delta) { + return m_impl.decode_get_dynamic_info_track(p_out, p_timestamp_delta); + } + + void decode_on_idle(abort_callback & p_abort) { + m_impl.decode_on_idle(p_abort); + } + + void remove_tags(abort_callback & abort) { + PFC_ASSERT(!m_remote); + m_impl.remove_tags( abort ); + file_info_impl info; + m_impl.get_info(info, abort); + m_meta.set_tag( info ); + } + + void retag_set_info(t_uint32 p_subsong,const file_info & p_info,abort_callback & p_abort) { + PFC_ASSERT(!m_remote); + if (p_subsong == 0) { + m_meta.set_tag(p_info); + } else { + m_meta.set_track_info(p_subsong,p_info); + } + } + + void retag_commit(abort_callback & p_abort) { + PFC_ASSERT(!m_remote); + file_info_impl info; + m_meta.get_tag(info); + m_impl.retag(pfc::implicit_cast(info), p_abort); + info.reset(); + m_impl.get_info(info, p_abort); + m_meta.set_tag( info ); + } + void _query_track_offsets(unsigned p_subsong, double& start, double& length) const { + m_meta.query_track_offsets(p_subsong,start,length); + } + bool expose_cuesheet() const { + return !m_remote && m_meta.have_cuesheet(); + } + private: + bool _run(audio_chunk & chunk, mem_block_container * raw, abort_callback & aborter) { + if (m_decodeLength >= 0 && m_decodePos >= m_decodeLength) return false; + if (raw == NULL) { + if (!m_impl.decode_run(chunk, aborter)) return false; + } else { + if (!m_impl.decode_run_raw(chunk, *raw, aborter)) return false; + } + + if (m_decodeLength >= 0) { + const uint64_t remaining = audio_math::time_to_samples( m_decodeLength - m_decodePos, chunk.get_sample_rate() ); + const size_t samplesGot = chunk.get_sample_count(); + if (remaining < samplesGot) { + m_decodePos = m_decodeLength; + if (remaining == 0) { // rare but possible as a result of rounding SNAFU - we're EOF but we didn't notice earlier + return false; + } + + chunk.set_sample_count( (size_t) remaining ); + if (raw != NULL) { + const t_size rawSize = raw->get_size(); + PFC_ASSERT( rawSize % samplesGot == 0 ); + raw->set_size( (t_size) ( (t_uint64) rawSize * remaining / samplesGot ) ); + } + } else { + m_decodePos += chunk.get_duration(); + } + } else { + m_decodePos += chunk.get_duration(); + } + return true; + } + t_base m_impl; + double m_decodeFrom, m_decodeLength, m_decodePos; + bool m_remote = false; + embeddedcue_metadata_manager m_meta; + }; +#ifdef FOOBAR2000_HAVE_CHAPTERIZER + template + class chapterizer_impl_t : public chapterizer + { + public: + bool is_our_path(const char * p_path) { + return I::g_is_our_path(p_path, pfc::string_extension(p_path)); + } + + void set_chapters(const char * p_path,chapter_list const & p_list,abort_callback & p_abort) { + input_wrapper_cue_t instance; + instance.open(0,p_path,input_open_info_write,p_abort); + + //stamp the cuesheet first + { + file_info_impl info; + instance.get_info(0,info,p_abort); + + pfc::string_formatter cuesheet; + + { + cue_creator::t_entry_list entries; + t_size n, m = p_list.get_chapter_count(); + const double pregap = p_list.get_pregap(); + double offset_acc = pregap; + for(n=0;nm_infos = p_list.get_info(n); + entry->m_file = "CDImage.wav"; + entry->m_track_number = (unsigned)(n+1); + entry->m_index_list.from_infos(entry->m_infos,offset_acc); + if (n == 0) entry->m_index_list.m_positions[0] = 0; + offset_acc += entry->m_infos.get_length(); + } + cue_creator::create(cuesheet,entries); + } + + info.meta_set("cuesheet",cuesheet); + + instance.retag_set_info(0,info,p_abort); + } + //stamp per-chapter infos + for(t_size walk = 0, total = p_list.get_chapter_count(); walk < total; ++walk) { + instance.retag_set_info( (uint32_t)( walk + 1 ), p_list.get_info(walk),p_abort); + } + + instance.retag_commit(p_abort); + } + + void get_chapters(const char * p_path,chapter_list & p_list,abort_callback & p_abort) { + + input_wrapper_cue_t instance; + instance.open(0,p_path,input_open_info_read,p_abort); + const t_uint32 total = instance.get_subsong_count(); + + if (instance.expose_cuesheet()) { + double start, len; + instance._query_track_offsets(1, start, len); + p_list.set_pregap( start ); + } + + + p_list.set_chapter_count(total); + for(t_uint32 walk = 0; walk < total; ++walk) { + file_info_impl info; + instance.get_info(instance.get_subsong(walk),info,p_abort); + p_list.set_info(walk,info); + } + } + + bool supports_pregaps() { + return true; + } + }; +#endif +}; + +//! Wrapper template for generating embedded cuesheet enabled inputs. +//! t_input_impl is a singletrack input implementation (see input_singletrack_impl for method declarations). +//! To declare an embedded cuesheet enabled input, change your input declaration from input_singletrack_factory_t to input_cuesheet_factory_t. +template +class input_cuesheet_factory_t { +public: + input_factory_t,t_flags > m_input_factory; +#ifdef FOOBAR2000_HAVE_CHAPTERIZER + service_factory_single_t > m_chapterizer_factory; +#endif +}; diff --git a/foobar2000/helpers/cue_parser_embedding.cpp b/foobar2000/helpers/cue_parser_embedding.cpp new file mode 100644 index 0000000..449d541 --- /dev/null +++ b/foobar2000/helpers/cue_parser_embedding.cpp @@ -0,0 +1,383 @@ +#include "stdafx.h" +#include "cue_parser.h" + +using namespace cue_parser; +using namespace file_info_record_helper; +static void build_cue_meta_name(const char * p_name,unsigned p_tracknumber,pfc::string_base & p_out) { + p_out.reset(); + p_out << "cue_track" << pfc::format_uint(p_tracknumber % 100,2) << "_" << p_name; +} + +static bool is_reserved_meta_entry(const char * p_name) { + return file_info::field_name_comparator::compare(p_name,"cuesheet") == 0; +} + +static bool is_global_meta_entry(const char * p_name) { + static const char header[] = "cue_track"; + return pfc::stricmp_ascii_ex(p_name,strlen(header),header,~0) != 0; +} +static bool is_allowed_field(const char * p_name) { + return !is_reserved_meta_entry(p_name) && is_global_meta_entry(p_name); +} +namespace { + class __get_tag_cue_track_list_builder { + public: + __get_tag_cue_track_list_builder(cue_creator::t_entry_list & p_entries) : m_entries(p_entries) {} + void operator() (unsigned p_trackno,const track_record & p_record) { + if (p_trackno > 0) { + cue_creator::t_entry_list::iterator iter = m_entries.insert_last(); + iter->m_trackType = "AUDIO"; + iter->m_file = p_record.m_file; + iter->m_flags = p_record.m_flags; + iter->m_index_list = p_record.m_index_list; + iter->m_track_number = p_trackno; + p_record.m_info.to_info(iter->m_infos); + } + } + private: + cue_creator::t_entry_list & m_entries; + }; + + typedef pfc::avltree_t field_name_list; + + class __get_tag__enum_fields_enumerator { + public: + __get_tag__enum_fields_enumerator(field_name_list & p_out) : m_out(p_out) {} + void operator() (unsigned p_trackno,const track_record & p_record) { + if (p_trackno > 0) p_record.m_info.enumerate_meta(*this); + } + template void operator() (const char * p_name,const t_value & p_value) { + m_out.add(p_name); + } + private: + field_name_list & m_out; + }; + + + class __get_tag__is_field_global_check { + private: + typedef file_info_record::t_meta_value t_value; + public: + __get_tag__is_field_global_check(const char * p_field) : m_field(p_field), m_value(NULL), m_state(true) {} + + void operator() (unsigned p_trackno,const track_record & p_record) { + if (p_trackno > 0 && m_state) { + const t_value * val = p_record.m_info.meta_query_ptr(m_field); + if (val == NULL) {m_state = false; return;} + if (m_value == NULL) { + m_value = val; + } else { + if (pfc::comparator_list::compare(*m_value,*val) != 0) { + m_state = false; return; + } + } + } + } + void finalize(file_info_record::t_meta_map & p_globals) { + if (m_state && m_value != NULL) { + p_globals.set(m_field,*m_value); + } + } + private: + const char * const m_field; + const t_value * m_value; + bool m_state; + }; + + class __get_tag__filter_globals { + public: + __get_tag__filter_globals(track_record_list const & p_tracks,file_info_record::t_meta_map & p_globals) : m_tracks(p_tracks), m_globals(p_globals) {} + + void operator() (const char * p_field) { + if (is_allowed_field(p_field)) { + __get_tag__is_field_global_check wrapper(p_field); + m_tracks.enumerate(wrapper); + wrapper.finalize(m_globals); + } + } + private: + const track_record_list & m_tracks; + file_info_record::t_meta_map & m_globals; + }; + + class __get_tag__local_field_filter { + public: + __get_tag__local_field_filter(const file_info_record::t_meta_map & p_globals,file_info_record::t_meta_map & p_output) : m_globals(p_globals), m_output(p_output), m_currenttrack(0) {} + void operator() (unsigned p_trackno,const track_record & p_track) { + if (p_trackno > 0) { + m_currenttrack = p_trackno; + p_track.m_info.enumerate_meta(*this); + } + } + void operator() (const char * p_name,const file_info_record::t_meta_value & p_value) { + PFC_ASSERT(m_currenttrack > 0); + if (!m_globals.have_item(p_name)) { + build_cue_meta_name(p_name,m_currenttrack,m_buffer); + m_output.set(m_buffer,p_value); + } + } + private: + unsigned m_currenttrack; + pfc::string8_fastalloc m_buffer; + const file_info_record::t_meta_map & m_globals; + file_info_record::t_meta_map & m_output; + }; +}; + +static bool meta_value_equals(const char* v1, const char* v2, bool asNumber) { + if (asNumber) { + // Special fix: leading zeros on track numbers + while( *v1 == '0' ) ++ v1; + while( *v2 == '0' ) ++ v2; + } + return strcmp(v1,v2) == 0; +} + +static void strip_redundant_track_meta(unsigned p_tracknumber,const file_info & p_cueinfo,file_info_record::t_meta_map & p_meta,const char * p_metaname, bool asNumber) { + const size_t metaindex = p_cueinfo.meta_find(p_metaname); + if (metaindex == SIZE_MAX) return; + pfc::string_formatter namelocal; + build_cue_meta_name(p_metaname,p_tracknumber,namelocal); + { + const file_info_record::t_meta_value * val = p_meta.query_ptr(namelocal); + if (val == NULL) return; + file_info_record::t_meta_value::const_iterator iter = val->first(); + for(t_size valwalk = 0, valcount = p_cueinfo.meta_enum_value_count(metaindex); valwalk < valcount; ++valwalk) { + if (iter.is_empty()) return; + + if (!meta_value_equals(*iter,p_cueinfo.meta_enum_value(metaindex,valwalk), asNumber)) return; + ++iter; + } + if (!iter.is_empty()) return; + } + //success + p_meta.remove(namelocal); +} + +void embeddedcue_metadata_manager::get_tag(file_info & p_info) const { + if (!have_cuesheet()) { + m_content.query_ptr((unsigned)0)->m_info.to_info(p_info); + p_info.meta_remove_field("cuesheet"); + } else { + cue_creator::t_entry_list entries; + { + __get_tag_cue_track_list_builder e(entries); + m_content.enumerate(e); + } + pfc::string_formatter cuesheet; + cue_creator::create(cuesheet,entries); + entries.remove_all(); + //parse it back to see what info got stored in the cuesheet and what needs to be stored outside cuesheet in the tags + cue_parser::parse_full(cuesheet,entries); + file_info_record output; + + + + + { + file_info_record::t_meta_map globals; + //1. find global infos and forward them + { + field_name_list fields; + { __get_tag__enum_fields_enumerator e(fields); m_content.enumerate(e);} + { __get_tag__filter_globals e(m_content,globals); fields.enumerate(e); } + } + + output.overwrite_meta(globals); + + //2. find local infos + {__get_tag__local_field_filter e(globals,output.m_meta); m_content.enumerate(e);} + } + + + //strip redundant titles and tracknumbers that the cuesheet already contains + for(cue_creator::t_entry_list::const_iterator iter = entries.first(); iter.is_valid(); ++iter) { + strip_redundant_track_meta(iter->m_track_number,iter->m_infos,output.m_meta,"tracknumber", true); + strip_redundant_track_meta(iter->m_track_number,iter->m_infos,output.m_meta,"title", false); + } + + + //add tech infos etc + + { + const track_record * rec = m_content.query_ptr((unsigned)0); + if (rec != NULL) { + output.set_length(rec->m_info.get_length()); + output.set_replaygain(rec->m_info.get_replaygain()); + output.overwrite_info(rec->m_info.m_info); + } + } + output.meta_set("cuesheet",cuesheet); + output.to_info(p_info); + } +} + +static bool resolve_cue_meta_name(const char * p_name,pfc::string_base & p_outname,unsigned & p_tracknumber) { + //"cue_trackNN_fieldname" + static const char header[] = "cue_track"; + if (pfc::stricmp_ascii_ex(p_name,strlen(header),header,~0) != 0) return false; + p_name += strlen(header); + if (!pfc::char_is_numeric(p_name[0]) || !pfc::char_is_numeric(p_name[1]) || p_name[2] != '_') return false; + unsigned tracknumber = pfc::atoui_ex(p_name,2); + if (tracknumber == 0) return false; + p_name += 3; + p_tracknumber = tracknumber; + p_outname = p_name; + return true; +} + + +namespace { + class __set_tag_global_field_relay { + public: + __set_tag_global_field_relay(const file_info & p_info,t_size p_index) : m_info(p_info), m_index(p_index) {} + void operator() (unsigned p_trackno,track_record & p_record) { + if (p_trackno > 0) { + p_record.m_info.transfer_meta_entry(m_info.meta_enum_name(m_index),m_info,m_index); + } + } + private: + const file_info & m_info; + const t_size m_index; + }; +} + +void embeddedcue_metadata_manager::set_tag(file_info const & p_info) { + m_content.remove_all(); + + { + track_record & track0 = m_content.find_or_add((unsigned)0); + track0.m_info.from_info(p_info); + track0.m_info.m_info.set("cue_embedded","no"); + } + + + + const char * cuesheet = p_info.meta_get("cuesheet",0); + if (cuesheet == NULL) { + return; + } + + //processing order + //1. cuesheet content + //2. overwrite with global metadata from the tag + //3. overwrite with local metadata from the tag + + { + cue_creator::t_entry_list entries; + try { + cue_parser::parse_full(cuesheet,entries); + } catch(exception_io_data const & e) { + console::complain("Attempting to embed an invalid cuesheet", e.what()); + return; + } + + { + const double length = p_info.get_length(); + for(cue_creator::t_entry_list::const_iterator iter = entries.first(); iter.is_valid(); ++iter ) { + if (iter->m_index_list.start() > length) { + console::info("Invalid cuesheet - index outside allowed range"); + return; + } + } + } + + for(cue_creator::t_entry_list::const_iterator iter = entries.first(); iter.is_valid(); ) { + cue_creator::t_entry_list::const_iterator next = iter; + ++next; + track_record & entry = m_content.find_or_add(iter->m_track_number); + entry.m_file = iter->m_file; + entry.m_flags = iter->m_flags; + entry.m_index_list = iter->m_index_list; + entry.m_info.from_info(iter->m_infos); + entry.m_info.from_info_overwrite_info(p_info); + entry.m_info.m_info.set("cue_embedded","yes"); + double begin = entry.m_index_list.start(), end = next.is_valid() ? next->m_index_list.start() : p_info.get_length(); + if (end <= begin) throw exception_io_data(); + entry.m_info.set_length(end - begin); + iter = next; + } + } + + for(t_size metawalk = 0, metacount = p_info.meta_get_count(); metawalk < metacount; ++metawalk) { + const char * name = p_info.meta_enum_name(metawalk); + const t_size valuecount = p_info.meta_enum_value_count(metawalk); + if (valuecount > 0 && !is_reserved_meta_entry(name) && is_global_meta_entry(name)) { + __set_tag_global_field_relay relay(p_info,metawalk); + m_content.enumerate(relay); + } + } + + { + pfc::string8_fastalloc namebuffer; + for(t_size metawalk = 0, metacount = p_info.meta_get_count(); metawalk < metacount; ++metawalk) { + const char * name = p_info.meta_enum_name(metawalk); + const t_size valuecount = p_info.meta_enum_value_count(metawalk); + unsigned trackno; + if (valuecount > 0 && !is_reserved_meta_entry(name) && resolve_cue_meta_name(name,namebuffer,trackno)) { + track_record * rec = m_content.query_ptr(trackno); + if (rec != NULL) { + rec->m_info.transfer_meta_entry(namebuffer,p_info,metawalk); + } + } + } + } +} + +void embeddedcue_metadata_manager::get_track_info(unsigned p_track,file_info & p_info) const { + const track_record * rec = m_content.query_ptr(p_track); + if (rec == NULL) throw exception_io_data(); + rec->m_info.to_info(p_info); +} + +void embeddedcue_metadata_manager::set_track_info(unsigned p_track,file_info const & p_info) { + track_record * rec = m_content.query_ptr(p_track); + if (rec == NULL) throw exception_io_data(); + rec->m_info.from_info_set_meta(p_info); + rec->m_info.set_replaygain(p_info.get_replaygain()); +} + +void embeddedcue_metadata_manager::query_track_offsets(unsigned p_track,double & p_begin,double & p_length) const { + const track_record * rec = m_content.query_ptr(p_track); + if (rec == NULL) throw exception_io_data(); + p_begin = rec->m_index_list.start(); + p_length = rec->m_info.get_length(); +} + +bool embeddedcue_metadata_manager::have_cuesheet() const { + return m_content.get_count() > 1; +} + +namespace { + class __remap_trackno_enumerator { + public: + __remap_trackno_enumerator(unsigned p_index) : m_countdown(p_index), m_result(0) {} + template void operator() (unsigned p_trackno,const t_blah&) { + if (p_trackno > 0 && m_result == 0) { + if (m_countdown == 0) { + m_result = p_trackno; + } else { + --m_countdown; + } + } + } + unsigned result() const {return m_result;} + private: + unsigned m_countdown; + unsigned m_result; + }; +}; + +unsigned embeddedcue_metadata_manager::remap_trackno(unsigned p_index) const { + if (have_cuesheet()) { + __remap_trackno_enumerator wrapper(p_index); + m_content.enumerate(wrapper); + return wrapper.result(); + } else { + return 0; + } +} + +t_size embeddedcue_metadata_manager::get_cue_track_count() const { + return m_content.get_count() - 1; +} \ No newline at end of file diff --git a/foobar2000/helpers/cuesheet_index_list.cpp b/foobar2000/helpers/cuesheet_index_list.cpp new file mode 100644 index 0000000..679f31a --- /dev/null +++ b/foobar2000/helpers/cuesheet_index_list.cpp @@ -0,0 +1,145 @@ +#include "stdafx.h" +#include "cuesheet_index_list.h" + +#ifndef _MSC_VER +#define sprintf_s sprintf +#endif + +bool t_cuesheet_index_list::is_valid() const { + if (m_positions[1] < m_positions[0]) return false; + for(t_size n = 2; n < count && m_positions[n] > 0; n++) { + if (m_positions[n] < m_positions[n-1]) return false; + } + return true; +} + +void t_cuesheet_index_list::to_infos(file_info & p_out) const +{ + double base = m_positions[1]; + + if (base > 0) { + p_out.info_set("referenced_offset",cuesheet_format_index_time(base)); + } + + if (m_positions[0] < base) + p_out.info_set("pregap",cuesheet_format_index_time(base - m_positions[0])); + else + p_out.info_remove("pregap"); + + p_out.info_remove("index 00"); + p_out.info_remove("index 01"); + + for(unsigned n=2;n 0) + p_out.info_set(namebuffer,cuesheet_format_index_time(position)); + else + p_out.info_remove(namebuffer); + } +} + +static bool parse_value(const char * p_name,double & p_out) +{ + if (p_name == NULL) return false; + try { + p_out = cuesheet_parse_index_time_e(p_name,strlen(p_name)); + } catch(std::exception const &) {return false;} + return true; +} + +bool t_cuesheet_index_list::from_infos(file_info const & p_in,double p_base) +{ + double pregap; + bool found = false; + if (!parse_value(p_in.info_get("pregap"),pregap)) pregap = 0; + else found = true; + m_positions[0] = p_base - pregap; + m_positions[1] = p_base; + for(unsigned n=2;n= 2) throw std::runtime_error("invalid INDEX time syntax"); + splitmarks[splitptr++] = ptr; + } + else if (!pfc::char_is_numeric(p_string[ptr])) throw std::runtime_error("invalid INDEX time syntax"); + } + + t_size minutes_base = 0, minutes_length = 0, seconds_base = 0, seconds_length = 0, frames_base = 0, frames_length = 0; + + switch(splitptr) + { + case 0: + frames_base = 0; + frames_length = p_length; + break; + case 1: + seconds_base = 0; + seconds_length = splitmarks[0]; + frames_base = splitmarks[0] + 1; + frames_length = p_length - frames_base; + break; + case 2: + minutes_base = 0; + minutes_length = splitmarks[0]; + seconds_base = splitmarks[0] + 1; + seconds_length = splitmarks[1] - seconds_base; + frames_base = splitmarks[1] + 1; + frames_length = p_length - frames_base; + break; + } + + unsigned ret = 0; + + if (frames_length > 0) ret += pfc::atoui_ex(p_string + frames_base,frames_length); + if (seconds_length > 0) ret += 75 * pfc::atoui_ex(p_string + seconds_base,seconds_length); + if (minutes_length > 0) ret += 60 * 75 * pfc::atoui_ex(p_string + minutes_base,minutes_length); + + return ret; +} diff --git a/foobar2000/helpers/cuesheet_index_list.h b/foobar2000/helpers/cuesheet_index_list.h new file mode 100644 index 0000000..db54fd6 --- /dev/null +++ b/foobar2000/helpers/cuesheet_index_list.h @@ -0,0 +1,35 @@ +#pragma once + +unsigned cuesheet_parse_index_time_ticks_e(const char * p_string,t_size p_length); +double cuesheet_parse_index_time_e(const char * p_string,t_size p_length); + +class cuesheet_format_index_time +{ +public: + cuesheet_format_index_time(double p_time); + inline operator const char*() const {return m_buffer;} +private: + pfc::string_formatter m_buffer; +}; + + +struct t_cuesheet_index_list +{ + enum {count = 100}; + t_cuesheet_index_list() {reset();} + void reset() {for(unsigned n=0;nptMaxTrackSize.x = r.right - r.left; + info->ptMaxTrackSize.y = r.bottom - r.top; + } + if (min_x && min_y) + { + r.left = 0; r.right = min_x; + r.top = 0; r.bottom = min_y; + MapDialogRect(hWnd,&r); + AdjustWindowRectEx(&r, dwStyle, FALSE, dwExStyle); + info->ptMinTrackSize.x = r.right - r.left; + info->ptMinTrackSize.y = r.bottom - r.top; + } + } + lResult = 0; + return TRUE; + case WM_INITDIALOG: + set_parent(hWnd); + { + t_size n; + for(n=0;n + +// Legacy class referenced by old code +// Do not use in new code, use libPPUI instead +class dialog_resize_helper : public CDialogResizeHelperCompat +{ + pfc::array_t rects; + RECT orig_client; + HWND parent; + HWND sizegrip; + unsigned min_x,min_y,max_x,max_y; + + pfc::array_t m_table; + + void set_parent(HWND wnd); + void reset(); + void on_wm_size(); +public: + inline void set_min_size(unsigned x,unsigned y) {min_x = x; min_y = y;} + inline void set_max_size(unsigned x,unsigned y) {max_x = x; max_y = y;} + void add_sizegrip(); + + //the old way + bool process_message(HWND wnd,UINT msg,WPARAM wp,LPARAM lp); + + //ATL-compatible + BOOL ProcessWindowMessage(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT& lResult); + + dialog_resize_helper(const param * src,unsigned count,unsigned p_min_x,unsigned p_min_y,unsigned p_max_x,unsigned p_max_y); + + ~dialog_resize_helper(); + + PFC_CLASS_NOT_COPYABLE_EX(dialog_resize_helper); +}; + +#endif // FOOBAR2000_DESKTOP_WINDOWS \ No newline at end of file diff --git a/foobar2000/helpers/dropdown_helper.cpp b/foobar2000/helpers/dropdown_helper.cpp new file mode 100644 index 0000000..005107b --- /dev/null +++ b/foobar2000/helpers/dropdown_helper.cpp @@ -0,0 +1,174 @@ +#include "stdafx.h" + +#ifdef FOOBAR2000_DESKTOP_WINDOWS + +#include "dropdown_helper.h" + +void _cfg_dropdown_history_base::build_list(pfc::ptr_list_t & out) +{ + pfc::string8 temp; get_state(temp); + const char * src = temp; + while(*src) + { + int ptr = 0; + while(src[ptr] && src[ptr]!=separator) ptr++; + if (ptr>0) + { + out.add_item(pfc::strdup_n(src,ptr)); + src += ptr; + } + while(*src==separator) src++; + } +} + +void _cfg_dropdown_history_base::parse_list(const pfc::ptr_list_t & src) +{ + t_size n; + pfc::string8_fastalloc temp; + for(n=0;n & list) +{ + t_size n, m = list.get_count(); + uSendMessage(wnd,CB_RESETCONTENT,0,0); + for(n=0;n list; + build_list(list); + g_setup_dropdown_fromlist(wnd, list); + if ( list.get_size() > 0 ) { + uSetWindowText(wnd, list[0] ); + } + list.free_all(); +} + +void _cfg_dropdown_history_base::setup_dropdown(HWND wnd) +{ + pfc::ptr_list_t list; + build_list(list); + g_setup_dropdown_fromlist(wnd,list); + list.free_all(); +} + +bool _cfg_dropdown_history_base::add_item(const char * item) +{ + if (!item || !*item) return false; + pfc::string8 meh; + if (strchr(item,separator)) + { + uReplaceChar(meh,item,-1,separator,'|',false); + item = meh; + } + pfc::ptr_list_t list; + build_list(list); + unsigned n; + bool found = false; + for(n=0;n m_max) list.delete_by_idx(list.get_count()-1); + list.insert_item(_strdup(item),0); + } + parse_list(list); + list.free_all(); + return found; +} + +bool _cfg_dropdown_history_base::add_item(const char *item, HWND combobox) { + const bool state = add_item(item); + if (state) uSendMessageText(combobox, CB_ADDSTRING, 0, item); + return state; +} + +bool _cfg_dropdown_history_base::is_empty() +{ + pfc::string8 temp; get_state(temp); + const char * src = temp; + while(*src) + { + if (*src!=separator) return false; + src++; + } + return true; +} + +bool _cfg_dropdown_history_base::on_context(HWND wnd,LPARAM coords) { + try { + int coords_x = (short)LOWORD(coords), coords_y = (short)HIWORD(coords); + if (coords_x == -1 && coords_y == -1) + { + RECT asdf; + GetWindowRect(wnd,&asdf); + coords_x = (asdf.left + asdf.right) / 2; + coords_y = (asdf.top + asdf.bottom) / 2; + } + enum {ID_ERASE_ALL = 1, ID_ERASE_ONE }; + HMENU menu = CreatePopupMenu(); + uAppendMenu(menu,MF_STRING,ID_ERASE_ALL,"Wipe history"); + { + pfc::string8 tempvalue; + uGetWindowText(wnd,tempvalue); + if (!tempvalue.is_empty()) + uAppendMenu(menu,MF_STRING,ID_ERASE_ONE,"Remove this history item"); + } + int cmd = TrackPopupMenu(menu,TPM_RIGHTBUTTON|TPM_NONOTIFY|TPM_RETURNCMD,coords_x,coords_y,0,wnd,0); + DestroyMenu(menu); + switch(cmd) + { + case ID_ERASE_ALL: + { + set_state(""); + pfc::string8 value;//preserve old value while wiping dropdown list + uGetWindowText(wnd,value); + uSendMessage(wnd,CB_RESETCONTENT,0,0); + uSetWindowText(wnd,value); + return true; + } + case ID_ERASE_ONE: + { + pfc::string8 value; + uGetWindowText(wnd,value); + + pfc::ptr_list_t list; + build_list(list); + bool found = false; + for(t_size n=0;n & out); + void parse_list(const pfc::ptr_list_t & src); +public: + enum {separator = '\n'}; + virtual void set_state(const char * val) = 0; + virtual void get_state(pfc::string_base & out) const = 0; + _cfg_dropdown_history_base(unsigned p_max) : m_max(p_max) {} + void on_init(HWND ctrl, const char * initVal) { + add_item(initVal); setup_dropdown(ctrl); uSetWindowText(ctrl, initVal); + } + void setup_dropdown(HWND wnd); + void setup_dropdown_set_value(HWND wnd); + void setup_dropdown(HWND wnd,UINT id) {setup_dropdown(GetDlgItem(wnd,id));} + bool add_item(const char * item); //returns true when the content has changed, false when not (the item was already on the list) + bool add_item(const char * item, HWND combobox); //immediately adds the item to the combobox + bool is_empty(); + bool on_context(HWND wnd,LPARAM coords); //returns true when the content has changed +}; + +class cfg_dropdown_history : public _cfg_dropdown_history_base { +public: + cfg_dropdown_history(const GUID & p_guid,unsigned p_max = 10,const char * init_vals = "") : _cfg_dropdown_history_base(p_max), m_state(p_guid, init_vals) {} + void set_state(const char * val) {m_state = val;} + void get_state(pfc::string_base & out) const {out = m_state;} +private: + cfg_string m_state; +}; + +class cfg_dropdown_history_mt : public _cfg_dropdown_history_base { +public: + cfg_dropdown_history_mt(const GUID & p_guid,unsigned p_max = 10,const char * init_vals = "") : _cfg_dropdown_history_base(p_max), m_state(p_guid, init_vals) {} + void set_state(const char * val) {m_state.set(val);} + void get_state(pfc::string_base & out) const {m_state.get(out);} +private: + cfg_string_mt m_state; +}; + +// ATL-compatible message map entry macro for installing dropdown list context menus. +#define DROPDOWN_HISTORY_HANDLER(ctrlID,var) \ + if(uMsg == WM_CONTEXTMENU) { \ + const HWND source = (HWND) wParam; \ + if (source != NULL && source == CWindow(hWnd).GetDlgItem(ctrlID)) { \ + var.on_context(source,lParam); \ + lResult = 0; \ + return TRUE; \ + } \ + } + +#endif // FOOBAR2000_DESKTOP_WINDOWS \ No newline at end of file diff --git a/foobar2000/helpers/duration_counter.h b/foobar2000/helpers/duration_counter.h new file mode 100644 index 0000000..36c62d3 --- /dev/null +++ b/foobar2000/helpers/duration_counter.h @@ -0,0 +1,94 @@ +#pragma once + +#include +#include + +//! Duration counter class - accumulates duration using sample values, without any kind of rounding error accumulation. +class duration_counter { +public: + duration_counter() : m_offset() { + } + void set(double v) { + m_sampleCounts.remove_all(); + m_offset = v; + } + void reset() { + set(0); + } + + void add(double v) { m_offset += v; } + void subtract(double v) { m_offset -= v; } + + double query() const { + double acc = m_offset; + for (t_map::const_iterator walk = m_sampleCounts.first(); walk.is_valid(); ++walk) { + acc += audio_math::samples_to_time(walk->m_value, walk->m_key); + } + return acc; + } + + uint64_t queryAsSampleCount(uint32_t rate) const { + uint64_t samples = 0; + double acc = m_offset; + for (t_map::const_iterator walk = m_sampleCounts.first(); walk.is_valid(); ++walk) { + if (walk->m_key == rate) samples += walk->m_value; + else acc += audio_math::samples_to_time(walk->m_value, walk->m_key); + } + return samples + audio_math::time_to_samples(acc, rate); + } + + void add(const audio_chunk & c) { + add(c.get_sample_count(), c.get_sample_rate()); + } +#ifdef FOOBAR2000_HAVE_DSP + void add(dsp_chunk_list const & c) { + const size_t num = c.get_count(); + for (size_t walk = 0; walk < num; ++walk) { + add(*c.get_item(walk)); + } + } +#endif + void add(t_uint64 sampleCount, t_uint32 sampleRate) { + PFC_ASSERT(sampleRate > 0); + if (sampleRate > 0 && sampleCount > 0) { + m_sampleCounts.find_or_add(sampleRate) += sampleCount; + } + } + void add(const duration_counter & other) { + add(other.m_offset); + for (t_map::const_iterator walk = other.m_sampleCounts.first(); walk.is_valid(); ++walk) { + add(walk->m_value, walk->m_key); + } + } + void subtract(const duration_counter & other) { + subtract(other.m_offset); + for (t_map::const_iterator walk = other.m_sampleCounts.first(); walk.is_valid(); ++walk) { + subtract(walk->m_value, walk->m_key); + } + } + void subtract(t_uint64 sampleCount, t_uint32 sampleRate) { + PFC_ASSERT(sampleRate > 0); + if (sampleRate > 0 && sampleCount > 0) { + t_uint64 * val = m_sampleCounts.query_ptr(sampleRate); + if (val == NULL) throw pfc::exception_invalid_params(); + if (*val < sampleCount) throw pfc::exception_invalid_params(); + else if (*val == sampleCount) { + m_sampleCounts.remove(sampleRate); + } else { + *val -= sampleCount; + } + + } + } + void subtract(const audio_chunk & c) { + subtract(c.get_sample_count(), c.get_sample_rate()); + } + template duration_counter & operator+=(const t_source & source) { add(source); return *this; } + template duration_counter & operator-=(const t_source & source) { subtract(source); return *this; } + template duration_counter & operator=(const t_source & source) { reset(); add(source); return *this; } +private: + double m_offset; + typedef pfc::map_t t_map; + t_map m_sampleCounts; +}; + diff --git a/foobar2000/helpers/dynamic_bitrate_helper.cpp b/foobar2000/helpers/dynamic_bitrate_helper.cpp new file mode 100644 index 0000000..ab29489 --- /dev/null +++ b/foobar2000/helpers/dynamic_bitrate_helper.cpp @@ -0,0 +1,76 @@ +#include "stdafx.h" + +#include "dynamic_bitrate_helper.h" + +static unsigned g_query_settings() +{ + t_int32 temp; + try { + config_object::g_get_data_int32(standard_config_objects::int32_dynamic_bitrate_display_rate,temp); + } catch(std::exception const &) {return 9;} + if (temp < 0) return 0; + return (unsigned) temp; +} + +dynamic_bitrate_helper::dynamic_bitrate_helper() +{ + reset(); +} + +void dynamic_bitrate_helper::init() +{ + if (!m_inited) + { + m_inited = true; + unsigned temp = g_query_settings(); + if (temp > 0) {m_enabled = true; m_update_interval = 1.0 / (double) temp; } + else {m_enabled = false; m_update_interval = 0; } + m_last_duration = 0; + m_update_bits = 0; + m_update_time = 0; + + } +} + +void dynamic_bitrate_helper::on_frame(double p_duration,t_size p_bits) +{ + init(); + m_last_duration = p_duration; + m_update_time += p_duration; + m_update_bits += p_bits; +} + +bool dynamic_bitrate_helper::on_update(file_info & p_out, double & p_timestamp_delta) +{ + init(); + + bool ret = false; + if (m_enabled) + { + if (m_update_time > m_update_interval) + { + int val = (int) ( ((double)m_update_bits / m_update_time + 500.0) / 1000.0 ); + if (val != p_out.info_get_bitrate_vbr()) + { + p_timestamp_delta = - (m_update_time - m_last_duration); //relative to last frame beginning; + p_out.info_set_bitrate_vbr(val); + ret = true; + } + m_update_bits = 0; + m_update_time = 0; + } + } + else + { + m_update_bits = 0; + m_update_time = 0; + } + + return ret; + +} + +void dynamic_bitrate_helper::reset() +{ + m_inited = false; +} diff --git a/foobar2000/helpers/dynamic_bitrate_helper.h b/foobar2000/helpers/dynamic_bitrate_helper.h new file mode 100644 index 0000000..a1c4d82 --- /dev/null +++ b/foobar2000/helpers/dynamic_bitrate_helper.h @@ -0,0 +1,17 @@ +#pragma once + +class dynamic_bitrate_helper +{ +public: + dynamic_bitrate_helper(); + void on_frame(double p_duration,t_size p_bits); + bool on_update(file_info & p_out, double & p_timestamp_delta); + void reset(); +private: + void init(); + double m_last_duration; + t_size m_update_bits; + double m_update_time; + double m_update_interval; + bool m_inited, m_enabled; +}; \ No newline at end of file diff --git a/foobar2000/helpers/fb2k_threads.h b/foobar2000/helpers/fb2k_threads.h new file mode 100644 index 0000000..b052938 --- /dev/null +++ b/foobar2000/helpers/fb2k_threads.h @@ -0,0 +1,88 @@ +#pragma once + +inline static t_size GetOptimalWorkerThreadCount() throw() { + return pfc::getOptimalWorkerThreadCount(); +} + +//! IMPORTANT: all classes derived from CVerySimpleThread must call WaitTillThreadDone() in their destructor, to avoid object destruction during a virtual function call! +class CVerySimpleThread : private pfc::thread { +public: + void StartThread(int priority) { + this->pfc::thread::startWithPriority( priority ); + } + void StartThread() { + this->StartThread( pfc::thread::currentPriority() ); + } + + bool IsThreadActive() const { + return this->pfc::thread::isActive(); + } + void WaitTillThreadDone() { + this->pfc::thread::waitTillDone(); + } +protected: + CVerySimpleThread() {} + virtual void ThreadProc() = 0; +private: + + void threadProc() { + this->ThreadProc(); + } + + PFC_CLASS_NOT_COPYABLE_EX(CVerySimpleThread) +}; + +//! IMPORTANT: all classes derived from CSimpleThread must call AbortThread()/WaitTillThreadDone() in their destructors, to avoid object destruction during a virtual function call! +class CSimpleThread : private completion_notify_receiver, private pfc::thread { +public: + void StartThread(int priority) { + AbortThread(); + m_abort.reset(); + m_ownNotify = create_task(0); + this->pfc::thread::startWithPriority( priority ); + } + void StartThread() { + this->StartThread( pfc::thread::currentPriority () ); + } + void AbortThread() { + m_abort.abort(); + CloseThread(); + } + bool IsThreadActive() const { + return this->pfc::thread::isActive(); + } + void WaitTillThreadDone() { + CloseThread(); + } +protected: + CSimpleThread() {} + ~CSimpleThread() {AbortThread();} + + virtual unsigned ThreadProc(abort_callback & p_abort) = 0; + //! Called when the thread has completed normally, with p_code equal to ThreadProc retval. Not called when AbortThread() or WaitTillThreadDone() was used to abort the thread / wait for the thread to finish. + virtual void ThreadDone(unsigned p_code) {}; +private: + void CloseThread() { + this->pfc::thread::waitTillDone(); + orphan_all_tasks(); + } + + void on_task_completion(unsigned p_id,unsigned p_status) { + if (IsThreadActive()) { + CloseThread(); + ThreadDone(p_status); + } + } + void threadProc() { + unsigned code = ~0; + try { + code = ThreadProc(m_abort); + } catch(...) {} + if (!m_abort.is_aborting()) m_ownNotify->on_completion_async(code); + } + abort_callback_impl m_abort; + completion_notify_ptr m_ownNotify; + + PFC_CLASS_NOT_COPYABLE_EX(CSimpleThread); +}; + diff --git a/foobar2000/helpers/fb2k_wfx.h b/foobar2000/helpers/fb2k_wfx.h new file mode 100644 index 0000000..2c1f33e --- /dev/null +++ b/foobar2000/helpers/fb2k_wfx.h @@ -0,0 +1,38 @@ +#pragma once + +struct fb2k_wfx { + audio_chunk::spec_t spec; + bool bFloat; + unsigned bps; + void parse( const WAVEFORMATEX * wfx ) { + spec.sampleRate = wfx->nSamplesPerSec; + spec.chanCount = wfx->nChannels; + spec.chanMask = audio_chunk::g_guess_channel_config( spec.chanCount ); + bps = wfx->wBitsPerSample; + switch( wfx->wFormatTag ) { + case WAVE_FORMAT_PCM: + bFloat = false; break; + case WAVE_FORMAT_IEEE_FLOAT: + bFloat = true; break; + case WAVE_FORMAT_EXTENSIBLE: + { + auto wfxe = (const WAVEFORMATEXTENSIBLE*) wfx; + auto newMask = audio_chunk::g_channel_config_from_wfx( wfxe->dwChannelMask ); + if ( audio_chunk::g_count_channels(newMask) == spec.chanCount ) { + spec.chanMask = newMask; + } + if ( wfxe->SubFormat == KSDATAFORMAT_SUBTYPE_IEEE_FLOAT ) { + bFloat = true; + } else if ( wfxe->SubFormat == KSDATAFORMAT_SUBTYPE_PCM ) { + bFloat = false; + } else { + throw exception_io_data(); + } + } + break; + default: + throw exception_io_data(); + } + + } +}; \ No newline at end of file diff --git a/foobar2000/helpers/fileReadAhead.h b/foobar2000/helpers/fileReadAhead.h new file mode 100644 index 0000000..ceba118 --- /dev/null +++ b/foobar2000/helpers/fileReadAhead.h @@ -0,0 +1,4 @@ +#pragma once + +//! Creates a file with a background thread reading ahead of the current position +file::ptr fileCreateReadAhead(file::ptr chain, size_t readAheadBytes, abort_callback & aborter ); diff --git a/foobar2000/helpers/file_cached.h b/foobar2000/helpers/file_cached.h new file mode 100644 index 0000000..cc60fe8 --- /dev/null +++ b/foobar2000/helpers/file_cached.h @@ -0,0 +1 @@ +// obsolete, moved to SDK \ No newline at end of file diff --git a/foobar2000/helpers/file_info_const_impl.cpp b/foobar2000/helpers/file_info_const_impl.cpp new file mode 100644 index 0000000..4dbee36 --- /dev/null +++ b/foobar2000/helpers/file_info_const_impl.cpp @@ -0,0 +1,287 @@ +#include "stdafx.h" + +#include "file_info_const_impl.h" + +// presorted - do not change without a proper strcmp resort +static const char * const standard_fieldnames[] = { + "ALBUM","ALBUM ARTIST","ARTIST","Album","Album Artist","Artist","COMMENT","Comment","DATE","DISCNUMBER","Date", + "Discnumber","GENRE","Genre","TITLE","TOTALTRACKS","TRACKNUMBER","Title","TotalTracks","Totaltracks","TrackNumber", + "Tracknumber","album","album artist","artist","comment","date","discnumber","genre","title","totaltracks","tracknumber", +}; + +// presorted - do not change without a proper strcmp resort +static const char * const standard_infonames[] = { + "bitrate","bitspersample","channels","codec","codec_profile","encoding","samplerate","tagtype","tool", +}; + +static const char * optimize_fieldname(const char * p_string) { + t_size index; + if (!pfc::binarySearch::run(standard_fieldnames,0,PFC_TABSIZE(standard_fieldnames),p_string,index)) return NULL; + return standard_fieldnames[index]; +} + +static const char * optimize_infoname(const char * p_string) { + t_size index; + if (!pfc::binarySearch::run(standard_infonames,0,PFC_TABSIZE(standard_infonames),p_string,index)) return NULL; + return standard_infonames[index]; +} + +/* +order of things + + meta entries + meta value map + info entries + string buffer + +*/ + +inline static char* stringbuffer_append(char * & buffer,const char * value) +{ + char * ret = buffer; + while(*value) *(buffer++) = *(value++); + *(buffer++) = 0; + return ret; +} + +#ifdef __file_info_const_impl_have_hintmap__ + +namespace { + class sort_callback_hintmap_impl : public pfc::sort_callback + { + public: + sort_callback_hintmap_impl(const file_info_const_impl::meta_entry * p_meta,file_info_const_impl::t_index * p_hintmap) + : m_meta(p_meta), m_hintmap(p_hintmap) + { + } + + int compare(t_size p_index1, t_size p_index2) const + { +// profiler(sort_callback_hintmap_impl_compare); + return pfc::stricmp_ascii(m_meta[m_hintmap[p_index1]].m_name,m_meta[m_hintmap[p_index2]].m_name); + } + + void swap(t_size p_index1, t_size p_index2) + { + pfc::swap_t(m_hintmap[p_index1],m_hintmap[p_index2]); + } + private: + const file_info_const_impl::meta_entry * m_meta; + file_info_const_impl::t_index * m_hintmap; + }; + + class bsearch_callback_hintmap_impl// : public pfc::bsearch_callback + { + public: + bsearch_callback_hintmap_impl( + const file_info_const_impl::meta_entry * p_meta, + const file_info_const_impl::t_index * p_hintmap, + const char * p_name, + t_size p_name_length) + : m_meta(p_meta), m_hintmap(p_hintmap), m_name(p_name), m_name_length(p_name_length) + { + } + + inline int test(t_size p_index) const + { + return pfc::stricmp_ascii_ex(m_meta[m_hintmap[p_index]].m_name,~0,m_name,m_name_length); + } + + private: + const file_info_const_impl::meta_entry * m_meta; + const file_info_const_impl::t_index * m_hintmap; + const char * m_name; + t_size m_name_length; + }; +} + +#endif//__file_info_const_impl_have_hintmap__ + +void file_info_const_impl::copy(const file_info & p_source) +{ +// profiler(file_info_const_impl__copy); + t_size meta_size = 0; + t_size info_size = 0; + t_size valuemap_size = 0; + t_size stringbuffer_size = 0; +#ifdef __file_info_const_impl_have_hintmap__ + t_size hintmap_size = 0; +#endif + + const char * optbuf[64]; + size_t optwalk = 0; + + { +// profiler(file_info_const_impl__copy__pass1); + t_size index; + m_meta_count = pfc::downcast_guarded(p_source.meta_get_count()); + meta_size = m_meta_count * sizeof(meta_entry); +#ifdef __file_info_const_impl_have_hintmap__ + hintmap_size = (m_meta_count > hintmap_cutoff) ? m_meta_count * sizeof(t_index) : 0; +#endif//__file_info_const_impl_have_hintmap__ + for(index = 0; index < m_meta_count; index++ ) + { + { + const char * name = p_source.meta_enum_name(index); + const char * opt = optimize_fieldname(name); + if (optwalk < PFC_TABSIZE(optbuf)) optbuf[optwalk++] = opt; + if (opt == NULL) stringbuffer_size += strlen(name) + 1; + } + + t_size val; const t_size val_max = p_source.meta_enum_value_count(index); + + if (val_max == 1) + { + stringbuffer_size += strlen(p_source.meta_enum_value(index,0)) + 1; + } + else + { + valuemap_size += val_max * sizeof(char*); + + for(val = 0; val < val_max; val++ ) + { + stringbuffer_size += strlen(p_source.meta_enum_value(index,val)) + 1; + } + } + } + + m_info_count = pfc::downcast_guarded(p_source.info_get_count()); + info_size = m_info_count * sizeof(info_entry); + for(index = 0; index < m_info_count; index++ ) + { + const char * name = p_source.info_enum_name(index); + const char * opt = optimize_infoname(name); + if (optwalk < PFC_TABSIZE(optbuf)) optbuf[optwalk++] = opt; + if (opt == NULL) stringbuffer_size += strlen(name) + 1; + stringbuffer_size += strlen(p_source.info_enum_value(index)) + 1; + } + } + + + { +// profiler(file_info_const_impl__copy__alloc); + m_buffer.set_size( +#ifdef __file_info_const_impl_have_hintmap__ + hintmap_size + +#endif + meta_size + info_size + valuemap_size + stringbuffer_size); + } + + char * walk = m_buffer.get_ptr(); + +#ifdef __file_info_const_impl_have_hintmap__ + t_index* hintmap = (hintmap_size > 0) ? (t_index*) walk : NULL; + walk += hintmap_size; +#endif + meta_entry * meta = (meta_entry*) walk; + walk += meta_size; + char ** valuemap = (char**) walk; + walk += valuemap_size; + info_entry * info = (info_entry*) walk; + walk += info_size; + char * stringbuffer = walk; + + m_meta = meta; + m_info = info; +#ifdef __file_info_const_impl_have_hintmap__ + m_hintmap = hintmap; +#endif + + optwalk = 0; + { +// profiler(file_info_const_impl__copy__pass2); + t_size index; + for( index = 0; index < m_meta_count; index ++ ) + { + t_size val; const t_size val_max = p_source.meta_enum_value_count(index); + + { + const char * name = p_source.meta_enum_name(index); + const char * name_opt; + + if (optwalk < PFC_TABSIZE(optbuf)) name_opt = optbuf[optwalk++]; + else name_opt = optimize_fieldname(name); + + if (name_opt == NULL) + meta[index].m_name = stringbuffer_append(stringbuffer, name ); + else + meta[index].m_name = name_opt; + } + + meta[index].m_valuecount = val_max; + + if (val_max == 1) + { + meta[index].m_valuemap = reinterpret_cast(stringbuffer_append(stringbuffer, p_source.meta_enum_value(index,0) )); + } + else + { + meta[index].m_valuemap = valuemap; + for( val = 0; val < val_max ; val ++ ) + *(valuemap ++ ) = stringbuffer_append(stringbuffer, p_source.meta_enum_value(index,val) ); + } + } + + for( index = 0; index < m_info_count; index ++ ) + { + const char * name = p_source.info_enum_name(index); + const char * name_opt; + + if (optwalk < PFC_TABSIZE(optbuf)) name_opt = optbuf[optwalk++]; + else name_opt = optimize_infoname(name); + + if (name_opt == NULL) + info[index].m_name = stringbuffer_append(stringbuffer, name ); + else + info[index].m_name = name_opt; + info[index].m_value = stringbuffer_append(stringbuffer, p_source.info_enum_value(index) ); + } + } + + m_length = p_source.get_length(); + m_replaygain = p_source.get_replaygain(); +#ifdef __file_info_const_impl_have_hintmap__ + if (hintmap != NULL) { +// profiler(file_info_const_impl__copy__hintmap); + for(t_size n=0;n(entry.m_valuemap); + else + return entry.m_valuemap[p_value_number]; +} diff --git a/foobar2000/helpers/file_info_const_impl.h b/foobar2000/helpers/file_info_const_impl.h new file mode 100644 index 0000000..507ba90 --- /dev/null +++ b/foobar2000/helpers/file_info_const_impl.h @@ -0,0 +1,80 @@ +#pragma once + +#define __file_info_const_impl_have_hintmap__ + +//! Special implementation of file_info that implements only const and copy methods. The difference between this and regular file_info_impl is amount of resources used and speed of the copy operation. +class file_info_const_impl : public file_info +{ +public: + file_info_const_impl(const file_info & p_source) {copy(p_source);} + file_info_const_impl(const file_info_const_impl & p_source) {copy(p_source);} + file_info_const_impl() {m_meta_count = m_info_count = 0; m_length = 0; m_replaygain.reset();} + + double get_length() const {return m_length;} + + t_size meta_get_count() const {return m_meta_count;} + const char* meta_enum_name(t_size p_index) const {return m_meta[p_index].m_name;} + t_size meta_enum_value_count(t_size p_index) const; + const char* meta_enum_value(t_size p_index,t_size p_value_number) const; + t_size meta_find_ex(const char * p_name,t_size p_name_length) const; + + t_size info_get_count() const {return m_info_count;} + const char* info_enum_name(t_size p_index) const {return m_info[p_index].m_name;} + const char* info_enum_value(t_size p_index) const {return m_info[p_index].m_value;} + + + const file_info_const_impl & operator=(const file_info & p_source) {copy(p_source); return *this;} + const file_info_const_impl & operator=(const file_info_const_impl & p_source) {copy(p_source); return *this;} + void copy(const file_info & p_source); + void reset(); + + replaygain_info get_replaygain() const {return m_replaygain;} + +private: + void set_length(double p_length) {uBugCheck();} + + t_size meta_set_ex(const char * p_name,t_size p_name_length,const char * p_value,t_size p_value_length) {uBugCheck();} + void meta_insert_value_ex(t_size p_index,t_size p_value_index,const char * p_value,t_size p_value_length) {uBugCheck();} + void meta_remove_mask(const bit_array & p_mask) {uBugCheck();} + void meta_reorder(const t_size * p_order) {uBugCheck();} + void meta_remove_values(t_size p_index,const bit_array & p_mask) {uBugCheck();} + void meta_modify_value_ex(t_size p_index,t_size p_value_index,const char * p_value,t_size p_value_length) {uBugCheck();} + + t_size info_set_ex(const char * p_name,t_size p_name_length,const char * p_value,t_size p_value_length) {uBugCheck();} + void info_remove_mask(const bit_array & p_mask) {uBugCheck();} + + void set_replaygain(const replaygain_info & p_info) {uBugCheck();} + + t_size meta_set_nocheck_ex(const char * p_name,t_size p_name_length,const char * p_value,t_size p_value_length) {uBugCheck();} + t_size info_set_nocheck_ex(const char * p_name,t_size p_name_length,const char * p_value,t_size p_value_length) {uBugCheck();} +public: + struct meta_entry { + const char * m_name; + t_size m_valuecount; + const char * const * m_valuemap; + }; + + struct info_entry { + const char * m_name; + const char * m_value; + }; + +#ifdef __file_info_const_impl_have_hintmap__ + typedef t_uint32 t_index; + enum {hintmap_cutoff = 20}; +#endif//__file_info_const_impl_have_hintmap__ +private: + pfc::array_t m_buffer; + t_index m_meta_count; + t_index m_info_count; + + const meta_entry * m_meta; + const info_entry * m_info; + +#ifdef __file_info_const_impl_have_hintmap__ + const t_index * m_hintmap; +#endif + + double m_length; + replaygain_info m_replaygain; +}; diff --git a/foobar2000/helpers/file_list_helper.cpp b/foobar2000/helpers/file_list_helper.cpp new file mode 100644 index 0000000..dd81e2d --- /dev/null +++ b/foobar2000/helpers/file_list_helper.cpp @@ -0,0 +1,79 @@ +#include "stdafx.h" + +#include "file_list_helper.h" + +#ifndef _MSC_VER +#define _strdup strdup +#endif + +static void file_list_remove_duplicates(pfc::ptr_list_t & out) +{ + t_size n, m = out.get_count(); + out.sort_t(metadb::path_compare); + pfc::bit_array_bittable mask(m); + t_size duplicates = 0; + for(n=1;n0) { + out.free_mask(mask); + } +} + + +namespace file_list_helper +{ + t_size file_list_from_metadb_handle_list::g_get_count(metadb_handle_list_cref data, t_size max) { + pfc::avltree_t content; + const t_size inCount = data.get_size(); + for(t_size walk = 0; walk < inCount && content.get_count() < max; ++walk) { + content += data[walk]->get_path(); + } + return content.get_count(); + } + void file_list_from_metadb_handle_list::_add(const char * p_what) { + char * temp = _strdup(p_what); + if (temp == NULL) throw std::bad_alloc(); + try {m_data.add_item(temp); } catch(...) {free(temp); throw;} + } + + void file_list_from_metadb_handle_list::init_from_list(const list_base_const_t & p_list) + { + m_data.free_all(); + + t_size n, m = p_list.get_count(); + for(n=0;nget_path() ); + } + file_list_remove_duplicates(m_data); + } + + void file_list_from_metadb_handle_list::init_from_list_display(const list_base_const_t & p_list) + { + m_data.free_all(); + + pfc::string8_fastalloc temp; + + t_size n, m = p_list.get_count(); + for(n=0;nget_path(),temp); + _add(temp); + } + file_list_remove_duplicates(m_data); + } + + file_list_from_metadb_handle_list::file_list_from_metadb_handle_list(metadb_handle_list_cref lst, bool bDisplayPaths) { + if ( bDisplayPaths ) init_from_list_display(lst); + else init_from_list( lst ); + } + + t_size file_list_from_metadb_handle_list::get_count() const {return m_data.get_count();} + void file_list_from_metadb_handle_list::get_item_ex(const char * & p_out,t_size n) const {p_out = m_data.get_item(n);} + + file_list_from_metadb_handle_list::~file_list_from_metadb_handle_list() + { + m_data.free_all(); + } + +} \ No newline at end of file diff --git a/foobar2000/helpers/file_list_helper.h b/foobar2000/helpers/file_list_helper.h new file mode 100644 index 0000000..b90743a --- /dev/null +++ b/foobar2000/helpers/file_list_helper.h @@ -0,0 +1,28 @@ +#pragma once + +namespace file_list_helper +{ + //list guaranteed to be sorted by metadb::path_compare + class file_list_from_metadb_handle_list : public pfc::list_base_const_t { + public: + file_list_from_metadb_handle_list() {} + file_list_from_metadb_handle_list( metadb_handle_list_cref lst, bool bDisplayPaths = false ); + + static t_size g_get_count(const list_base_const_t & p_list, t_size max = ~0); + + void init_from_list(const list_base_const_t & p_list); + void init_from_list_display(const list_base_const_t & p_list); + + t_size get_count() const; + void get_item_ex(const char * & p_out,t_size n) const; + + ~file_list_from_metadb_handle_list(); + + private: + void _add(const char * p_what); + pfc::ptr_list_t m_data; + }; + + +}; + diff --git a/foobar2000/helpers/file_move_helper.cpp b/foobar2000/helpers/file_move_helper.cpp new file mode 100644 index 0000000..9fb5f0b --- /dev/null +++ b/foobar2000/helpers/file_move_helper.cpp @@ -0,0 +1,40 @@ +#include "stdafx.h" + +#include "file_move_helper.h" + +bool file_move_helper::g_on_deleted(const pfc::list_base_const_t & p_files) +{ + file_operation_callback::g_on_files_deleted(p_files); + return true; +} + +t_size file_move_helper::g_filter_dead_files_sorted_make_mask(pfc::list_base_t & p_data,const pfc::list_base_const_t & p_dead,bit_array_var & p_mask) +{ + t_size n, m = p_data.get_count(); + t_size found = 0; + for(n=0;nget_path(),dummy); + if (dead) found++; + p_mask.set(n,dead); + } + return found; +} + +t_size file_move_helper::g_filter_dead_files_sorted(pfc::list_base_t & p_data,const pfc::list_base_const_t & p_dead) +{ + pfc::bit_array_bittable mask(p_data.get_count()); + t_size found = g_filter_dead_files_sorted_make_mask(p_data,p_dead,mask); + if (found > 0) p_data.remove_mask(mask); + return found; +} + +t_size file_move_helper::g_filter_dead_files(pfc::list_base_t & p_data,const pfc::list_base_const_t & p_dead) +{ + pfc::ptr_list_t temp; + temp.add_items(p_dead); + temp.sort_t(metadb::path_compare); + return g_filter_dead_files_sorted(p_data,temp); +} + diff --git a/foobar2000/helpers/file_move_helper.h b/foobar2000/helpers/file_move_helper.h new file mode 100644 index 0000000..19986c5 --- /dev/null +++ b/foobar2000/helpers/file_move_helper.h @@ -0,0 +1,10 @@ +#pragma once + +class file_move_helper { +public: + static bool g_on_deleted(const pfc::list_base_const_t & p_files); + + static t_size g_filter_dead_files_sorted_make_mask(pfc::list_base_t & p_data,const pfc::list_base_const_t & p_dead,bit_array_var & p_mask); + static t_size g_filter_dead_files_sorted(pfc::list_base_t & p_data,const pfc::list_base_const_t & p_dead); + static t_size g_filter_dead_files(pfc::list_base_t & p_data,const pfc::list_base_const_t & p_dead); +}; diff --git a/foobar2000/helpers/file_readonly.h b/foobar2000/helpers/file_readonly.h new file mode 100644 index 0000000..9acbb9c --- /dev/null +++ b/foobar2000/helpers/file_readonly.h @@ -0,0 +1,4 @@ +#pragma once +// fb2k mobile compat + +#include "../SDK/filesystem_helper.h" \ No newline at end of file diff --git a/foobar2000/helpers/file_win32_wrapper.cpp b/foobar2000/helpers/file_win32_wrapper.cpp new file mode 100644 index 0000000..f8b955b --- /dev/null +++ b/foobar2000/helpers/file_win32_wrapper.cpp @@ -0,0 +1,281 @@ +#include "stdafx.h" + +#ifdef _WIN32 + +#include "file_win32_wrapper.h" + +namespace file_win32_helpers { + t_filesize get_size(HANDLE p_handle) { + union { + t_uint64 val64; + t_uint32 val32[2]; + } u; + + u.val64 = 0; + SetLastError(NO_ERROR); + u.val32[0] = GetFileSize(p_handle,reinterpret_cast(&u.val32[1])); + if (GetLastError()!=NO_ERROR) exception_io_from_win32(GetLastError()); + return u.val64; + } + void seek(HANDLE p_handle,t_sfilesize p_position,file::t_seek_mode p_mode) { + union { + t_int64 temp64; + struct { + DWORD temp_lo; + LONG temp_hi; + }; + }; + + temp64 = p_position; + SetLastError(ERROR_SUCCESS); + temp_lo = SetFilePointer(p_handle,temp_lo,&temp_hi,(DWORD)p_mode); + if (GetLastError() != ERROR_SUCCESS) exception_io_from_win32(GetLastError()); + } + + void fillOverlapped(OVERLAPPED & ol, HANDLE myEvent, t_filesize s) { + ol.hEvent = myEvent; + ol.Offset = (DWORD)( s & 0xFFFFFFFF ); + ol.OffsetHigh = (DWORD)(s >> 32); + } + + void writeOverlappedPass(HANDLE handle, HANDLE myEvent, t_filesize position, const void * in,DWORD inBytes, abort_callback & abort) { + abort.check(); + if (inBytes == 0) return; + OVERLAPPED ol = {}; + fillOverlapped(ol, myEvent, position); + ResetEvent(myEvent); + DWORD bytesWritten; + SetLastError(NO_ERROR); + if (WriteFile( handle, in, inBytes, &bytesWritten, &ol)) { + // succeeded already? + if (bytesWritten != inBytes) throw exception_io(); + return; + } + + { + const DWORD code = GetLastError(); + if (code != ERROR_IO_PENDING) exception_io_from_win32(code); + } + const HANDLE handles[] = {myEvent, abort.get_abort_event()}; + SetLastError(NO_ERROR); + DWORD state = WaitForMultipleObjects(_countof(handles), handles, FALSE, INFINITE); + if (state == WAIT_OBJECT_0) { + try { + WIN32_IO_OP( GetOverlappedResult(handle,&ol,&bytesWritten,TRUE) ); + } catch(...) { + CancelIo(handle); + throw; + } + if (bytesWritten != inBytes) throw exception_io(); + return; + } + CancelIo(handle); + throw exception_aborted(); + } + + void writeOverlapped(HANDLE handle, HANDLE myEvent, t_filesize & position, const void * in, size_t inBytes, abort_callback & abort) { + enum {writeMAX = 16*1024*1024}; + size_t done = 0; + while(done < inBytes) { + size_t delta = inBytes - done; + if (delta > writeMAX) delta = writeMAX; + writeOverlappedPass(handle, myEvent, position, (const BYTE*)in + done, (DWORD) delta, abort); + done += delta; + position += delta; + } + } + void writeStreamOverlapped(HANDLE handle, HANDLE myEvent, const void * in, size_t inBytes, abort_callback & abort) { + enum {writeMAX = 16*1024*1024}; + size_t done = 0; + while(done < inBytes) { + size_t delta = inBytes - done; + if (delta > writeMAX) delta = writeMAX; + writeOverlappedPass(handle, myEvent, 0, (const BYTE*)in + done, (DWORD) delta, abort); + done += delta; + } + } + + DWORD readOverlappedPass(HANDLE handle, HANDLE myEvent, t_filesize position, void * out, DWORD outBytes, abort_callback & abort) { + abort.check(); + if (outBytes == 0) return 0; + OVERLAPPED ol = {}; + fillOverlapped(ol, myEvent, position); + ResetEvent(myEvent); + DWORD bytesDone; + SetLastError(NO_ERROR); + if (ReadFile( handle, out, outBytes, &bytesDone, &ol)) { + // succeeded already? + return bytesDone; + } + + { + const DWORD code = GetLastError(); + switch(code) { + case ERROR_HANDLE_EOF: + case ERROR_BROKEN_PIPE: + return 0; + case ERROR_IO_PENDING: + break; // continue + default: + exception_io_from_win32(code); + }; + } + + const HANDLE handles[] = {myEvent, abort.get_abort_event()}; + SetLastError(NO_ERROR); + DWORD state = WaitForMultipleObjects(_countof(handles), handles, FALSE, INFINITE); + if (state == WAIT_OBJECT_0) { + SetLastError(NO_ERROR); + if (!GetOverlappedResult(handle,&ol,&bytesDone,TRUE)) { + const DWORD code = GetLastError(); + if (code == ERROR_HANDLE_EOF || code == ERROR_BROKEN_PIPE) bytesDone = 0; + else { + CancelIo(handle); + exception_io_from_win32(code); + } + } + return bytesDone; + } + CancelIo(handle); + throw exception_aborted(); + } + size_t readOverlapped(HANDLE handle, HANDLE myEvent, t_filesize & position, void * out, size_t outBytes, abort_callback & abort) { + enum {readMAX = 16*1024*1024}; + size_t done = 0; + while(done < outBytes) { + size_t delta = outBytes - done; + if (delta > readMAX) delta = readMAX; + delta = readOverlappedPass(handle, myEvent, position, (BYTE*) out + done, (DWORD) delta, abort); + if (delta == 0) break; + done += delta; + position += delta; + } + return done; + } + + size_t readStreamOverlapped(HANDLE handle, HANDLE myEvent, void * out, size_t outBytes, abort_callback & abort) { + enum {readMAX = 16*1024*1024}; + size_t done = 0; + while(done < outBytes) { + size_t delta = outBytes - done; + if (delta > readMAX) delta = readMAX; + delta = readOverlappedPass(handle, myEvent, 0, (BYTE*) out + done, (DWORD) delta, abort); + if (delta == 0) break; + done += delta; + } + return done; + } + + typedef BOOL (WINAPI * pCancelSynchronousIo_t)(HANDLE hThread); + + + struct createFileData_t { + LPCTSTR lpFileName; + DWORD dwDesiredAccess; + DWORD dwShareMode; + LPSECURITY_ATTRIBUTES lpSecurityAttributes; + DWORD dwCreationDisposition; + DWORD dwFlagsAndAttributes; + HANDLE hTemplateFile; + HANDLE hResult; + DWORD dwErrorCode; + }; + + static unsigned CALLBACK createFileProc(void * data) { + createFileData_t * cfd = (createFileData_t*)data; + SetLastError(0); + cfd->hResult = CreateFile(cfd->lpFileName, cfd->dwDesiredAccess, cfd->dwShareMode, cfd->lpSecurityAttributes, cfd->dwCreationDisposition, cfd->dwFlagsAndAttributes, cfd->hTemplateFile); + cfd->dwErrorCode = GetLastError(); + return 0; + } + + HANDLE createFile(LPCTSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode, LPSECURITY_ATTRIBUTES lpSecurityAttributes, DWORD dwCreationDisposition, DWORD dwFlagsAndAttributes, HANDLE hTemplateFile, abort_callback & abort) { + abort.check(); + + return CreateFile(lpFileName, dwDesiredAccess, dwShareMode, lpSecurityAttributes, dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile); + + // CancelSynchronousIo() doesn't fucking work. Useless. +#if 0 + pCancelSynchronousIo_t pCancelSynchronousIo = (pCancelSynchronousIo_t) GetProcAddress(GetModuleHandle(TEXT("kernel32.dll")), "CancelSynchronousIo"); + if (pCancelSynchronousIo == NULL) { +#ifdef _DEBUG + uDebugLog() << "Async CreateFile unavailable - using regular"; +#endif + return CreateFile(lpFileName, dwDesiredAccess, dwShareMode, lpSecurityAttributes, dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile); + } else { +#ifdef _DEBUG + uDebugLog() << "Starting async CreateFile..."; + pfc::hires_timer t; t.start(); +#endif + createFileData_t data = {lpFileName, dwDesiredAccess, dwShareMode, lpSecurityAttributes, dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile, NULL, 0}; + HANDLE hThread = (HANDLE) _beginthreadex(NULL, 0, createFileProc, &data, 0, NULL); + HANDLE waitHandles[2] = {hThread, abort.get_abort_event()}; + switch(WaitForMultipleObjects(2, waitHandles, FALSE, INFINITE)) { + case WAIT_OBJECT_0: // succeeded + break; + case WAIT_OBJECT_0 + 1: // abort +#ifdef _DEBUG + uDebugLog() << "Aborting async CreateFile..."; +#endif + pCancelSynchronousIo(hThread); + WaitForSingleObject(hThread, INFINITE); + break; + default: + uBugCheck(); + } + CloseHandle(hThread); + SetLastError(data.dwErrorCode); +#ifdef _DEBUG + uDebugLog() << "Async CreateFile completed in " << pfc::format_time_ex(t.query(), 6) << ", status: " << (uint32_t) data.dwErrorCode; +#endif + if (abort.is_aborting()) { + if (data.hResult != INVALID_HANDLE_VALUE) CloseHandle(data.hResult); + throw exception_aborted(); + } + return data.hResult; + } +#endif + } + + size_t lowLevelIO(HANDLE hFile, const GUID & guid, size_t arg1, void * arg2, size_t arg2size, bool canWrite, abort_callback & abort) { + if ( guid == file_lowLevelIO::guid_flushFileBuffers ) { + if (!canWrite) { + PFC_ASSERT(!"File opened for reading, not writing"); + throw exception_io_denied(); + } + WIN32_IO_OP( ::FlushFileBuffers(hFile) ); + return 1; + } else if ( guid == file_lowLevelIO::guid_getFileTimes ) { + if ( arg2size == sizeof(file_lowLevelIO::filetimes_t) ) { + if (canWrite) WIN32_IO_OP(::FlushFileBuffers(hFile)); + auto ft = reinterpret_cast(arg2); + static_assert(sizeof(t_filetimestamp) == sizeof(FILETIME), "struct sanity"); + WIN32_IO_OP( GetFileTime( hFile, (FILETIME*)&ft->creation, (FILETIME*)&ft->lastAccess, (FILETIME*)&ft->lastWrite) ); + return 1; + } + } else if ( guid == file_lowLevelIO::guid_setFileTimes ) { + if (arg2size == sizeof(file_lowLevelIO::filetimes_t)) { + if (!canWrite) { + PFC_ASSERT(!"File opened for reading, not writing"); + throw exception_io_denied(); + } + WIN32_IO_OP(::FlushFileBuffers(hFile)); + auto ft = reinterpret_cast(arg2); + static_assert(sizeof(t_filetimestamp) == sizeof(FILETIME), "struct sanity"); + const FILETIME * pCreation = nullptr; + const FILETIME * pLastAccess = nullptr; + const FILETIME * pLastWrite = nullptr; + if ( ft->creation != filetimestamp_invalid ) pCreation = (const FILETIME*)&ft->creation; + if ( ft->lastAccess != filetimestamp_invalid ) pLastAccess = (const FILETIME*)&ft->lastAccess; + if ( ft->lastWrite != filetimestamp_invalid ) pLastWrite = (const FILETIME*)&ft->lastWrite; + WIN32_IO_OP( SetFileTime(hFile, pCreation, pLastAccess, pLastWrite) ); + return 1; + } + } + return 0; + } + +} + +#endif // _WIN32 + diff --git a/foobar2000/helpers/file_win32_wrapper.h b/foobar2000/helpers/file_win32_wrapper.h new file mode 100644 index 0000000..ffb3818 --- /dev/null +++ b/foobar2000/helpers/file_win32_wrapper.h @@ -0,0 +1,254 @@ +#pragma once + +#include + +#ifdef _WIN32 +namespace file_win32_helpers { + t_filesize get_size(HANDLE p_handle); + void seek(HANDLE p_handle,t_sfilesize p_position,file::t_seek_mode p_mode); + void fillOverlapped(OVERLAPPED & ol, HANDLE myEvent, t_filesize s); + void writeOverlappedPass(HANDLE handle, HANDLE myEvent, t_filesize position, const void * in,DWORD inBytes, abort_callback & abort); + void writeOverlapped(HANDLE handle, HANDLE myEvent, t_filesize & position, const void * in, size_t inBytes, abort_callback & abort); + void writeStreamOverlapped(HANDLE handle, HANDLE myEvent, const void * in, size_t inBytes, abort_callback & abort); + DWORD readOverlappedPass(HANDLE handle, HANDLE myEvent, t_filesize position, void * out, DWORD outBytes, abort_callback & abort); + size_t readOverlapped(HANDLE handle, HANDLE myEvent, t_filesize & position, void * out, size_t outBytes, abort_callback & abort); + size_t readStreamOverlapped(HANDLE handle, HANDLE myEvent, void * out, size_t outBytes, abort_callback & abort); + HANDLE createFile(LPCTSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode, LPSECURITY_ATTRIBUTES lpSecurityAttributes, DWORD dwCreationDisposition, DWORD dwFlagsAndAttributes, HANDLE hTemplateFile, abort_callback & abort); + size_t lowLevelIO(HANDLE hFile, const GUID & guid, size_t arg1, void * arg2, size_t arg2size, bool canWrite, abort_callback & abort); +}; + +template +class file_win32_wrapper_t : public service_multi_inherit { +public: + file_win32_wrapper_t(HANDLE p_handle) : m_handle(p_handle), m_position(0) + { + } + + static file::ptr g_CreateFile(const char * p_path,DWORD p_access,DWORD p_sharemode,LPSECURITY_ATTRIBUTES p_security_attributes,DWORD p_createmode,DWORD p_flags,HANDLE p_template) { + SetLastError(NO_ERROR); + HANDLE handle = uCreateFile(p_path,p_access,p_sharemode,p_security_attributes,p_createmode,p_flags,p_template); + if (handle == INVALID_HANDLE_VALUE) { + const DWORD code = GetLastError(); + if (p_access & GENERIC_WRITE) win32_file_write_failure(code, p_path); + else exception_io_from_win32(code); + } + try { + return g_create_from_handle(handle); + } catch(...) {CloseHandle(handle); throw;} + } + + static service_ptr_t g_create_from_handle(HANDLE p_handle) { + return new service_impl_t >(p_handle); + } + + + void reopen(abort_callback & p_abort) {seek(0,p_abort);} + + void write(const void * p_buffer,t_size p_bytes,abort_callback & p_abort) { + if (!p_writeable) throw exception_io_denied(); + + PFC_STATIC_ASSERT(sizeof(t_size) >= sizeof(DWORD)); + + t_size bytes_written_total = 0; + + if (sizeof(t_size) == sizeof(DWORD)) { + p_abort.check_e(); + DWORD bytes_written = 0; + SetLastError(ERROR_SUCCESS); + if (!WriteFile(m_handle,p_buffer,(DWORD)p_bytes,&bytes_written,0)) exception_io_from_win32(GetLastError()); + if (bytes_written != p_bytes) throw exception_io("Write failure"); + bytes_written_total = bytes_written; + m_position += bytes_written; + } else { + while(bytes_written_total < p_bytes) { + p_abort.check_e(); + DWORD bytes_written = 0; + DWORD delta = (DWORD) pfc::min_t(p_bytes - bytes_written_total, 0x80000000u); + SetLastError(ERROR_SUCCESS); + if (!WriteFile(m_handle,(const t_uint8*)p_buffer + bytes_written_total,delta,&bytes_written,0)) exception_io_from_win32(GetLastError()); + if (bytes_written != delta) throw exception_io("Write failure"); + bytes_written_total += bytes_written; + m_position += bytes_written; + } + } + } + + t_size read(void * p_buffer,t_size p_bytes,abort_callback & p_abort) { + PFC_STATIC_ASSERT(sizeof(t_size) >= sizeof(DWORD)); + + t_size bytes_read_total = 0; + if (sizeof(t_size) == sizeof(DWORD)) { + p_abort.check_e(); + DWORD bytes_read = 0; + SetLastError(ERROR_SUCCESS); + if (!ReadFile(m_handle,p_buffer,pfc::downcast_guarded(p_bytes),&bytes_read,0)) exception_io_from_win32(GetLastError()); + bytes_read_total = bytes_read; + m_position += bytes_read; + } else { + while(bytes_read_total < p_bytes) { + p_abort.check_e(); + DWORD bytes_read = 0; + DWORD delta = (DWORD) pfc::min_t(p_bytes - bytes_read_total, 0x80000000u); + SetLastError(ERROR_SUCCESS); + if (!ReadFile(m_handle,(t_uint8*)p_buffer + bytes_read_total,delta,&bytes_read,0)) exception_io_from_win32(GetLastError()); + bytes_read_total += bytes_read; + m_position += bytes_read; + if (bytes_read != delta) break; + } + } + return bytes_read_total; + } + + + t_filesize get_size(abort_callback & p_abort) { + p_abort.check_e(); + return file_win32_helpers::get_size(m_handle); + } + + t_filesize get_position(abort_callback & p_abort) { + p_abort.check_e(); + return m_position; + } + + void resize(t_filesize p_size,abort_callback & p_abort) { + if (!p_writeable) throw exception_io_denied(); + p_abort.check_e(); + if (m_position != p_size) { + file_win32_helpers::seek(m_handle,p_size,file::seek_from_beginning); + } + SetLastError(ERROR_SUCCESS); + if (!SetEndOfFile(m_handle)) { + DWORD code = GetLastError(); + if (m_position != p_size) try {file_win32_helpers::seek(m_handle,m_position,file::seek_from_beginning);} catch(...) {} + exception_io_from_win32(code); + } + if (m_position > p_size) m_position = p_size; + if (m_position != p_size) file_win32_helpers::seek(m_handle,m_position,file::seek_from_beginning); + } + + + void seek(t_filesize p_position,abort_callback & p_abort) { + if (!p_seekable) throw exception_io_object_not_seekable(); + p_abort.check_e(); + if (p_position > file_win32_helpers::get_size(m_handle)) throw exception_io_seek_out_of_range(); + file_win32_helpers::seek(m_handle,p_position,file::seek_from_beginning); + m_position = p_position; + } + + bool can_seek() {return p_seekable;} + bool get_content_type(pfc::string_base & out) {return false;} + bool is_in_memory() {return false;} + void on_idle(abort_callback & p_abort) {p_abort.check_e();} + + t_filetimestamp get_timestamp(abort_callback & p_abort) { + p_abort.check_e(); + if (p_writeable) FlushFileBuffers(m_handle); + SetLastError(ERROR_SUCCESS); + t_filetimestamp temp; + if (!GetFileTime(m_handle,0,0,(FILETIME*)&temp)) exception_io_from_win32(GetLastError()); + return temp; + } + + bool is_remote() {return false;} + ~file_win32_wrapper_t() {CloseHandle(m_handle);} + + size_t lowLevelIO(const GUID & guid, size_t arg1, void * arg2, size_t arg2size, abort_callback & abort) override { + return file_win32_helpers::lowLevelIO(m_handle, guid, arg1, arg2, arg2size, p_writeable, abort); + } +protected: + HANDLE m_handle; + t_filesize m_position; +}; + +template +class file_win32_wrapper_overlapped_t : public service_multi_inherit< file, file_lowLevelIO > { +public: + file_win32_wrapper_overlapped_t(HANDLE file) : m_handle(file), m_position() { + WIN32_OP( (m_event = CreateEvent(NULL, TRUE, FALSE, NULL)) != NULL ); + } + ~file_win32_wrapper_overlapped_t() {CloseHandle(m_event); CloseHandle(m_handle);} + void write(const void * p_buffer,t_size p_bytes,abort_callback & p_abort) { + if (!p_writeable) throw exception_io_denied(); + return file_win32_helpers::writeOverlapped(m_handle, m_event, m_position, p_buffer, p_bytes, p_abort); + } + t_size read(void * p_buffer,t_size p_bytes,abort_callback & p_abort) { + return file_win32_helpers::readOverlapped(m_handle, m_event, m_position, p_buffer, p_bytes, p_abort); + } + + void reopen(abort_callback & p_abort) {seek(0,p_abort);} + + + t_filesize get_size(abort_callback & p_abort) { + p_abort.check_e(); + return file_win32_helpers::get_size(m_handle); + } + + t_filesize get_position(abort_callback & p_abort) { + p_abort.check_e(); + return m_position; + } + + void resize(t_filesize p_size,abort_callback & p_abort) { + if (!p_writeable) throw exception_io_denied(); + p_abort.check_e(); + file_win32_helpers::seek(m_handle,p_size,file::seek_from_beginning); + SetLastError(ERROR_SUCCESS); + if (!SetEndOfFile(m_handle)) { + DWORD code = GetLastError(); + exception_io_from_win32(code); + } + if (m_position > p_size) m_position = p_size; + } + + + void seek(t_filesize p_position,abort_callback & p_abort) { + p_abort.check_e(); + if (p_position > file_win32_helpers::get_size(m_handle)) throw exception_io_seek_out_of_range(); + // file_win32_helpers::seek(m_handle,p_position,file::seek_from_beginning); + m_position = p_position; + } + + bool can_seek() {return true;} + bool get_content_type(pfc::string_base & out) {return false;} + bool is_in_memory() {return false;} + void on_idle(abort_callback & p_abort) {p_abort.check_e();} + + t_filetimestamp get_timestamp(abort_callback & p_abort) { + p_abort.check_e(); + if (p_writeable) FlushFileBuffers(m_handle); + SetLastError(ERROR_SUCCESS); + t_filetimestamp temp; + if (!GetFileTime(m_handle,0,0,(FILETIME*)&temp)) exception_io_from_win32(GetLastError()); + return temp; + } + + bool is_remote() {return false;} + + + static file::ptr g_CreateFile(const char * p_path,DWORD p_access,DWORD p_sharemode,LPSECURITY_ATTRIBUTES p_security_attributes,DWORD p_createmode,DWORD p_flags,HANDLE p_template) { + p_flags |= FILE_FLAG_OVERLAPPED; + SetLastError(NO_ERROR); + HANDLE handle = uCreateFile(p_path,p_access,p_sharemode,p_security_attributes,p_createmode,p_flags,p_template); + if (handle == INVALID_HANDLE_VALUE) { + const DWORD code = GetLastError(); + if (p_access & GENERIC_WRITE) win32_file_write_failure(code, p_path); + else exception_io_from_win32(code); + } + try { + return g_create_from_handle(handle); + } catch(...) {CloseHandle(handle); throw;} + } + + static file::ptr g_create_from_handle(HANDLE p_handle) { + return new service_impl_t >(p_handle); + } + + size_t lowLevelIO(const GUID & guid, size_t arg1, void * arg2, size_t arg2size, abort_callback & abort) override { + return file_win32_helpers::lowLevelIO(m_handle, guid, arg1, arg2, arg2size, p_writeable, abort); + } + +protected: + HANDLE m_event, m_handle; + t_filesize m_position; +}; +#endif // _WIN32 diff --git a/foobar2000/helpers/filetimetools.cpp b/foobar2000/helpers/filetimetools.cpp new file mode 100644 index 0000000..52b7883 --- /dev/null +++ b/foobar2000/helpers/filetimetools.cpp @@ -0,0 +1,103 @@ +#include "stdafx.h" + + +#ifndef _WIN32 +#error PORTME +#endif + +#include "filetimetools.h" + +static bool is_spacing(char c) {return c == ' ' || c==10 || c==13 || c == '\t';} + +static bool is_spacing(const char * str, t_size len) { + for(t_size walk = 0; walk < len; ++walk) if (!is_spacing(str[walk])) return false; + return true; +} + +typedef exception_io_data exception_time_error; + +static unsigned ParseDateElem(const char * ptr, t_size len) { + unsigned ret = 0; + for(t_size walk = 0; walk < len; ++walk) { + const char c = ptr[walk]; + if (c < '0' || c > '9') throw exception_time_error(); + ret = ret * 10 + (unsigned)(c - '0'); + } + return ret; +} + +t_filetimestamp foobar2000_io::filetimestamp_from_string(const char * date) { + // Accepted format + // YYYY-MM-DD HH:MM:SS + try { + t_size remaining = strlen(date); + SYSTEMTIME st = {}; + st.wDay = 1; st.wMonth = 1; + for(;;) { +#define ADVANCE(n) { PFC_ASSERT( remaining >= n); date += n; remaining -= n; } +#define ADVANCE_TEST(n) { if (remaining < n) throw exception_time_error(); } +#define PARSE(var, digits) { ADVANCE_TEST(digits); var = (WORD) ParseDateElem(date, digits); ADVANCE(digits) ; } +#define TEST_END( durationIncrement ) +#define SKIP(c) { ADVANCE_TEST(1); if (date[0] != c) throw exception_time_error(); ADVANCE(1); } +#define SKIP_SPACING() {while(remaining > 0 && is_spacing(*date)) ADVANCE(1);} + SKIP_SPACING(); + PARSE( st.wYear, 4 ); if (st.wYear < 1601) throw exception_time_error(); + TEST_END(wYear); SKIP('-'); + PARSE( st.wMonth, 2 ); if (st.wMonth < 1 || st.wMonth > 12) throw exception_time_error(); + TEST_END(wMonth); SKIP('-'); + PARSE( st.wDay, 2); if (st.wDay < 1 || st.wDay > 31) throw exception_time_error(); + TEST_END(wDay); SKIP(' '); + PARSE( st.wHour, 2); if (st.wHour >= 24) throw exception_time_error(); + TEST_END(wHour); SKIP(':'); + PARSE( st.wMinute, 2); if (st.wMinute >= 60) throw exception_time_error(); + TEST_END(wMinute); SKIP(':'); + PARSE( st.wSecond, 2); if (st.wSecond >= 60) throw exception_time_error(); + SKIP_SPACING(); + TEST_END( wSecond ); +#undef ADVANCE +#undef ADVANCE_TEST +#undef PARSE +#undef TEST_END +#undef SKIP +#undef SKIP_SPACING + if (remaining > 0) throw exception_time_error(); + break; + } + t_filetimestamp base, out; + if (!SystemTimeToFileTime(&st, (FILETIME*) &base)) throw exception_time_error(); + if (!LocalFileTimeToFileTime((const FILETIME*)&base, (FILETIME*)&out)) throw exception_time_error(); + return out; + } catch(exception_time_error) { + return filetimestamp_invalid; + } +} + +static const char g_invalidMsg[] = ""; + +format_filetimestamp::format_filetimestamp(t_filetimestamp p_timestamp) { + try { + SYSTEMTIME st; FILETIME ft; + if (FileTimeToLocalFileTime((FILETIME*)&p_timestamp,&ft)) { + if (FileTimeToSystemTime(&ft,&st)) { + m_buffer + << pfc::format_uint(st.wYear,4) << "-" << pfc::format_uint(st.wMonth,2) << "-" << pfc::format_uint(st.wDay,2) << " " + << pfc::format_uint(st.wHour,2) << ":" << pfc::format_uint(st.wMinute,2) << ":" << pfc::format_uint(st.wSecond,2); + return; + } + } + } catch(...) {} + m_buffer = g_invalidMsg; +} + +format_filetimestamp_utc::format_filetimestamp_utc(t_filetimestamp p_timestamp) { + try { + SYSTEMTIME st; + if (FileTimeToSystemTime((const FILETIME*)&p_timestamp,&st)) { + m_buffer + << pfc::format_uint(st.wYear,4) << "-" << pfc::format_uint(st.wMonth,2) << "-" << pfc::format_uint(st.wDay,2) << " " + << pfc::format_uint(st.wHour,2) << ":" << pfc::format_uint(st.wMinute,2) << ":" << pfc::format_uint(st.wSecond,2); + return; + } + } catch(...) {} + m_buffer = g_invalidMsg; +} diff --git a/foobar2000/helpers/filetimetools.h b/foobar2000/helpers/filetimetools.h new file mode 100644 index 0000000..c7a88ee --- /dev/null +++ b/foobar2000/helpers/filetimetools.h @@ -0,0 +1,25 @@ +#pragma once + +namespace foobar2000_io { + t_filetimestamp filetimestamp_from_string(const char * date); + + //! Warning: this formats according to system timezone settings, created strings should be used for display only, never for storage. + class format_filetimestamp { + public: + format_filetimestamp(t_filetimestamp p_timestamp); + operator const char*() const {return m_buffer;} + const char * get_ptr() const {return m_buffer;} + private: + pfc::string_fixed_t<32> m_buffer; + }; + + class format_filetimestamp_utc { + public: + format_filetimestamp_utc(t_filetimestamp p_timestamp); + operator const char*() const {return m_buffer;} + const char * get_ptr() const {return m_buffer;} + private: + pfc::string_fixed_t<32> m_buffer; + }; + +} diff --git a/foobar2000/helpers/foobar2000+atl.h b/foobar2000/helpers/foobar2000+atl.h new file mode 100644 index 0000000..5470c40 --- /dev/null +++ b/foobar2000/helpers/foobar2000+atl.h @@ -0,0 +1,16 @@ +#pragma once + +#include "../SDK/foobar2000-winver.h" + +#define _SECURE_ATL 1 + +#include "../SDK/foobar2000.h" + +#include +#include +#include +#include +#include +#include +#include +#include diff --git a/foobar2000/helpers/foobar2000_sdk_helpers.vcxproj b/foobar2000/helpers/foobar2000_sdk_helpers.vcxproj new file mode 100644 index 0000000..380d208 --- /dev/null +++ b/foobar2000/helpers/foobar2000_sdk_helpers.vcxproj @@ -0,0 +1,250 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + + {EE47764E-A202-4F85-A767-ABDAB4AFF35F} + foobar2000_sdk_helpers + + + + StaticLibrary + false + Unicode + v141 + + + StaticLibrary + false + Unicode + true + v141 + + + + + + + + + + + + + <_ProjectFileVersion>10.0.30319.1 + + + + MinSpace + true + false + Fast + false + Use + stdafx.h + Level3 + true + ProgramDatabase + MultiThreadedDLL + ../.. + /d2notypeopt %(AdditionalOptions) + 4715 + true + true + NDEBUG;_UNICODE;UNICODE;%(PreprocessorDefinitions) + + + NDEBUG;%(PreprocessorDefinitions) + 0x0409 + + + true + + + + + Disabled + EnableFastChecks + Use + stdafx.h + Level3 + true + ProgramDatabase + MultiThreadedDebugDLL + ../.. + 4715 + true + + + _DEBUG;%(PreprocessorDefinitions) + 0x0409 + + + true + + + + + + Disabled + EnableFastChecks + + + + + Disabled + EnableFastChecks + + + + + Disabled + EnableFastChecks + + + Disabled + EnableFastChecks + + + Disabled + EnableFastChecks + + + Disabled + EnableFastChecks + + + Disabled + EnableFastChecks + + + + + + + + + + + + + + + Disabled + EnableFastChecks + Create + Create + + + Disabled + EnableFastChecks + + + Disabled + EnableFastChecks + + + + + + + + + Disabled + EnableFastChecks + + + + Disabled + EnableFastChecks + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/foobar2000/helpers/foobar2000_sdk_helpers.vcxproj.filters b/foobar2000/helpers/foobar2000_sdk_helpers.vcxproj.filters new file mode 100644 index 0000000..02d7065 --- /dev/null +++ b/foobar2000/helpers/foobar2000_sdk_helpers.vcxproj.filters @@ -0,0 +1,350 @@ + + + + + {f9bf58c4-374f-49a5-94db-1f5ae50beca1} + cpp;c;cxx;rc;def;r;odl;idl;hpj;bat + + + {07b1c50a-a3ad-4711-9ae0-d1411b80fd7a} + h;hpp;hxx;hm;inl + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + \ No newline at end of file diff --git a/foobar2000/helpers/fullFileBuffer.h b/foobar2000/helpers/fullFileBuffer.h new file mode 100644 index 0000000..4ed0e78 --- /dev/null +++ b/foobar2000/helpers/fullFileBuffer.h @@ -0,0 +1,18 @@ +#pragma once + +class fullFileBuffer { +public: + fullFileBuffer() { + //hMutex = CreateMutex(NULL, FALSE, pfc::stringcvt::string_os_from_utf8(pfc::string_formatter() << "{3ABC4D98-2510-431C-A720-26BEB45F0278}-" << (uint32_t) GetCurrentProcessId())); + } + ~fullFileBuffer() { + //CloseHandle(hMutex); + } + file::ptr open(const char * path, abort_callback & abort, file::ptr hint, t_filesize sizeMax = 1024 * 1024 * 256); + +private: + fullFileBuffer(const fullFileBuffer&); + void operator=(const fullFileBuffer&); + + //HANDLE hMutex; +}; diff --git a/foobar2000/helpers/helpers.h b/foobar2000/helpers/helpers.h new file mode 100644 index 0000000..83f947c --- /dev/null +++ b/foobar2000/helpers/helpers.h @@ -0,0 +1,41 @@ +#pragma once +// #pragma message("Avoid using this header. #include individual ones instead.") + +#include "duration_counter.h" +#include "input_helpers.h" +#include "create_directory_helper.h" +#include "dialog_resize_helper.h" +#include "dropdown_helper.h" +#include "window_placement_helper.h" +#include "win32_dialog.h" +#include "cuesheet_index_list.h" +#include "cue_creator.h" +#include "cue_parser.h" +#include "text_file_loader.h" +#include "file_list_helper.h" +#include "stream_buffer_helper.h" +#include "file_info_const_impl.h" +#include "dynamic_bitrate_helper.h" +#include "cfg_guidlist.h" +#include "file_move_helper.h" +#include "file_cached.h" +#include "seekabilizer.h" +#include "bitreader_helper.h" +#include "mp3_utils.h" +#include "win32_misc.h" +#include "fb2k_threads.h" +#include "COM_utils.h" +#include "metadb_io_hintlist.h" +#include "meta_table_builder.h" +#include "icon_remapping_wildcard.h" +#include "CallForwarder.h" +#include "playlist_position_reference_tracker.h" +#include "ThreadUtils.h" +#include "ProcessUtils.h" +#include "VisUtils.h" +#include "filetimetools.h" +#include "ProfileCache.h" +#include "file_win32_wrapper.h" +#include "fullFileBuffer.h" +#include "writer_wav.h" +#include "readers.h" \ No newline at end of file diff --git a/foobar2000/helpers/icon_remapping_wildcard.h b/foobar2000/helpers/icon_remapping_wildcard.h new file mode 100644 index 0000000..4faefde --- /dev/null +++ b/foobar2000/helpers/icon_remapping_wildcard.h @@ -0,0 +1,15 @@ +#pragma once + +class icon_remapping_wildcard_impl : public icon_remapping { +public: + icon_remapping_wildcard_impl(const char * p_pattern,const char * p_iconname) : m_pattern(p_pattern), m_iconname(p_iconname) {} + bool query(const char * p_extension,pfc::string_base & p_iconname) { + if (wildcard_helper::test(p_extension,m_pattern,true)) { + p_iconname = m_iconname; return true; + } else { + return false; + } + } +private: + pfc::string8 m_pattern,m_iconname; +}; diff --git a/foobar2000/helpers/image_load_save.cpp b/foobar2000/helpers/image_load_save.cpp new file mode 100644 index 0000000..29f3b02 --- /dev/null +++ b/foobar2000/helpers/image_load_save.cpp @@ -0,0 +1,88 @@ +#include "StdAfx.h" +#include "image_load_save.h" +#include +#include "../SDK/imageLoaderLite.h" + +namespace fb2k { + bool imageSaveDialog(album_art_data_ptr content, HWND wndParent, const char* initDir, bool bAsync) { + pfc::string8 fileTypes = "All files|*.*"; + pfc::string8 ext; + + try { + auto info = fb2k::imageLoaderLite::get()->getInfo(content); + if (info.formatName) { + pfc::string8 nameCapitalized = pfc::stringToUpper( info.formatName ); + ext = pfc::stringToLower( info.formatName ); + if (nameCapitalized == "WEBP") nameCapitalized = "WebP"; + pfc::string8 extmask; + if (ext == "jpeg") { + ext = "jpg"; + extmask = "*.jpg;*.jpeg"; + } else { + extmask << "*." << ext; + } + fileTypes.reset(); + fileTypes << nameCapitalized << " files|" << extmask; + } + } catch (...) {} + pfc::string8 fn; + + if (!uGetOpenFileName(wndParent, fileTypes, 0, ext.length() > 0 ? ext.c_str() : nullptr, "Export picture file", initDir, fn, TRUE)) return false; + + auto bErrord = std::make_shared(false); + auto work = [content, fn, bErrord] { + try { + auto f = fileOpenWriteNew(fn, fb2k::noAbort, 0.5); + f->write(content->get_ptr(), content->get_size(), fb2k::noAbort); + } catch(std::exception const & e) { + * bErrord = true; + pfc::string8 msg; + msg << "Image file could not be written: " << e; + fb2k::inMainThread([msg] { + popup_message::g_show(msg, "Information"); + }); + } + }; + if (bAsync) { + fb2k::splitTask(work); + return true; + } else { + work(); + return ! *bErrord; + } + } + + bool imageLoadDialog(pfc::string_base& outFN, HWND wndParent, const char* initDir) { + return !!uGetOpenFileName(wndParent, FB2K_GETOPENFILENAME_PICTUREFILES_ALL, 0, nullptr, "Import picture file", initDir, outFN, FALSE); + } + album_art_data::ptr imageLoadDialog(HWND wndParent, const char* initDir) { + album_art_data::ptr ret; + pfc::string8 fn; + if (imageLoadDialog(fn, wndParent, initDir)) { + try { + ret = readPictureFile(fn, fb2k::noAbort); + } catch (std::exception const& e) { + popup_message::g_show(PFC_string_formatter() << "Image file could not be read: " << e, "Information"); + } + } + return ret; + } + + album_art_data_ptr readPictureFile(const char* p_path, abort_callback& p_abort) { + PFC_ASSERT(p_path != nullptr); + PFC_ASSERT(*p_path != 0); + p_abort.check(); + + // Pointless, not a media file, path often from openfiledialog and not canonical + // file_lock_ptr lock = file_lock_manager::get()->acquire_read(p_path, p_abort); + + file_ptr l_file; + filesystem::g_open_timeout(l_file, p_path, filesystem::open_mode_read, 0.5, p_abort); + service_ptr_t instance = new service_impl_t(); + t_filesize size = l_file->get_size_ex(p_abort); + if (size > 1024 * 1024 * 64) throw std::runtime_error("File too large"); + instance->from_stream(l_file.get_ptr(), (t_size)size, p_abort); + return instance; + } + +} \ No newline at end of file diff --git a/foobar2000/helpers/image_load_save.h b/foobar2000/helpers/image_load_save.h new file mode 100644 index 0000000..7b71bf3 --- /dev/null +++ b/foobar2000/helpers/image_load_save.h @@ -0,0 +1,13 @@ +#pragma once + +namespace fb2k { + bool imageLoadDialog( pfc::string_base & outFN, HWND wndParent, const char * initDir ); + + album_art_data::ptr imageLoadDialog( HWND wndParent, const char * initDir ); + + //! bAllowAsync: run file writing offthread. In such case the caller will not be made aware if writing failed. \n + //! Error popup is shown if actual file writing fails. + bool imageSaveDialog(album_art_data_ptr content, HWND wndParent, const char * initDir , bool bAllowAsync = true ); + + album_art_data::ptr readPictureFile( const char * path, abort_callback & a); +} \ No newline at end of file diff --git a/foobar2000/helpers/inplace_edit.cpp b/foobar2000/helpers/inplace_edit.cpp new file mode 100644 index 0000000..41d6eb9 --- /dev/null +++ b/foobar2000/helpers/inplace_edit.cpp @@ -0,0 +1,25 @@ +#include "stdafx.h" +#include "inplace_edit.h" + +// Functionality moved to libPPUI + +namespace InPlaceEdit { + static reply_t wrapCN( completion_notify::ptr cn ) { + return [cn](unsigned code) { cn->on_completion(code); }; + } + HWND Start(HWND p_parentwnd, const RECT & p_rect, bool p_multiline, pfc::rcptr_t p_content, completion_notify_ptr p_notify) { + return Start(p_parentwnd, p_rect, p_multiline, p_content, wrapCN(p_notify) ); + } + + HWND StartEx(HWND p_parentwnd, const RECT & p_rect, unsigned p_flags, pfc::rcptr_t p_content, completion_notify_ptr p_notify, IUnknown * ACData, DWORD ACOpts ) { + return StartEx(p_parentwnd, p_rect, p_flags, p_content, wrapCN(p_notify), ACData, ACOpts ); + } + + void Start_FromListView(HWND p_listview, unsigned p_item, unsigned p_subitem, unsigned p_linecount, pfc::rcptr_t p_content, completion_notify_ptr p_notify) { + Start_FromListView(p_listview,p_item, p_subitem, p_linecount, p_content, wrapCN(p_notify) ); + } + void Start_FromListViewEx(HWND p_listview, unsigned p_item, unsigned p_subitem, unsigned p_linecount, unsigned p_flags, pfc::rcptr_t p_content, completion_notify_ptr p_notify) { + Start_FromListViewEx(p_listview, p_item, p_subitem, p_linecount, p_flags, p_content, wrapCN(p_notify) ); + } + +} \ No newline at end of file diff --git a/foobar2000/helpers/inplace_edit.h b/foobar2000/helpers/inplace_edit.h new file mode 100644 index 0000000..5d72bcb --- /dev/null +++ b/foobar2000/helpers/inplace_edit.h @@ -0,0 +1,13 @@ +#pragma once + +#include + +namespace InPlaceEdit { + + HWND Start(HWND p_parentwnd,const RECT & p_rect,bool p_multiline,pfc::rcptr_t p_content,completion_notify_ptr p_notify); + + HWND StartEx(HWND p_parentwnd,const RECT & p_rect,unsigned p_flags,pfc::rcptr_t p_content,completion_notify_ptr p_notify, IUnknown * ACData = NULL, DWORD ACOpts = 0); + + void Start_FromListView(HWND p_listview,unsigned p_item,unsigned p_subitem,unsigned p_linecount,pfc::rcptr_t p_content,completion_notify_ptr p_notify); + void Start_FromListViewEx(HWND p_listview,unsigned p_item,unsigned p_subitem,unsigned p_linecount,unsigned p_flags,pfc::rcptr_t p_content,completion_notify_ptr p_notify); +} diff --git a/foobar2000/helpers/input_fix_seeking.h b/foobar2000/helpers/input_fix_seeking.h new file mode 100644 index 0000000..35beda8 --- /dev/null +++ b/foobar2000/helpers/input_fix_seeking.h @@ -0,0 +1,65 @@ +#pragma once + +#include "duration_counter.h" + +// Wrapper to implement input_flag_allow_inaccurate_seeking semantics with decoders that do not implement seeking properly. +// If input_flag_allow_inaccurate_seeking is not specified, brute force seeking is used. +template +class input_fix_seeking : public base_t { +public: + input_fix_seeking() : m_active() {} + + void decode_initialize(unsigned p_flags,abort_callback & p_abort) { + base_t::decode_initialize( p_flags, p_abort ); + m_active = ( p_flags & input_flag_allow_inaccurate_seeking ) == 0; + m_position = 0; + m_decodeFrom = 0; + } + + void decode_initialize(t_uint32 p_subsong,unsigned p_flags,abort_callback & p_abort) { + base_t::decode_initialize( p_subsong, p_flags, p_abort ); + m_active = ( p_flags & input_flag_allow_inaccurate_seeking ) == 0; + m_position = 0; + } + + bool decode_run(audio_chunk & p_chunk,abort_callback & p_abort) { + if ( ! m_active ) { + return base_t::decode_run ( p_chunk, p_abort ); + } + for ( ;; ) { + p_abort.check(); + if (! base_t::decode_run( p_chunk, p_abort ) ) return false; + + double p = m_position.query(); + m_position += p_chunk; + if ( m_decodeFrom > p ) { + auto skip = audio_math::time_to_samples( m_decodeFrom - p, p_chunk.get_sample_rate() ); + if ( skip >= p_chunk.get_sample_count() ) continue; + if ( skip > 0 ) { + p_chunk.skip_first_samples( (size_t) skip ); + } + } + return true; + } + } + + void decode_seek(double p_seconds,abort_callback & p_abort) { + if ( m_active ) { + if ( ! this->decode_can_seek() ) throw exception_io_object_not_seekable(); + m_decodeFrom = p_seconds; + if ( m_decodeFrom < m_position.query() ) { + base_t::decode_seek(0, p_abort); m_position.reset(); + } + } else { + base_t::decode_seek(p_seconds, p_abort); + } + } + + bool decode_run_raw(audio_chunk & p_chunk, mem_block_container & p_raw, abort_callback & p_abort) { + throw pfc::exception_not_implemented(); // shitformats that need this class are not likely to care + } +private: + bool m_active; + duration_counter m_position; + double m_decodeFrom; +}; diff --git a/foobar2000/helpers/input_helper_cue.cpp b/foobar2000/helpers/input_helper_cue.cpp new file mode 100644 index 0000000..02ce69e --- /dev/null +++ b/foobar2000/helpers/input_helper_cue.cpp @@ -0,0 +1,221 @@ +#include "stdafx.h" +#include "input_helper_cue.h" +#include "../SDK/mem_block_container.h" + + +namespace { + class input_dec_binary : public input_decoder_v2 { + enum { + m_rate = 44100, + m_bps = 16, + m_channels = 2, + m_channelMask = audio_chunk::channel_config_stereo, + m_sampleBytes = (m_bps/8)*m_channels, + m_readAtOnce = 588, + m_readAtOnceBytes = m_readAtOnce * m_sampleBytes + }; + public: + input_dec_binary( file::ptr f ) : m_file(f) {} + t_uint32 get_subsong_count() override {return 0;} + t_uint32 get_subsong(t_uint32 p_index) override {return 0;} + + void get_info(t_uint32 p_subsong,file_info & p_info,abort_callback & p_abort) override { + p_info.reset(); + p_info.info_set_int("samplerate", m_rate); + p_info.info_set_int("channels", m_channels); + p_info.info_set_int("bitspersample", m_bps); + p_info.info_set("encoding","lossless"); + p_info.info_set_bitrate((m_bps * m_channels * m_rate + 500 /* rounding for bps to kbps*/ ) / 1000 /* bps to kbps */); + p_info.info_set("codec", "PCM"); + + try { + auto stats = get_file_stats(p_abort); + if ( stats.m_size != filesize_invalid ) { + p_info.set_length( audio_math::samples_to_time( stats.m_size / 4, 44100 ) ); + } + } catch(exception_io) {} + } + + + t_filestats get_file_stats(abort_callback & p_abort) override { + return m_file->get_stats(p_abort); + } + void initialize(t_uint32 p_subsong,unsigned p_flags,abort_callback & p_abort) override { + m_file->reopen( p_abort ); + } + bool run(audio_chunk & p_chunk,abort_callback & p_abort) override { + mem_block_container_impl stfu; + return run_raw(p_chunk, stfu, p_abort); + } + bool run_raw(audio_chunk & out, mem_block_container & outRaw, abort_callback & abort) override { + size_t bytes = m_readAtOnceBytes; + outRaw.set_size( bytes ); + size_t got = m_file->read(outRaw.get_ptr(), bytes, abort); + got -= got % m_sampleBytes; + if ( got == 0 ) return false; + if ( got < bytes ) outRaw.set_size( got ); + out.set_data_fixedpoint_signed( outRaw.get_ptr(), got, m_rate, m_channels, m_bps, m_channelMask); + return true; + } + void seek(double p_seconds,abort_callback & p_abort) override { + m_file->seek( audio_math::time_to_samples( p_seconds, m_rate ) * m_sampleBytes, p_abort ); + } + bool can_seek() override { + return m_file->can_seek(); + } + bool get_dynamic_info(file_info & p_out, double & p_timestamp_delta) override {return false;} + bool get_dynamic_info_track(file_info & p_out, double & p_timestamp_delta) override {return false;} + void on_idle(abort_callback & p_abort) override {} + void set_logger(event_logger::ptr ptr) override {} + private: + const file::ptr m_file; + }; +} + + +void input_helper_cue::get_info_binary( const char * path, file_info & out, abort_callback & abort ) { + auto f = fileOpenReadExisting( path, abort ); + auto obj = fb2k::service_new< input_dec_binary > ( f ); + obj->get_info( 0, out, abort ); +} + +void input_helper_cue::open(service_ptr_t p_filehint,const playable_location & p_location,unsigned p_flags,abort_callback & p_abort,double p_start,double p_length, bool binary) { + p_abort.check(); + + m_start = p_start; + m_position = 0; + m_dynamic_info_trigger = false; + m_dynamic_info_track_trigger = false; + + if ( binary ) { + { + const char * path = p_location.get_path(); + auto f = fileOpenReadExisting( path, p_abort ); + auto obj = fb2k::service_new< input_dec_binary > ( f ); + + m_input.attach( obj, path ); + m_input.open_decoding( 0, p_flags, p_abort ); + } + } else { + m_input.open(p_filehint,p_location,p_flags,p_abort,true,true); + } + + + if (!m_input.can_seek()) throw exception_io_object_not_seekable(); + + if (m_start > 0) { + m_input.seek(m_start,p_abort); + } + + if (p_length > 0) { + m_length = p_length; + } else { + file_info_impl temp; + m_input.get_info(0,temp,p_abort); + double ref_length = temp.get_length(); + if (ref_length <= 0) throw exception_io_data(); + m_length = ref_length - m_start + p_length /* negative or zero */; + if (m_length <= 0) throw exception_io_data(); + } +} + +void input_helper_cue::close() {m_input.close();} +bool input_helper_cue::is_open() {return m_input.is_open();} + +bool input_helper_cue::_m_input_run(audio_chunk & p_chunk, mem_block_container * p_raw, abort_callback & p_abort) { + if (p_raw == NULL) { + return m_input.run(p_chunk, p_abort); + } else { + return m_input.run_raw(p_chunk, *p_raw, p_abort); + } +} +bool input_helper_cue::_run(audio_chunk & p_chunk, mem_block_container * p_raw, abort_callback & p_abort) { + p_abort.check(); + + if (m_length > 0) { + if (m_position >= m_length) return false; + + if (!_m_input_run(p_chunk, p_raw, p_abort)) return false; + + m_dynamic_info_trigger = true; + m_dynamic_info_track_trigger = true; + + t_uint64 max = (t_uint64)audio_math::time_to_samples(m_length - m_position, p_chunk.get_sample_rate()); + if (max == 0) + {//handle rounding accidents, this normally shouldn't trigger + m_position = m_length; + return false; + } + + t_size samples = p_chunk.get_sample_count(); + if ((t_uint64)samples > max) + { + p_chunk.set_sample_count((unsigned)max); + if (p_raw != NULL) { + const t_size rawSize = p_raw->get_size(); + PFC_ASSERT(rawSize % samples == 0); + p_raw->set_size((t_size)((t_uint64)rawSize * max / samples)); + } + m_position = m_length; + } + else + { + m_position += p_chunk.get_duration(); + } + return true; + } + else + { + if (!_m_input_run(p_chunk, p_raw, p_abort)) return false; + m_position += p_chunk.get_duration(); + return true; + } +} +bool input_helper_cue::run_raw(audio_chunk & p_chunk, mem_block_container & p_raw, abort_callback & p_abort) { + return _run(p_chunk, &p_raw, p_abort); +} + +bool input_helper_cue::run(audio_chunk & p_chunk, abort_callback & p_abort) { + return _run(p_chunk, NULL, p_abort); +} + +void input_helper_cue::seek(double p_seconds, abort_callback & p_abort) +{ + m_dynamic_info_trigger = false; + m_dynamic_info_track_trigger = false; + if (m_length <= 0 || p_seconds < m_length) { + m_input.seek(p_seconds + m_start, p_abort); + m_position = p_seconds; + } + else { + m_position = m_length; + } +} + +bool input_helper_cue::can_seek() { return true; } + +void input_helper_cue::on_idle(abort_callback & p_abort) { m_input.on_idle(p_abort); } + +bool input_helper_cue::get_dynamic_info(file_info & p_out, double & p_timestamp_delta) { + if (m_dynamic_info_trigger) { + m_dynamic_info_trigger = false; + return m_input.get_dynamic_info(p_out, p_timestamp_delta); + } + else { + return false; + } +} + +bool input_helper_cue::get_dynamic_info_track(file_info & p_out, double & p_timestamp_delta) { + if (m_dynamic_info_track_trigger) { + m_dynamic_info_track_trigger = false; + return m_input.get_dynamic_info_track(p_out, p_timestamp_delta); + } + else { + return false; + } +} + +const char * input_helper_cue::get_path() const { return m_input.get_path(); } + +void input_helper_cue::get_info(t_uint32 p_subsong, file_info & p_info, abort_callback & p_abort) { m_input.get_info(p_subsong, p_info, p_abort); } diff --git a/foobar2000/helpers/input_helper_cue.h b/foobar2000/helpers/input_helper_cue.h new file mode 100644 index 0000000..10d9916 --- /dev/null +++ b/foobar2000/helpers/input_helper_cue.h @@ -0,0 +1,33 @@ +#pragma once + +#include "input_helpers.h" + + +class input_helper_cue { +public: + void open(service_ptr_t p_filehint,const playable_location & p_location,unsigned p_flags,abort_callback & p_abort,double p_start,double p_length, bool binary = false); + static void get_info_binary( const char * path, file_info & out, abort_callback & abort ); + + void close(); + bool is_open(); + bool run(audio_chunk & p_chunk,abort_callback & p_abort); + bool run_raw(audio_chunk & p_chunk, mem_block_container & p_raw, abort_callback & p_abort); + void seek(double seconds,abort_callback & p_abort); + bool can_seek(); + void on_idle(abort_callback & p_abort); + bool get_dynamic_info(file_info & p_out,double & p_timestamp_delta); + bool get_dynamic_info_track(file_info & p_out,double & p_timestamp_delta); + void set_logger(event_logger::ptr ptr) {m_input.set_logger(ptr);} + + const char * get_path() const; + + void get_info(t_uint32 p_subsong,file_info & p_info,abort_callback & p_abort); + +private: + bool _run(audio_chunk & p_chunk, mem_block_container * p_raw, abort_callback & p_abort); + bool _m_input_run(audio_chunk & p_chunk, mem_block_container * p_raw, abort_callback & p_abort); + input_helper m_input; + double m_start,m_length,m_position; + bool m_dynamic_info_trigger,m_dynamic_info_track_trigger; + bool m_binary; +}; diff --git a/foobar2000/helpers/input_helpers.cpp b/foobar2000/helpers/input_helpers.cpp new file mode 100644 index 0000000..f8ea6ee --- /dev/null +++ b/foobar2000/helpers/input_helpers.cpp @@ -0,0 +1,561 @@ +#include "stdafx.h" + +#include "input_helpers.h" +#include "fullFileBuffer.h" +#include "file_list_helper.h" +#include "fileReadAhead.h" + + +input_helper::ioFilter_t input_helper::ioFilter_full_buffer(t_filesize val ) { + if (val == 0) return nullptr; + return [val] ( file_ptr & f, const char * path, abort_callback & aborter) { + if (!filesystem::g_is_remote_or_unrecognized(path)) { + fullFileBuffer a; + f = a.open(path, aborter, f, val); + return true; + } + return false; + }; +} + +input_helper::ioFilter_t input_helper::ioFilter_block_buffer(size_t arg) { + if (arg == 0) return nullptr; + + return [arg](file_ptr & p_file, const char * p_path, abort_callback & p_abort) { + if (!filesystem::g_is_remote_or_unrecognized(p_path)) { + if (p_file.is_empty()) filesystem::g_open_read(p_file, p_path, p_abort); + if (!p_file->is_in_memory() && !p_file->is_remote() && p_file->can_seek()) { + file_cached::g_create(p_file, p_file, p_abort, (size_t)arg); + return true; + } + } + return false; + }; +} + +static input_helper::ioFilter_t makeReadAhead(size_t arg, bool bRemote) { + if (arg == 0) return nullptr; + + return [arg, bRemote](file_ptr & p_file, const char * p_path, abort_callback & p_abort) { + if (p_file.is_empty()) { + filesystem::ptr fs; + if (!filesystem::g_get_interface(fs, p_path)) return false; + if (bRemote != fs->is_remote(p_path)) return false; + fs->open(p_file, p_path, filesystem::open_mode_read, p_abort); + } else if (bRemote != p_file->is_remote()) return false; + if (p_file->is_in_memory()) return false; + p_file = fileCreateReadAhead(p_file, (size_t)arg, p_abort); + return true; + }; +} + +input_helper::ioFilter_t input_helper::ioFilter_remote_read_ahead(size_t arg) { + return makeReadAhead(arg, true); +} + +input_helper::ioFilter_t input_helper::ioFilter_local_read_ahead(size_t arg) { + return makeReadAhead(arg, false); +} + +void input_helper::open(service_ptr_t p_filehint,metadb_handle_ptr p_location,unsigned p_flags,abort_callback & p_abort,bool p_from_redirect,bool p_skip_hints) +{ + open(p_filehint,p_location->get_location(),p_flags,p_abort,p_from_redirect,p_skip_hints); +} + +void input_helper::fileOpenTools(service_ptr_t & p_file,const char * p_path,input_helper::ioFilters_t const & filters, abort_callback & p_abort) { + for( auto walk = filters.begin(); walk != filters.end(); ++ walk ) { + auto f = *walk; + if (f) { + if (f(p_file, p_path, p_abort)) break; + } + } +} + +bool input_helper::need_file_reopen(const char * newPath) const { + return m_input.is_empty() || playable_location::path_compare(m_path, newPath) != 0; +} + +bool input_helper::open_path(const char * path, abort_callback & abort, decodeOpen_t const & other) { + abort.check(); + + m_logger = other.m_logger; + + if (!need_file_reopen(path)) { + if ( other.m_logger.is_valid() ) { + input_decoder_v2::ptr v2; + if (m_input->service_query_t(v2)) v2->set_logger(other.m_logger); + } + return false; + } + m_input.release(); + + service_ptr_t l_file = other.m_hint; + fileOpenTools(l_file, path, other.m_ioFilters, abort); + + TRACK_CODE("input_entry::g_open_for_decoding", + m_input ^= input_entry::g_open(input_decoder::class_guid, l_file, path, m_logger, abort, other.m_from_redirect ); + ); + +#ifndef FOOBAR2000_MODERN + if (!other.m_skip_hints) { + try { + metadb_io::get()->hint_reader(m_input.get_ptr(), path, abort); + } + catch (exception_io_data) { + //Don't fail to decode when this barfs, might be barfing when reading info from another subsong than the one we're trying to decode etc. + m_input.release(); + if (l_file.is_valid()) l_file->reopen(abort); + TRACK_CODE("input_entry::g_open_for_decoding", + m_input ^= input_entry::g_open(input_decoder::class_guid, l_file, path, m_logger, abort, other.m_from_redirect); + ); + } + } +#endif + + if (other.m_shim) m_input = other.m_shim(m_input, path, abort); + + m_path = path; + return true; +} + +void input_helper::open_decoding(t_uint32 subsong, t_uint32 flags, abort_callback & p_abort) { + TRACK_CODE("input_decoder::initialize", m_input->initialize(subsong, flags, p_abort)); +} + +void input_helper::open(const playable_location & location, abort_callback & abort, decodeOpen_t const & other) { + open_path(location.get_path(), abort, other); + + if (other.m_setSampleRate != 0) { + this->extended_param(input_params::set_preferred_sample_rate, other.m_setSampleRate, nullptr, 0); + } + + open_decoding(location.get_subsong(), other.m_flags, abort); +} + +void input_helper::attach(input_decoder::ptr dec, const char * path) { + m_input = dec; + m_path = path; +} + +void input_helper::open(service_ptr_t p_filehint, const playable_location & p_location, unsigned p_flags, abort_callback & p_abort, bool p_from_redirect, bool p_skip_hints) { + decodeOpen_t o; + o.m_hint = p_filehint; + o.m_flags = p_flags; + o.m_from_redirect = p_from_redirect; + o.m_skip_hints = p_skip_hints; + this->open(p_location, p_abort, o); +} + + +void input_helper::close() { + m_input.release(); +} + +bool input_helper::is_open() { + return m_input.is_valid(); +} + +void input_helper::set_pause(bool state) { + input_decoder_v3::ptr v3; + if (m_input->service_query_t(v3)) v3->set_pause(state); +} +bool input_helper::flush_on_pause() { + input_decoder_v3::ptr v3; + if (m_input->service_query_t(v3)) return v3->flush_on_pause(); + else return false; +} + + +void input_helper::set_logger(event_logger::ptr ptr) { + m_logger = ptr; + input_decoder_v2::ptr v2; + if (m_input->service_query_t(v2)) v2->set_logger(ptr); +} + +bool input_helper::run_raw(audio_chunk & p_chunk, mem_block_container & p_raw, abort_callback & p_abort) { + input_decoder_v2::ptr v2; + if (!m_input->service_query_t(v2)) throw pfc::exception_not_implemented(); + return v2->run_raw(p_chunk, p_raw, p_abort); +} + +bool input_helper::run(audio_chunk & p_chunk, abort_callback & p_abort) { + TRACK_CODE("input_decoder::run", return m_input->run(p_chunk, p_abort)); +} + +void input_helper::seek(double seconds, abort_callback & p_abort) { + TRACK_CODE("input_decoder::seek", m_input->seek(seconds, p_abort)); +} + +bool input_helper::can_seek() { + return m_input->can_seek(); +} + +bool input_helper::query_position( double & val ) { + return extended_param(input_params::query_position, 0, &val, sizeof(val) ) != 0; +} + +size_t input_helper::extended_param(const GUID & type, size_t arg1, void * arg2, size_t arg2size) { + input_decoder_v4::ptr v4; + if (v4 &= m_input) { + return v4->extended_param(type, arg1, arg2, arg2size); + } + return 0; +} +input_helper::decodeInfo_t input_helper::decode_info() { + decodeInfo_t ret = {}; + if (m_input.is_valid()) { + ret.m_can_seek = can_seek(); + ret.m_flush_on_pause = flush_on_pause(); + if (ret.m_can_seek) { + ret.m_seeking_expensive = extended_param(input_params::seeking_expensive, 0, nullptr, 0) != 0; + } + } + return ret; +} + +void input_helper::on_idle(abort_callback & p_abort) { + if (m_input.is_valid()) { + TRACK_CODE("input_decoder::on_idle",m_input->on_idle(p_abort)); + } +} + +bool input_helper::get_dynamic_info(file_info & p_out,double & p_timestamp_delta) { + TRACK_CODE("input_decoder::get_dynamic_info",return m_input->get_dynamic_info(p_out,p_timestamp_delta)); +} + +bool input_helper::get_dynamic_info_track(file_info & p_out,double & p_timestamp_delta) { + TRACK_CODE("input_decoder::get_dynamic_info_track",return m_input->get_dynamic_info_track(p_out,p_timestamp_delta)); +} + +void input_helper::get_info(t_uint32 p_subsong,file_info & p_info,abort_callback & p_abort) { + TRACK_CODE("input_decoder::get_info",m_input->get_info(p_subsong,p_info,p_abort)); +} + +const char * input_helper::get_path() const { + return m_path; +} + + +input_helper::input_helper() +{ +} + + +void input_helper::g_get_info(const playable_location & p_location,file_info & p_info,abort_callback & p_abort,bool p_from_redirect) { + service_ptr_t instance; + input_entry::g_open_for_info_read(instance,0,p_location.get_path(),p_abort,p_from_redirect); + instance->get_info(p_location.get_subsong_index(),p_info,p_abort); +} + +void input_helper::g_set_info(const playable_location & p_location,file_info & p_info,abort_callback & p_abort,bool p_from_redirect) { + service_ptr_t instance; + input_entry::g_open_for_info_write(instance,0,p_location.get_path(),p_abort,p_from_redirect); + instance->set_info(p_location.get_subsong_index(),p_info,p_abort); + instance->commit(p_abort); +} + + +bool dead_item_filter::run(const pfc::list_base_const_t & p_list,bit_array_var & p_mask) { + file_list_helper::file_list_from_metadb_handle_list path_list; + path_list.init_from_list(p_list); + metadb_handle_list valid_handles; + auto l_metadb = metadb::get(); + for(t_size pathidx=0;pathidxhandle_create(temp,make_playable_location(path,0)); + valid_handles.add_item(temp); + } else { + try { + service_ptr_t reader; + + input_entry::g_open_for_info_read(reader,0,path,*this); + t_uint32 count = reader->get_subsong_count(); + for(t_uint32 n=0;nget_subsong(n); + l_metadb->handle_create(ptr,make_playable_location(path,index)); + valid_handles.add_item(ptr); + } + } catch(...) {} + } + } + + if (is_aborting()) return false; + + valid_handles.sort_by_pointer(); + for(t_size listidx=0;listidx & p_list,bit_array_var & p_mask,abort_callback & p_abort) +{ + dead_item_filter_impl_simple filter(p_abort); + return filter.run(p_list,p_mask); +} + +void input_info_read_helper::open(const char * p_path,abort_callback & p_abort) { + if (m_input.is_empty() || playable_location::path_compare(m_path,p_path) != 0) + { + TRACK_CODE("input_entry::g_open_for_info_read",input_entry::g_open_for_info_read(m_input,0,p_path,p_abort)); + + m_path = p_path; + } +} + +void input_info_read_helper::get_info(const playable_location & p_location,file_info & p_info,t_filestats & p_stats,abort_callback & p_abort) { + open(p_location.get_path(),p_abort); + + TRACK_CODE("input_info_reader::get_file_stats",p_stats = m_input->get_file_stats(p_abort)); + + TRACK_CODE("input_info_reader::get_info",m_input->get_info(p_location.get_subsong_index(),p_info,p_abort)); +} + +void input_info_read_helper::get_info_check(const playable_location & p_location,file_info & p_info,t_filestats & p_stats,bool & p_reloaded,abort_callback & p_abort) { + open(p_location.get_path(),p_abort); + t_filestats newstats; + TRACK_CODE("input_info_reader::get_file_stats",newstats = m_input->get_file_stats(p_abort)); + if (metadb_handle::g_should_reload(p_stats,newstats,true)) { + p_stats = newstats; + TRACK_CODE("input_info_reader::get_info",m_input->get_info(p_location.get_subsong_index(),p_info,p_abort)); + p_reloaded = true; + } else { + p_reloaded = false; + } +} + + + + +// openAudioData code + +namespace { + + class file_decodedaudio : public file_readonly { + public: + void init(const playable_location & loc, input_helper::decodeOpen_t const & arg, abort_callback & aborter) { + m_length = -1; m_lengthKnown = false; + m_subsong = loc.get_subsong(); + m_decoder ^= input_entry::g_open( input_decoder::class_guid, arg.m_hint, loc.get_path(), arg.m_logger, aborter, arg.m_from_redirect); + m_seekable = ( arg.m_flags & input_flag_no_seeking ) == 0 && m_decoder->can_seek(); + reopenDecoder(aborter); + readChunk(aborter, true); + } + void init(const playable_location & loc, bool bSeekable, file::ptr fileHint, abort_callback & aborter) { + m_length = -1; m_lengthKnown = false; + m_subsong = loc.get_subsong(); + input_entry::g_open_for_decoding(m_decoder, fileHint, loc.get_path(), aborter); + m_seekable = bSeekable && m_decoder->can_seek(); + reopenDecoder(aborter); + readChunk(aborter, true); + } + + void reopen(abort_callback & aborter) { + + // Have valid chunk and it is the first chunk in the stream? Reset read pointers and bail, no need to reopen + if (m_chunk.get_sample_count() > 0 && m_chunkBytesPtr == m_currentPosition) { + m_currentPosition = 0; + m_chunkBytesPtr = 0; + m_eof = false; + return; + } + + reopenDecoder(aborter); + readChunk(aborter); + } + + bool is_remote() { + return false; + } +#if FOOBAR2000_TARGET_VERSION >= 2000 + t_filestats get_stats(abort_callback & aborter) { + t_filestats fs = m_decoder->get_file_stats(aborter); + fs.m_size = get_size(aborter); + return fs; + } +#else + t_filetimestamp get_timestamp(abort_callback & p_abort) { + return m_decoder->get_file_stats(p_abort).m_timestamp; + } +#endif + void on_idle(abort_callback & p_abort) { + m_decoder->on_idle(p_abort); + } + bool get_content_type(pfc::string_base & p_out) { + return false; + } + bool can_seek() { + return m_seekable; + } + void seek(t_filesize p_position, abort_callback & p_abort) { + if (!m_seekable) throw exception_io_object_not_seekable(); + + + { + t_filesize chunkBegin = m_currentPosition - m_chunkBytesPtr; + t_filesize chunkEnd = chunkBegin + curChunkBytes(); + if (p_position >= chunkBegin && p_position < chunkEnd) { + m_chunkBytesPtr = (size_t)(p_position - chunkBegin); + m_currentPosition = p_position; + m_eof = false; + return; + } + } + + { + t_filesize s = get_size(p_abort); + if (s != filesize_invalid) { + if (p_position > s) throw exception_io_seek_out_of_range(); + if (p_position == s) { + m_chunk.reset(); + m_chunkBytesPtr = 0; + m_currentPosition = p_position; + m_eof = true; + return; + } + } + } + + + + const size_t row = m_spec.chanCount * sizeof(audio_sample); + t_filesize samples = p_position / row; + const double seekTime = audio_math::samples_to_time(samples, m_spec.sampleRate); + m_decoder->seek(seekTime, p_abort); + m_eof = false; // do this before readChunk + if (!this->readChunk(p_abort)) { + throw std::runtime_error( ( PFC_string_formatter() << "Premature EOF in referenced audio file at " << pfc::format_time_ex(seekTime, 6) << " out of " << pfc::format_time_ex(length(p_abort), 6) ).get_ptr() ); + } + m_chunkBytesPtr = p_position % row; + if (m_chunkBytesPtr > curChunkBytes()) { + // Should not ever happen + m_chunkBytesPtr = 0; + throw std::runtime_error("Decoder returned invalid data"); + } + + m_currentPosition = p_position; + m_eof = false; + } + + t_filesize get_size(abort_callback & aborter) { + const double l = length(aborter); + if (l <= 0) return filesize_invalid; + return audio_math::time_to_samples(l, m_spec.sampleRate) * m_spec.chanCount * sizeof(audio_sample); + } + t_filesize get_position(abort_callback & p_abort) { + return m_currentPosition; + } + t_size read(void * p_buffer, t_size p_bytes, abort_callback & p_abort) { + size_t done = 0; + for (;;) { + p_abort.check(); + { + const size_t inChunk = curChunkBytes(); + const size_t inChunkRemaining = inChunk - m_chunkBytesPtr; + const size_t delta = pfc::min_t(inChunkRemaining, (p_bytes - done)); + memcpy((uint8_t*)p_buffer + done, (const uint8_t*)m_chunk.get_data() + m_chunkBytesPtr, delta); + m_chunkBytesPtr += delta; + done += delta; + m_currentPosition += delta; + + if (done == p_bytes) break; + } + if (!readChunk(p_abort)) break; + } + return done; + } + audio_chunk::spec_t const & get_spec() const { return m_spec; } + private: + void reopenDecoder(abort_callback & aborter) { + uint32_t flags = input_flag_no_looping; + if (!m_seekable) flags |= input_flag_no_seeking; + m_decoder->initialize(m_subsong, flags, aborter); + m_eof = false; + m_currentPosition = 0; + } + size_t curChunkBytes() const { + return m_chunk.get_used_size() * sizeof(audio_sample); + } + bool readChunk(abort_callback & aborter, bool initial = false) { + m_chunkBytesPtr = 0; + for (;;) { + if (m_eof || !m_decoder->run(m_chunk, aborter)) { + if (initial) throw std::runtime_error("Decoder produced no data"); + m_eof = true; + m_chunk.reset(); + return false; + } + if (m_chunk.is_valid()) break; + } + audio_chunk::spec_t spec = m_chunk.get_spec(); + if (initial) m_spec = spec; + else if (m_spec != spec) throw std::runtime_error("Sample format change in mid stream"); + return true; + } + double length(abort_callback & aborter) { + if (!m_lengthKnown) { + file_info_impl temp; + m_decoder->get_info(m_subsong, temp, aborter); + m_length = temp.get_length(); + m_lengthKnown = true; + } + return m_length; + } + audio_chunk_fast_impl m_chunk; + size_t m_chunkBytesPtr; + audio_chunk::spec_t m_spec; + double m_length; + bool m_lengthKnown; + bool m_seekable; + uint32_t m_subsong; + input_decoder::ptr m_decoder; + bool m_eof; + t_filesize m_currentPosition; + }; + +} + +openAudioData_t openAudioData2(playable_location const & loc, input_helper::decodeOpen_t const & openArg, abort_callback & aborter) { + service_ptr_t f; f = new service_impl_t < file_decodedaudio >; + f->init(loc, openArg, aborter); + + openAudioData_t oad = {}; + oad.audioData = f; + oad.audioSpec = f->get_spec(); + return oad; +} + +openAudioData_t openAudioData(playable_location const & loc, bool bSeekable, file::ptr fileHint, abort_callback & aborter) { + service_ptr_t f; f = new service_impl_t < file_decodedaudio > ; + f->init(loc, bSeekable, fileHint, aborter); + + openAudioData_t oad = {}; + oad.audioData = f; + oad.audioSpec = f->get_spec(); + return oad; +} diff --git a/foobar2000/helpers/input_helpers.h b/foobar2000/helpers/input_helpers.h new file mode 100644 index 0000000..3253426 --- /dev/null +++ b/foobar2000/helpers/input_helpers.h @@ -0,0 +1,130 @@ +#pragma once + +#include +#include + +class input_helper { +public: + input_helper(); + + typedef std::function shim_t; + + typedef std::function< bool ( file::ptr &, const char *, abort_callback & ) > ioFilter_t; + typedef std::list ioFilters_t; + + struct decodeInfo_t { + bool m_flush_on_pause; + bool m_can_seek; + bool m_seeking_expensive; + }; + + struct decodeOpen_t { + bool m_from_redirect = false; + bool m_skip_hints = false; + unsigned m_flags = 0; + file::ptr m_hint; + unsigned m_setSampleRate = 0; + + ioFilters_t m_ioFilters; + event_logger::ptr m_logger; + shim_t m_shim; + }; + + void open(service_ptr_t p_filehint,metadb_handle_ptr p_location,unsigned p_flags,abort_callback & p_abort,bool p_from_redirect = false,bool p_skip_hints = false); + void open(service_ptr_t p_filehint,const playable_location & p_location,unsigned p_flags,abort_callback & p_abort,bool p_from_redirect = false,bool p_skip_hints = false); + void attach(input_decoder::ptr dec, const char * path); + + void open(const playable_location & location, abort_callback & abort, decodeOpen_t const & other); + void open(metadb_handle_ptr location, abort_callback & abort, decodeOpen_t const & other) {this->open(location->get_location(), abort, other);} + + + //! Multilevel open helpers. + //! @returns Diagnostic/helper value: true if the decoder had to be re-opened entirely, false if the instance was reused. + bool open_path(const char * path, abort_callback & abort, decodeOpen_t const & other); + //! Multilevel open helpers. + void open_decoding(t_uint32 subsong, t_uint32 flags, abort_callback & p_abort); + + bool need_file_reopen(const char * newPath) const; + + decodeInfo_t decode_info(); + + void close(); + bool is_open(); + bool run(audio_chunk & p_chunk,abort_callback & p_abort); + bool run_raw(audio_chunk & p_chunk, mem_block_container & p_raw, abort_callback & p_abort); + void seek(double seconds,abort_callback & p_abort); + bool can_seek(); + size_t extended_param( const GUID & type, size_t arg1, void * arg2, size_t arg2size); + static ioFilter_t ioFilter_full_buffer(t_filesize val ); + static ioFilter_t ioFilter_block_buffer(size_t val); + static ioFilter_t ioFilter_remote_read_ahead( size_t val ); + static ioFilter_t ioFilter_local_read_ahead(size_t val); + + void on_idle(abort_callback & p_abort); + bool get_dynamic_info(file_info & p_out,double & p_timestamp_delta); + bool get_dynamic_info_track(file_info & p_out,double & p_timestamp_delta); + void set_logger(event_logger::ptr ptr); + void set_pause(bool state); + bool flush_on_pause(); + + //! If this decoder has its own special position reporting, decoder-signaled logical decoding position will be returned. \n + //! Otherwise, position calculated from returned audio duration should be assumed. \n + //! Very few special-purpose decoders do this. + bool query_position( double & val ); + + //! Retrieves path of currently open file. + const char * get_path() const; + + //! Retrieves info about specific subsong of currently open file. + void get_info(t_uint32 p_subsong,file_info & p_info,abort_callback & p_abort); + + static void g_get_info(const playable_location & p_location,file_info & p_info,abort_callback & p_abort,bool p_from_redirect = false); + static void g_set_info(const playable_location & p_location,file_info & p_info,abort_callback & p_abort,bool p_from_redirect = false); + + + static bool g_mark_dead(const pfc::list_base_const_t & p_list,bit_array_var & p_mask,abort_callback & p_abort); + +private: + void fileOpenTools(service_ptr_t & p_file,const char * p_path, ioFilters_t const & filters, abort_callback & p_abort); + service_ptr_t m_input; + pfc::string8 m_path; + event_logger::ptr m_logger; +}; + +class NOVTABLE dead_item_filter : public abort_callback { +public: + virtual void on_progress(t_size p_position,t_size p_total) = 0; + + bool run(const pfc::list_base_const_t & p_list,bit_array_var & p_mask); +}; + +class input_info_read_helper { +public: + input_info_read_helper() {} + void get_info(const playable_location & p_location,file_info & p_info,t_filestats & p_stats,abort_callback & p_abort); + void get_info_check(const playable_location & p_location,file_info & p_info,t_filestats & p_stats,bool & p_reloaded,abort_callback & p_abort); +private: + void open(const char * p_path,abort_callback & p_abort); + + pfc::string8 m_path; + service_ptr_t m_input; +}; + + + + +//! openAudioData return value, see openAudioData() +struct openAudioData_t { + file::ptr audioData; // audio data stream + audio_chunk::spec_t audioSpec; // format description (sample rate, channel layout). +}; + +//! Opens the specified location as a stream of audio_samples. \n +//! Returns a file object that allows you to read the audio data stream as if it was a physical file, together with audio stream format description (sample rate, channel layout). \n +//! Please keep in mind that certain features of the returned file object are only as reliable as the underlying file format or decoder implementation allows them to be. \n +//! Reported exact file size may be either unavailable or unreliable if the file format does not let us known the exact value without decoding the whole file. \n +//! Seeking may be inaccurate with certain file formats. \n +//! In general, all file object methods will work as intended on core-supported file formats such as FLAC or WavPack. \n +//! However, if you want 100% functionality regardless of file format being worked with, mirror the content to a temp file and let go of the file object returned by openAudioData(). +openAudioData_t openAudioData(playable_location const & loc, bool bSeekable, file::ptr fileHint, abort_callback & aborter); +openAudioData_t openAudioData2(playable_location const & loc, input_helper::decodeOpen_t const & openArg, abort_callback & aborter); diff --git a/foobar2000/helpers/input_logging.h b/foobar2000/helpers/input_logging.h new file mode 100644 index 0000000..13d5cc7 --- /dev/null +++ b/foobar2000/helpers/input_logging.h @@ -0,0 +1,35 @@ +#pragma once + + +class input_logging : public input_stubs { +public: + input_logging() { + set_logger(nullptr); + } + + event_logger_recorder::ptr log_record( std::function f ) { + auto rec = event_logger_recorder::create(); + { + pfc::vartoggle_t< event_logger::ptr > toggle( m_logger, rec ); + f(); + } + return rec; + } + + void set_logger( event_logger::ptr logger ) { + if ( logger.is_valid() ) { + m_haveCustomLogger = true; + m_logger = logger; + } else { + m_haveCustomLogger = false; + m_logger = new service_impl_t(); + } + } +protected: + event_logger::ptr m_logger; + bool m_haveCustomLogger = false; +}; + +#define FB2K_INPUT_LOG_STATUS(X) FB2K_LOG_STATUS(m_logger, X) +#define FB2K_INPUT_LOG_WARNING(X) FB2K_LOG_WARNING(m_logger, X) +#define FB2K_INPUT_LOG_ERROR(X) FB2K_LOG_ERROR(m_logger, X) diff --git a/foobar2000/helpers/input_stream_info_reader.h b/foobar2000/helpers/input_stream_info_reader.h new file mode 100644 index 0000000..f437c4e --- /dev/null +++ b/foobar2000/helpers/input_stream_info_reader.h @@ -0,0 +1,35 @@ +#pragma once + +#ifdef FOOBAR2000_DESKTOP + +template +class input_stream_info_reader_impl : public input_stream_info_reader { +public: + input_t theInput; + + uint32_t get_stream_count() { + return theInput.get_stream_count(); + } + void get_stream_info(uint32_t index, file_info & out, abort_callback & a) { + theInput.get_stream_info(index, out, a); + } + uint32_t get_default_stream() { + return theInput.get_default_stream(); + } +}; + +template +class input_stream_info_reader_entry_impl : public input_stream_info_reader_entry { +public: + input_stream_info_reader::ptr open(const char * path, file::ptr fileHint, abort_callback & abort) { + typedef input_stream_info_reader_impl obj_t; + service_ptr_t p = new service_impl_t(); + p->theInput.open(fileHint, path, input_open_info_read, abort); + return p; + } + GUID get_guid() { + return input_t::g_get_guid(); + } +}; + +#endif // FOOBAR2000_DESKTOP diff --git a/foobar2000/helpers/meta_table_builder.h b/foobar2000/helpers/meta_table_builder.h new file mode 100644 index 0000000..d27178b --- /dev/null +++ b/foobar2000/helpers/meta_table_builder.h @@ -0,0 +1,143 @@ +#pragma once + +class _meta_table_enum_wrapper { +public: + _meta_table_enum_wrapper(file_info & p_info) : m_info(p_info) {} + template + void operator() (const char * p_name,const t_values & p_values) { + t_size index = ~0; + for(typename t_values::const_iterator iter = p_values.first(); iter.is_valid(); ++iter) { + if (index == ~0) index = m_info.__meta_add_unsafe(p_name,*iter); + else m_info.meta_add_value(index,*iter); + } + } +private: + file_info & m_info; +}; + +class _meta_table_enum_wrapper_RG { +public: + _meta_table_enum_wrapper_RG(file_info & p_info) : m_info(p_info) {} + template + void operator() (const char * p_name,const t_values & p_values) { + if (p_values.get_count() > 0) { + if (!m_info.info_set_replaygain(p_name, *p_values.first())) { + t_size index = ~0; + for(typename t_values::const_iterator iter = p_values.first(); iter.is_valid(); ++iter) { + if (index == ~0) index = m_info.__meta_add_unsafe(p_name,*iter); + else m_info.meta_add_value(index,*iter); + } + } + } + } +private: + file_info & m_info; +}; + +//! Purpose: building a file_info metadata table from loose input without search-for-existing-entry bottleneck +class meta_table_builder { +public: + typedef pfc::chain_list_v2_t t_entry; + typedef pfc::map_t t_content; + + t_content & content() {return m_data;} + t_content const & content() const {return m_data;} + + void add(const char * p_name,const char * p_value,t_size p_value_len = ~0) { + if (file_info::g_is_valid_field_name(p_name)) { + _add(p_name).insert_last()->set_string(p_value,p_value_len); + } + } + + void remove(const char * p_name) { + m_data.remove(p_name); + } + void set(const char * p_name,const char * p_value,t_size p_value_len = ~0) { + if (file_info::g_is_valid_field_name(p_name)) { + t_entry & entry = _add(p_name); + entry.remove_all(); + entry.insert_last()->set_string(p_value,p_value_len); + } + } + t_entry & add(const char * p_name) { + if (!file_info::g_is_valid_field_name(p_name)) uBugCheck();//we return a reference, nothing smarter to do + return _add(p_name); + } + void deduplicate(const char * name) { + t_entry * e; + if (!m_data.query_ptr(name, e)) return; + pfc::avltree_t found; + for(t_entry::iterator iter = e->first(); iter.is_valid(); ) { + t_entry::iterator next = iter; ++next; + const char * v = *iter; + if (!found.add_item_check(v)) e->remove(iter); + iter = next; + } + } + void keep_one(const char * name) { + t_entry * e; + if (!m_data.query_ptr(name, e)) return; + while(e->get_count() > 1) e->remove(e->last()); + } + void tidy_VorbisComment() { + deduplicate("album artist"); + deduplicate("publisher"); + keep_one("totaltracks"); + keep_one("totaldiscs"); + } + void finalize(file_info & p_info) const { + p_info.meta_remove_all(); + _meta_table_enum_wrapper e(p_info); + m_data.enumerate(e); + } + void finalize_withRG(file_info & p_info) const { + p_info.meta_remove_all(); p_info.set_replaygain(replaygain_info_invalid); + _meta_table_enum_wrapper_RG e(p_info); + m_data.enumerate(e); + } + + void from_info(const file_info & p_info) { + m_data.remove_all(); + from_info_overwrite(p_info); + } + void from_info_withRG(const file_info & p_info) { + m_data.remove_all(); + from_info_overwrite(p_info); + from_RG_overwrite(p_info.get_replaygain()); + } + void from_RG_overwrite(replaygain_info info) { + replaygain_info::t_text_buffer buffer; + if (info.format_album_gain(buffer)) set("replaygain_album_gain", buffer); + if (info.format_track_gain(buffer)) set("replaygain_track_gain", buffer); + if (info.format_album_peak(buffer)) set("replaygain_album_peak", buffer); + if (info.format_track_peak(buffer)) set("replaygain_track_peak", buffer); + } + void from_info_overwrite(const file_info & p_info) { + for(t_size metawalk = 0, metacount = p_info.meta_get_count(); metawalk < metacount; ++metawalk ) { + const t_size valuecount = p_info.meta_enum_value_count(metawalk); + if (valuecount > 0) { + t_entry & entry = add(p_info.meta_enum_name(metawalk)); + entry.remove_all(); + for(t_size valuewalk = 0; valuewalk < valuecount; ++valuewalk) { + entry.insert_last(p_info.meta_enum_value(metawalk,valuewalk)); + } + } + } + } + void reset() {m_data.remove_all();} + + void fix_itunes_compilation() { + static const char cmp[] = "itunescompilation"; + if (m_data.have_item(cmp)) { + // m_data.remove(cmp); + if (!m_data.have_item("album artist")) add("album artist", "Various Artists"); + } + } +private: + + t_entry & _add(const char * p_name) { + return m_data.find_or_add(p_name); + } + + t_content m_data; +}; diff --git a/foobar2000/helpers/metadb_handle_set.h b/foobar2000/helpers/metadb_handle_set.h new file mode 100644 index 0000000..3543d35 --- /dev/null +++ b/foobar2000/helpers/metadb_handle_set.h @@ -0,0 +1,71 @@ +#pragma once +#include +class metadb_handle; + +// Roughly same as pfc::avltree_t or std::set, but optimized for use with large amounts of items +class metadb_handle_set { +public: + metadb_handle_set() {} + template + bool add_item_check(ptr_t const & item) { return add_item_check_(&*item); } + template + bool remove_item(ptr_t const & item) { return remove_item_(&*item); } + + bool add_item_check_(metadb_handle * p) { + bool rv = m_content.insert(p).second; + if (rv) p->service_add_ref(); + return rv; + } + bool remove_item_(metadb_handle * p) { + bool rv = m_content.erase(p) != 0; + if (rv) p->service_release(); + return rv; + } + + size_t get_count() const { + return m_content.size(); + } + template + bool contains(ptr_t const & item) const { + return m_content.count(&*item) != 0; + } + template + bool have_item(ptr_t const & item) const { + return m_content.count(&*item) != 0; + } + void operator+=(metadb_handle::ptr const & item) { + add_item_check_(item.get_ptr()); + } + void operator-=(metadb_handle::ptr const & item) { + remove_item_(item.get_ptr()); + } + void operator+=(metadb_handle::ptr && item) { + auto p = item.detach(); + bool added = m_content.insert(p).second; + if (!added) p->service_release(); + } + void remove_all() { + for (auto iter = m_content.begin(); iter != m_content.end(); ++iter) { + metadb_handle * p = (*iter); + p->service_release(); + } + m_content.clear(); + } + template + void enumerate(callback_t & cb) const { + for (auto iter = m_content.begin(); iter != m_content.end(); ++iter) { + cb(*iter); + } + } + typedef std::set impl_t; + typedef impl_t::const_iterator const_iterator; + const_iterator begin() const { return m_content.begin(); } + const_iterator end() const { return m_content.end(); } +private: + + std::set m_content; + +private: + metadb_handle_set(const metadb_handle_set &) = delete; + void operator=(const metadb_handle_set&) = delete; +}; diff --git a/foobar2000/helpers/metadb_io_hintlist.h b/foobar2000/helpers/metadb_io_hintlist.h new file mode 100644 index 0000000..ff0d8d8 --- /dev/null +++ b/foobar2000/helpers/metadb_io_hintlist.h @@ -0,0 +1,33 @@ +#pragma once + +// Obsolete, use metadb_hint_list instead when possible, wrapper provided for compatibility with old code + +class metadb_io_hintlist { +public: + void hint_reader(service_ptr_t p_reader, const char * p_path,abort_callback & p_abort) { + init(); + m_hints->add_hint_reader( p_path, p_reader, p_abort ); + m_pendingCount += p_reader->get_subsong_count(); + } + void add(metadb_handle_ptr const & p_handle,const file_info & p_info,t_filestats const & p_stats,bool p_fresh) { + init(); + m_hints->add_hint( p_handle, p_info, p_stats, p_fresh ); + ++m_pendingCount; + } + void run() { + if ( m_hints.is_valid() ) { + m_hints->on_done(); + m_hints.release(); + } + m_pendingCount = 0; + } + size_t get_pending_count() const { return m_pendingCount; } +private: + void init() { + if ( m_hints.is_empty() ) { + m_hints = metadb_io_v2::get()->create_hint_list(); + } + } + metadb_hint_list::ptr m_hints; + size_t m_pendingCount = 0; +}; diff --git a/foobar2000/helpers/mp3_utils.cpp b/foobar2000/helpers/mp3_utils.cpp new file mode 100644 index 0000000..8ff9fc2 --- /dev/null +++ b/foobar2000/helpers/mp3_utils.cpp @@ -0,0 +1,276 @@ +#include "stdafx.h" + +#include "mp3_utils.h" +#include "bitreader_helper.h" + +using namespace bitreader_helper; + +static unsigned extract_header_bits(const t_uint8 p_header[4],unsigned p_base,unsigned p_bits) +{ + PFC_ASSERT(p_base+p_bits<=32); + return (unsigned) extract_bits(p_header,p_base,p_bits); +} + +namespace { + + class header_parser + { + public: + header_parser(const t_uint8 p_header[4]) : m_bitptr(0) + { + memcpy(m_header,p_header,4); + } + unsigned read(unsigned p_bits) + { + unsigned ret = extract_header_bits(m_header,m_bitptr,p_bits); + m_bitptr += p_bits; + return ret; + } + private: + t_uint8 m_header[4]; + unsigned m_bitptr; + }; +} + +typedef t_uint16 uint16; + +static const uint16 bitrate_table_l1v1[16] = { 0, 32, 64, 96,128,160,192,224,256,288,320,352,384,416,448, 0}; +static const uint16 bitrate_table_l2v1[16] = { 0, 32, 48, 56, 64, 80, 96,112,128,160,192,224,256,320,384, 0}; +static const uint16 bitrate_table_l3v1[16] = { 0, 32, 40, 48, 56, 64, 80, 96,112,128,160,192,224,256,320, 0}; +static const uint16 bitrate_table_l1v2[16] = { 0, 32, 48, 56, 64, 80, 96,112,128,144,160,176,192,224,256, 0}; +static const uint16 bitrate_table_l23v2[16] = { 0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96,112,128,144,160, 0}; +static const uint16 sample_rate_table[] = {11025,12000,8000}; + +unsigned mp3_utils::QueryMPEGFrameSize(const t_uint8 p_header[4]) +{ + TMPEGFrameInfo info; + if (!ParseMPEGFrameHeader(info,p_header)) return 0; + return info.m_bytes; +} + +bool mp3_utils::ParseMPEGFrameHeader(TMPEGFrameInfo & p_info,const t_uint8 p_header[4]) +{ + enum {MPEG_LAYER_1 = 3, MPEG_LAYER_2 = 2, MPEG_LAYER_3 = 1}; + enum {_MPEG_1 = 3, _MPEG_2 = 2, _MPEG_25 = 0}; + + header_parser parser(p_header); + if (parser.read(11) != 0x7FF) return false; + unsigned mpeg_version = parser.read(2); + unsigned layer = parser.read(2); + unsigned protection = parser.read(1); + unsigned bitrate_index = parser.read(4); + unsigned sample_rate_index = parser.read(2); + if (sample_rate_index == 3) return false;//reserved + unsigned paddingbit = parser.read(1); + int paddingdelta = 0; + parser.read(1);//private + unsigned channel_mode = parser.read(2); + unsigned channel_mode_ext = parser.read(2);//channel_mode_extension + parser.read(1);//copyright + parser.read(1);//original + parser.read(2);//emphasis + + unsigned bitrate = 0,sample_rate = 0; + + switch(layer) + { + default: + return false; + case MPEG_LAYER_3: + paddingdelta = paddingbit ? 1 : 0; + + p_info.m_layer = 3; + switch(mpeg_version) + { + case _MPEG_1: + p_info.m_duration = 1152; + bitrate = bitrate_table_l3v1[bitrate_index]; + break; + case _MPEG_2: + case _MPEG_25: + p_info.m_duration = 576; + bitrate = bitrate_table_l23v2[bitrate_index]; + break; + default: + return false; + } + + break; + case MPEG_LAYER_2: + paddingdelta = paddingbit ? 1 : 0; + p_info.m_duration = 1152; + p_info.m_layer = 2; + switch(mpeg_version) + { + case _MPEG_1: + bitrate = bitrate_table_l2v1[bitrate_index]; + break; + case _MPEG_2: + case _MPEG_25: + bitrate = bitrate_table_l23v2[bitrate_index]; + break; + default: + return false; + } + break; + case MPEG_LAYER_1: + paddingdelta = paddingbit ? 4 : 0; + p_info.m_duration = 384; + p_info.m_layer = 1; + switch(mpeg_version) + { + case _MPEG_1: + bitrate = bitrate_table_l1v1[bitrate_index]; + break; + case _MPEG_2: + case _MPEG_25: + bitrate = bitrate_table_l1v2[bitrate_index]; + break; + default: + return false; + } + break; + } + if (bitrate == 0) return false; + + sample_rate = sample_rate_table[sample_rate_index]; + if (sample_rate == 0) return false; + switch(mpeg_version) + { + case _MPEG_1: + sample_rate *= 4; + p_info.m_mpegversion = MPEG_1; + break; + case _MPEG_2: + sample_rate *= 2; + p_info.m_mpegversion = MPEG_2; + break; + case _MPEG_25: + p_info.m_mpegversion = MPEG_25; + break; + } + + switch(channel_mode) + { + case 0: + case 1: + case 2: + p_info.m_channels = 2; + break; + case 3: + p_info.m_channels = 1; + break; + } + + + p_info.m_channel_mode = channel_mode; + p_info.m_channel_mode_ext = channel_mode_ext; + + p_info.m_sample_rate = sample_rate; + p_info.m_sample_rate_idx = sample_rate_index; + + p_info.m_bitrate = bitrate; + p_info.m_bitrate_idx = bitrate_index; + + p_info.m_bytes = ( bitrate /*kbps*/ * (1000/8) /* kbps-to-bytes*/ * p_info.m_duration /*samples-per-frame*/ ) / sample_rate + paddingdelta; + + if (p_info.m_layer == 1) p_info.m_bytes &= ~3; + + p_info.m_crc = protection == 0; + + return true; +} + +unsigned mp3header::get_samples_per_frame() +{ + mp3_utils::TMPEGFrameInfo fr; + if (!decode(fr)) return 0; + return fr.m_duration; +} + +bool mp3_utils::IsSameStream(TMPEGFrameInfo const & p_frame1,TMPEGFrameInfo const & p_frame2) { + return + // FFmpeg writes VBR headers with null channel mode... + /* p_frame1.m_channel_mode == p_frame2.m_channel_mode && */ + p_frame1.m_sample_rate == p_frame2.m_sample_rate && + p_frame1.m_layer == p_frame2.m_layer && + p_frame1.m_mpegversion == p_frame2.m_mpegversion; +} + + + +bool mp3_utils::ValidateFrameCRC(const t_uint8 * frameData, t_size frameSize, TMPEGFrameInfo const & info) { + if (frameSize < info.m_bytes) return false; //FAIL, incomplete data + if (!info.m_crc) return true; //nothing to check, frame appears valid + return ExtractFrameCRC(frameData, frameSize, info) == CalculateFrameCRC(frameData, frameSize, info); +} + +static t_uint32 CRC_update(unsigned value, t_uint32 crc) +{ + enum { CRC16_POLYNOMIAL = 0x8005 }; + unsigned i; + value <<= 8; + for (i = 0; i < 8; i++) { + value <<= 1; + crc <<= 1; + if (((crc ^ value) & 0x10000)) crc ^= CRC16_POLYNOMIAL; + } + return crc; +} + + +void mp3_utils::RecalculateFrameCRC(t_uint8 * frameData, t_size frameSize, TMPEGFrameInfo const & info) { + PFC_ASSERT( frameSize >= info.m_bytes && info.m_crc ); + + const t_uint16 crc = CalculateFrameCRC(frameData, frameSize, info); + frameData[4] = (t_uint8)(crc >> 8); + frameData[5] = (t_uint8)(crc & 0xFF); +} + +static t_uint16 grabFrameCRC(const t_uint8 * frameData, t_size sideInfoLen) { + t_uint32 crc = 0xffff; + crc = CRC_update(frameData[2], crc); + crc = CRC_update(frameData[3], crc); + for (t_size i = 6; i < sideInfoLen; i++) { + crc = CRC_update(frameData[i], crc); + } + + return (t_uint32) (crc & 0xFFFF); +} + +t_uint16 mp3_utils::ExtractFrameCRC(const t_uint8 * frameData, t_size frameSize, TMPEGFrameInfo const & info) { + PFC_ASSERT( frameSize >= info.m_bytes && info.m_crc ); + + return ((t_uint16)frameData[4] << 8) | (t_uint16)frameData[5]; + +} +t_uint16 mp3_utils::CalculateFrameCRC(const t_uint8 * frameData, t_size frameSize, TMPEGFrameInfo const & info) { + PFC_ASSERT( frameSize >= info.m_bytes && info.m_crc ); + + t_size sideInfoLen = 0; + if (info.m_mpegversion == MPEG_1) + sideInfoLen = (info.m_channels == 1) ? 4 + 17 : 4 + 32; + else + sideInfoLen = (info.m_channels == 1) ? 4 + 9 : 4 + 17; + + //CRC + sideInfoLen += 2; + + PFC_ASSERT( sideInfoLen <= frameSize ); + + return grabFrameCRC(frameData, sideInfoLen); +} + + +bool mp3_utils::ValidateFrameCRC(const t_uint8 * frameData, t_size frameSize) { + if (frameSize < 4) return false; //FAIL, not a valid frame + TMPEGFrameInfo info; + if (!ParseMPEGFrameHeader(info, frameData)) return false; //FAIL, not a valid frame + return ValidateFrameCRC(frameData, frameSize, info); +} + + +bool mp3_utils::ParseMPEGFrameHeader(TMPEGFrameInfo & p_info, const void * bytes, size_t bytesAvail) { + if (bytesAvail < 4) return false; //FAIL, not a valid frame + return ParseMPEGFrameHeader(p_info, reinterpret_cast(bytes)); +} \ No newline at end of file diff --git a/foobar2000/helpers/mp3_utils.h b/foobar2000/helpers/mp3_utils.h new file mode 100644 index 0000000..d884691 --- /dev/null +++ b/foobar2000/helpers/mp3_utils.h @@ -0,0 +1,75 @@ +#pragma once + +namespace mp3_utils +{ + + enum { + MPG_MD_STEREO=0, + MPG_MD_JOINT_STEREO=1, + MPG_MD_DUAL_CHANNEL=2, + MPG_MD_MONO=3, + }; + + typedef t_uint8 byte; + + enum {MPEG_1, MPEG_2, MPEG_25}; + + struct TMPEGFrameInfo + { + unsigned m_bytes; + unsigned m_bitrate_idx; // original bitrate index value + unsigned m_bitrate; // kbps + unsigned m_sample_rate_idx; // original samples per second index value + unsigned m_sample_rate; // samples per second + unsigned m_layer; // 1, 2 or 3 + unsigned m_mpegversion; // MPEG_1, MPEG_2, MPEG_25 + unsigned m_channels; // 1 or 2 + unsigned m_duration; // samples + unsigned m_channel_mode; // MPG_MD_* + unsigned m_channel_mode_ext; + bool m_crc; + }; + + + bool ParseMPEGFrameHeader(TMPEGFrameInfo & p_info,const t_uint8 p_header[4]); + bool ParseMPEGFrameHeader(TMPEGFrameInfo & p_info, const void * bytes, size_t bytesAvail); + bool ValidateFrameCRC(const t_uint8 * frameData, t_size frameSize); + bool ValidateFrameCRC(const t_uint8 * frameData, t_size frameSize, TMPEGFrameInfo const & frameInfo); + + //! Assumes valid frame with CRC (frameInfo.m_crc set, frameInfo.m_bytes <= frameSize). + t_uint16 ExtractFrameCRC(const t_uint8 * frameData, t_size frameSize, TMPEGFrameInfo const & frameInfo); + //! Assumes valid frame with CRC (frameInfo.m_crc set, frameInfo.m_bytes <= frameSize). + t_uint16 CalculateFrameCRC(const t_uint8 * frameData, t_size frameSize, TMPEGFrameInfo const & frameInfo); + //! Assumes valid frame with CRC (frameInfo.m_crc set, frameInfo.m_bytes <= frameSize). + void RecalculateFrameCRC(t_uint8 * frameData, t_size frameSize, TMPEGFrameInfo const & frameInfo); + + unsigned QueryMPEGFrameSize(const t_uint8 p_header[4]); + bool IsSameStream(TMPEGFrameInfo const & p_frame1,TMPEGFrameInfo const & p_frame2); +}; + +class mp3header +{ + t_uint8 bytes[4]; +public: + + inline void copy(const mp3header & src) {memcpy(bytes,src.bytes,4);} + inline void copy_raw(const void * src) {memcpy(bytes,src,4);} + + inline mp3header(const mp3header & src) {copy(src);} + inline mp3header() {} + + inline const mp3header & operator=(const mp3header & src) {copy(src); return *this;} + + inline void get_bytes(void * out) {memcpy(out,bytes,4);} + inline unsigned get_frame_size() const {return mp3_utils::QueryMPEGFrameSize(bytes);} + inline bool decode(mp3_utils::TMPEGFrameInfo & p_out) {return mp3_utils::ParseMPEGFrameHeader(p_out,bytes);} + + unsigned get_samples_per_frame(); +}; + +static inline mp3header mp3header_from_buffer(const void * p_buffer) +{ + mp3header temp; + temp.copy_raw(p_buffer); + return temp; +} \ No newline at end of file diff --git a/foobar2000/helpers/packet_decoder_aac_common.cpp b/foobar2000/helpers/packet_decoder_aac_common.cpp new file mode 100644 index 0000000..4b987fb --- /dev/null +++ b/foobar2000/helpers/packet_decoder_aac_common.cpp @@ -0,0 +1,247 @@ +#include "stdafx.h" + +#include "packet_decoder_aac_common.h" + +#include "../SDK/filesystem_helper.h" +#include "bitreader_helper.h" + +size_t packet_decoder_aac_common::skipADTSHeader( const uint8_t * data,size_t size ) { + if ( size < 7 ) throw exception_io_data(); + PFC_ASSERT( bitreader_helper::extract_int(data, 0, 12) == 0xFFF); + if (bitreader_helper::extract_bit(data, 12+1+2)) { + return 7; // ABSENT flag + } + if (size < 9) throw exception_io_data(); + return 9; +} + +pfc::array_t packet_decoder_aac_common::parseDecoderSetup( const GUID & p_owner,t_size p_param1,const void * p_param2,t_size p_param2size) { + + if ( p_owner == owner_ADTS ) { + pfc::array_t ret; + ret.resize( 2 ); + ret[0] = 0; ret[1] = 0; + // ret: + // 5 bits AOT + // 4 bits freqindex + // 4 bits channelconfig + + // source: + // 12 bits 0xFFF + // 4 bits disregard + // 2 bits AOT-1 @ 16 + // 4 bits freqindex @ 18 + // 1 bit disregard + // 3 bits channelconfig @ 23 + // 26 bits total, 4 bytes minimum + + + if ( p_param2size < 4 ) throw exception_io_data(); + const uint8_t * source = (const uint8_t*) p_param2; + if ( bitreader_helper::extract_int(source, 0, 12) != 0xFFF ) throw exception_io_data(); + size_t aot = bitreader_helper::extract_int(source, 16, 2) + 1; + if ( aot >= 31 ) throw exception_io_data(); + size_t freqindex = bitreader_helper::extract_int(source, 18, 4); + if ( freqindex > 12 ) throw exception_io_data(); + size_t channelconfig = bitreader_helper::extract_bits( source, 23, 3); + + bitreader_helper::write_int(ret.get_ptr(), 0, 5, aot); + bitreader_helper::write_int(ret.get_ptr(), 5, 4, freqindex); + bitreader_helper::write_int(ret.get_ptr(), 9, 4, channelconfig); + return ret; + } else if ( p_owner == owner_ADIF ) { + // bah + } else if ( p_owner == owner_MP4 ) + { + if ( p_param1 == 0x40 || p_param1 == 0x66 || p_param1 == 0x67 || p_param1 == 0x68 ) { + pfc::array_t ret; + ret.set_data_fromptr( (const uint8_t*) p_param2, p_param2size); + return ret; + } + } + else if ( p_owner == owner_matroska ) + { + const matroska_setup * setup = ( const matroska_setup * ) p_param2; + if ( p_param2size == sizeof(*setup) ) + { + if ( !strcmp(setup->codec_id, "A_AAC") || !strncmp(setup->codec_id, "A_AAC/", 6) ) { + pfc::array_t ret; + ret.set_data_fromptr( (const uint8_t*) setup->codec_private, setup->codec_private_size ); + return ret; + } + } + } + throw exception_io_data(); +} + +#if 0 +bool packet_decoder_aac_common::parseDecoderSetup(const GUID &p_owner, t_size p_param1, const void *p_param2, t_size p_param2size, const void *&outCodecPrivate, size_t &outCodecPrivateSize) { + outCodecPrivate = NULL; + outCodecPrivateSize = 0; + + if ( p_owner == owner_ADTS ) { return true; } + else if ( p_owner == owner_ADIF ) { return true; } + else if ( p_owner == owner_MP4 ) + { + if ( p_param1 == 0x40 || p_param1 == 0x66 || p_param1 == 0x67 || p_param1 == 0x68 ) { + outCodecPrivate = p_param2; outCodecPrivateSize = p_param2size; + return true; + } + } + else if ( p_owner == owner_matroska ) + { + const matroska_setup * setup = ( const matroska_setup * ) p_param2; + if ( p_param2size == sizeof(*setup) ) + { + if ( !strcmp(setup->codec_id, "A_AAC") || !strncmp(setup->codec_id, "A_AAC/", 6) ) { + outCodecPrivate = (const uint8_t *) setup->codec_private; + outCodecPrivateSize = setup->codec_private_size; + return true; + } + } + } + return false; + +} +#endif + +bool packet_decoder_aac_common::testDecoderSetup( const GUID & p_owner, t_size p_param1, const void * p_param2, t_size p_param2size ) { + if ( p_owner == owner_ADTS ) { return true; } + else if ( p_owner == owner_ADIF ) { return true; } + else if ( p_owner == owner_MP4 ) + { + if ( p_param1 == 0x40 || p_param1 == 0x66 || p_param1 == 0x67 || p_param1 == 0x68 ) { + return true; + } + } + else if ( p_owner == owner_matroska ) + { + const matroska_setup * setup = ( const matroska_setup * ) p_param2; + if ( p_param2size == sizeof(*setup) ) + { + if ( !strcmp(setup->codec_id, "A_AAC") || !strncmp(setup->codec_id, "A_AAC/", 6) ) { + return true; + } + } + } + return false; +} + + +namespace { + class esds_maker : public stream_writer_buffer_simple { + public: + void write_esds_obj( uint8_t code, const void * data, size_t size, abort_callback & aborter ) { + if ( size >= ( 1 << 28 ) ) throw pfc::exception_overflow(); + write_byte(code, aborter); + for ( int i = 3; i >= 0; -- i ) { + uint8_t c = (uint8_t)( 0x7F & ( size >> 7 * i ) ); + if ( i > 0 ) c |= 0x80; + write_byte(c, aborter); + } + write( data, size, aborter ); + } + void write_esds_obj( uint8_t code, esds_maker const & other, abort_callback & aborter ) { + write_esds_obj( code, other.m_buffer.get_ptr(), other.m_buffer.get_size(), aborter ); + } + void write_byte( uint8_t byte , abort_callback & aborter ) { + write( &byte, 1, aborter ); + } + }; +} + +void packet_decoder_aac_common::make_ESDS( pfc::array_t & outESDS, const void * inCodecPrivate, size_t inCodecPrivateSize ) { + if (inCodecPrivateSize > 1024*1024) throw exception_io_data(); // sanity + auto & p_abort = fb2k::noAbort; + + esds_maker esds4; + + const uint8_t crap[] = {0x40, 0x15, 0x00, 0x00, 0x00, 0x00, 0x05, 0x34, 0x08, 0x00, 0x02, 0x3D, 0x55}; + esds4.write( crap, sizeof(crap), p_abort ); + + { + esds_maker esds5; + esds5.write( inCodecPrivate, inCodecPrivateSize, p_abort ); + esds4.write_esds_obj(5, esds5, p_abort); + } + + + esds_maker esds3; + + esds3.write_byte( 0, p_abort ); + esds3.write_byte( 1, p_abort ); + esds3.write_byte( 0, p_abort ); + esds3.write_esds_obj(4, esds4, p_abort); + + // esds6 after esds4, but doesn't seem that important + + esds_maker final; + final.write_esds_obj(3, esds3, p_abort); + outESDS.set_data_fromptr( final.m_buffer.get_ptr(), final.m_buffer.get_size() ); + + /* + static const uint8_t esdsTemplate[] = { + 0x03, 0x80, 0x80, 0x80, 0x25, 0x00, 0x01, 0x00, 0x04, 0x80, 0x80, 0x80, 0x17, 0x40, 0x15, 0x00, + 0x00, 0x00, 0x00, 0x05, 0x34, 0x08, 0x00, 0x02, 0x3D, 0x55, 0x05, 0x80, 0x80, 0x80, 0x05, 0x12, + 0x30, 0x56, 0xE5, 0x00, 0x06, 0x80, 0x80, 0x80, 0x01, 0x02 + }; + */ + + // ESDS: 03 80 80 80 25 00 01 00 04 80 80 80 17 40 15 00 00 00 00 05 34 08 00 02 3D 55 05 80 80 80 05 12 30 56 E5 00 06 80 80 80 01 02 + // For: 12 30 56 E5 00 + +} + +const uint32_t aac_sample_rates[] = { + 96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350 +}; + +static unsigned readSamplingFreq(bitreader_helper::bitreader_limited& r) { + unsigned samplingRateIndex = (unsigned)r.read(4); + if (samplingRateIndex == 15) { + return (unsigned)r.read(24); + } else { + if (samplingRateIndex >= PFC_TABSIZE(aac_sample_rates)) throw exception_io_data(); + return aac_sample_rates[samplingRateIndex]; + } +} + +packet_decoder_aac_common::audioSpecificConfig_t packet_decoder_aac_common::parseASC(const void * p_, size_t s) { + // Source: https://wiki.multimedia.cx/index.php?title=MPEG-4_Audio + bitreader_helper::bitreader_limited r((const uint8_t*)p_, 0, s * 8); + + audioSpecificConfig_t cfg = {}; + cfg.m_objectType = (unsigned) r.read(5); + if (cfg.m_objectType == 31) { + cfg.m_objectType = 32 + (unsigned) r.read(6); + } + + cfg.m_sampleRate = readSamplingFreq(r); + + cfg.m_channels = (unsigned) r.read( 4 ); + + if (cfg.m_objectType == 5 || cfg.m_objectType == 29) { + cfg.m_explicitSBR = true; + cfg.m_explicitPS = (cfg.m_objectType == 29); + cfg.m_sbrRate = readSamplingFreq(r); + cfg.m_objectType = (unsigned)r.read(5); + } + + switch (cfg.m_objectType) { + case 1: case 2: case 3: case 4: case 17: case 23: + cfg.m_shortWindow = (r.read(1) != 0); + break; + } + + return cfg; +} + +unsigned packet_decoder_aac_common::get_ASC_object_type(const void * p_, size_t s) { + // Source: https://wiki.multimedia.cx/index.php?title=MPEG-4_Audio + bitreader_helper::bitreader_limited r((const uint8_t*)p_, 0, s * 8); + unsigned objectType = (unsigned) r.read(5); + if (objectType == 31) { + objectType = 32 + (unsigned) r.read(6); + } + return objectType; +} diff --git a/foobar2000/helpers/packet_decoder_aac_common.h b/foobar2000/helpers/packet_decoder_aac_common.h new file mode 100644 index 0000000..e123902 --- /dev/null +++ b/foobar2000/helpers/packet_decoder_aac_common.h @@ -0,0 +1,37 @@ +#pragma once +#include "../SDK/packet_decoder.h" + +/* +Helper code with common AAC packet_decoder functionality. Primarily meant for foo_input_std-internal use. +*/ + +class packet_decoder_aac_common : public packet_decoder { +public: + static pfc::array_t parseDecoderSetup( const GUID & p_owner,t_size p_param1,const void * p_param2,t_size p_param2size); + static bool testDecoderSetup( const GUID & p_owner, t_size p_param1, const void * p_param2, t_size p_param2size ); + static size_t skipADTSHeader( const uint8_t * data,size_t size ); + + static unsigned get_max_frame_dependency_() + { + return 2; + } + static double get_max_frame_dependency_time_() + { + return 1024.0 / 8000.0; + } + + static void make_ESDS( pfc::array_t & outESDS, const void * inCodecPrivate, size_t inCodecPrivateSize ); + + struct audioSpecificConfig_t { + unsigned m_objectType; + unsigned m_sampleRate; + unsigned m_channels; + unsigned m_sbrRate; + bool m_shortWindow; + bool m_explicitSBR, m_explicitPS; + }; + + static audioSpecificConfig_t parseASC(const void *, size_t); + + static unsigned get_ASC_object_type(const void *, size_t); +}; diff --git a/foobar2000/helpers/packet_decoder_mp3_common.cpp b/foobar2000/helpers/packet_decoder_mp3_common.cpp new file mode 100644 index 0000000..1643695 --- /dev/null +++ b/foobar2000/helpers/packet_decoder_mp3_common.cpp @@ -0,0 +1,44 @@ +#include "stdafx.h" +#include "packet_decoder_mp3_common.h" +#include "mp3_utils.h" + +unsigned packet_decoder_mp3_common::parseDecoderSetup( const GUID & p_owner,t_size p_param1,const void * p_param2,t_size p_param2size ) +{ + if (p_owner == owner_MP3) return 3; + else if (p_owner == owner_MP2) return 2; + else if (p_owner == owner_MP1) return 1; + else if (p_owner == owner_MP4) + { + switch(p_param1) + { + case 0x6B: + return 3; + break; + case 0x69: + return 3; + break; + default: + return 0; + } + } + else if (p_owner == owner_matroska) + { + if (p_param2size==sizeof(matroska_setup)) + { + const matroska_setup * setup = (const matroska_setup*) p_param2; + if (!strcmp(setup->codec_id,"A_MPEG/L3")) return 3; + else if (!strcmp(setup->codec_id,"A_MPEG/L2")) return 2; + else if (!strcmp(setup->codec_id,"A_MPEG/L1")) return 1; + else return 0; + } + else return 0; + } + else return 0; +} + +unsigned packet_decoder_mp3_common::layer_from_frame(const void * frame, size_t size) { + using namespace mp3_utils; + TMPEGFrameInfo info; + if (!ParseMPEGFrameHeader(info, frame, size)) throw exception_io_data(); + return info.m_layer; +} \ No newline at end of file diff --git a/foobar2000/helpers/packet_decoder_mp3_common.h b/foobar2000/helpers/packet_decoder_mp3_common.h new file mode 100644 index 0000000..c708faa --- /dev/null +++ b/foobar2000/helpers/packet_decoder_mp3_common.h @@ -0,0 +1,84 @@ +#pragma once + +/* +Helper code with common MP3 packet_decoder functionality. Primarily meant for foo_input_std-internal use. +*/ + +class packet_decoder_mp3_common : public packet_decoder { +public: + static unsigned parseDecoderSetup( const GUID & p_owner,t_size p_param1,const void * p_param2,t_size p_param2size ); + + static unsigned layer_from_frame(const void *, size_t); + + static unsigned get_max_frame_dependency_() {return 10;} + static double get_max_frame_dependency_time_() {return 10.0 * 1152.0 / 32000.0;} +}; + + +template +class packet_decoder_mp3 : public packet_decoder +{ + impl_t m_decoder; + unsigned m_layer; + + void init(unsigned p_layer) { + m_layer = p_layer; + m_decoder.reset_after_seek(); + } + + bool m_MP4fixes; + +public: + packet_decoder_mp3() : m_layer(0), m_MP4fixes() + { + } + + ~packet_decoder_mp3() + { + } + + t_size set_stream_property(const GUID & p_type, t_size p_param1, const void * p_param2, t_size p_param2size) { + return m_decoder.set_stream_property(p_type, p_param1, p_param2, p_param2size); + } + + void get_info(file_info & p_info) + { + switch (m_layer) + { + case 1: p_info.info_set("codec", "MP1"); break; + case 2: p_info.info_set("codec", "MP2"); break; + case 3: p_info.info_set("codec", "MP3"); break; + default: + throw exception_io_data(); + } + p_info.info_set("encoding", "lossy"); + } + + unsigned get_max_frame_dependency() { return 10; } + double get_max_frame_dependency_time() { return 10.0 * 1152.0 / 32000.0; } + + static bool g_is_our_setup(const GUID & p_owner, t_size p_param1, const void * p_param2, t_size p_param2size) { + return packet_decoder_mp3_common::parseDecoderSetup(p_owner, p_param1, p_param2, p_param2size) != 0; + } + + void open(const GUID & p_owner, bool p_decode, t_size p_param1, const void * p_param2, t_size p_param2size, abort_callback & p_abort) + { + m_MP4fixes = (p_owner == owner_MP4); + unsigned layer = packet_decoder_mp3_common::parseDecoderSetup(p_owner, p_param1, p_param2, p_param2size); + if (layer == 0) throw exception_io_data(); + init(layer); + } + + void reset_after_seek() { + m_decoder.reset_after_seek(); + } + + void decode(const void * p_buffer, t_size p_bytes, audio_chunk & p_chunk, abort_callback & p_abort) { + m_decoder.decode(p_buffer, p_bytes, p_chunk, p_abort); + } + + bool analyze_first_frame_supported() { return m_MP4fixes; } + void analyze_first_frame(const void * p_buffer, t_size p_bytes, abort_callback & p_abort) { + m_layer = packet_decoder_mp3_common::layer_from_frame(p_buffer, p_bytes); + } +}; diff --git a/foobar2000/helpers/playlist_position_reference_tracker.h b/foobar2000/helpers/playlist_position_reference_tracker.h new file mode 100644 index 0000000..4b15aac --- /dev/null +++ b/foobar2000/helpers/playlist_position_reference_tracker.h @@ -0,0 +1,75 @@ +#pragma once + +class playlist_position_reference_tracker : public playlist_callback_impl_base { +public: + //! @param p_trackitem Specifies whether we want to track some specific item rather than just an offset in a playlist. When set to true, item index becomes invalidated when the item we're tracking is removed. + playlist_position_reference_tracker(bool p_trackitem = true) : playlist_callback_impl_base(~0), m_trackitem(p_trackitem), m_playlist(pfc_infinite), m_item(pfc_infinite) {} + + void on_items_added(t_size p_playlist,t_size p_start, const pfc::list_base_const_t & p_data,const bit_array & p_selection) { + if (p_playlist == m_playlist && m_item != pfc_infinite && p_start <= m_item) { + m_item += p_data.get_count(); + } + } + void on_items_reordered(t_size p_playlist,const t_size * p_order,t_size p_count) { + if (p_playlist == m_playlist) { + if (m_item < p_count) { + m_item = order_helper::g_find_reverse(p_order,m_item); + } else { + m_item = pfc_infinite; + } + } + } + + void on_items_removed(t_size p_playlist,const bit_array & p_mask,t_size p_old_count,t_size p_new_count) { + if (p_playlist == m_playlist) { + if (m_item < p_old_count) { + const t_size item_before = m_item; + for(t_size walk = p_mask.find_first(true,0,p_old_count); walk < p_old_count; walk = p_mask.find_next(true,walk,p_old_count)) { + if (walk < item_before) { + m_item--; + } else if (walk == item_before) { + if (m_trackitem) m_item = pfc_infinite; + break; + } else { + break; + } + } + if (m_item >= p_new_count) m_item = pfc_infinite; + } else { + m_item = pfc_infinite; + } + } + } + + //todo? could be useful in some cases + void on_items_replaced(t_size p_playlist,const bit_array & p_mask,const pfc::list_base_const_t & p_data) {} + + void on_playlist_created(t_size p_index,const char * p_name,t_size p_name_len) { + if (m_playlist != pfc_infinite && p_index <= m_playlist) m_playlist++; + } + void on_playlists_reorder(const t_size * p_order,t_size p_count) { + if (m_playlist < p_count) m_playlist = order_helper::g_find_reverse(p_order,m_playlist); + else m_playlist = pfc_infinite; + } + void on_playlists_removed(const bit_array & p_mask,t_size p_old_count,t_size p_new_count) { + if (m_playlist < p_old_count) { + const t_size playlist_before = m_playlist; + for(t_size walk = p_mask.find_first(true,0,p_old_count); walk < p_old_count; walk = p_mask.find_next(true,walk,p_old_count)) { + if (walk < playlist_before) { + m_playlist--; + } else if (walk == playlist_before) { + m_playlist = pfc_infinite; + break; + } else { + break; + } + } + } else { + m_playlist = pfc_infinite; + } + } + + t_size m_playlist, m_item; +private: + const bool m_trackitem; +}; diff --git a/foobar2000/helpers/reader_pretend_nonseekable.h b/foobar2000/helpers/reader_pretend_nonseekable.h new file mode 100644 index 0000000..f281acc --- /dev/null +++ b/foobar2000/helpers/reader_pretend_nonseekable.h @@ -0,0 +1,61 @@ +#pragma once + +class reader_pretend_nonseekable : public file { +public: + reader_pretend_nonseekable( file::ptr f, bool pretendRemote = true ) : m_file(f), m_pretendRemote(pretendRemote) {} + + t_filesize get_size(abort_callback & p_abort) { + return m_file->get_size(p_abort); + } + + + t_filesize get_position(abort_callback & p_abort) { + return m_file->get_position(p_abort); + } + + void resize(t_filesize p_size, abort_callback & p_abort) { + throw exception_io_denied(); + } + + void seek(t_filesize p_position, abort_callback & p_abort) { + throw exception_io_object_not_seekable(); + } + + void seek_ex(t_sfilesize p_position, t_seek_mode p_mode, abort_callback & p_abort) { + throw exception_io_object_not_seekable(); + } + + bool can_seek() {return false;} + + bool get_content_type(pfc::string_base & p_out) { + return m_file->get_content_type(p_out); + } + + bool is_in_memory() { return m_file->is_in_memory(); } + + void on_idle(abort_callback & p_abort) {m_file->on_idle(p_abort);} + + t_filetimestamp get_timestamp(abort_callback & p_abort) { return m_file->get_timestamp(p_abort); } + + void reopen(abort_callback & p_abort) { +#if PFC_DEBUG + auto pos = get_position(p_abort); + FB2K_console_formatter() << "pretend nonseekable reader reopen @ " << pos; + if ( pos > 0 ) { + pfc::nop(); + } +#endif + m_file->reopen(p_abort); + } + + bool is_remote() {return m_pretendRemote;} + + size_t read( void * ptr, size_t bytes, abort_callback & abort ) { return m_file->read(ptr, bytes, abort); } + void read_object(void * p_buffer, t_size p_bytes, abort_callback & p_abort) {m_file->read_object(p_buffer, p_bytes, p_abort); } + t_filesize skip(t_filesize p_bytes, abort_callback & p_abort) { return m_file->skip(p_bytes, p_abort); } + void skip_object(t_filesize p_bytes, abort_callback & p_abort) { m_file->skip_object(p_bytes, p_abort); } + void write( const void * ptr, size_t bytes, abort_callback & abort ) { throw exception_io_denied(); } +private: + const file::ptr m_file; + const bool m_pretendRemote; +}; \ No newline at end of file diff --git a/foobar2000/helpers/readers.cpp b/foobar2000/helpers/readers.cpp new file mode 100644 index 0000000..99a95ef --- /dev/null +++ b/foobar2000/helpers/readers.cpp @@ -0,0 +1,383 @@ +#include "StdAfx.h" +#include "readers.h" +#include "fullFileBuffer.h" +#include "fileReadAhead.h" + +#include + +t_size reader_membuffer_base::read(void * p_buffer, t_size p_bytes, abort_callback & p_abort) { + p_abort.check_e(); + t_size max = get_buffer_size(); + if (max < m_offset) uBugCheck(); + max -= m_offset; + t_size delta = p_bytes; + if (delta > max) delta = max; + memcpy(p_buffer, (char*)get_buffer() + m_offset, delta); + m_offset += delta; + return delta; +} + +void reader_membuffer_base::seek(t_filesize position, abort_callback & p_abort) { + p_abort.check_e(); + t_filesize max = get_buffer_size(); + if (position == filesize_invalid || position > max) throw exception_io_seek_out_of_range(); + m_offset = (t_size)position; +} + + + + +file::ptr fullFileBuffer::open(const char * path, abort_callback & abort, file::ptr hint, t_filesize sizeMax) { + //mutexScope scope(hMutex, abort); + + file::ptr f; + if (hint.is_valid()) f = hint; + else filesystem::g_open_read(f, path, abort); + + if (sizeMax != filesize_invalid) { + t_filesize fs = f->get_size(abort); + if (fs > sizeMax) return f; + } + try { + service_ptr_t r = new service_impl_t(); + r->init(f, abort); + f = r; + } + catch (std::bad_alloc) {} + return f; +} + + + + + + + + + + +#include +#include "rethrow.h" +#include +#include + +namespace { + struct dynInfoEntry_t { + file_info_impl m_info; + t_filesize m_offset; + }; + struct readAheadInstance_t { + file::ptr m_file; + size_t m_readAhead, m_wakeUpThreschold; + + pfc::array_t m_buffer; + size_t m_bufferBegin, m_bufferEnd; + pfc::event m_canRead, m_canWrite; + pfc::mutex m_guard; + ThreadUtils::CRethrow m_error; + t_filesize m_seekto; + abort_callback_impl m_abort; + bool m_remote; + bool m_atEOF = false; + + bool m_haveDynamicInfo; + std::list m_dynamicInfo; + }; + typedef std::shared_ptr readAheadInstanceRef; + static const t_filesize seek_reopen = (filesize_invalid-1); + class fileReadAhead : public file_readonly_t { + public: + readAheadInstanceRef m_instance; + ~fileReadAhead() { + if ( m_instance ) { + auto & i = *m_instance; + pfc::mutexScope guard( i.m_guard ); + i.m_abort.set(); + i.m_canWrite.set_state(true); + } + } + void initialize( file::ptr chain, size_t readAhead, abort_callback & aborter ) { + m_stats = chain->get_stats( aborter ); + if (!chain->get_content_type(m_contentType)) m_contentType = ""; + m_canSeek = chain->can_seek(); + m_position = chain->get_position( aborter ); + + + auto i = std::make_shared();; + i->m_file = chain; + i->m_remote = chain->is_remote(); + i->m_readAhead = readAhead; + i->m_wakeUpThreschold = readAhead * 3 / 4; + i->m_buffer.set_size_discard( readAhead * 2 ); + i->m_bufferBegin = 0; i->m_bufferEnd = 0; + i->m_canWrite.set_state(true); + i->m_seekto = filesize_invalid; + m_instance = i; + + { + file_dynamicinfo::ptr dyn; + if (dyn &= chain) { + m_haveStaticInfo = dyn->get_static_info(m_staticInfo); + i->m_haveDynamicInfo = dyn->is_dynamic_info_enabled(); + } + } + + fb2k::splitTask( [i] { +#ifdef PFC_SET_THREAD_DESCRIPTION + PFC_SET_THREAD_DESCRIPTION("Fb2k Read-Ahead Thread"); +#endif + worker(*i); + } ); + } + t_size read(void * p_buffer,t_size p_bytes,abort_callback & p_abort) { + auto & i = * m_instance; + size_t done = 0; + bool initial = true; + while( done < p_bytes ) { + if ( !initial ) { + // Do not invoke waiting with common case read with lots of data in the buffer + pfc::event::g_twoEventWait( i.m_canRead.get_handle(), p_abort.get_abort_event(), -1); + } + p_abort.check(); + pfc::mutexScope guard ( i.m_guard ); + size_t got = i.m_bufferEnd - i.m_bufferBegin; + if (got == 0) { + i.m_error.rethrow(); + if ( initial && ! i.m_atEOF ) { + initial = false; continue; // proceed to wait for more data + } + break; // EOF + } + + size_t delta = pfc::min_t( p_bytes - done, got ); + + const bool wakeUpBefore = got < i.m_wakeUpThreschold; + + auto bufptr = i.m_buffer.get_ptr(); + if ( p_buffer != nullptr ) memcpy( (uint8_t*) p_buffer + done, bufptr + i.m_bufferBegin, delta ); + done += delta; + i.m_bufferBegin += delta; + got -= delta; + m_position += delta; + + if (!i.m_error.didFail() && !i.m_atEOF) { + if ( got == 0 ) i.m_canRead.set_state( false ); + const bool wakeUpNow = got < i.m_wakeUpThreschold; + // Only set the event when *crossing* the boundary + // we will get a lot of wakeUpNow when nearing EOF + if ( wakeUpNow && ! wakeUpBefore ) i.m_canWrite.set_state( true ); + } + initial = false; + if ( i.m_atEOF ) break; // go no further + } + // FB2K_console_formatter() << "ReadAhead read: " << p_bytes << " => " << done; + return done; + } + t_filesize get_size(abort_callback & p_abort) { + p_abort.check(); + return m_stats.m_size; + } + t_filesize get_position(abort_callback & p_abort) { + p_abort.check(); + return m_position; + } + + void seek(t_filesize p_position,abort_callback & p_abort) { + p_abort.check(); + if (!m_canSeek) throw exception_io_object_not_seekable(); + if ( m_stats.m_size != filesize_invalid && p_position > m_stats.m_size ) throw exception_io_seek_out_of_range(); + + auto posNow = get_position(p_abort); + + if ( p_position >= posNow && p_position < posNow + m_instance->m_readAhead ) { + // FB2K_console_formatter() << "ReadAhead skip: " << posNow << " => " << p_position; + auto toSkip = p_position - posNow; + if ( toSkip > 0 ) read(nullptr, (size_t) toSkip, p_abort); + return; + } + // FB2K_console_formatter() << "ReadAhead seek: " << posNow << " => " << p_position; + + seekInternal( p_position ); + } + bool can_seek() { + return m_canSeek; + } + bool get_content_type(pfc::string_base & p_out) { + if (m_contentType.length() == 0) return false; + p_out = m_contentType; return true; + } + t_filestats get_stats( abort_callback & p_abort ) { + p_abort.check(); + return m_stats; + + } + t_filetimestamp get_timestamp(abort_callback & p_abort) { + p_abort.check(); + return m_stats.m_timestamp; + } + bool is_remote() { + return m_instance->m_remote; + } + + void reopen( abort_callback & p_abort ) { + if ( get_position( p_abort ) == 0 ) return; + seekInternal( seek_reopen ); + } + + bool get_static_info(class file_info & p_out) { + if ( ! m_haveStaticInfo ) return false; + mergeInfo(p_out, m_staticInfo); + return true; + } + bool is_dynamic_info_enabled() { + return m_instance->m_haveDynamicInfo; + + } + static void mergeInfo( file_info & out, const file_info & in ) { + out.copy_meta(in); + out.overwrite_info(in); + } + bool get_dynamic_info_v2(class file_info & out, t_filesize & outOffset) { + auto & i = * m_instance; + if ( ! i.m_haveDynamicInfo ) return false; + + insync( i.m_guard ); + auto ptr = i.m_dynamicInfo.begin(); + for ( ;; ) { + if ( ptr == i.m_dynamicInfo.end() ) break; + if ( ptr->m_offset > m_position ) break; + ++ ptr; + } + + if ( ptr == i.m_dynamicInfo.begin() ) return false; + + auto iter = ptr; --iter; + mergeInfo(out, iter->m_info); + outOffset = iter->m_offset; + i.m_dynamicInfo.erase( i.m_dynamicInfo.begin(), ptr ); + + return true; + } + private: + void seekInternal( t_filesize p_position ) { + auto & i = * m_instance; + insync( i.m_guard ); + i.m_error.rethrow(); + i.m_bufferBegin = i.m_bufferEnd = 0; + i.m_canWrite.set_state(true); + i.m_seekto = p_position; + i.m_atEOF = false; + i.m_canRead.set_state(false); + + m_position = ( p_position == seek_reopen ) ? 0 : p_position; + } + static void worker( readAheadInstance_t & i ) { + ThreadUtils::CRethrow err; + err.exec( [&i] { + bool atEOF = false; + uint8_t* bufptr = i.m_buffer.get_ptr(); + const size_t readAtOnceLimit = i.m_remote ? 256 : 4*1024; + for ( ;; ) { + i.m_canWrite.wait_for(-1); + size_t readHowMuch = 0, readOffset = 0; + { + pfc::mutexScope guard(i.m_guard); + i.m_abort.check(); + if ( i.m_seekto != filesize_invalid ) { + if ( i.m_seekto == seek_reopen ) { + i.m_file->reopen( i.m_abort ); + } else { + i.m_file->seek( i.m_seekto, i.m_abort ); + } + + i.m_seekto = filesize_invalid; + atEOF = false; + } + size_t got = i.m_bufferEnd - i.m_bufferBegin; + + if ( i.m_bufferBegin >= i.m_readAhead ) { + memmove( bufptr, bufptr + i.m_bufferBegin, got ); + i.m_bufferBegin = 0; + i.m_bufferEnd = got; + } + + if ( got < i.m_readAhead ) { + readHowMuch = i.m_readAhead - got; + readOffset = i.m_bufferEnd; + } + } + + if ( readHowMuch > readAtOnceLimit ) { + readHowMuch = readAtOnceLimit; + } + + bool dynInfoGot = false; + dynInfoEntry_t dynInfo; + + if ( readHowMuch > 0 ) { + readHowMuch = i.m_file->read( bufptr + readOffset, readHowMuch, i.m_abort ); + + if ( readHowMuch == 0 ) atEOF = true; + + if ( i.m_haveDynamicInfo ) { + file_dynamicinfo::ptr dyn; + if ( dyn &= i.m_file ) { + file_dynamicinfo_v2::ptr dyn2; + if ( dyn2 &= dyn ) { + dynInfoGot = dyn2->get_dynamic_info_v2(dynInfo.m_info, dynInfo.m_offset); + } else { + dynInfoGot = dyn->get_dynamic_info( dynInfo.m_info ); + if (dynInfoGot) { + dynInfo.m_offset = dyn->get_position( i.m_abort ); + } + } + } + } + } + + { + pfc::mutexScope guard( i.m_guard ); + i.m_abort.check(); + if ( i.m_seekto != filesize_invalid ) { + // Seek request happened while we were reading - discard and continue + continue; + } + i.m_atEOF = atEOF; + i.m_canRead.set_state( true ); + i.m_bufferEnd += readHowMuch; + size_t got = i.m_bufferEnd - i.m_bufferBegin; + if ( atEOF || got >= i.m_readAhead ) i.m_canWrite.set_state(false); + + if ( dynInfoGot ) { + i.m_dynamicInfo.push_back( std::move(dynInfo) ); + } + + } + } + } ); + + if ( err.didFail( ) ) { + pfc::mutexScope guard( i.m_guard ); + i.m_error = err; + i.m_canRead.set_state(true); + } + } + + bool m_canSeek; + t_filestats m_stats; + pfc::string8 m_contentType; + t_filesize m_position; + + + bool m_haveStaticInfo; + file_info_impl m_staticInfo; + }; + +} + + +file::ptr fileCreateReadAhead(file::ptr chain, size_t readAheadBytes, abort_callback & aborter ) { + auto obj = fb2k::service_new(); + obj->initialize( chain, readAheadBytes, aborter ); + return obj; +} diff --git a/foobar2000/helpers/readers.h b/foobar2000/helpers/readers.h new file mode 100644 index 0000000..dc84f3b --- /dev/null +++ b/foobar2000/helpers/readers.h @@ -0,0 +1,314 @@ +#pragma once + +class NOVTABLE reader_membuffer_base : public file_readonly { +public: + reader_membuffer_base() : m_offset(0) {} + + t_size read(void * p_buffer, t_size p_bytes, abort_callback & p_abort); + + void write(const void * p_buffer, t_size p_bytes, abort_callback & p_abort) { throw exception_io_denied(); } + + t_filesize get_size(abort_callback & p_abort) { return get_buffer_size(); } + t_filesize get_position(abort_callback & p_abort) { return m_offset; } + void seek(t_filesize position, abort_callback & p_abort); + void reopen(abort_callback & p_abort) { seek(0, p_abort); } + + bool can_seek() { return true; } + bool is_in_memory() { return true; } + +protected: + virtual const void * get_buffer() = 0; + virtual t_size get_buffer_size() = 0; + virtual t_filetimestamp get_timestamp(abort_callback & p_abort) = 0; + virtual bool get_content_type(pfc::string_base &) { return false; } + inline void seek_internal(t_size p_offset) { if (p_offset > get_buffer_size()) throw exception_io_seek_out_of_range(); m_offset = p_offset; } +private: + t_size m_offset; +}; + +class reader_membuffer_simple : public reader_membuffer_base { +public: + reader_membuffer_simple(const void * ptr, t_size size, t_filetimestamp ts = filetimestamp_invalid, bool is_remote = false) : m_isRemote(is_remote), m_ts(ts) { + m_data.set_size_discard(size); + memcpy(m_data.get_ptr(), ptr, size); + } + const void * get_buffer() { return m_data.get_ptr(); } + t_size get_buffer_size() { return m_data.get_size(); } + t_filetimestamp get_timestamp(abort_callback & p_abort) { return m_ts; } + bool is_remote() { return m_isRemote; } +private: + pfc::array_staticsize_t m_data; + t_filetimestamp m_ts; + bool m_isRemote; +}; + +class reader_membuffer_mirror : public reader_membuffer_base +{ +public: + t_filetimestamp get_timestamp(abort_callback & p_abort) { return m_timestamp; } + bool is_remote() { return m_remote; } + + //! Returns false when the object could not be mirrored (too big) or did not need mirroring. + static bool g_create(service_ptr_t & p_out, const service_ptr_t & p_src, abort_callback & p_abort) { + service_ptr_t ptr = new service_impl_t(); + if (!ptr->init(p_src, p_abort)) return false; + p_out = ptr.get_ptr(); + return true; + } + bool get_content_type(pfc::string_base & out) { + if (m_contentType.is_empty()) return false; + out = m_contentType; return true; + } +private: + const void * get_buffer() { return m_buffer.get_ptr(); } + t_size get_buffer_size() { return m_buffer.get_size(); } + + bool init(const service_ptr_t & p_src, abort_callback & p_abort) { + if (p_src->is_in_memory()) return false;//already buffered + if (!p_src->get_content_type(m_contentType)) m_contentType.reset(); + m_remote = p_src->is_remote(); + + t_size size = pfc::downcast_guarded(p_src->get_size(p_abort)); + + m_buffer.set_size(size); + + p_src->reopen(p_abort); + + p_src->read_object(m_buffer.get_ptr(), size, p_abort); + + m_timestamp = p_src->get_timestamp(p_abort); + + return true; + } + + + t_filetimestamp m_timestamp; + pfc::array_t m_buffer; + bool m_remote; + pfc::string8 m_contentType; + +}; + +class reader_limited : public file_readonly { + service_ptr_t r; + t_filesize begin; + t_filesize end; + +public: + static file::ptr g_create(file::ptr base, t_filesize offset, t_filesize size, abort_callback & abort) { + service_ptr_t r = new service_impl_t(); + if (offset + size < offset) throw pfc::exception_overflow(); + r->init(base, offset, offset + size, abort); + return r; + } + reader_limited() { begin = 0;end = 0; } + reader_limited(const service_ptr_t & p_r, t_filesize p_begin, t_filesize p_end, abort_callback & p_abort) { + r = p_r; + begin = p_begin; + end = p_end; + reopen(p_abort); + } + + void init(const service_ptr_t & p_r, t_filesize p_begin, t_filesize p_end, abort_callback & p_abort) { + r = p_r; + begin = p_begin; + end = p_end; + reopen(p_abort); + } + + t_filetimestamp get_timestamp(abort_callback & p_abort) { return r->get_timestamp(p_abort); } + + t_size read(void *p_buffer, t_size p_bytes, abort_callback & p_abort) { + t_filesize pos; + pos = r->get_position(p_abort); + if (p_bytes > end - pos) p_bytes = (t_size)(end - pos); + return r->read(p_buffer, p_bytes, p_abort); + } + + t_filesize get_size(abort_callback & p_abort) { return end - begin; } + + t_filesize get_position(abort_callback & p_abort) { + return r->get_position(p_abort) - begin; + } + + void seek(t_filesize position, abort_callback & p_abort) { + r->seek(position + begin, p_abort); + } + bool can_seek() { return r->can_seek(); } + bool is_remote() { return r->is_remote(); } + + bool get_content_type(pfc::string_base &) { return false; } + + void reopen(abort_callback & p_abort) { + seekInternal(begin, p_abort); + } +private: + void seekInternal(t_filesize position, abort_callback & abort) { + if (r->can_seek()) { + r->seek(position, abort); + } + else { + t_filesize positionWas = r->get_position(abort); + if (positionWas == filesize_invalid || positionWas > position) { + r->reopen(abort); + try { r->skip_object(position, abort); } + catch (exception_io_data) { throw exception_io_seek_out_of_range(); } + } + else { + t_filesize skipMe = position - positionWas; + if (skipMe > 0) { + try { r->skip_object(skipMe, abort); } + catch (exception_io_data) { throw exception_io_seek_out_of_range(); } + } + } + } + } +}; + + + +// A more clever version of reader_membuffer_*. +// Behaves more nicely with large files within 32bit address space. +class reader_bigmem : public file_readonly { +public: + reader_bigmem() : m_offset() {} + t_size read(void * p_buffer, t_size p_bytes, abort_callback & p_abort) { + pfc::min_acc(p_bytes, remaining()); + m_mem.read(p_buffer, p_bytes, m_offset); + m_offset += p_bytes; + return p_bytes; + } + void read_object(void * p_buffer, t_size p_bytes, abort_callback & p_abort) { + if (p_bytes > remaining()) throw exception_io_data_truncation(); + m_mem.read(p_buffer, p_bytes, m_offset); + m_offset += p_bytes; + } + t_filesize skip(t_filesize p_bytes, abort_callback & p_abort) { + pfc::min_acc(p_bytes, (t_filesize)remaining()); + m_offset += (size_t)p_bytes; + return p_bytes; + } + void skip_object(t_filesize p_bytes, abort_callback & p_abort) { + if (p_bytes > remaining()) throw exception_io_data_truncation(); + m_offset += (size_t)p_bytes; + } + + t_filesize get_size(abort_callback & p_abort) { p_abort.check(); return m_mem.size(); } + t_filesize get_position(abort_callback & p_abort) { p_abort.check(); return m_offset; } + void seek(t_filesize p_position, abort_callback & p_abort) { + if (p_position > m_mem.size()) throw exception_io_seek_out_of_range(); + m_offset = (size_t)p_position; + } + bool can_seek() { return true; } + bool is_in_memory() { return true; } + void reopen(abort_callback & p_abort) { seek(0, p_abort); } + + // To be overridden by individual derived classes + bool get_content_type(pfc::string_base & p_out) { return false; } + t_filetimestamp get_timestamp(abort_callback & p_abort) { return filetimestamp_invalid; } + bool is_remote() { return false; } +protected: + void resize(size_t newSize) { + m_offset = 0; + m_mem.resize(newSize); + } + size_t remaining() const { return m_mem.size() - m_offset; } + pfc::bigmem m_mem; + size_t m_offset; +}; + +class reader_bigmem_mirror : public reader_bigmem { +public: + reader_bigmem_mirror() {} + + void init(file::ptr source, abort_callback & abort) { + source->reopen(abort); + t_filesize fs = source->get_size(abort); + if (fs > 1024 * 1024 * 1024) { // reject > 1GB + throw std::bad_alloc(); + } + size_t s = (size_t)fs; + resize(s); + for (size_t walk = 0; walk < m_mem._sliceCount(); ++walk) { + source->read(m_mem._slicePtr(walk), m_mem._sliceSize(walk), abort); + } + + if (!source->get_content_type(m_contentType)) m_contentType.reset(); + m_isRemote = source->is_remote(); + m_ts = source->get_timestamp(abort); + } + + bool get_content_type(pfc::string_base & p_out) { + if (m_contentType.is_empty()) return false; + p_out = m_contentType; return true; + } + t_filetimestamp get_timestamp(abort_callback & p_abort) { return m_ts; } + bool is_remote() { return m_isRemote; } +private: + t_filetimestamp m_ts; + pfc::string8 m_contentType; + bool m_isRemote; +}; + +class file_chain : public file { +public: + t_size read(void * p_buffer, t_size p_bytes, abort_callback & p_abort) { + return m_file->read(p_buffer, p_bytes, p_abort); + } + void read_object(void * p_buffer, t_size p_bytes, abort_callback & p_abort) { + m_file->read_object(p_buffer, p_bytes, p_abort); + } + t_filesize skip(t_filesize p_bytes, abort_callback & p_abort) { + return m_file->skip(p_bytes, p_abort); + } + void skip_object(t_filesize p_bytes, abort_callback & p_abort) { + m_file->skip_object(p_bytes, p_abort); + } + void write(const void * p_buffer, t_size p_bytes, abort_callback & p_abort) { + m_file->write(p_buffer, p_bytes, p_abort); + } + + t_filesize get_size(abort_callback & p_abort) { + return m_file->get_size(p_abort); + } + + t_filesize get_position(abort_callback & p_abort) { + return m_file->get_position(p_abort); + } + + void resize(t_filesize p_size, abort_callback & p_abort) { + m_file->resize(p_size, p_abort); + } + + void seek(t_filesize p_position, abort_callback & p_abort) { + m_file->seek(p_position, p_abort); + } + + void seek_ex(t_sfilesize p_position, t_seek_mode p_mode, abort_callback & p_abort) { + m_file->seek_ex(p_position, p_mode, p_abort); + } + + bool can_seek() { return m_file->can_seek(); } + bool get_content_type(pfc::string_base & p_out) { return m_file->get_content_type(p_out); } + bool is_in_memory() { return m_file->is_in_memory(); } + void on_idle(abort_callback & p_abort) { m_file->on_idle(p_abort); } +#if FOOBAR2000_TARGET_VERSION >= 2000 + t_filestats get_stats(abort_callback & abort) { return m_file->get_stats(abort); } +#else + t_filetimestamp get_timestamp(abort_callback & p_abort) { return m_file->get_timestamp(p_abort); } +#endif + void reopen(abort_callback & p_abort) { m_file->reopen(p_abort); } + bool is_remote() { return m_file->is_remote(); } + + file_chain(file::ptr chain) : m_file(chain) {} +private: + file::ptr m_file; +}; + +class file_chain_readonly : public file_chain { +public: + void write(const void * p_buffer, t_size p_bytes, abort_callback & p_abort) { throw exception_io_denied(); } + void resize(t_filesize p_size, abort_callback & p_abort) { throw exception_io_denied(); } + file_chain_readonly(file::ptr chain) : file_chain(chain) {} + static file::ptr create(file::ptr chain) { return new service_impl_t< file_chain_readonly >(chain); } +}; diff --git a/foobar2000/helpers/rethrow.h b/foobar2000/helpers/rethrow.h new file mode 100644 index 0000000..e0eba2d --- /dev/null +++ b/foobar2000/helpers/rethrow.h @@ -0,0 +1,15 @@ +#pragma once + +#include + +namespace ThreadUtils { + class CRethrow { + private: + std::function m_rethrow; + public: + bool exec( std::function f ) throw(); + void rethrow() const; + bool didFail() const { return !! m_rethrow; } + void clear() { m_rethrow = nullptr; } + }; +} diff --git a/foobar2000/helpers/seekabilizer.cpp b/foobar2000/helpers/seekabilizer.cpp new file mode 100644 index 0000000..37ad959 --- /dev/null +++ b/foobar2000/helpers/seekabilizer.cpp @@ -0,0 +1,221 @@ +#include "stdafx.h" + +#include "seekabilizer.h" + +enum {backread_on_seek = 1024}; + +void seekabilizer_backbuffer::initialize(t_size p_size) +{ + m_depth = m_cursor = 0; + + m_buffer.set_size(p_size); +} + +void seekabilizer_backbuffer::write(const void * p_buffer,t_size p_bytes) +{ + if (p_bytes >= m_buffer.get_size()) + { + memcpy(m_buffer.get_ptr(),(const t_uint8*)p_buffer + p_bytes - m_buffer.get_size(),m_buffer.get_size()); + m_cursor = 0; + m_depth = m_buffer.get_size(); + } + else + { + const t_uint8* sourceptr = (const t_uint8*) p_buffer; + t_size remaining = p_bytes; + while(remaining > 0) + { + t_size delta = m_buffer.get_size() - m_cursor; + if (delta > remaining) delta = remaining; + + memcpy(m_buffer.get_ptr() + m_cursor,sourceptr,delta); + + sourceptr += delta; + remaining -= delta; + m_cursor = (m_cursor + delta) % m_buffer.get_size(); + + m_depth = pfc::min_t(m_buffer.get_size(),m_depth + delta); + + } + } +} + +void seekabilizer_backbuffer::read(t_size p_backlogdepth,void * p_buffer,t_size p_bytes) const +{ + PFC_ASSERT(p_backlogdepth <= m_depth); + PFC_ASSERT(p_backlogdepth >= p_bytes); + + + t_uint8* targetptr = (t_uint8*) p_buffer; + t_size remaining = p_bytes; + t_size cursor = (m_cursor + m_buffer.get_size() - p_backlogdepth) % m_buffer.get_size(); + + while(remaining > 0) + { + t_size delta = m_buffer.get_size() - cursor; + if (delta > remaining) delta = remaining; + + memcpy(targetptr,m_buffer.get_ptr() + cursor,delta); + + targetptr += delta; + remaining -= delta; + cursor = (cursor + delta) % m_buffer.get_size(); + } +} + +t_size seekabilizer_backbuffer::get_depth() const +{ + return m_depth; +} + +t_size seekabilizer_backbuffer::get_max_depth() const +{ + return m_buffer.get_size(); +} + +void seekabilizer_backbuffer::reset() +{ + m_depth = m_cursor = 0; +} + + +void seekabilizer::initialize(service_ptr_t p_base,t_size p_buffer_size,abort_callback & p_abort) { + m_buffer.initialize(p_buffer_size); + m_file = p_base; + m_position = m_position_base = 0; + m_size = m_file->get_size(p_abort); +} + +void seekabilizer::g_seekabilize(service_ptr_t & p_reader,t_size p_buffer_size,abort_callback & p_abort) { + if (p_reader.is_valid() && p_reader->is_remote() && p_buffer_size > 0) { + service_ptr_t instance = new service_impl_t(); + instance->initialize(p_reader,p_buffer_size,p_abort); + p_reader = instance.get_ptr(); + } +} + +t_size seekabilizer::read(void * p_buffer,t_size p_bytes,abort_callback & p_abort) { + p_abort.check_e(); + + if (m_position > m_position_base + pfc::max_t(m_buffer.get_max_depth(),backread_on_seek) && m_file->can_seek()) { + m_buffer.reset(); + t_filesize target = m_position; + if (target < backread_on_seek) target = 0; + else target -= backread_on_seek; + m_file->seek(target,p_abort); + m_position_base = target; + } + + //seek ahead + while(m_position > m_position_base) { + enum {tempsize = 1024}; + t_uint8 temp[tempsize]; + t_size delta = (t_size) pfc::min_t(tempsize,m_position - m_position_base); + t_size bytes_read = 0; + bytes_read = m_file->read(temp,delta,p_abort); + m_buffer.write(temp,bytes_read); + m_position_base += bytes_read; + + if (bytes_read < delta) { + return 0; + } + } + + t_size done = 0; + t_uint8 * targetptr = (t_uint8*) p_buffer; + + //try to read backbuffer + if (m_position < m_position_base) { + if (m_position_base - m_position > (t_filesize)m_buffer.get_depth()) throw exception_io_seek_out_of_range(); + t_size backread_depth = (t_size) (m_position_base - m_position); + t_size delta = pfc::min_t(backread_depth,p_bytes-done); + m_buffer.read(backread_depth,targetptr,delta); + done += delta; + m_position += delta; + } + + //regular read + if (done < p_bytes) + { + t_size bytes_read; + bytes_read = m_file->read(targetptr+done,p_bytes-done,p_abort); + + m_buffer.write(targetptr+done,bytes_read); + + done += bytes_read; + m_position += bytes_read; + m_position_base += bytes_read; + } + + return done; +} + +t_filesize seekabilizer::get_size(abort_callback & p_abort) { + p_abort.check_e(); + return m_size; +} + +t_filesize seekabilizer::get_position(abort_callback & p_abort) { + p_abort.check_e(); + return m_position; +} + +void seekabilizer::seek(t_filesize p_position,abort_callback & p_abort) { + PFC_ASSERT(m_position_base >= m_buffer.get_depth()); + p_abort.check_e(); + + if (m_size != filesize_invalid && p_position > m_size) throw exception_io_seek_out_of_range(); + + t_filesize lowest = m_position_base - m_buffer.get_depth(); + + if (p_position < lowest) { + if (m_file->can_seek()) { + m_buffer.reset(); + t_filesize target = p_position; + t_size delta = m_buffer.get_max_depth(); + if (delta > backread_on_seek) delta = backread_on_seek; + if (target > delta) target -= delta; + else target = 0; + m_file->seek(target,p_abort); + m_position_base = target; + } + else { + m_buffer.reset(); + m_file->reopen(p_abort); + m_position_base = 0; + } + } + + m_position = p_position; +} + +bool seekabilizer::can_seek() +{ + return true; +} + +bool seekabilizer::get_content_type(pfc::string_base & p_out) {return m_file->get_content_type(p_out);} + +bool seekabilizer::is_in_memory() {return false;} + +void seekabilizer::on_idle(abort_callback & p_abort) {return m_file->on_idle(p_abort);} + +t_filetimestamp seekabilizer::get_timestamp(abort_callback & p_abort) { + p_abort.check_e(); + return m_file->get_timestamp(p_abort); +} + +void seekabilizer::reopen(abort_callback & p_abort) { + if (m_position_base - m_buffer.get_depth() == 0) { + seek(0,p_abort); + } else { + m_position = m_position_base = 0; + m_buffer.reset(); + m_file->reopen(p_abort); + } +} + +bool seekabilizer::is_remote() +{ + return m_file->is_remote(); +} diff --git a/foobar2000/helpers/seekabilizer.h b/foobar2000/helpers/seekabilizer.h new file mode 100644 index 0000000..7da3caa --- /dev/null +++ b/foobar2000/helpers/seekabilizer.h @@ -0,0 +1,38 @@ +#pragma once + +class seekabilizer_backbuffer +{ +public: + void initialize(t_size p_size); + void write(const void * p_buffer,t_size p_bytes); + void read(t_size p_backlogdepth,void * p_buffer,t_size p_bytes) const; + t_size get_depth() const; + void reset(); + t_size get_max_depth() const; +private: + pfc::array_t m_buffer; + t_size m_depth,m_cursor; +}; + +class seekabilizer : public file_readonly { +public: + void initialize(service_ptr_t p_base,t_size p_buffer_size,abort_callback & p_abort); + + static void g_seekabilize(service_ptr_t & p_reader,t_size p_buffer_size,abort_callback & p_abort); + + t_size read(void * p_buffer,t_size p_bytes,abort_callback & p_abort); + t_filesize get_size(abort_callback & p_abort); + t_filesize get_position(abort_callback & p_abort); + void seek(t_filesize p_position,abort_callback & p_abort); + bool can_seek(); + bool get_content_type(pfc::string_base & p_out); + bool is_in_memory(); + void on_idle(abort_callback & p_abort); + t_filetimestamp get_timestamp(abort_callback & p_abort); + void reopen(abort_callback & p_abort); + bool is_remote(); +private: + service_ptr_t m_file; + seekabilizer_backbuffer m_buffer; + t_filesize m_size,m_position,m_position_base; +}; \ No newline at end of file diff --git a/foobar2000/helpers/stream_buffer_helper.cpp b/foobar2000/helpers/stream_buffer_helper.cpp new file mode 100644 index 0000000..5af0828 --- /dev/null +++ b/foobar2000/helpers/stream_buffer_helper.cpp @@ -0,0 +1,89 @@ +#include "stdafx.h" + +#include "stream_buffer_helper.h" + +stream_reader_buffered::stream_reader_buffered(stream_reader * p_base,t_size p_buffer) : m_base(p_base) +{ + m_buffer.set_size_in_range(pfc::min_t(1024, p_buffer), p_buffer); + m_bufferRemaining = 0; +} + +t_size stream_reader_buffered::read(void * p_buffer,t_size p_bytes,abort_callback & p_abort) { + if (p_bytes <= m_bufferRemaining) { + memcpy( p_buffer, m_bufferPtr, p_bytes ); + m_bufferRemaining -= p_bytes; + m_bufferPtr += p_bytes; + return p_bytes; + } + + p_abort.check(); + char * output = (char*) p_buffer; + t_size output_ptr = 0; + + while(output_ptr < p_bytes) { + { + t_size delta = pfc::min_t(p_bytes - output_ptr, m_bufferRemaining); + if (delta > 0) + { + memcpy(output + output_ptr, m_bufferPtr, delta); + output_ptr += delta; + m_bufferPtr += delta; + m_bufferRemaining -= delta; + } + } + + if (m_bufferRemaining == 0) + { + t_size bytes_read; + bytes_read = m_base->read(m_buffer.get_ptr(), m_buffer.get_size(), p_abort); + m_bufferPtr = m_buffer.get_ptr(); + m_bufferRemaining = bytes_read; + + if (m_bufferRemaining == 0) break; + } + + } + + return output_ptr; +} + +stream_writer_buffered::stream_writer_buffered(stream_writer * p_base,t_size p_buffer) + : m_base(p_base) +{ + m_buffer.set_size_in_range(pfc::min_t(1024, p_buffer), p_buffer); + m_buffer_ptr = 0; +} + +void stream_writer_buffered::write(const void * p_buffer,t_size p_bytes,abort_callback & p_abort) { + p_abort.check_e(); + const char * source = (const char*)p_buffer; + t_size source_remaining = p_bytes; + const t_size buffer_size = m_buffer.get_size(); + if (source_remaining >= buffer_size) + { + flush(p_abort); + m_base->write_object(source,source_remaining,p_abort); + return; + } + + if (m_buffer_ptr + source_remaining >= buffer_size) + { + t_size delta = buffer_size - m_buffer_ptr; + memcpy(m_buffer.get_ptr() + m_buffer_ptr, source,delta); + source += delta; + source_remaining -= delta; + m_buffer_ptr += delta; + flush(p_abort); + } + + memcpy(m_buffer.get_ptr() + m_buffer_ptr, source,source_remaining); + m_buffer_ptr += source_remaining; +} + + +void stream_writer_buffered::flush(abort_callback & p_abort) { + if (m_buffer_ptr > 0) { + m_base->write_object(m_buffer.get_ptr(),m_buffer_ptr,p_abort); + m_buffer_ptr = 0; + } +} \ No newline at end of file diff --git a/foobar2000/helpers/stream_buffer_helper.h b/foobar2000/helpers/stream_buffer_helper.h new file mode 100644 index 0000000..01eaa0c --- /dev/null +++ b/foobar2000/helpers/stream_buffer_helper.h @@ -0,0 +1,29 @@ +#pragma once + +class stream_reader_buffered : public stream_reader +{ +public: + stream_reader_buffered(stream_reader * p_base,t_size p_buffer); + t_size read(void * p_buffer,t_size p_bytes,abort_callback & p_abort); +private: + stream_reader * m_base; + pfc::array_t m_buffer; + const char * m_bufferPtr; + size_t m_bufferRemaining; +}; + +class stream_writer_buffered : public stream_writer +{ +public: + stream_writer_buffered(stream_writer * p_base,t_size p_buffer); + + void write(const void * p_buffer,t_size p_bytes,abort_callback & p_abort); + + void flush(abort_callback & p_abort); + +private: + stream_writer * m_base; + pfc::array_t m_buffer; + t_size m_buffer_ptr; +}; + diff --git a/foobar2000/helpers/tag_write_callback_impl.h b/foobar2000/helpers/tag_write_callback_impl.h new file mode 100644 index 0000000..a012c64 --- /dev/null +++ b/foobar2000/helpers/tag_write_callback_impl.h @@ -0,0 +1,79 @@ +#pragma once + +#ifdef PFC_WINDOWS_STORE_APP +#include +#endif + +#if defined(_WIN32) && defined(FOOBAR2000_MOBILE) +#include +#include +#endif + + +class tag_write_callback_impl : public tag_write_callback { +public: + tag_write_callback_impl(const char * p_origpath) : m_origpath(p_origpath) { + m_fs = filesystem::get(p_origpath); + } + bool open_temp_file(service_ptr_t & p_out,abort_callback & p_abort) { + pfc::dynamic_assert(m_tempfile.is_empty()); + generate_temp_location_for_file(m_temppath,m_origpath,/*pfc::string_extension(m_origpath)*/ "tmp","retagging temporary file"); + service_ptr_t l_tempfile; + try { + openTempFile(l_tempfile, p_abort); + } catch(exception_io) {return false;} + p_out = m_tempfile = l_tempfile; + return true; + } + bool got_temp_file() const {return m_tempfile.is_valid();} + + // p_owner must be the only reference to open source file, it will be closed + reopened + //WARNING: if this errors out, it may leave caller with null file pointer; take appropriate measures not to crash in such cases + void finalize(service_ptr_t & p_owner,abort_callback & p_abort) { + if (m_tempfile.is_valid()) { + m_tempfile->flushFileBuffers_(p_abort); + + if (p_owner.is_valid()) { + try { + file::g_copy_creation_time(p_owner, m_tempfile, p_abort); + } catch (exception_io) {} + } + + m_tempfile.release(); + p_owner.release(); + handleFileMove(m_temppath, m_origpath, p_abort); + input_open_file_helper(p_owner,m_origpath,input_open_info_write,p_abort); + } + } + // Alternate finalizer without owner file object, caller takes responsibility for closing the source file before calling + void finalize_no_reopen( abort_callback & p_abort ) { + if (m_tempfile.is_valid()) { + m_tempfile->flushFileBuffers_(p_abort); + m_tempfile.release(); + handleFileMove(m_temppath, m_origpath, p_abort); + } + } + void handle_failure() throw() { + if (m_tempfile.is_valid()) { + m_tempfile.release(); + try { + retryOnSharingViolation( 1, fb2k::noAbort, [&] { + m_fs->remove(m_temppath, fb2k::noAbort); + } ); + } catch(...) {} + } + } +private: + void openTempFile(file::ptr & out, abort_callback & abort) { + out = m_fs->openWriteNew(m_temppath, abort, 1 ); + } + void handleFileMove(const char * from, const char * to, abort_callback & abort) { + PFC_ASSERT(m_fs->is_our_path(from)); + PFC_ASSERT(m_fs->is_our_path(to)); + FB2K_RETRY_FILE_MOVE(m_fs->replace_file(from, to, abort), abort, 10 ); + } + pfc::string8 m_origpath; + pfc::string8 m_temppath; + service_ptr_t m_tempfile; + filesystem::ptr m_fs; +}; diff --git a/foobar2000/helpers/text_file_loader.cpp b/foobar2000/helpers/text_file_loader.cpp new file mode 100644 index 0000000..0f3e6bb --- /dev/null +++ b/foobar2000/helpers/text_file_loader.cpp @@ -0,0 +1,124 @@ +#include "StdAfx.h" + +#include "text_file_loader.h" + +#include + +static const unsigned char utf8_header[3] = {0xEF,0xBB,0xBF}; + +namespace text_file_loader +{ + void write(const service_ptr_t & p_file,abort_callback & p_abort,const char * p_string,bool is_utf8) + { + p_file->seek(0,p_abort); + p_file->set_eof(p_abort); + if (is_utf8) + { + p_file->write_object(utf8_header,sizeof(utf8_header),p_abort); + p_file->write_object(p_string,strlen(p_string),p_abort); + } + else + { +#ifdef _WIN32 + pfc::stringcvt::string_ansi_from_utf8 bah(p_string); + p_file->write_object(bah,bah.length(),p_abort); +#else + throw exception_io_data(); +#endif + } + } + void read(const service_ptr_t & p_file, abort_callback & p_abort, pfc::string_base & p_out, bool & is_utf8) { + read_v2( p_file, p_abort, p_out, is_utf8, false ); + } + void read_v2(const service_ptr_t & p_file,abort_callback & p_abort,pfc::string_base & p_out,bool & is_utf8, bool forceUTF8) { + p_out.reset(); + p_file->reopen( p_abort ); + + pfc::array_t mem; + t_filesize size64; + size64 = p_file->get_size(p_abort); + if (size64 == filesize_invalid)//typically HTTP + { + pfc::string8 ansitemp; + t_size done; + enum { delta = 1024 * 64, max = 1024 * 512 }; + + is_utf8 = forceUTF8; + char temp[3]; + done = p_file->read(temp, 3, p_abort); + if (done != 3) + { + if (done > 0) { + if ( is_utf8 ) { + p_out.set_string( temp, done ); + } else { + p_out = pfc::stringcvt::string_utf8_from_ansi(temp, done); + } + + } + return; + } + if (!memcmp(utf8_header, temp, 3)) is_utf8 = true; + else if (is_utf8) p_out.add_string(temp,3); + else ansitemp.add_string(temp, 3); + + mem.set_size(delta); + + for(;;) + { + done = p_file->read(mem.get_ptr(),delta,p_abort); + if (done > 0) + { + if (is_utf8) p_out.add_string(mem.get_ptr(),done); + else ansitemp.add_string(mem.get_ptr(),done); + } + if (done < delta) break; + } + + if (!is_utf8) + { + p_out = pfc::stringcvt::string_utf8_from_ansi(ansitemp); + } + + return; + } + else + { + if (size64>1024*1024*128) throw exception_io_data();//hard limit + t_size size = pfc::downcast_guarded(size64); + mem.set_size(size+1); + char * asdf = mem.get_ptr(); + p_file->read_object(asdf,size,p_abort); + asdf[size]=0; + if (size>=3 && !memcmp(utf8_header,asdf,3)) { + is_utf8 = true; + p_out.add_string(asdf+3); + } else if (forceUTF8) { + is_utf8 = true; + p_out = asdf; + } else { + is_utf8 = false; + p_out = pfc::stringcvt::string_utf8_from_ansi(asdf); + } + return; + } + } + + void write(const char * p_path,abort_callback & p_abort,const char * p_string,bool is_utf8) + { + service_ptr_t f; + filesystem::g_open_write_new(f,p_path,p_abort); + write(f,p_abort,p_string,is_utf8); + } + + void read(const char * p_path, abort_callback & p_abort, pfc::string_base & p_out, bool & is_utf8) { + read_v2( p_path, p_abort, p_out, is_utf8, false ); + } + void read_v2(const char * p_path,abort_callback & p_abort,pfc::string_base & p_out,bool & is_utf8, bool forceUTF8) + { + service_ptr_t f; + filesystem::g_open_read(f,p_path,p_abort); + read_v2(f,p_abort,p_out,is_utf8,forceUTF8); + } + +} \ No newline at end of file diff --git a/foobar2000/helpers/text_file_loader.h b/foobar2000/helpers/text_file_loader.h new file mode 100644 index 0000000..e6b757b --- /dev/null +++ b/foobar2000/helpers/text_file_loader.h @@ -0,0 +1,14 @@ +#pragma once + +namespace text_file_loader +{ + void write(const service_ptr_t & p_file,abort_callback & p_abort,const char * p_string,bool is_utf8); + void read(const service_ptr_t & p_file,abort_callback & p_abort,pfc::string_base & p_out,bool & is_utf8); + void read_v2(const service_ptr_t & p_file, abort_callback & p_abort, pfc::string_base & p_out, bool & is_utf8, bool forceUTF8); + + + void write(const char * p_path,abort_callback & p_abort,const char * p_string,bool is_utf8); + void read(const char * p_path,abort_callback & p_abort,pfc::string_base & p_out,bool & is_utf8); + void read_v2(const char * p_path, abort_callback & p_abort, pfc::string_base & p_out, bool & is_utf8, bool forceUTF8); + +}; \ No newline at end of file diff --git a/foobar2000/helpers/text_file_loader_v2.cpp b/foobar2000/helpers/text_file_loader_v2.cpp new file mode 100644 index 0000000..a0f2e90 --- /dev/null +++ b/foobar2000/helpers/text_file_loader_v2.cpp @@ -0,0 +1,25 @@ +#include "StdAfx.h" +#include "text_file_loader_v2.h" +#include "text_file_loader.h" + +void text_file_loader_v2::load(file::ptr f, abort_callback & abort) { + m_lines.clear(); + bool dummy; + text_file_loader::read_v2(f, abort, m_data, dummy, m_forceUTF8); + + m_lines.reserve(128); + + char * p = const_cast(m_data.get_ptr()); + bool line = false; + const size_t len = m_data.length(); + for (size_t walk = 0; walk < len; ++walk) { + char & c = p[walk]; + if (c == '\n' || c == '\r') { + c = 0; + line = false; + } else if (!line) { + m_lines.push_back(&c); + line = true; + } + } +} \ No newline at end of file diff --git a/foobar2000/helpers/text_file_loader_v2.h b/foobar2000/helpers/text_file_loader_v2.h new file mode 100644 index 0000000..a0147e6 --- /dev/null +++ b/foobar2000/helpers/text_file_loader_v2.h @@ -0,0 +1,14 @@ +#pragma once +#include +#include + +class text_file_loader_v2 { +public: + bool m_forceUTF8 = false; + + void load(file::ptr f, abort_callback & abort); + + std::vector< const char * > m_lines; + + pfc::string8 m_data; +}; \ No newline at end of file diff --git a/foobar2000/helpers/track_property_callback_impl.cpp b/foobar2000/helpers/track_property_callback_impl.cpp new file mode 100644 index 0000000..10a2609 --- /dev/null +++ b/foobar2000/helpers/track_property_callback_impl.cpp @@ -0,0 +1,124 @@ +#include "StdAfx.h" + +#include +#include + +#include "track_property_callback_impl.h" + + +void track_property_callback_impl::set_property(const char * p_group, double p_sortpriority, const char * p_name, const char * p_value) { + propertyname_container temp; + temp.m_name = p_name; + temp.m_priority = p_sortpriority; + + pfc::string8 fixEOL; + if (m_cutMultiLine && strchr(p_value, '\n') != nullptr) { + fixEOL = p_value; fixEOL.fix_eol(); p_value = fixEOL; + } + + m_entries.find_or_add(p_group).set(temp, p_value); +} + +bool track_property_callback_impl::is_group_wanted(const char * p_group) { + if (m_groupFilter) return m_groupFilter(p_group); + return true; +} + +void track_property_callback_impl::merge(track_property_callback_impl const & other) { + for (auto iterGroup = other.m_entries.first(); iterGroup.is_valid(); ++iterGroup) { + auto & in = iterGroup->m_value; + auto & out = m_entries[iterGroup->m_key]; + for (auto iterEntry = in.first(); iterEntry.is_valid(); ++iterEntry) { + out.set(iterEntry->m_key, iterEntry->m_value); + } + } +} + +static bool is_filtered_info_field(const char * p_name) { + service_ptr_t ptr; + service_enum_t e; + while (e.next(ptr)) { + if (ptr->is_our_tech_info(p_name)) return true; + } + return false; +} + +static const char strGroupOther[] = "Other"; + +static void enumOtherHere(track_property_callback_impl & callback, metadb_info_container::ptr info_) { + const file_info * infoptr = &info_->info(); + for (t_size n = 0, m = infoptr->info_get_count(); n < m; n++) { + const char * name = infoptr->info_enum_name(n); + if (!is_filtered_info_field(name)) { + pfc::string_formatter temp; + temp << "<"; + uAddStringUpper(temp, name); + temp << ">"; + callback.set_property("Other", 0, temp, infoptr->info_enum_value(n)); + } + } +} + +static void enumOther( track_property_callback_impl & callback, metadb_handle_list_cref items, track_property_provider_v3_info_source * infoSource ) { + if (items.get_count() == 1 ) { + enumOtherHere(callback, infoSource->get_info(0) ); + } +} + +void enumerateTrackProperties( track_property_callback_impl & callback, std::function< metadb_handle_list_cref () > itemsSource, std::function infoSource, std::function abortSource) { + + + if ( core_api::is_main_thread() ) { + // should not get here like this + // but that does make our job easier + auto & items = itemsSource(); + auto info = infoSource(); + track_property_provider::ptr ptr; + service_enum_t e; + while (e.next(ptr)) { + ptr->enumerate_properties_helper(items, info, callback, abortSource() ); + } + if ( callback.is_group_wanted( strGroupOther ) ) { + enumOther(callback, items, info ); + } + return; + } + + std::list > lstWaitFor; + std::list > lstMerge; + track_property_provider::ptr ptr; + service_enum_t e; + while (e.next(ptr)) { + auto evt = std::make_shared(); + auto cb = std::make_shared< track_property_callback_impl >(callback); // clone watched group info + auto work = [ptr, itemsSource, evt, cb, infoSource, abortSource] { + try { + ptr->enumerate_properties_helper(itemsSource(), infoSource(), *cb, abortSource()); + } catch (...) {} + evt->set_state(true); + }; + + track_property_provider_v4::ptr v4; + if (v4 &= ptr) { + // Supports v4 = split a worker thread, work in parallel + pfc::splitThread(work); + } else { + // No v4 = delegate to main thread. Ugly but gets the job done. + fb2k::inMainThread(work); + } + + lstWaitFor.push_back(std::move(evt)); + lstMerge.push_back(std::move(cb)); + } + + if (callback.is_group_wanted(strGroupOther)) { + enumOther(callback, itemsSource(), infoSource()); + } + + for (auto i = lstWaitFor.begin(); i != lstWaitFor.end(); ++i) { + abortSource().waitForEvent(**i, -1); + } + for (auto i = lstMerge.begin(); i != lstMerge.end(); ++i) { + callback.merge(** i); + } +} \ No newline at end of file diff --git a/foobar2000/helpers/track_property_callback_impl.h b/foobar2000/helpers/track_property_callback_impl.h new file mode 100644 index 0000000..7b2e30c --- /dev/null +++ b/foobar2000/helpers/track_property_callback_impl.h @@ -0,0 +1,51 @@ +#pragma once + +#include + +class groupname_comparator { +public: + static int compare(pfc::stringp p_name1,pfc::stringp p_name2) { + int temp = uStringCompare(p_name1,p_name2); + if (temp != 0) return temp; + return strcmp(p_name1,p_name2); + } +}; + +struct propertyname_container { + pfc::string m_name; + double m_priority; +}; + +class propertyname_comparator { +public: + static int compare(const propertyname_container & p_item1,const propertyname_container & p_item2) { + int state = pfc::compare_t(p_item1.m_priority,p_item2.m_priority); + if (state != 0) return state; + return uStringCompare(p_item1.m_name.ptr(),p_item2.m_name.ptr()); + } +}; + +typedef pfc::map_t property_group; + +typedef pfc::map_t t_property_group_list; + +class track_property_callback_impl : public track_property_callback_v2 { +public: + void set_property(const char * p_group,double p_sortpriority,const char * p_name,const char * p_value) override; + bool is_group_wanted(const char * p_group) override; + + void merge( track_property_callback_impl const & other ); + + t_property_group_list m_entries; + + bool m_cutMultiLine = false; + typedef std::function groupFilter_t; + groupFilter_t m_groupFilter; +}; + +// Helper function to walk all track property providers in an optimized multithread manner +// Various *source arguments have been std::function'd so you can reference your own data structures gracefully +// If the function is aborted, it returns immediately - while actual worker threads may not yet have completed, and may still reference *source arguments. +// You must ensure - by means of std::shared_ptr<> or such - that all of the *source arguments remain accessible even after enumerateTrackProperties() returns, until the std::functions are released. +// Legacy track property providers that do not support off main thread operation will be invoked via main_thread_callback in main thread, and the function will stall until they have returned (unless aborted). +void enumerateTrackProperties(track_property_callback_impl & callback, std::function< metadb_handle_list_cref() > itemsSource, std::function infoSource, std::function abortSource); diff --git a/foobar2000/helpers/ui_element_helpers.cpp b/foobar2000/helpers/ui_element_helpers.cpp new file mode 100644 index 0000000..f52b48b --- /dev/null +++ b/foobar2000/helpers/ui_element_helpers.cpp @@ -0,0 +1,390 @@ +#include "stdafx.h" + +#if FOOBAR2000_TARGET_VERSION >= 79 + +#include "ui_element_helpers.h" +#include +#include "atl-misc.h" + +namespace ui_element_helpers { + + ui_element_instance_ptr instantiate_dummy(HWND p_parent,ui_element_config::ptr cfg, ui_element_instance_callback_ptr p_callback) { + service_ptr_t ptr; + if (!find(ptr,pfc::guid_null)) uBugCheck(); + return ptr->instantiate(p_parent,cfg,p_callback); + } + ui_element_instance_ptr instantiate(HWND p_parent,ui_element_config::ptr cfg,ui_element_instance_callback_ptr p_callback) { + try { + service_ptr_t ptr; + if (!find(ptr,cfg->get_guid())) throw exception_io_data("UI Element Not Found"); + auto ret = ptr->instantiate(p_parent,cfg,p_callback); + if (ret.is_empty()) throw std::runtime_error("Null UI Element returned"); + return ret; + } catch(std::exception const & e) { + console::complain("UI Element instantiation failure",e); + return instantiate_dummy(p_parent,cfg,p_callback); + } + } + + ui_element_instance_ptr update(ui_element_instance_ptr p_element,HWND p_parent,ui_element_config::ptr cfg,ui_element_instance_callback_ptr p_callback) { + if (p_element.is_valid() && cfg->get_guid() == p_element->get_guid()) { + p_element->set_configuration(cfg); + return p_element; + } else { + return instantiate(p_parent,cfg,p_callback); + } + } + + bool find(service_ptr_t & p_out,const GUID & p_guid) { + return service_by_guid(p_out,p_guid); + } + + ui_element_children_enumerator_ptr enumerate_children(ui_element_config::ptr cfg) { + service_ptr_t ptr; + if (!find(ptr,cfg->get_guid())) return NULL; + try { + return ptr->enumerate_children(cfg); + } catch(exception_io_data) { + return NULL; + } + } +}; + +void ui_element_helpers::replace_with_new_element(ui_element_instance_ptr & p_item,const GUID & p_guid,HWND p_parent,ui_element_instance_callback_ptr p_callback) { + auto & l_abort = fb2k::noAbort; + ui_element_config::ptr cfg; + try { + if (p_item.is_empty()) { + service_ptr_t ptr; + if (!find(ptr,p_guid)) throw exception_io_data("UI Element Not Found"); + cfg = ptr->get_default_configuration(); + p_item = ptr->instantiate(p_parent,cfg,p_callback); + } else if (p_item->get_guid() != p_guid) { + service_ptr_t ptr; + if (!find(ptr,p_guid)) throw exception_io_data("UI Element Not Found"); + cfg = ptr->import(p_item->get_configuration()); + //p_item.release(); + if (cfg.is_empty()) cfg = ptr->get_default_configuration(); + p_item = ptr->instantiate(p_parent,cfg,p_callback); + } + } catch(std::exception const & e) { + console::complain("UI Element instantiation failure",e); + if (cfg.is_empty()) cfg = ui_element_config::g_create_empty(); + p_item = instantiate_dummy(p_parent,cfg,p_callback); + } +} + + + +namespace { + class CMyMenuSelectionReceiver : public CMenuSelectionReceiver { + public: + CMyMenuSelectionReceiver(HWND p_wnd,ui_element_helpers::ui_element_edit_tools * p_host,ui_element_instance_ptr p_client,unsigned p_client_id,unsigned p_host_base,unsigned p_client_base) : CMenuSelectionReceiver(p_wnd), m_host(p_host), m_client(p_client), m_client_id(p_client_id), m_host_base(p_host_base), m_client_base(p_client_base) {} + bool QueryHint(unsigned p_id,pfc::string_base & p_out) { + if (p_id >= m_client_base) { + return m_client->edit_mode_context_menu_get_description(p_id,m_client_base,p_out); + } else if (p_id >= m_host_base) { + return m_host->host_edit_mode_context_menu_get_description(m_client_id,p_id,m_host_base,p_out); + } else { + const char * msg = ui_element_helpers::ui_element_edit_tools::description_from_menu_command(p_id); + if (msg == NULL) return false; + p_out = msg; + return true; + } + } + private: + ui_element_helpers::ui_element_edit_tools * const m_host; + ui_element_instance_ptr const m_client; + unsigned const m_client_id,m_host_base,m_client_base; + }; +}; + +namespace HostHelperIDs { + enum {ID_LABEL, ID_REPLACE = 1, ID_ADD_NEW, ID_CUT, ID_COPY, ID_PASTE, ID_CUSTOM_BASE}; +} + +void ui_element_helpers::ui_element_edit_tools::standard_edit_context_menu(LPARAM p_point,ui_element_instance_ptr p_item,unsigned p_id,HWND p_parent) { + using namespace HostHelperIDs; + static_api_ptr_t api; + POINT pt; + bool fromkeyboard = false; + if (p_point == (LPARAM)(-1)) { + fromkeyboard = true; + if (!p_item->edit_mode_context_menu_get_focus_point(pt)) { + CRect rect; + WIN32_OP_D( CWindow(p_item->get_wnd()).GetWindowRect(&rect) ); + pt = rect.CenterPoint(); + } + } else { + pt.x = (short)LOWORD(p_point); + pt.y = (short)HIWORD(p_point); + } + + CMenu menu; + WIN32_OP( menu.CreatePopupMenu() ); + + const GUID sourceItemGuid = p_item->get_guid(); + const bool sourceItemEmpty = !!(sourceItemGuid == pfc::guid_null); + + if (sourceItemEmpty) { + WIN32_OP_D( menu.AppendMenu(MF_STRING,ID_ADD_NEW,TEXT(AddNewUIElementCommand)) ); + WIN32_OP_D( menu.SetMenuDefaultItem(ID_ADD_NEW) ); + } else { + service_ptr_t elem; + pfc::string8 name; + if (find(elem,sourceItemGuid)) { + elem->get_name(name); + } else { + name = ""; + } + WIN32_OP_D( menu.AppendMenu(MF_STRING | MF_DISABLED,ID_LABEL,pfc::stringcvt::string_os_from_utf8(name)) ); + WIN32_OP_D( menu.AppendMenu(MF_SEPARATOR,(UINT_PTR)0,TEXT("")) ); + WIN32_OP_D( menu.AppendMenu(MF_STRING,ID_REPLACE,TEXT(ReplaceUIElementCommand)) ); + } + WIN32_OP_D( menu.AppendMenu(MF_SEPARATOR,(UINT_PTR)0,TEXT("")) ); + + //menu.AppendMenu(MF_STRING,ID_REPLACE,TEXT(ReplaceUIElementCommand)); + WIN32_OP_D( menu.AppendMenu(MF_STRING | (sourceItemEmpty ? (MF_DISABLED|MF_GRAYED) : 0),ID_CUT,TEXT(CutUIElementCommand)) ); + WIN32_OP_D( menu.AppendMenu(MF_STRING | (sourceItemEmpty ? (MF_DISABLED|MF_GRAYED) : 0),ID_COPY,TEXT(CopyUIElementCommand)) ); + WIN32_OP_D( menu.AppendMenu(MF_STRING | (api->is_paste_available() ? 0 : (MF_DISABLED|MF_GRAYED)),ID_PASTE,TEXT(PasteUIElementCommand)) ); + + unsigned custom_walk = ID_CUSTOM_BASE; + unsigned custom_base_host = ~0, custom_base_client = ~0; + + if (host_edit_mode_context_menu_test(p_id,pt,fromkeyboard)) { + menu.AppendMenu(MF_SEPARATOR,(UINT_PTR)0,TEXT("")); + custom_base_host = custom_walk; + host_edit_mode_context_menu_build(p_id,pt,fromkeyboard,menu,custom_walk); + } + + { + if (p_item->edit_mode_context_menu_test(pt,fromkeyboard)) { + WIN32_OP_D( menu.AppendMenu(MF_SEPARATOR,(UINT_PTR)0,TEXT("")) ); + custom_base_client = custom_walk; + p_item->edit_mode_context_menu_build(pt,fromkeyboard,menu,custom_walk); + } + } + int cmd; + + { + ui_element_highlight_scope s(p_item->get_wnd()); + CMyMenuSelectionReceiver receiver(p_parent,this,p_item,p_id,custom_base_host,custom_base_client); + cmd = menu.TrackPopupMenu(TPM_RIGHTBUTTON|TPM_NONOTIFY|TPM_RETURNCMD,pt.x,pt.y,receiver); + } + + if (cmd > 0) { + switch(cmd) { + case ID_REPLACE: + case ID_ADD_NEW: + replace_dialog(p_item->get_wnd(),p_id,p_item->get_guid()); + break; + case ID_COPY: + api->copy(p_item); + break; + case ID_CUT: + api->copy(p_item); + p_item.release(); + host_replace_element(p_id,pfc::guid_null); + break; + case ID_PASTE: + host_paste_element(p_id); + break; + default: + if ((unsigned)cmd >= custom_base_client) { + p_item->edit_mode_context_menu_command(pt,fromkeyboard,(unsigned)cmd,custom_base_client); + } else if ((unsigned)cmd >= custom_base_host) { + host_edit_mode_context_menu_command(p_id,pt,fromkeyboard,(unsigned)cmd,custom_base_host); + } + break; + } + } +} + +void ui_element_helpers::ui_element_edit_tools::on_elem_replace(unsigned p_id, GUID const & newGuid) { + m_replace_dialog.Detach(); + + if ( newGuid != pfc::guid_null ) { + host_replace_element(p_id,newGuid); + } +} + +void ui_element_helpers::ui_element_edit_tools::replace_dialog(HWND p_parent,unsigned p_id,const GUID & p_current) { + _release_replace_dialog(); + + auto ks = m_killSwitch; + + auto reply = ui_element_replace_dialog_notify::create( [=] ( GUID newGUID ) { + if ( !*ks) { + on_elem_replace(p_id, newGUID ); + } + } ); + + HWND dlg = ui_element_common_methods_v3::get()->replace_element_dialog_start( p_parent, p_current, reply ); + + m_replace_dialog.Attach( dlg ); +} + +const char * ui_element_helpers::ui_element_edit_tools::description_from_menu_command(unsigned p_id) { + using namespace HostHelperIDs; + switch(p_id) { + case ID_REPLACE: + return ReplaceUIElementDescription; + case ID_CUT: + return CutUIElementDescription; + case ID_COPY: + return CopyUIElementDescription; + case ID_PASTE: + return PasteUIElementDescription; + case ID_ADD_NEW: + return AddNewUIElementDescription; + default: + return NULL; + } +} + +void ui_element_helpers::ui_element_edit_tools::_release_replace_dialog() { + if (m_replace_dialog.m_hWnd != NULL) { + m_replace_dialog.DestroyWindow(); + } +} + +bool ui_element_helpers::enforce_min_max_info(CWindow p_window,ui_element_min_max_info const & p_info) { + CRect rect; + WIN32_OP_D( p_window.GetWindowRect(&rect) ); + t_uint32 width = (t_uint32) rect.Width(); + t_uint32 height = (t_uint32) rect.Height(); + bool changed = false; + if (width < p_info.m_min_width) {changed = true; width = p_info.m_min_width;} + if (width > p_info.m_max_width) {changed = true; width = p_info.m_max_width;} + if (height < p_info.m_min_height) {changed = true; height = p_info.m_min_height;} + if (height > p_info.m_max_height) {changed = true; height = p_info.m_max_height;} + if (changed) { + WIN32_OP_D( p_window.SetWindowPos(NULL,0,0,width,height,SWP_NOMOVE|SWP_NOACTIVATE|SWP_NOZORDER) ); + } + return changed; +} + + + +void ui_element_helpers::handle_WM_GETMINMAXINFO(LPARAM p_lp,const ui_element_min_max_info & p_myinfo) { + MINMAXINFO * info = reinterpret_cast(p_lp); + /*console::formatter() << "handle_WM_GETMINMAXINFO"; + console::formatter() << p_myinfo.m_min_width << ", " << p_myinfo.m_min_height; + console::formatter() << info->ptMinTrackSize << ", " << info->ptMaxTrackSize;*/ + pfc::max_acc(info->ptMinTrackSize.x,(LONG)p_myinfo.m_min_width); + pfc::max_acc(info->ptMinTrackSize.y,(LONG)p_myinfo.m_min_height); + if ((LONG) p_myinfo.m_max_width >= 0) pfc::min_acc(info->ptMaxTrackSize.x, (LONG) p_myinfo.m_max_width); + if ((LONG) p_myinfo.m_max_height >= 0) pfc::min_acc(info->ptMaxTrackSize.y, (LONG) p_myinfo.m_max_height); + //console::formatter() << info->ptMinTrackSize << ", " << info->ptMaxTrackSize; +} + +bool ui_element_helpers::ui_element_edit_tools::host_paste_element(unsigned p_id) { + pfc::com_ptr_t obj; + if (SUCCEEDED(OleGetClipboard(obj.receive_ptr()))) { + DWORD effect; + ui_element_config::ptr cfg; + if (static_api_ptr_t()->parse_dataobject(obj,cfg,effect)) { + host_replace_element(p_id, cfg); + IDataObjectUtils::SetDataObjectDWORD(obj, RegisterClipboardFormat(CFSTR_PERFORMEDDROPEFFECT), effect); + IDataObjectUtils::PasteSucceeded(obj,effect); + if (effect == DROPEFFECT_MOVE) OleSetClipboard(NULL); + return true; + } + } + return false; +} + + +bool ui_element_helpers::recurse_for_elem_config(ui_element_config::ptr root, ui_element_config::ptr & out, const GUID & toFind) { + const GUID rootID = root->get_guid(); + if (rootID == toFind) { + out = root; return true; + } + ui_element::ptr elem; + if (!find(elem, rootID)) return false; + ui_element_children_enumerator::ptr children; + try { + children = elem->enumerate_children(root); + } catch(exception_io_data) {return false;} + if (children.is_empty()) return false; + const t_size childrenTotal = children->get_count(); + for(t_size walk = 0; walk < childrenTotal; ++walk) { + if (recurse_for_elem_config(children->get_item(walk), out, toFind)) return true; + } + return false; +} + +bool ui_element_helpers::ui_element_instance_host_base::grabTopPriorityVisibleChild(ui_element_instance_ptr & out, t_size & outWhich, double & outPriority) { + double bestPriority = 0; + ui_element_instance_ptr best; + t_size bestWhich = ~0; + const t_size count = host_get_children_count(); + for (t_size walk = 0; walk < count; ++walk) if (this->host_is_child_visible(walk) ) { + ui_element_instance_ptr item = host_get_child(walk); + if (item.is_valid() ) { + const double priority = item->get_focus_priority(); + if (best.is_empty() || childPriorityCompare(walk, priority, bestPriority)) { + best = item; bestPriority = priority; bestWhich = walk; + } + } + } + if (best.is_empty()) return false; + out = best; outPriority = bestPriority; outWhich = bestWhich; return true; +} +bool ui_element_helpers::ui_element_instance_host_base::grabTopPriorityChild(ui_element_instance_ptr & out, t_size & outWhich, double & outPriority, const GUID & subclass) { + double bestPriority = 0; + ui_element_instance_ptr best; + t_size bestWhich = ~0; + const t_size count = host_get_children_count(); + for (t_size walk = 0; walk < count; ++walk) { + ui_element_instance_ptr item = host_get_child(walk); + if (item.is_valid()) { + double priority; + if (item->get_focus_priority_subclass(priority, subclass)) { + if (best.is_empty() || childPriorityCompare(walk, priority, bestPriority)) { + best = item; bestPriority = priority; bestWhich = walk; + } + } + } + } + if (best.is_empty()) return false; + out = best; outPriority = bestPriority; outWhich = bestWhich; return true; +} + +void ui_element_instance_standard_context_menu(service_ptr_t p_elem, LPARAM p_pt) { + CPoint pt; + bool fromKeyboard; + if (p_pt == -1) { + fromKeyboard = true; + if (!p_elem->edit_mode_context_menu_get_focus_point(pt)) { + CRect rc; + WIN32_OP_D(GetWindowRect(p_elem->get_wnd(), rc)); + pt = rc.CenterPoint(); + } + } else { + fromKeyboard = false; + pt = p_pt; + } + if (p_elem->edit_mode_context_menu_test(pt, fromKeyboard)) { + const unsigned idBase = 1; + CMenu menu; + WIN32_OP(menu.CreatePopupMenu()); + p_elem->edit_mode_context_menu_build(pt, fromKeyboard, menu, idBase); + + int cmd; + { + CMenuSelectionReceiver_UiElement receiver(p_elem, idBase); + cmd = menu.TrackPopupMenu(TPM_RIGHTBUTTON | TPM_NONOTIFY | TPM_RETURNCMD, pt.x, pt.y, receiver); + } + if (cmd > 0) p_elem->edit_mode_context_menu_command(pt, fromKeyboard, cmd, idBase); + } +} +void ui_element_instance_standard_context_menu_eh(service_ptr_t p_elem, LPARAM p_pt) { + try { + ui_element_instance_standard_context_menu(p_elem, p_pt); + } catch (std::exception const & e) { + console::complain("Context menu failure", e); + } +} + +#endif // FOOBAR2000_TARGET_VERSION >= 79 diff --git a/foobar2000/helpers/ui_element_helpers.h b/foobar2000/helpers/ui_element_helpers.h new file mode 100644 index 0000000..48e7c98 --- /dev/null +++ b/foobar2000/helpers/ui_element_helpers.h @@ -0,0 +1,377 @@ +#pragma once + +// ==================================================================================================== +// ui_element_helpers +// A framework for creating UI Elements that host other elements. +// All foo_ui_std elements that host other elements - such as splitters or tabs - are based on this. +// Note that API 79 (v1.4) or newer is required, earlier did not provide ui_element_common_methods_v3. +// ==================================================================================================== + +#if FOOBAR2000_TARGET_VERSION >= 79 + +#include +#include + +namespace ui_element_helpers { + template class ui_element_instance_callback_multi_impl : public ui_element_instance_callback_v3 { + public: + ui_element_instance_callback_multi_impl(t_size id, t_receiver * p_receiver) : m_receiver(p_receiver), m_id(id) {} + void on_min_max_info_change() { + if (m_receiver != NULL) m_receiver->on_min_max_info_change(); + } + bool query_color(const GUID & p_what,t_ui_color & p_out) { + if (m_receiver != NULL) return m_receiver->query_color(p_what,p_out); + else return false; + } + + bool request_activation(service_ptr_t p_item) { + if (m_receiver) return m_receiver->request_activation(m_id); + else return false; + } + + bool is_edit_mode_enabled() { + if (m_receiver) return m_receiver->is_edit_mode_enabled(); + else return false; + } + void request_replace(service_ptr_t p_item) { + if (m_receiver) m_receiver->request_replace(m_id); + } + + t_ui_font query_font_ex(const GUID & p_what) { + if (m_receiver) return m_receiver->query_font_ex(p_what); + else return NULL; + } + + t_size notify(ui_element_instance * source, const GUID & what, t_size param1, const void * param2, t_size param2size) { + if (m_receiver) return m_receiver->host_notify(source, what, param1, param2, param2size); + else return 0; + } + void orphan() {m_receiver = NULL;} + + bool is_elem_visible(service_ptr_t elem) { + if (m_receiver) return m_receiver->is_elem_visible(m_id); + else return false; + } + + void override_id(t_size id) {m_id = id;} + + void on_alt_pressed(bool) {} + private: + t_size m_id; + t_receiver * m_receiver; + }; + class ui_element_instance_callback_receiver_multi { + public: + virtual void on_min_max_info_change() {} + virtual bool query_color(const GUID & p_what,t_ui_color & p_out) {return false;} + virtual bool request_activation(t_size which) {return false;} + virtual bool is_edit_mode_enabled() {return false;} + virtual void request_replace(t_size which) {} + virtual t_ui_font query_font_ex(const GUID&) {return NULL;} + virtual bool is_elem_visible(t_size which) {return true;} + virtual t_size host_notify(ui_element_instance * source, const GUID & what, t_size param1, const void * param2, t_size param2size) {return 0;} + + void ui_element_instance_callback_handle_remove(bit_array const & mask, t_size const oldCount) { + t_callback_list newCallbacks; + t_size newWalk = 0; + for(t_size walk = 0; walk < oldCount; ++walk) { + if (mask[walk]) { + t_callback_ptr ptr; + if (m_callbacks.query(walk,ptr)) { + ptr->orphan(); m_callbacks.remove(walk); + } + } else { + if (newWalk != walk) { + t_callback_ptr ptr; + if (m_callbacks.query(walk,ptr)) { + m_callbacks.remove(walk); + ptr->override_id(newWalk); + m_callbacks.set(newWalk,ptr); + } + } + ++newWalk; + } + } + } + + void ui_element_instance_callback_handle_reorder(const t_size * order, t_size count) { + t_callback_list newCallbacks; + for(t_size walk = 0; walk < count; ++walk) { + t_callback_ptr ptr; + if (m_callbacks.query(order[walk],ptr)) { + ptr->override_id(walk); + newCallbacks.set(walk,ptr); + m_callbacks.remove(order[walk]); + } + } + + PFC_ASSERT( m_callbacks.get_count() == 0 ); + ui_element_instance_callback_release_all(); + + m_callbacks = newCallbacks; + } + + ui_element_instance_callback_ptr ui_element_instance_callback_get_ptr(t_size which) { + t_callback_ptr ptr; + if (!m_callbacks.query(which,ptr)) { + ptr = new service_impl_t(which,this); + m_callbacks.set(which,ptr); + } + return ptr; + } + ui_element_instance_callback_ptr ui_element_instance_callback_create(t_size which) { + ui_element_instance_callback_release(which); + t_callback_ptr ptr = new service_impl_t(which,this); + m_callbacks.set(which,ptr); + return ptr; + } + void ui_element_instance_callback_release_all() { + for(t_callback_list::const_iterator walk = m_callbacks.first(); walk.is_valid(); ++walk) { + walk->m_value->orphan(); + } + m_callbacks.remove_all(); + } + void ui_element_instance_callback_release(t_size which) { + t_callback_ptr ptr; + if (m_callbacks.query(which,ptr)) { + ptr->orphan(); + m_callbacks.remove(which); + } + } + protected: + ~ui_element_instance_callback_receiver_multi() { + ui_element_instance_callback_release_all(); + } + ui_element_instance_callback_receiver_multi() {} + + private: + typedef ui_element_instance_callback_receiver_multi t_self; + typedef ui_element_instance_callback_multi_impl t_callback; + typedef service_ptr_t t_callback_ptr; + typedef pfc::map_t t_callback_list; + t_callback_list m_callbacks; + }; + + + //! Parses container tree to find configuration of specified element inside a layout configuration. + bool recurse_for_elem_config(ui_element_config::ptr root, ui_element_config::ptr & out, const GUID & toFind); + + ui_element_instance_ptr create_root_container(HWND p_parent,ui_element_instance_callback_ptr p_callback); + ui_element_instance_ptr instantiate(HWND p_parent,ui_element_config::ptr cfg,ui_element_instance_callback_ptr p_callback); + ui_element_instance_ptr instantiate_dummy(HWND p_parent,ui_element_config::ptr cfg,ui_element_instance_callback_ptr p_callback); + ui_element_instance_ptr update(ui_element_instance_ptr p_element,HWND p_parent,ui_element_config::ptr cfg,ui_element_instance_callback_ptr p_callback); + bool find(service_ptr_t & p_out,const GUID & p_guid); + ui_element_children_enumerator_ptr enumerate_children(ui_element_config::ptr cfg); + + void replace_with_new_element(ui_element_instance_ptr & p_item,const GUID & p_guid,HWND p_parent,ui_element_instance_callback_ptr p_callback); + + class ui_element_highlight_scope { + public: + ui_element_highlight_scope(HWND wndElem) { + m_highlight = ui_element_common_methods_v3::get()->highlight_element( wndElem ); + } + ~ui_element_highlight_scope() { + DestroyWindow(m_highlight); + } + private: + ui_element_highlight_scope(const ui_element_highlight_scope&) = delete; + void operator=(const ui_element_highlight_scope&) = delete; + HWND m_highlight; + }; + + //! Helper class; provides edit-mode context menu functionality and interacts with "Replace UI Element" dialog. \n + //! Do not use directly - derive from ui_element_instance_host_base instead. + class ui_element_edit_tools { + public: + //! Override me + virtual void host_replace_element(unsigned p_id, ui_element_config::ptr cfg) {} + //! Override me + virtual void host_replace_element(unsigned p_id,const GUID & p_newguid) {} + + //! Override me optionally if you customize edit mode context menu + virtual bool host_edit_mode_context_menu_test(unsigned p_childid,const POINT & p_point,bool p_fromkeyboard) {return false;} + //! Override me optionally if you customize edit mode context menu + virtual void host_edit_mode_context_menu_build(unsigned p_childid,const POINT & p_point,bool p_fromkeyboard,HMENU p_menu,unsigned & p_id_base) {} + //! Override me optionally if you customize edit mode context menu + virtual void host_edit_mode_context_menu_command(unsigned p_childid,const POINT & p_point,bool p_fromkeyboard,unsigned p_id,unsigned p_id_base) {} + //! Override me optionally if you customize edit mode context menu + virtual bool host_edit_mode_context_menu_get_description(unsigned p_childid,unsigned p_id,unsigned p_id_base,pfc::string_base & p_out) {return false;} + + //! Initiates "Replace UI Element" dialog for one of your sub-elements. + void replace_dialog(HWND p_parent,unsigned p_id,const GUID & p_current); + + //! Shows edit mode context menu for your element. + void standard_edit_context_menu(LPARAM p_point,ui_element_instance_ptr p_item,unsigned p_id,HWND p_parent); + + static const char * description_from_menu_command(unsigned p_id); + + bool host_paste_element(unsigned p_id); + + BEGIN_MSG_MAP(ui_element_edit_tools) + MESSAGE_HANDLER(WM_DESTROY,OnDestroy) + END_MSG_MAP() + protected: + ui_element_edit_tools() {} + private: + void on_elem_replace(unsigned p_id,GUID const & newElem); + void _release_replace_dialog(); + LRESULT OnDestroy(UINT,WPARAM,LPARAM,BOOL& bHandled) {bHandled = FALSE; *m_killSwitch = true; _release_replace_dialog(); return 0;} + + CWindow m_replace_dialog; + std::shared_ptr m_killSwitch = std::make_shared(); + + ui_element_edit_tools( const ui_element_edit_tools & ) = delete; + void operator=( const ui_element_edit_tools & ) = delete; + }; + + //! Base class for ui_element_instances that host other elements. + class ui_element_instance_host_base : public ui_element_instance, protected ui_element_instance_callback_receiver_multi, protected ui_element_edit_tools { + protected: + // Any derived class must pass their messages to us, by CHAIN_MSG_MAP(ui_element_instance_host_base) + BEGIN_MSG_MAP(ui_element_instance_host_base) + CHAIN_MSG_MAP(ui_element_edit_tools) + MESSAGE_HANDLER(WM_SETTINGCHANGE,OnSettingChange); + END_MSG_MAP() + + //override me + virtual ui_element_instance_ptr host_get_child(t_size which) = 0; + //override me + virtual t_size host_get_children_count() = 0; + //override me (tabs) + virtual void host_bring_to_front(t_size which) {} + //override me + virtual void on_min_max_info_change() {m_callback->on_min_max_info_change();} + //override me + virtual void host_replace_child(t_size which) = 0; + + virtual bool host_is_child_visible(t_size which) {return true;} + + void host_child_visibility_changed(t_size which, bool state) { + if (m_callback->is_elem_visible_(this)) { + ui_element_instance::ptr item = host_get_child(which); + if (item.is_valid()) item->notify(ui_element_notify_visibility_changed,state ? 1 : 0,NULL,0); + } + } + + + bool is_elem_visible(t_size which) { + if (!m_callback->is_elem_visible_(this)) return false; + return this->host_is_child_visible(which); + } + + GUID get_subclass() {return ui_element_subclass_containers;} + + double get_focus_priority() { + ui_element_instance_ptr item; double priority; t_size which; + if (!grabTopPriorityVisibleChild(item,which,priority)) return 0; + return priority; + } + void set_default_focus() { + ui_element_instance_ptr item; double priority; t_size which; + if (!grabTopPriorityVisibleChild(item,which,priority)) { + this->set_default_focus_fallback(); + } else { + host_bring_to_front(which); + item->set_default_focus(); + } + } + + bool get_focus_priority_subclass(double & p_out,const GUID & p_subclass) { + ui_element_instance_ptr item; double priority; t_size which; + if (!grabTopPriorityChild(item,which,priority,p_subclass)) return false; + p_out = priority; + return true; + } + bool set_default_focus_subclass(const GUID & p_subclass) { + ui_element_instance_ptr item; double priority; t_size which; + if (!grabTopPriorityChild(item,which,priority,p_subclass)) return false; + host_bring_to_front(which); + return item->set_default_focus_subclass(p_subclass); + } + void notify(const GUID & p_what, t_size p_param1, const void * p_param2, t_size p_param2size) { + if (p_what == ui_element_notify_visibility_changed) { + const t_size total = host_get_children_count(); + for(t_size walk = 0; walk < total; ++walk) { + if (this->host_is_child_visible(walk)) { + ui_element_instance_ptr item = host_get_child(walk); + if (item.is_valid()) item->notify(p_what,p_param1,p_param2,p_param2size); + } + } + } else if (p_what == ui_element_notify_get_element_labels) { + handleGetLabels(p_param1, p_param2, p_param2size); + } else { + const t_size total = host_get_children_count(); + for(t_size walk = 0; walk < total; ++walk) { + ui_element_instance_ptr item = host_get_child(walk); + if (item.is_valid()) item->notify(p_what,p_param1,p_param2,p_param2size); + } + } + } + bool query_color(const GUID & p_what,t_ui_color & p_out) {return m_callback->query_color(p_what,p_out);} + bool request_activation(t_size which) { + if (!m_callback->request_activation(this)) return false; + host_bring_to_front(which); + return true; + } + bool is_edit_mode_enabled() {return m_callback->is_edit_mode_enabled();} + + t_ui_font query_font_ex(const GUID& id) {return m_callback->query_font_ex(id);} + + void request_replace(t_size which) { + host_replace_child(which); + } + private: + void handleGetLabelsChild(ui_element_instance::ptr child, t_size which, t_size param1, const void * param2, t_size param2size) { + if (child->get_subclass() == ui_element_subclass_containers) { + child->notify(ui_element_notify_get_element_labels, param1, param2, param2size); + } else if (child->get_guid() != pfc::guid_null && child->get_wnd() != NULL && this->host_is_child_visible(which)) { + FB2K_DYNAMIC_ASSERT(param2 != NULL); + reinterpret_cast(const_cast(param2))->set_visible_element(child); + } + } + void handleGetLabels(t_size param1, const void * param2, t_size param2size) { + const t_size childrenTotal = host_get_children_count(); + for(t_size childWalk = 0; childWalk < childrenTotal; ++childWalk) { + ui_element_instance_ptr item = host_get_child(childWalk); + if (item.is_valid()) handleGetLabelsChild(item, childWalk, param1, param2, param2size); + } + } + LRESULT OnSettingChange(UINT msg,WPARAM wp,LPARAM lp,BOOL& bHandled) { + bHandled = FALSE; + const t_size total = host_get_children_count(); + for(t_size walk = 0; walk < total; ++walk) { + ui_element_instance::ptr item = host_get_child(walk); + if (item.is_valid()) { + ::SendMessage(item->get_wnd(),msg,wp,lp); + } + } + return 0; + } + t_size whichChild(ui_element_instance_ptr child) { + const t_size count = host_get_children_count(); + for(t_size walk = 0; walk < count; ++walk) { + if (child == host_get_child(walk)) return walk; + } + return ~0; + } + bool childPriorityCompare(t_size which, double priority, double bestPriority) { + if (host_is_child_visible(which)) return priority >= bestPriority; + else return priority > bestPriority; + } + bool grabTopPriorityVisibleChild(ui_element_instance_ptr & out,t_size & outWhich,double & outPriority); + bool grabTopPriorityChild(ui_element_instance_ptr & out,t_size & outWhich,double & outPriority,const GUID & subclass); + protected: + ui_element_instance_host_base(ui_element_instance_callback::ptr callback) : m_callback(callback) {} + const ui_element_instance_callback::ptr m_callback; + }; + + + bool enforce_min_max_info(CWindow p_window,ui_element_min_max_info const & p_info); + + void handle_WM_GETMINMAXINFO(LPARAM p_lp,const ui_element_min_max_info & p_myinfo); +}; + +void ui_element_instance_standard_context_menu(service_ptr_t p_elem, LPARAM p_pt); +void ui_element_instance_standard_context_menu_eh(service_ptr_t p_elem, LPARAM p_pt); + +#endif // FOOBAR2000_TARGET_VERSION >= 79 diff --git a/foobar2000/helpers/win32_dialog.cpp b/foobar2000/helpers/win32_dialog.cpp new file mode 100644 index 0000000..ae4a1b7 --- /dev/null +++ b/foobar2000/helpers/win32_dialog.cpp @@ -0,0 +1,294 @@ +#include "stdafx.h" + +#ifdef FOOBAR2000_DESKTOP_WINDOWS + +#include "win32_misc.h" +#include "win32_dialog.h" + +namespace dialog_helper { + + + INT_PTR CALLBACK dialog::DlgProc(HWND wnd,UINT msg,WPARAM wp,LPARAM lp) + { + dialog * p_this; + BOOL rv; + if (msg==WM_INITDIALOG) + { + p_this = reinterpret_cast(lp); + p_this->wnd = wnd; + SetWindowLongPtr(wnd,DWLP_USER,lp); + + if (p_this->m_is_modal) p_this->m_modal_scope.initialize(wnd); + } + else p_this = reinterpret_cast(GetWindowLongPtr(wnd,DWLP_USER)); + + rv = p_this ? p_this->on_message(msg,wp,lp) : FALSE; + + if (msg==WM_DESTROY && p_this) + { + SetWindowLongPtr(wnd,DWLP_USER,0); +// p_this->wnd = 0; + } + + return rv; + } + + + int dialog::run_modal(unsigned id,HWND parent) + { + assert(wnd == 0); + if (wnd != 0) return -1; + m_is_modal = true; + return uDialogBox(id,parent,DlgProc,reinterpret_cast(this)); + } + HWND dialog::run_modeless(unsigned id,HWND parent) + { + assert(wnd == 0); + if (wnd != 0) return 0; + m_is_modal = false; + return uCreateDialog(id,parent,DlgProc,reinterpret_cast(this)); + } + + void dialog::end_dialog(int code) + { + assert(m_is_modal); + if (m_is_modal) uEndDialog(wnd,code); + } + + + + + + + + + + + int dialog_modal::run(unsigned p_id,HWND p_parent,HINSTANCE p_instance) + { + int status; + + // note: uDialogBox() has its own modal scope, we don't want that to trigger + // if this is ever changed, move deinit to WM_DESTROY handler in DlgProc + + status = (int)DialogBoxParam(p_instance,MAKEINTRESOURCE(p_id),p_parent,DlgProc,reinterpret_cast(this)); + + m_modal_scope.deinitialize(); + + return status; + } + + void dialog_modal::end_dialog(int p_code) + { + EndDialog(m_wnd,p_code); + } + + + INT_PTR CALLBACK dialog_modal::DlgProc(HWND wnd,UINT msg,WPARAM wp,LPARAM lp) + { + dialog_modal * _this; + if (msg==WM_INITDIALOG) + { + _this = reinterpret_cast(lp); + _this->m_wnd = wnd; + SetWindowLongPtr(wnd,DWLP_USER,lp); + + _this->m_modal_scope.initialize(wnd); + } + else _this = reinterpret_cast(GetWindowLongPtr(wnd,DWLP_USER)); + + assert(_this == 0 || _this->m_wnd == wnd); + + return _this ? _this->on_message(msg,wp,lp) : FALSE; + } + + + bool dialog_modeless::create(unsigned p_id,HWND p_parent,HINSTANCE p_instance) { + assert(!m_is_in_create); + if (m_is_in_create) return false; + pfc::vartoggle_t scope(m_is_in_create,true); + if (CreateDialogParam(p_instance,MAKEINTRESOURCE(p_id),p_parent,DlgProc,reinterpret_cast(this)) == 0) return false; + return m_wnd != 0; + } + + dialog_modeless::~dialog_modeless() { + assert(!m_is_in_create); + switch(m_destructor_status) + { + case destructor_none: + m_destructor_status = destructor_normal; + if (m_wnd != 0) + { + DestroyWindow(m_wnd); + m_wnd = 0; + } + break; + case destructor_fromwindow: + if (m_wnd != 0) SetWindowLongPtr(m_wnd,DWLP_USER,0); + break; + default: + //should never trigger + pfc::crash(); + break; + } + } + + void dialog_modeless::on_window_destruction() + { + if (m_is_in_create) + { + m_wnd = 0; + } + else + switch(m_destructor_status) + { + case destructor_none: + m_destructor_status = destructor_fromwindow; + delete this; + break; + case destructor_fromwindow: + pfc::crash(); + break; + default: + break; + } + } + + BOOL dialog_modeless::on_message_wrap(UINT msg,WPARAM wp,LPARAM lp) + { + if (m_destructor_status == destructor_none) + return on_message(msg,wp,lp); + else + return FALSE; + } + + INT_PTR CALLBACK dialog_modeless::DlgProc(HWND wnd,UINT msg,WPARAM wp,LPARAM lp) + { + dialog_modeless * thisptr; + BOOL rv; + if (msg == WM_INITDIALOG) + { + thisptr = reinterpret_cast(lp); + thisptr->m_wnd = wnd; + SetWindowLongPtr(wnd,DWLP_USER,lp); + modeless_dialog_manager::g_add(wnd); + } + else thisptr = reinterpret_cast(GetWindowLongPtr(wnd,DWLP_USER)); + + rv = thisptr ? thisptr->on_message_wrap(msg,wp,lp) : FALSE; + + if (msg == WM_DESTROY) + modeless_dialog_manager::g_remove(wnd); + + if (msg == WM_DESTROY && thisptr != 0) + thisptr->on_window_destruction(); + + return rv; + } + + + + + + + + + + + + + + + + + dialog_modeless_v2::dialog_modeless_v2(unsigned p_id,HWND p_parent,HINSTANCE p_instance,bool p_stealfocus) : m_wnd(0), m_status(status_construction), m_stealfocus(p_stealfocus) + { + WIN32_OP( CreateDialogParam(p_instance,MAKEINTRESOURCE(p_id),p_parent,DlgProc,reinterpret_cast(this)) != NULL ); + m_status = status_lifetime; + } + + dialog_modeless_v2::~dialog_modeless_v2() + { + bool is_window_being_destroyed = (m_status == status_destruction_requested); + m_status = status_destruction; + + if (m_wnd != 0) + { + if (is_window_being_destroyed) + detach_window(); + else + DestroyWindow(m_wnd); + } + } + + INT_PTR CALLBACK dialog_modeless_v2::DlgProc(HWND wnd,UINT msg,WPARAM wp,LPARAM lp) + { + dialog_modeless_v2 * thisptr; + BOOL rv = FALSE; + if (msg == WM_INITDIALOG) + { + thisptr = reinterpret_cast(lp); + assert(thisptr->m_status == status_construction); + thisptr->m_wnd = wnd; + SetWindowLongPtr(wnd,DWLP_USER,lp); + if (GetWindowLong(wnd,GWL_STYLE) & WS_POPUP) { + modeless_dialog_manager::g_add(wnd); + } + } + else thisptr = reinterpret_cast(GetWindowLongPtr(wnd,DWLP_USER)); + + if (thisptr != NULL) rv = thisptr->on_message_internal(msg,wp,lp); + + if (msg == WM_DESTROY) + { + modeless_dialog_manager::g_remove(wnd); + } + + return rv; + } + + + void dialog_modeless_v2::detach_window() + { + if (m_wnd != 0) + { + SetWindowLongPtr(m_wnd,DWLP_USER,0); + m_wnd = 0; + } + } + + + BOOL dialog_modeless_v2::on_message_internal(UINT msg,WPARAM wp,LPARAM lp) + { + if (m_status == status_lifetime || m_status == status_destruction_requested) + { + if (msg == WM_DESTROY) + { + assert(m_status == status_lifetime); + m_status = status_destruction_requested; + delete this; + return TRUE; + } + else + return on_message(msg,wp,lp); + } + else if (m_status == status_construction) + { + if (msg == WM_INITDIALOG) return m_stealfocus ? TRUE : FALSE; + else return FALSE; + } + else return FALSE; + } +} + +HWND uCreateDialog(UINT id,HWND parent,DLGPROC proc,LPARAM param) +{ + return CreateDialogParam(core_api::get_my_instance(),MAKEINTRESOURCE(id),parent,proc,param); +} + +int uDialogBox(UINT id,HWND parent,DLGPROC proc,LPARAM param) +{ + return (int)DialogBoxParam(core_api::get_my_instance(),MAKEINTRESOURCE(id),parent,proc,param); +} + +#endif // FOOBAR2000_DESKTOP_WINDOWS diff --git a/foobar2000/helpers/win32_dialog.h b/foobar2000/helpers/win32_dialog.h new file mode 100644 index 0000000..1abcb65 --- /dev/null +++ b/foobar2000/helpers/win32_dialog.h @@ -0,0 +1,122 @@ +#pragma once + +#ifdef FOOBAR2000_DESKTOP_WINDOWS + +//DEPRECATED dialog helpers - kept only for compatibility with old code - do not use in new code, use WTL instead. + +namespace dialog_helper +{ + + class dialog + { + protected: + + dialog() : wnd(0), m_is_modal(false) {} + ~dialog() { } + + virtual BOOL on_message(UINT msg,WPARAM wp,LPARAM lp)=0; + + void end_dialog(int code); + + public: + inline HWND get_wnd() {return wnd;} + + __declspec(deprecated) int run_modal(unsigned id,HWND parent); + + __declspec(deprecated) HWND run_modeless(unsigned id,HWND parent); + private: + HWND wnd; + static INT_PTR CALLBACK DlgProc(HWND wnd,UINT msg,WPARAM wp,LPARAM lp); + + bool m_is_modal; + + modal_dialog_scope m_modal_scope; + }; + + //! This class is meant to be instantiated on-stack, as a local variable. Using new/delete operators instead or even making this a member of another object works, but does not make much sense because of the way this works (single run() call). + class dialog_modal + { + public: + __declspec(deprecated) int run(unsigned p_id,HWND p_parent,HINSTANCE p_instance = core_api::get_my_instance()); + protected: + virtual BOOL on_message(UINT msg,WPARAM wp,LPARAM lp)=0; + + inline dialog_modal() : m_wnd(0) {} + void end_dialog(int p_code); + inline HWND get_wnd() const {return m_wnd;} + private: + static INT_PTR CALLBACK DlgProc(HWND wnd,UINT msg,WPARAM wp,LPARAM lp); + + HWND m_wnd; + modal_dialog_scope m_modal_scope; + }; + + //! This class is meant to be used with new/delete operators only. Destroying the window - outside create() / WM_INITDIALOG - will result in object calling delete this. If object is deleted directly using delete operator, WM_DESTROY handler may not be called so it should not be used (use destructor of derived class instead). + //! Classes derived from dialog_modeless must not be instantiated in any other way than operator new(). + /*! Typical usage : \n + class mydialog : public dialog_helper::dialog_modeless {...}; + (...) + bool createmydialog() + { + mydialog * instance = new mydialog; + if (instance == 0) return flase; + if (!instance->create(...)) {delete instance; return false;} + return true; + } + + */ + class dialog_modeless + { + public: + //! Creates the dialog window. This will call on_message with WM_INITDIALOG. To abort creation, you can call DestroyWindow() on our window; it will not delete the object but make create() return false instead. You should not delete the object from inside WM_INITDIALOG handler or anything else possibly called from create(). + //! @returns true on success, false on failure. + __declspec(deprecated) bool create(unsigned p_id,HWND p_parent,HINSTANCE p_instance = core_api::get_my_instance()); + protected: + //! Standard windows message handler (DialogProc-style). Use get_wnd() to retrieve our dialog window handle. + virtual BOOL on_message(UINT msg,WPARAM wp,LPARAM lp)=0; + + inline dialog_modeless() : m_wnd(0), m_destructor_status(destructor_none), m_is_in_create(false) {} + inline HWND get_wnd() const {return m_wnd;} + virtual ~dialog_modeless(); + private: + static INT_PTR CALLBACK DlgProc(HWND wnd,UINT msg,WPARAM wp,LPARAM lp); + void on_window_destruction(); + + BOOL on_message_wrap(UINT msg,WPARAM wp,LPARAM lp); + + HWND m_wnd; + enum {destructor_none,destructor_normal,destructor_fromwindow} m_destructor_status; + bool m_is_in_create; + }; + + + class dialog_modeless_v2 + { + protected: + __declspec(deprecated) explicit dialog_modeless_v2(unsigned p_id,HWND p_parent,HINSTANCE p_instance = core_api::get_my_instance(),bool p_stealfocus = true); + virtual ~dialog_modeless_v2(); + HWND get_wnd() const {return m_wnd;} + virtual BOOL on_message(UINT msg,WPARAM wp,LPARAM lp) {return FALSE;} + + static dialog_modeless_v2 * __unsafe__instance_from_window(HWND p_wnd) {return reinterpret_cast(GetWindowLongPtr(p_wnd,DWLP_USER));} + private: + static INT_PTR CALLBACK DlgProc(HWND wnd,UINT msg,WPARAM wp,LPARAM lp); + void detach_window(); + BOOL on_message_internal(UINT msg,WPARAM wp,LPARAM lp); + enum {status_construction, status_lifetime, status_destruction_requested, status_destruction} m_status; + HWND m_wnd; + const bool m_stealfocus; + + const dialog_modeless_v2 & operator=(const dialog_modeless_v2 &); + dialog_modeless_v2(const dialog_modeless_v2 &); + }; + +}; + +//! Wrapper (provided mainly for old code), simplifies parameters compared to standard CreateDialog() by using core_api::get_my_instance(). +HWND uCreateDialog(UINT id,HWND parent,DLGPROC proc,LPARAM param = 0); +//! Wrapper (provided mainly for old code), simplifies parameters compared to standard DialogBox() by using core_api::get_my_instance(). +int uDialogBox(UINT id,HWND parent,DLGPROC proc,LPARAM param = 0); + + +#endif // FOOBAR2000_DESKTOP_WINDOWS \ No newline at end of file diff --git a/foobar2000/helpers/win32_misc.cpp b/foobar2000/helpers/win32_misc.cpp new file mode 100644 index 0000000..bc9ecfc --- /dev/null +++ b/foobar2000/helpers/win32_misc.cpp @@ -0,0 +1,215 @@ +#include "stdafx.h" +#include "win32_misc.h" + +#ifdef FOOBAR2000_MOBILE_WINDOWS +#include +#endif + +#ifdef _WIN32 + +mutexScope::mutexScope(HANDLE hMutex_, abort_callback & abort) : hMutex(hMutex_) { + HANDLE h[2] = { hMutex, abort.get_abort_event() }; + switch (WaitForMultipleObjectsEx(2, h, FALSE, INFINITE, FALSE)) { + case WAIT_OBJECT_0: + break; // and enter + case WAIT_OBJECT_0 + 1: + throw exception_aborted(); + default: + uBugCheck(); + } +} +mutexScope::~mutexScope() { + ReleaseMutex(hMutex); +} + +CMutex::CMutex(const TCHAR * name) { + WIN32_OP_CRITICAL("CreateMutex", m_hMutex = CreateMutex(NULL, FALSE, name)); +} +CMutex::~CMutex() { + CloseHandle(m_hMutex); +} + +void CMutex::AcquireByHandle(HANDLE hMutex, abort_callback & aborter) { + SetLastError(0); + HANDLE hWait[2] = { hMutex, aborter.get_abort_event() }; + switch (WaitForMultipleObjects(2, hWait, FALSE, INFINITE)) { + case WAIT_FAILED: + WIN32_OP_FAIL_CRITICAL("WaitForSingleObject"); + case WAIT_OBJECT_0: + return; + case WAIT_OBJECT_0 + 1: + PFC_ASSERT(aborter.is_aborting()); + throw exception_aborted(); + default: + uBugCheck(); + } +} + +void CMutex::Acquire(abort_callback& aborter) { + AcquireByHandle(Handle(), aborter); +} + +void CMutex::Release() { + ReleaseMutex(Handle()); +} + + +CMutexScope::CMutexScope(CMutex & mutex, DWORD timeOutMS, const char * timeOutBugMsg) : m_mutex(mutex) { + SetLastError(0); + const unsigned div = 4; + for (unsigned walk = 0; walk < div; ++walk) { + switch (WaitForSingleObject(m_mutex.Handle(), timeOutMS / div)) { + case WAIT_FAILED: + WIN32_OP_FAIL_CRITICAL("WaitForSingleObject"); + case WAIT_OBJECT_0: + return; + case WAIT_TIMEOUT: + break; + default: + uBugCheck(); + } + } + TRACK_CODE(timeOutBugMsg, uBugCheck()); +} + +CMutexScope::CMutexScope(CMutex & mutex) : m_mutex(mutex) { + SetLastError(0); + switch (WaitForSingleObject(m_mutex.Handle(), INFINITE)) { + case WAIT_FAILED: + WIN32_OP_FAIL_CRITICAL("WaitForSingleObject"); + case WAIT_OBJECT_0: + return; + default: + uBugCheck(); + } +} + +CMutexScope::CMutexScope(CMutex & mutex, abort_callback & aborter) : m_mutex(mutex) { + mutex.Acquire(aborter); +} + +CMutexScope::~CMutexScope() { + ReleaseMutex(m_mutex.Handle()); +} + +#endif + +#ifdef FOOBAR2000_DESKTOP_WINDOWS + +void registerclass_scope_delayed::toggle_on(UINT p_style,WNDPROC p_wndproc,int p_clsextra,int p_wndextra,HICON p_icon,HCURSOR p_cursor,HBRUSH p_background,const TCHAR * p_class_name,const TCHAR * p_menu_name) { + toggle_off(); + WNDCLASS wc = {}; + wc.style = p_style; + wc.lpfnWndProc = p_wndproc; + wc.cbClsExtra = p_clsextra; + wc.cbWndExtra = p_wndextra; + wc.hInstance = core_api::get_my_instance(); + wc.hIcon = p_icon; + wc.hCursor = p_cursor; + wc.hbrBackground = p_background; + wc.lpszMenuName = p_menu_name; + wc.lpszClassName = p_class_name; + WIN32_OP_CRITICAL("RegisterClass", (m_class = RegisterClass(&wc)) != 0); +} + +void registerclass_scope_delayed::toggle_off() { + if (m_class != 0) { + UnregisterClass((LPCTSTR)m_class,core_api::get_my_instance()); + m_class = 0; + } +} + +void CModelessDialogEntry::Set(HWND p_new) { + auto api = modeless_dialog_manager::get(); + if (m_wnd) api->remove(m_wnd); + m_wnd = p_new; + if (m_wnd) api->add(m_wnd); +} + +OleInitializeScope::OleInitializeScope() { + if (FAILED(OleInitialize(NULL))) throw pfc::exception("OleInitialize() failure"); +} +OleInitializeScope::~OleInitializeScope() { + OleUninitialize(); +} + +CoInitializeScope::CoInitializeScope() { + if (FAILED(CoInitialize(NULL))) throw pfc::exception("CoInitialize() failed"); +} +CoInitializeScope::CoInitializeScope(DWORD params) { + if (FAILED(CoInitializeEx(NULL, params))) throw pfc::exception("CoInitialize() failed"); +} +CoInitializeScope::~CoInitializeScope() { + CoUninitialize(); +} + +void winLocalFileScope::open(const char * inPath, file::ptr inReader, abort_callback & aborter) { + close(); + if (inPath != NULL) { + if (_extract_native_path_ptr(inPath)) { + pfc::string8 prefixed; + pfc::winPrefixPath(prefixed, inPath); + m_path = pfc::stringcvt::string_wide_from_utf8(prefixed); + m_isTemp = false; + return; + } + } + + pfc::string8 tempPath; + if (!uGetTempPath(tempPath)) uBugCheck(); + tempPath.add_filename(PFC_string_formatter() << pfc::print_guid(pfc::createGUID()) << ".rar"); + + m_path = pfc::stringcvt::string_wide_from_utf8(tempPath); + + if (inReader.is_empty()) { + if (inPath == NULL) uBugCheck(); + inReader = fileOpenReadExisting(inPath, aborter, 1.0); + } + + file::ptr writer = fileOpenWriteNew(PFC_string_formatter() << "file://" << tempPath, aborter, 1.0); + file::g_transfer_file(inReader, writer, aborter); + m_isTemp = true; +} +void winLocalFileScope::close() { + if (m_isTemp && m_path.length() > 0) { + pfc::lores_timer timer; + timer.start(); + for (;;) { + if (DeleteFile(m_path.c_str())) break; + if (timer.query() > 1.0) break; + Sleep(10); + } + } + m_path.clear(); +} + +bool IsWindowsS() { + bool ret = false; +#if FB2K_TARGET_MICROSOFT_STORE + static bool cached = false; + static bool inited = false; + if ( inited ) { + ret = cached; + } else { + HKEY key; + if (RegOpenKey(HKEY_LOCAL_MACHINE, L"SYSTEM\\CurrentControlSet\\Control\\CI\\Policy", &key) == 0) { + DWORD dwVal = 0, dwType; + DWORD valSize; + valSize = sizeof(dwVal); + if (RegQueryValueEx(key, L"SkuPolicyRequired", nullptr, &dwType, (LPBYTE)&dwVal, &valSize) == 0) { + if (dwType == REG_DWORD && dwVal != 0) ret = true; + } + RegCloseKey(key); + } + cached = ret; + inited = true; + } +#endif + return ret; +} + +WORD GetOSVersion() { + // wrap libPPUI function + return ::GetOSVersionCode(); +} +#endif // FOOBAR2000_DESKTOP_WINDOWS diff --git a/foobar2000/helpers/win32_misc.h b/foobar2000/helpers/win32_misc.h new file mode 100644 index 0000000..1e6267f --- /dev/null +++ b/foobar2000/helpers/win32_misc.h @@ -0,0 +1,168 @@ +#pragma once + +#ifdef _WIN32 + + +#include +#include + + +class mutexScope { +public: + mutexScope(HANDLE hMutex_, abort_callback & abort); + ~mutexScope(); +private: + PFC_CLASS_NOT_COPYABLE_EX(mutexScope); + HANDLE hMutex; +}; + +#ifdef FOOBAR2000_DESKTOP_WINDOWS + +class registerclass_scope_delayed { +public: + registerclass_scope_delayed() {} + bool is_registered() const {return m_class != 0;} + void toggle_on(UINT p_style,WNDPROC p_wndproc,int p_clsextra,int p_wndextra,HICON p_icon,HCURSOR p_cursor,HBRUSH p_background,const TCHAR * p_classname,const TCHAR * p_menuname); + void toggle_off(); + ATOM get_class() const {return m_class;} + + ~registerclass_scope_delayed() {toggle_off();} +private: + registerclass_scope_delayed(const registerclass_scope_delayed &) = delete; + const registerclass_scope_delayed & operator=(const registerclass_scope_delayed &) = delete; + + ATOM m_class = 0; +}; + + +typedef CGlobalLockScope CGlobalLock; // compatibility + +class OleInitializeScope { +public: + OleInitializeScope(); + ~OleInitializeScope(); + +private: + PFC_CLASS_NOT_COPYABLE_EX(OleInitializeScope); +}; + +class CoInitializeScope { +public: + CoInitializeScope(); + CoInitializeScope(DWORD params); + ~CoInitializeScope(); +private: + PFC_CLASS_NOT_COPYABLE_EX(CoInitializeScope) +}; + +WORD GetOSVersion(); + +#if _WIN32_WINNT >= 0x501 +#define WS_EX_COMPOSITED_Safe() WS_EX_COMPOSITED +#else +static DWORD WS_EX_COMPOSITED_Safe() { + return (GetOSVersion() < 0x501) ? 0 : 0x02000000L; +} +#endif + + +class CModelessDialogEntry { +public: + CModelessDialogEntry() : m_wnd() {} + CModelessDialogEntry(HWND p_wnd) : m_wnd() {Set(p_wnd);} + ~CModelessDialogEntry() {Set(NULL);} + + void Set(HWND p_new); +private: + PFC_CLASS_NOT_COPYABLE_EX(CModelessDialogEntry); + HWND m_wnd; +}; + +class CDLL { +public: +#ifdef _DEBUG + static LPTOP_LEVEL_EXCEPTION_FILTER _GetEH() { + LPTOP_LEVEL_EXCEPTION_FILTER rv = SetUnhandledExceptionFilter(NULL); + SetUnhandledExceptionFilter(rv); + return rv; + } +#endif + CDLL(const wchar_t * Name) : hMod() { + Load(Name); + } + CDLL() : hMod() {} + void Load(const wchar_t * Name) { + PFC_ASSERT( hMod == NULL ); +#ifdef _DEBUG + auto handlerBefore = _GetEH(); +#endif + WIN32_OP( hMod = LoadLibrary(Name) ); +#ifdef _DEBUG + PFC_ASSERT( handlerBefore == _GetEH() ); +#endif + } + + + ~CDLL() { + if (hMod) FreeLibrary(hMod); + } + template void Bind(funcptr_t & outFunc, const char * name) { + WIN32_OP( outFunc = (funcptr_t)GetProcAddress(hMod, name) ); + } + + HMODULE hMod; + + PFC_CLASS_NOT_COPYABLE_EX(CDLL); +}; + +class winLocalFileScope { +public: + void open(const char * inPath, file::ptr inReader, abort_callback & aborter); + void close(); + + winLocalFileScope() {} + winLocalFileScope(const char * inPath, file::ptr inReader, abort_callback & aborter) : m_isTemp() { + open(inPath, inReader, aborter); + } + + ~winLocalFileScope() { + close(); + } + + const wchar_t * Path() const { return m_path.c_str(); } + bool isTemp() const { return m_isTemp; } +private: + bool m_isTemp = false; + std::wstring m_path; +}; + +#endif // FOOBAR2000_DESKTOP_WINDOWS + + +class CMutex { +public: + CMutex(const TCHAR * name = NULL); + ~CMutex(); + HANDLE Handle() {return m_hMutex;} + static void AcquireByHandle( HANDLE hMutex, abort_callback & aborter ); + void Acquire( abort_callback& aborter ); + void Release(); +private: + CMutex(const CMutex&); void operator=(const CMutex&); + HANDLE m_hMutex; +}; + +class CMutexScope { +public: + CMutexScope(CMutex & mutex, DWORD timeOutMS, const char * timeOutBugMsg); + CMutexScope(CMutex & mutex); + CMutexScope(CMutex & mutex, abort_callback & aborter); + ~CMutexScope(); +private: + CMutexScope(const CMutexScope &); void operator=(const CMutexScope&); + CMutex & m_mutex; +}; + +bool IsWindowsS(); + +#endif // _WIN32 diff --git a/foobar2000/helpers/window_placement_helper.cpp b/foobar2000/helpers/window_placement_helper.cpp new file mode 100644 index 0000000..2583c2e --- /dev/null +++ b/foobar2000/helpers/window_placement_helper.cpp @@ -0,0 +1,231 @@ +#include "stdafx.h" + +#ifdef FOOBAR2000_DESKTOP_WINDOWS + +#include "window_placement_helper.h" + +static bool g_is_enabled() +{ + return standard_config_objects::query_remember_window_positions(); +} + +static BOOL CALLBACK __MonitorEnumProc( + HMONITOR hMonitor, // handle to display monitor + HDC hdcMonitor, // handle to monitor DC + LPRECT lprcMonitor, // monitor intersection rectangle + LPARAM dwData // data + ) { + RECT * clip = (RECT*)dwData; + RECT newclip; + if (UnionRect(&newclip,clip,lprcMonitor)) { + *clip = newclip; + } + return TRUE; +} + +static bool test_rect(const RECT * rc) { + RECT clip = {}; + if (EnumDisplayMonitors(NULL,NULL,__MonitorEnumProc,(LPARAM)&clip)) { + const LONG sanitycheck = 4; + const LONG cwidth = clip.right - clip.left; + const LONG cheight = clip.bottom - clip.top; + + const LONG width = rc->right - rc->left; + const LONG height = rc->bottom - rc->top; + + if (width > cwidth * sanitycheck || height > cheight * sanitycheck) return false; + } + + return MonitorFromRect(rc,MONITOR_DEFAULTTONULL) != NULL; +} + + +bool cfg_window_placement_common::read_from_window(HWND window) +{ + WINDOWPLACEMENT wp = {}; + if (g_is_enabled()) { + wp.length = sizeof(wp); + if (!GetWindowPlacement(window,&wp)) { + PFC_ASSERT(!"GetWindowPlacement fail!"); + memset(&wp,0,sizeof(wp)); + } else { + // bad, breaks with taskbar on top + /*if (wp.showCmd == SW_SHOWNORMAL) { + GetWindowRect(window, &wp.rcNormalPosition); + }*/ + + if ( !IsWindowVisible( window ) ) wp.showCmd = SW_HIDE; + } + /*else + { + if (!IsWindowVisible(window)) wp.showCmd = SW_HIDE; + }*/ + } + m_data = wp; + return m_data.length == sizeof(m_data); +} + +bool cfg_window_placement_common::apply_to_window(HWND window, bool allowHidden) { + bool ret = false; + if (g_is_enabled()) + { + if (m_data.length == sizeof(m_data) && test_rect(&m_data.rcNormalPosition)) + { + if (allowHidden || m_data.showCmd != SW_HIDE) { + if (m_data.showCmd == SW_HIDE && (m_data.flags & WPF_RESTORETOMAXIMIZED)) { + // Special case of hidden-from-maximized + auto fix = m_data; + fix.showCmd = SW_SHOWMINIMIZED; + if (SetWindowPlacement(window, &fix)) { + ShowWindow(window, SW_HIDE); + ret = true; + } + } else { + if (SetWindowPlacement(window, &m_data)) { + ret = true; + } + } + } + } + } + + return ret; +} + +void cfg_window_placement::on_window_creation_silent(HWND window) { + PFC_ASSERT(!m_windows.have_item(window)); + m_windows.add_item(window); +} +bool cfg_window_placement::on_window_creation(HWND window, bool allowHidden) { + + PFC_ASSERT(!m_windows.have_item(window)); + m_windows.add_item(window); + return apply_to_window(window, allowHidden); +} + + +void cfg_window_placement::on_window_destruction(HWND window) +{ + if (m_windows.have_item(window)) + { + read_from_window(window); + m_windows.remove_item(window); + } +} + +void cfg_window_placement_common::get_data_raw(stream_writer* p_stream, abort_callback& p_abort) { + if (m_data.length == sizeof(m_data)) { + p_stream->write_object(&m_data, sizeof(m_data), p_abort); + } +} + +void cfg_window_placement::get_data_raw(stream_writer * p_stream,abort_callback & p_abort) { + if (g_is_enabled()) { + { + t_size n, m = m_windows.get_count(); + for(n=0;nread_object(&temp, sizeof(temp), p_abort); + if (temp.length == sizeof(temp)) m_data = temp; + } +} + + +cfg_window_size::cfg_window_size(const GUID & p_guid) : cfg_var(p_guid), m_width(~0), m_height(~0) {} + +static BOOL SetWindowSize(HWND p_wnd,unsigned p_x,unsigned p_y) +{ + if (p_x != ~0 && p_y != ~0) + return SetWindowPos(p_wnd,0,0,0,p_x,p_y,SWP_NOACTIVATE|SWP_NOMOVE|SWP_NOZORDER); + else + return FALSE; +} + +bool cfg_window_size::on_window_creation(HWND p_wnd) +{ + bool ret = false; + PFC_ASSERT(!m_windows.have_item(p_wnd)); + m_windows.add_item(p_wnd); + + if (g_is_enabled()) + { + if (SetWindowSize(p_wnd,m_width,m_height)) ret = true; + } + + return ret; +} + +void cfg_window_size::on_window_destruction(HWND p_wnd) +{ + if (m_windows.have_item(p_wnd)) + { + read_from_window(p_wnd); + m_windows.remove_item(p_wnd); + } +} + +bool cfg_window_size::read_from_window(HWND p_wnd) +{ + if (g_is_enabled()) + { + RECT r; + if (GetWindowRect(p_wnd,&r)) + { + m_width = r.right - r.left; + m_height = r.bottom - r.top; + return true; + } + else + { + m_width = m_height = ~0; + return false; + } + } + else + { + m_width = m_height = ~0; + return false; + } +} + +void cfg_window_size::get_data_raw(stream_writer * p_stream,abort_callback & p_abort) { + if (g_is_enabled()) { + { + t_size n, m = m_windows.get_count(); + for(n=0;nwrite_lendian_t(m_width,p_abort); + p_stream->write_lendian_t(m_height,p_abort); + } + } +} + +void cfg_window_size::set_data_raw(stream_reader * p_stream,t_size p_sizehint,abort_callback & p_abort) { + if (p_sizehint == 0) return; + t_uint32 width,height; + try { + p_stream->read_lendian_t(width,p_abort); + p_stream->read_lendian_t(height,p_abort); + } catch(exception_io_data) {return;} + + m_width = width; m_height = height; +} +#endif // FOOBAR2000_DESKTOP_WINDOWS diff --git a/foobar2000/helpers/window_placement_helper.h b/foobar2000/helpers/window_placement_helper.h new file mode 100644 index 0000000..5813036 --- /dev/null +++ b/foobar2000/helpers/window_placement_helper.h @@ -0,0 +1,51 @@ +#pragma once + +#ifdef FOOBAR2000_DESKTOP_WINDOWS + +class cfg_window_placement_common : public cfg_var { +public: + cfg_window_placement_common(const GUID& guid) : cfg_var(guid) {} + bool read_from_window(HWND window); + bool apply_to_window(HWND window, bool allowHidden); +protected: + void get_data_raw(stream_writer* p_stream, abort_callback& p_abort) override; + void set_data_raw(stream_reader* p_stream, t_size p_sizehint, abort_callback& p_abort) override; + WINDOWPLACEMENT m_data = {}; +}; + +class cfg_window_placement : public cfg_window_placement_common +{ +public: + bool on_window_creation(HWND window, bool allowHidden = false);//returns true if window position has been changed, false if not + void on_window_creation_silent(HWND window); + void on_window_destruction(HWND window); + cfg_window_placement(const GUID& guid) : cfg_window_placement_common(guid) {} +protected: + void get_data_raw(stream_writer* p_stream, abort_callback& p_abort) override; +private: + pfc::list_hybrid_t m_windows; +}; + +class cfg_window_placement_v2 : public cfg_window_placement_common { +public: + cfg_window_placement_v2(const GUID& guid) : cfg_window_placement_common(guid) {} + // All already in cfg_window_placement_common +}; + + + +class cfg_window_size : public cfg_var +{ +public: + bool on_window_creation(HWND window);//returns true if window position has been changed, false if not + void on_window_destruction(HWND window); + bool read_from_window(HWND window); + void get_data_raw(stream_writer * p_stream,abort_callback & p_abort); + void set_data_raw(stream_reader * p_stream,t_size p_sizehint,abort_callback & p_abort); + cfg_window_size(const GUID & p_guid); +private: + pfc::list_hybrid_t m_windows; + t_uint32 m_width,m_height; +}; + +#endif // FOOBAR2000_DESKTOP_WINDOWS diff --git a/foobar2000/helpers/winmm-types.h b/foobar2000/helpers/winmm-types.h new file mode 100644 index 0000000..0a416b3 --- /dev/null +++ b/foobar2000/helpers/winmm-types.h @@ -0,0 +1,4 @@ +#pragma once + +// Blank header for source compatibility +// WAVEFORMATEX and such are declared here on non Windows \ No newline at end of file diff --git a/foobar2000/helpers/writer_wav.cpp b/foobar2000/helpers/writer_wav.cpp new file mode 100644 index 0000000..bdceccb --- /dev/null +++ b/foobar2000/helpers/writer_wav.cpp @@ -0,0 +1,386 @@ +#include "StdAfx.h" + +#include + +#ifndef _WIN32 +#include "winmm-types.h" +#endif + +#include "writer_wav.h" + + +static const GUID guid_RIFF = pfc::GUID_from_text("66666972-912E-11CF-A5D6-28DB04C10000"); +static const GUID guid_WAVE = pfc::GUID_from_text("65766177-ACF3-11D3-8CD1-00C04F8EDB8A"); +static const GUID guid_FMT = pfc::GUID_from_text("20746D66-ACF3-11D3-8CD1-00C04F8EDB8A"); +static const GUID guid_DATA = pfc::GUID_from_text("61746164-ACF3-11D3-8CD1-00C04F8EDB8A"); + +struct RIFF_chunk_desc { + GUID m_guid; + const char * m_name; +}; + +static const RIFF_chunk_desc RIFF_chunks[] = { + {guid_RIFF, "RIFF"}, + {guid_WAVE, "WAVE"}, + {guid_FMT , "fmt "}, + {guid_DATA, "data"}, +}; + +bool wavWriterSetup_t::needWFXE() const { + if (this->m_bpsValid != this->m_bps) return true; + switch(m_channels) + { + case 1: + return m_channel_mask != audio_chunk::channel_config_mono; + case 2: + return m_channel_mask != audio_chunk::channel_config_stereo; +/* case 4: + m_wfxe = m_setup.m_channel_mask != (audio_chunk::channel_front_left | audio_chunk::channel_front_right | audio_chunk::channel_back_left | audio_chunk::channel_back_right); + break; + case 6: + m_wfxe = m_setup.m_channel_mask != audio_chunk::channel_config_5point1; + break;*/ + default: + return true; + } + +} + +void wavWriterSetup_t::initialize3(const audio_chunk::spec_t & spec, unsigned bps, unsigned bpsValid, bool bFloat, bool bDither, bool bWave64) { + m_bps = bps; + m_bpsValid = bpsValid; + m_samplerate = spec.sampleRate; + m_channels = spec.chanCount; + m_channel_mask = spec.chanMask; + m_float = bFloat; + m_dither = bDither; + m_wave64 = bWave64; +} + +void wavWriterSetup_t::initialize2(const audio_chunk & p_chunk, unsigned p_bps, unsigned p_bpsValid, bool p_float, bool p_dither, bool p_wave64) { + m_bps = p_bps; + m_bpsValid = p_bpsValid; + m_samplerate = p_chunk.get_srate(); + m_channels = p_chunk.get_channels(); + m_channel_mask = p_chunk.get_channel_config(); + m_float = p_float; + m_dither = p_dither; + m_wave64 = p_wave64; +} + +void wavWriterSetup_t::initialize(const audio_chunk & p_chunk, unsigned p_bps, bool p_float, bool p_dither, bool p_wave64) +{ + unsigned bpsValid = p_bps; + unsigned bps = (p_bps + 7) & ~7; + initialize2(p_chunk, bps, bpsValid, p_float, p_dither, p_wave64); +} + +void wavWriterSetup_t::setup_wfx(WAVEFORMATEX & p_wfx) +{ + p_wfx.wFormatTag = m_float ? WAVE_FORMAT_IEEE_FLOAT : WAVE_FORMAT_PCM; + p_wfx.nChannels = m_channels; + p_wfx.nSamplesPerSec = m_samplerate; + p_wfx.nAvgBytesPerSec = (m_bps >> 3) * m_channels * m_samplerate; + p_wfx.nBlockAlign = (m_bps>>3) * m_channels; + p_wfx.wBitsPerSample = m_bps; + p_wfx.cbSize = 0; +} + +void wavWriterSetup_t::setup_wfxe(WAVEFORMATEXTENSIBLE & p_wfxe) +{ + setup_wfx(p_wfxe.Format); + p_wfxe.Format.wFormatTag=WAVE_FORMAT_EXTENSIBLE; + p_wfxe.Format.cbSize=22; + p_wfxe.Samples.wValidBitsPerSample = this->m_bpsValid; + p_wfxe.dwChannelMask = audio_chunk::g_channel_config_to_wfx(m_channel_mask); + p_wfxe.SubFormat = m_float ? KSDATAFORMAT_SUBTYPE_IEEE_FLOAT : KSDATAFORMAT_SUBTYPE_PCM; + +} + +void CWavWriter::writeID(const GUID & id, abort_callback & abort) { + if (is64()) { + m_file->write_object_t(id, abort); + } else { + for(t_size walk = 0; walk < PFC_TABSIZE(RIFF_chunks); ++walk) { + if (id == RIFF_chunks[walk].m_guid) { + m_file->write(RIFF_chunks[walk].m_name, 4, abort); return; + } + } + uBugCheck(); + } +} + +void CWavWriter::writeSize(t_uint64 size, abort_callback & abort) { + if (is64()) { + if (size != ~0) size += 24; + m_file->write_lendian_t(size, abort); + } else { + t_uint32 clipped; + if (size > 0xFFFFFFFF) clipped = 0xFFFFFFFF; + else clipped = (t_uint32) size; + m_file->write_lendian_t(clipped, abort); + } +} + +size_t CWavWriter::align(abort_callback & abort) { + t_uint8 dummy[8] = {}; + const t_uint32 val = is64() ? 8 : 2; + t_filesize pos = m_file->get_position(abort); + t_size delta = (val - (pos%val)) % val; + if (delta > 0) m_file->write(dummy, delta, abort); + return delta; +} + +void CWavWriter::open(const char * p_path, const wavWriterSetup_t & p_setup, abort_callback & p_abort) +{ + service_ptr_t l_file; + filesystem::g_open_write_new(l_file,p_path,p_abort); + open(l_file,p_setup,p_abort); +} + +namespace { +PFC_DECLARE_EXCEPTION(exceptionBadBitDepth, exception_io_data, "Invalid bit depth specified"); +} +void CWavWriter::open(service_ptr_t p_file, const wavWriterSetup_t & p_setup, abort_callback & p_abort) +{ + m_file = p_file; + m_setup = p_setup; + + if (m_setup.m_channels == 0 || m_setup.m_channels > 18 || m_setup.m_channels != audio_chunk::g_count_channels(m_setup.m_channel_mask)) throw exception_io_data(); + + if (!audio_chunk::g_is_valid_sample_rate(m_setup.m_samplerate)) throw exception_io_data(); + + if (m_setup.m_bpsValid > m_setup.m_bps) throw exceptionBadBitDepth(); + + if (m_setup.m_float) + { + if (m_setup.m_bps != 32 && m_setup.m_bps != 64) throw exceptionBadBitDepth(); + if (m_setup.m_bpsValid != m_setup.m_bps) throw exceptionBadBitDepth(); + } + else + { + if (m_setup.m_bps != 8 && m_setup.m_bps != 16 && m_setup.m_bps != 24 && m_setup.m_bps != 32) throw exceptionBadBitDepth(); + if (m_setup.m_bpsValid < 1) throw exceptionBadBitDepth(); + } + + m_wfxe = m_setup.needWFXE(); + + writeID(guid_RIFF, p_abort); + m_offset_fix1 = m_file->get_position(p_abort); + writeSize(~0, p_abort); + + writeID(guid_WAVE, p_abort); + + writeID(guid_FMT, p_abort); + if (m_wfxe) { + writeSize(sizeof(WAVEFORMATEXTENSIBLE),p_abort); + + WAVEFORMATEXTENSIBLE wfxe; + m_setup.setup_wfxe(wfxe); + m_file->write_object(&wfxe,sizeof(wfxe),p_abort); + } else { + writeSize(sizeof(PCMWAVEFORMAT),p_abort); + + WAVEFORMATEX wfx; + m_setup.setup_wfx(wfx); + m_file->write_object(&wfx,/* blah */ sizeof(PCMWAVEFORMAT),p_abort); + } + align(p_abort); + + writeID(guid_DATA, p_abort); + m_offset_fix2 = m_file->get_position(p_abort); + writeSize(~0, p_abort); + m_offset_fix1_delta = m_file->get_position(p_abort) - chunkOverhead(); + + + m_bytes_written = 0; + + if (!m_setup.m_float) + { + m_postprocessor = standard_api_create_t(); + } +} + +void CWavWriter::write_raw( const void * raw, size_t rawSize, abort_callback & p_abort ) { + m_file->write_object(raw,rawSize,p_abort); + m_bytes_written += rawSize; +} + +void CWavWriter::write(const audio_chunk & p_chunk, abort_callback & p_abort) +{ + if (p_chunk.get_channels() != m_setup.m_channels + || p_chunk.get_channel_config() != m_setup.m_channel_mask + || p_chunk.get_srate() != m_setup.m_samplerate + ) throw exception_unexpected_audio_format_change(); + + + if (m_setup.m_float) + { + switch(m_setup.m_bps) + { + case 32: + { +#if audio_sample_size == 32 + t_size bytes = p_chunk.get_sample_count() * p_chunk.get_channels() * sizeof(audio_sample); + write_raw( p_chunk.get_data(),bytes,p_abort ); +#else + enum {tempsize = 256}; + float temp[tempsize]; + t_size todo = p_chunk.get_sample_count() * p_chunk.get_channels(); + const audio_sample * readptr = p_chunk.get_data(); + while(todo > 0) + { + unsigned n,delta = todo; + if (delta > tempsize) delta = tempsize; + for(n=0;nwrite_object_e(p_chunk.get_data(),bytes,p_abort); + m_bytes_written += bytes; + } + break; +#endif + default: + throw exception_io_data(); + } + } + else + { + m_postprocessor->run(p_chunk,m_postprocessor_output,m_setup.m_bpsValid,m_setup.m_bps,m_setup.m_dither,1.0f); + write_raw( m_postprocessor_output.get_ptr(),m_postprocessor_output.get_size(), p_abort ); + } +} + +void CWavWriter::finalize(abort_callback & p_abort) +{ + if (m_file.is_valid()) + { + const size_t alignG = align(p_abort); + + if (m_file->can_seek()) { + m_file->seek(m_offset_fix1,p_abort); + writeSize(m_bytes_written + alignG + m_offset_fix1_delta, p_abort); + m_file->seek(m_offset_fix2,p_abort); + writeSize(m_bytes_written, p_abort); + } + m_file.release(); + } + m_postprocessor.release(); +} + +void CWavWriter::close() +{ + m_file.release(); + m_postprocessor.release(); +} + +audio_chunk::spec_t CWavWriter::get_spec() const { + audio_chunk::spec_t spec = {}; + spec.sampleRate = m_setup.m_samplerate; + spec.chanCount = m_setup.m_channels; + spec.chanMask = m_setup.m_channel_mask; + return spec; +} + +namespace { +class fileWav : public foobar2000_io::file { +public: + size_t read( void * buffer, size_t bytes, abort_callback & aborter ) { + aborter.check(); + uint8_t * out = (uint8_t*) buffer; + size_t ret = 0; + if (m_position < m_header.size()) { + size_t delta = (size_t) ( m_header.size() - m_position ); + if (delta > bytes) delta = bytes; + memcpy( out, &m_header[(size_t)m_position], delta ); + m_position += delta; + out += delta; ret += delta; bytes -= delta; + } + if (bytes > 0) { + m_data->seek( m_position, aborter ); + size_t didRead = m_data->read( out, bytes, aborter ); + m_position += didRead; + ret += didRead; + } + return ret; + } + void write( const void * buffer, size_t bytes, abort_callback & aborter ) { + throw exception_io_denied(); + } + // old fb2k SDK workaround + fileWav( std::vector const & header, file::ptr data) { + m_data = data; + m_position = 0; + m_header = header; + } + fileWav( std::vector && header, file::ptr data) { + m_data = data; + m_position = 0; + m_header = std::move(header); + } + t_filesize get_size(abort_callback & p_abort) { + t_filesize s = m_data->get_size( p_abort ); + if (s != filesize_invalid) s += m_header.size(); + return s; + } + t_filesize get_position(abort_callback & p_abort) { + return m_position; + } + void resize(t_filesize p_size,abort_callback & p_abort) { + throw exception_io_denied(); + } + void seek(t_filesize p_position,abort_callback & p_abort) { + if (p_position > get_size(p_abort)) throw exception_io_seek_out_of_range(); + m_position = p_position; + } + bool can_seek() { + return true; + } + bool get_content_type(pfc::string_base & p_out) { return false; } + void reopen(abort_callback & p_abort) { seek(0, p_abort); } + bool is_remote() { + return m_data->is_remote(); + } + t_filestats get_stats(abort_callback & p_abort) { + t_filestats s = m_data->get_stats( p_abort ); + if (s.m_size != filesize_invalid) s.m_size += m_header.size(); + return s; + } +private: + std::vector m_header; + t_filesize m_position; + file::ptr m_data; +}; +} + +static std::vector makeWavHeader( const wavWriterSetup_t & setup, t_filesize dataSize, abort_callback & aborter ) { + std::vector ret; + file::ptr temp; filesystem::g_open_tempmem( temp, aborter ); + { + CWavWriter w; + w.open( temp, setup, aborter ); + } + const size_t s = pfc::downcast_guarded( temp->get_size( aborter ) ); + if (s > 0) { + ret.resize( s ); + temp->seek( 0, aborter ); + temp->read_object( &ret[0], s, aborter ); + } + return ret; +} + +file::ptr makeLiveWAVFile( const wavWriterSetup_t & setup, file::ptr data ) { + t_filesize size = data->get_size( fb2k::noAbort ); + auto vec = makeWavHeader( setup, size, fb2k::noAbort ); + return new service_impl_t< fileWav >( std::move(vec), data ); +} diff --git a/foobar2000/helpers/writer_wav.h b/foobar2000/helpers/writer_wav.h new file mode 100644 index 0000000..f69d8e8 --- /dev/null +++ b/foobar2000/helpers/writer_wav.h @@ -0,0 +1,53 @@ +#pragma once + +#ifdef _WIN32 +#include +#endif + + +struct wavWriterSetup_t +{ + unsigned m_bps,m_bpsValid,m_samplerate,m_channels,m_channel_mask; + bool m_float,m_dither, m_wave64; + + + void initialize(const audio_chunk & p_chunk,unsigned p_bps,bool p_float,bool p_dither, bool p_wave64 = false); + void initialize2(const audio_chunk & p_chunk,unsigned p_bps, unsigned p_bpsValid,bool p_float,bool p_dither, bool p_wave64 = false); + void initialize3(const audio_chunk::spec_t & spec, unsigned bps, unsigned bpsValid, bool bFloat, bool bDither, bool bWave64 = false); + +#ifdef _WAVEFORMATEX_ + void setup_wfx(WAVEFORMATEX & p_wfx); +#endif +#ifdef _WAVEFORMATEXTENSIBLE_ + void setup_wfxe(WAVEFORMATEXTENSIBLE & p_wfx); +#endif + bool needWFXE() const; +}; + +class CWavWriter +{ +public: + void open(const char * p_path, const wavWriterSetup_t & p_setup, abort_callback & p_abort); + void open(service_ptr_t p_file, const wavWriterSetup_t & p_setup, abort_callback & p_abort); + void write(const audio_chunk & p_chunk,abort_callback & p_abort); + void write_raw( const void * raw, size_t rawSize, abort_callback & p_abort ); + void finalize(abort_callback & p_abort); + void close(); + bool is_open() const { return m_file.is_valid(); } + audio_chunk::spec_t get_spec() const; +private: + size_t align(abort_callback & abort); + void writeSize(t_uint64 size, abort_callback & abort); + bool is64() const {return m_setup.m_wave64;} + t_uint32 chunkOverhead() const {return is64() ? 24 : 8;} + t_uint32 idOverhead() const {return is64() ? 16 : 4;} + void writeID(const GUID & id, abort_callback & abort); + service_ptr_t m_file; + service_ptr_t m_postprocessor; + wavWriterSetup_t m_setup; + bool m_wfxe; + t_uint64 m_offset_fix1,m_offset_fix2,m_offset_fix1_delta,m_bytes_written; + mem_block_container_aligned_incremental_impl<16> m_postprocessor_output; +}; + +file::ptr makeLiveWAVFile( const wavWriterSetup_t & setup, file::ptr data ); diff --git a/foobar2000/shared/audio_math.h b/foobar2000/shared/audio_math.h new file mode 100644 index 0000000..89101f6 --- /dev/null +++ b/foobar2000/shared/audio_math.h @@ -0,0 +1,65 @@ +#ifndef audio_sample_size +#error PFC not included? +#endif + +/* +PROBLEM: +audio_math is implemented in pfc (pfc::audio_math) and in shared.dll (::audio_math) +We must overlay shared.dll methods on top of PFC ones +*/ + +namespace audio_math +{ + //! p_source/p_output can point to same buffer + void SHARED_EXPORT scale(const audio_sample * p_source,t_size p_count,audio_sample * p_output,audio_sample p_scale); + void SHARED_EXPORT convert_to_int16(const audio_sample * p_source,t_size p_count,t_int16 * p_output,audio_sample p_scale); + void SHARED_EXPORT convert_to_int32(const audio_sample * p_source,t_size p_count,t_int32 * p_output,audio_sample p_scale); + audio_sample SHARED_EXPORT convert_to_int16_calculate_peak(const audio_sample * p_source,t_size p_count,t_int16 * p_output,audio_sample p_scale); + void SHARED_EXPORT convert_from_int16(const t_int16 * p_source,t_size p_count,audio_sample * p_output,audio_sample p_scale); + void SHARED_EXPORT convert_from_int32(const t_int32 * p_source,t_size p_count,audio_sample * p_output,audio_sample p_scale); + audio_sample SHARED_EXPORT convert_to_int32_calculate_peak(const audio_sample * p_source,t_size p_count,t_int32 * p_output,audio_sample p_scale); + audio_sample SHARED_EXPORT calculate_peak(const audio_sample * p_source,t_size p_count); + void SHARED_EXPORT kill_denormal(audio_sample * p_buffer,t_size p_count); + void SHARED_EXPORT add_offset(audio_sample * p_buffer,audio_sample p_delta,t_size p_count); +} + +namespace audio_math_shareddll = audio_math; +typedef pfc::audio_math audio_math_pfc; + +// Overlay class, overrides specific pfc::audio_math methods +class fb2k_audio_math : public audio_math_pfc { +public: + static void scale(const audio_sample * p_source,t_size p_count,audio_sample * p_output,audio_sample p_scale) { + audio_math_shareddll::scale(p_source, p_count, p_output, p_scale); + } + static void convert_to_int16(const audio_sample * p_source,t_size p_count,t_int16 * p_output,audio_sample p_scale) { + audio_math_shareddll::convert_to_int16(p_source, p_count, p_output, p_scale); + } + static void convert_to_int32(const audio_sample * p_source,t_size p_count,t_int32 * p_output,audio_sample p_scale) { + audio_math_shareddll::convert_to_int32(p_source, p_count, p_output, p_scale); + } + static audio_sample convert_to_int16_calculate_peak(const audio_sample * p_source,t_size p_count,t_int16 * p_output,audio_sample p_scale) { + return audio_math_shareddll::convert_to_int16_calculate_peak(p_source,p_count,p_output,p_scale); + } + static void convert_from_int16(const t_int16 * p_source,t_size p_count,audio_sample * p_output,audio_sample p_scale) { + audio_math_shareddll::convert_from_int16(p_source,p_count,p_output,p_scale); + } + static void convert_from_int32(const t_int32 * p_source,t_size p_count,audio_sample * p_output,audio_sample p_scale) { + audio_math_shareddll::convert_from_int32(p_source,p_count,p_output,p_scale); + } + static audio_sample convert_to_int32_calculate_peak(const audio_sample * p_source,t_size p_count,t_int32 * p_output,audio_sample p_scale) { + return audio_math_shareddll::convert_to_int32_calculate_peak(p_source,p_count,p_output,p_scale); + } + static audio_sample calculate_peak(const audio_sample * p_source,t_size p_count) { + return audio_math_shareddll::calculate_peak(p_source,p_count); + } + static void kill_denormal(audio_sample * p_buffer,t_size p_count) { + audio_math_shareddll::kill_denormal(p_buffer, p_count); + } + static void add_offset(audio_sample * p_buffer,audio_sample p_delta,t_size p_count) { + audio_math_shareddll::add_offset(p_buffer,p_delta,p_count); + } +}; + +// Anyone trying to talk to audio_math namespace will reach fb2k_audio_math which calls the right thing +#define audio_math fb2k_audio_math diff --git a/foobar2000/shared/fb2kdebug.h b/foobar2000/shared/fb2kdebug.h new file mode 100644 index 0000000..0faa478 --- /dev/null +++ b/foobar2000/shared/fb2kdebug.h @@ -0,0 +1,131 @@ +#pragma once + +// since fb2k 1.5 +namespace fb2k { + class panicHandler { + public: + virtual void onPanic() = 0; + }; +} + +// since fb2k 1.5 +extern "C" +{ + void SHARED_EXPORT uAddPanicHandler(fb2k::panicHandler*); + void SHARED_EXPORT uRemovePanicHandler(fb2k::panicHandler*); +} + +extern "C" +{ + LPCSTR SHARED_EXPORT uGetCallStackPath(); + LONG SHARED_EXPORT uExceptFilterProc(LPEXCEPTION_POINTERS param); + PFC_NORETURN void SHARED_EXPORT uBugCheck(); + +#ifdef _DEBUG + void SHARED_EXPORT fb2kDebugSelfTest(); +#endif +} + +class uCallStackTracker +{ + t_size param; +public: + explicit SHARED_EXPORT uCallStackTracker(const char * name); + SHARED_EXPORT ~uCallStackTracker(); +}; + + + +#if FB2K_SUPPORT_CRASH_LOGS +#define TRACK_CALL(X) uCallStackTracker TRACKER__##X(#X) +#define TRACK_CALL_TEXT(X) uCallStackTracker TRACKER__BLAH(X) +#define TRACK_CODE(description,code) {uCallStackTracker __call_tracker(description); code;} +#else +#define TRACK_CALL(X) +#define TRACK_CALL_TEXT(X) +#define TRACK_CODE(description,code) {code;} +#endif + +#if FB2K_SUPPORT_CRASH_LOGS +static int uExceptFilterProc_inline(LPEXCEPTION_POINTERS param) { + uDumpCrashInfo(param); + TerminateProcess(GetCurrentProcess(), 0); + return 0;// never reached +} +#endif + + +#define FB2K_DYNAMIC_ASSERT( X ) { if (!(X)) uBugCheck(); } + +#define __except_instacrash __except(uExceptFilterProc(GetExceptionInformation())) +#if FB2K_SUPPORT_CRASH_LOGS +#define fb2k_instacrash_scope(X) __try { X; } __except_instacrash {} +#else +#define fb2k_instacrash_scope(X) {X;} +#endif + +PFC_NORETURN static void fb2kCriticalError(DWORD code, DWORD argCount = 0, const ULONG_PTR * args = NULL) { + fb2k_instacrash_scope( RaiseException(code,EXCEPTION_NONCONTINUABLE,argCount,args); ); +} +PFC_NORETURN static void fb2kDeadlock() { + fb2kCriticalError(0x63d81b66); +} + +static void fb2kWaitForCompletion(HANDLE hEvent) { + switch(WaitForSingleObject(hEvent, INFINITE)) { + case WAIT_OBJECT_0: + return; + default: + uBugCheck(); + } +} + +static void fb2kWaitForThreadCompletion(HANDLE hWaitFor, DWORD threadID) { + (void) threadID; + switch(WaitForSingleObject(hWaitFor, INFINITE)) { + case WAIT_OBJECT_0: + return; + default: + uBugCheck(); + } +} + +static void fb2kWaitForThreadCompletion2(HANDLE hWaitFor, HANDLE hThread, DWORD threadID) { + (void) threadID; + switch(WaitForSingleObject(hWaitFor, INFINITE)) { + case WAIT_OBJECT_0: + return; + default: + uBugCheck(); + } +} + + +static void __cdecl _OverrideCrtAbort_handler(int signal) { + const ULONG_PTR args[] = {(ULONG_PTR)signal}; + RaiseException(0x6F8E1DC8 /* random GUID */, EXCEPTION_NONCONTINUABLE, _countof(args), args); +} + +static void __cdecl _PureCallHandler() { + RaiseException(0xf6538887 /* random GUID */, EXCEPTION_NONCONTINUABLE, 0, 0); +} + +static void _InvalidParameter( + const wchar_t * expression, + const wchar_t * function, + const wchar_t * file, + unsigned int line, + uintptr_t pReserved +) { + (void)pReserved; (void) line; (void) file; (void) function; (void) expression; + RaiseException(0xd142b808 /* random GUID */, EXCEPTION_NONCONTINUABLE, 0, 0); +} + +static void OverrideCrtAbort() { + const int signals[] = {SIGINT, SIGTERM, SIGBREAK, SIGABRT}; + for(size_t i=0; i<_countof(signals); i++) signal(signals[i], _OverrideCrtAbort_handler); + _set_abort_behavior(0, UINT_MAX); + + _set_purecall_handler(_PureCallHandler); + _set_invalid_parameter_handler(_InvalidParameter); +} diff --git a/foobar2000/shared/filedialogs.h b/foobar2000/shared/filedialogs.h new file mode 100644 index 0000000..a5b2be8 --- /dev/null +++ b/foobar2000/shared/filedialogs.h @@ -0,0 +1,7 @@ +class uGetOpenFileNameMultiResult_impl : public uGetOpenFileNameMultiResult { + pfc::list_t m_data; +public: + void AddItem(pfc::stringp param) {m_data.add_item(param);} + t_size get_count() const {return m_data.get_count();} + void get_item_ex(const char * & out,t_size n) const {out = m_data[n].ptr();} +}; diff --git a/foobar2000/shared/shared.h b/foobar2000/shared/shared.h new file mode 100644 index 0000000..6313520 --- /dev/null +++ b/foobar2000/shared/shared.h @@ -0,0 +1,566 @@ +#ifndef _SHARED_DLL__SHARED_H_ +#define _SHARED_DLL__SHARED_H_ + +#include "../../pfc/pfc.h" + +#include + +// Global flag - whether it's OK to leak static objects as they'll be released anyway by process death +#define FB2K_LEAK_STATIC_OBJECTS PFC_LEAK_STATIC_OBJECTS + +#define FB2K_TARGET_MICROSOFT_STORE 0 +#define FB2K_SUPPORT_CRASH_LOGS (!FB2K_TARGET_MICROSOFT_STORE) + +#include + +#ifndef WIN32 +#error N/A +#endif + +#ifndef STRICT +#define STRICT +#endif + +#include +#include +#include +#include +//#include +#include + +#ifndef NOTHROW +#ifdef _MSC_VER +#define NOTHROW __declspec(nothrow) +#else +#define NOTHROW +#endif +#endif + +#define SHARED_API /*NOTHROW*/ __stdcall + +#ifndef SHARED_EXPORTS +#define SHARED_EXPORT __declspec(dllimport) SHARED_API +#else +#define SHARED_EXPORT __declspec(dllexport) SHARED_API +#endif + +extern "C" { + +//SHARED_EXPORT BOOL IsUnicode(); +#ifdef UNICODE +#define IsUnicode() 1 +#else +#define IsUnicode() 0 +#endif + +LRESULT SHARED_EXPORT uSendMessageText(HWND wnd,UINT msg,WPARAM wp,const char * text); +LRESULT SHARED_EXPORT uSendDlgItemMessageText(HWND wnd,UINT id,UINT msg,WPARAM wp,const char * text); +BOOL SHARED_EXPORT uGetWindowText(HWND wnd,pfc::string_base & out); +BOOL SHARED_EXPORT uSetWindowText(HWND wnd,const char * p_text); +BOOL SHARED_EXPORT uSetWindowTextEx(HWND wnd,const char * p_text,unsigned p_text_length); +BOOL SHARED_EXPORT uGetDlgItemText(HWND wnd,UINT id,pfc::string_base & out); +BOOL SHARED_EXPORT uSetDlgItemText(HWND wnd,UINT id,const char * p_text); +BOOL SHARED_EXPORT uSetDlgItemTextEx(HWND wnd,UINT id,const char * p_text,unsigned p_text_length); +BOOL SHARED_EXPORT uBrowseForFolder(HWND parent,const char * title,pfc::string_base & out); +BOOL SHARED_EXPORT uBrowseForFolderWithFile(HWND parent,const char * title,pfc::string_base & out,const char * p_file_to_find); +int SHARED_EXPORT uMessageBox(HWND wnd,const char * text,const char * caption,UINT type); +void SHARED_EXPORT uOutputDebugString(const char * msg); +BOOL SHARED_EXPORT uAppendMenu(HMENU menu,UINT flags,UINT_PTR id,const char * content); +BOOL SHARED_EXPORT uInsertMenu(HMENU menu,UINT position,UINT flags,UINT_PTR id,const char * content); +int SHARED_EXPORT uStringCompare(const char * elem1, const char * elem2); +int SHARED_EXPORT uCharCompare(t_uint32 p_char1,t_uint32 p_char2); +int SHARED_EXPORT uStringCompare_ConvertNumbers(const char * elem1,const char * elem2); +HINSTANCE SHARED_EXPORT uLoadLibrary(const char * name); +HANDLE SHARED_EXPORT uCreateEvent(LPSECURITY_ATTRIBUTES lpEventAttributes,BOOL bManualReset,BOOL bInitialState, const char * lpName); +HANDLE SHARED_EXPORT GetInfiniteWaitEvent(); +DWORD SHARED_EXPORT uGetModuleFileName(HMODULE hMod,pfc::string_base & out); +BOOL SHARED_EXPORT uSetClipboardString(const char * ptr); +BOOL SHARED_EXPORT uGetClipboardString(pfc::string_base & out); +BOOL SHARED_EXPORT uSetClipboardRawData(UINT format,const void * ptr,t_size size);//does not empty the clipboard +BOOL SHARED_EXPORT uGetClassName(HWND wnd,pfc::string_base & out); +t_size SHARED_EXPORT uCharLength(const char * src); +BOOL SHARED_EXPORT uDragQueryFile(HDROP hDrop,UINT idx,pfc::string_base & out); +UINT SHARED_EXPORT uDragQueryFileCount(HDROP hDrop); +BOOL SHARED_EXPORT uGetTextExtentPoint32(HDC dc,const char * text,UINT cb,LPSIZE size);//note, cb is number of bytes, not actual unicode characters in the string (read: plain strlen() will do) +BOOL SHARED_EXPORT uExtTextOut(HDC dc,int x,int y,UINT flags,const RECT * rect,const char * text,UINT cb,const int * lpdx); +BOOL SHARED_EXPORT uTextOutColors(HDC dc,const char * src,UINT len,int x,int y,const RECT * clip,BOOL is_selected,DWORD default_color); +BOOL SHARED_EXPORT uTextOutColorsTabbed(HDC dc,const char * src,UINT src_len,const RECT * item,int border,const RECT * clip,BOOL selected,DWORD default_color,BOOL use_columns); +UINT SHARED_EXPORT uGetTextHeight(HDC dc); +UINT SHARED_EXPORT uGetFontHeight(HFONT font); +BOOL SHARED_EXPORT uChooseColor(DWORD * p_color,HWND parent,DWORD * p_custom_colors); +HCURSOR SHARED_EXPORT uLoadCursor(HINSTANCE hIns,const char * name); +HICON SHARED_EXPORT uLoadIcon(HINSTANCE hIns,const char * name); +HMENU SHARED_EXPORT uLoadMenu(HINSTANCE hIns,const char * name); +BOOL SHARED_EXPORT uGetEnvironmentVariable(const char * name,pfc::string_base & out); +HMODULE SHARED_EXPORT uGetModuleHandle(const char * name); +UINT SHARED_EXPORT uRegisterWindowMessage(const char * name); +BOOL SHARED_EXPORT uMoveFile(const char * src,const char * dst); +BOOL SHARED_EXPORT uDeleteFile(const char * fn); +DWORD SHARED_EXPORT uGetFileAttributes(const char * fn); +BOOL SHARED_EXPORT uFileExists(const char * fn); +BOOL SHARED_EXPORT uRemoveDirectory(const char * fn); +HANDLE SHARED_EXPORT uCreateFile(const char * p_path,DWORD p_access,DWORD p_sharemode,LPSECURITY_ATTRIBUTES p_security_attributes,DWORD p_createmode,DWORD p_flags,HANDLE p_template); +HANDLE SHARED_EXPORT uCreateFileMapping(HANDLE hFile,LPSECURITY_ATTRIBUTES lpFileMappingAttributes,DWORD flProtect,DWORD dwMaximumSizeHigh,DWORD dwMaximumSizeLow,const char * lpName); +BOOL SHARED_EXPORT uCreateDirectory(const char * fn,LPSECURITY_ATTRIBUTES blah); +HANDLE SHARED_EXPORT uCreateMutex(LPSECURITY_ATTRIBUTES blah,BOOL bInitialOwner,const char * name); +BOOL SHARED_EXPORT uGetLongPathName(const char * name,pfc::string_base & out);//may just fail to work on old windows versions, present on win98/win2k+ +BOOL SHARED_EXPORT uGetFullPathName(const char * name,pfc::string_base & out); +BOOL SHARED_EXPORT uSearchPath(const char * path, const char * filename, const char * extension, pfc::string_base & p_out); +BOOL SHARED_EXPORT uFixPathCaps(const char * path,pfc::string_base & p_out); +//BOOL SHARED_EXPORT uFixPathCapsQuick(const char * path,pfc::string_base & p_out); +void SHARED_EXPORT uGetCommandLine(pfc::string_base & out); +BOOL SHARED_EXPORT uGetTempPath(pfc::string_base & out); +BOOL SHARED_EXPORT uGetTempFileName(const char * path_name,const char * prefix,UINT unique,pfc::string_base & out); +BOOL SHARED_EXPORT uGetOpenFileName(HWND parent,const char * p_ext_mask,unsigned def_ext_mask,const char * p_def_ext,const char * p_title,const char * p_directory,pfc::string_base & p_filename,BOOL b_save); +//note: uGetOpenFileName extension mask uses | as separator, not null +HANDLE SHARED_EXPORT uLoadImage(HINSTANCE hIns,const char * name,UINT type,int x,int y,UINT flags); +UINT SHARED_EXPORT uRegisterClipboardFormat(const char * name); +BOOL SHARED_EXPORT uGetClipboardFormatName(UINT format,pfc::string_base & out); +BOOL SHARED_EXPORT uFormatSystemErrorMessage(pfc::string_base & p_out,DWORD p_code); + +HANDLE SHARED_EXPORT uSortStringCreate(const char * src); +int SHARED_EXPORT uSortStringCompare(HANDLE string1,HANDLE string2); +int SHARED_EXPORT uSortStringCompareEx(HANDLE string1,HANDLE string2,DWORD flags);//flags - see win32 CompareString +int SHARED_EXPORT uSortPathCompare(HANDLE string1,HANDLE string2); +void SHARED_EXPORT uSortStringFree(HANDLE string); + +// New in 1.1.12 +HANDLE SHARED_EXPORT CreateFileAbortable( __in LPCWSTR lpFileName, + __in DWORD dwDesiredAccess, + __in DWORD dwShareMode, + __in_opt LPSECURITY_ATTRIBUTES lpSecurityAttributes, + __in DWORD dwCreationDisposition, + __in DWORD dwFlagsAndAttributes, + // Template file handle NOT supported. + __in_opt HANDLE hAborter + ); + + +int SHARED_EXPORT uCompareString(DWORD flags,const char * str1,unsigned len1,const char * str2,unsigned len2); + +class NOVTABLE uGetOpenFileNameMultiResult : public pfc::list_base_const_t +{ +public: + inline t_size GetCount() {return get_count();} + inline const char * GetFileName(t_size index) {return get_item(index);} + virtual ~uGetOpenFileNameMultiResult() {} +}; + +typedef uGetOpenFileNameMultiResult * puGetOpenFileNameMultiResult; + +puGetOpenFileNameMultiResult SHARED_EXPORT uGetOpenFileNameMulti(HWND parent,const char * p_ext_mask,unsigned def_ext_mask,const char * p_def_ext,const char * p_title,const char * p_directory); + +// new in fb2k 1.1.1 +puGetOpenFileNameMultiResult SHARED_EXPORT uBrowseForFolderEx(HWND parent,const char * title, const char * initPath); +puGetOpenFileNameMultiResult SHARED_EXPORT uEvalKnownFolder(const GUID& id); + + +class NOVTABLE uFindFile +{ +protected: + uFindFile() {} +public: + virtual BOOL FindNext()=0; + virtual const char * GetFileName()=0; + virtual t_uint64 GetFileSize()=0; + virtual DWORD GetAttributes()=0; + virtual FILETIME GetCreationTime()=0; + virtual FILETIME GetLastAccessTime()=0; + virtual FILETIME GetLastWriteTime()=0; + virtual ~uFindFile() {}; + inline bool IsDirectory() {return (GetAttributes() & FILE_ATTRIBUTE_DIRECTORY) ? true : false;} +}; + +typedef uFindFile * puFindFile; + +puFindFile SHARED_EXPORT uFindFirstFile(const char * path); + +HINSTANCE SHARED_EXPORT uShellExecute(HWND wnd,const char * oper,const char * file,const char * params,const char * dir,int cmd); +HWND SHARED_EXPORT uCreateStatusWindow(LONG style,const char * text,HWND parent,UINT id); + +BOOL SHARED_EXPORT uShellNotifyIcon(DWORD dwMessage,HWND wnd,UINT id,UINT callbackmsg,HICON icon,const char * tip); +BOOL SHARED_EXPORT uShellNotifyIconEx(DWORD dwMessage,HWND wnd,UINT id,UINT callbackmsg,HICON icon,const char * tip,const char * balloon_title,const char * balloon_msg); + +HWND SHARED_EXPORT uCreateWindowEx(DWORD dwExStyle,const char * lpClassName,const char * lpWindowName,DWORD dwStyle,int x,int y,int nWidth,int nHeight,HWND hWndParent,HMENU hMenu,HINSTANCE hInstance,LPVOID lpParam); + +BOOL SHARED_EXPORT uGetSystemDirectory(pfc::string_base & out); +BOOL SHARED_EXPORT uGetWindowsDirectory(pfc::string_base & out); +BOOL SHARED_EXPORT uSetCurrentDirectory(const char * path); +BOOL SHARED_EXPORT uGetCurrentDirectory(pfc::string_base & out); +BOOL SHARED_EXPORT uExpandEnvironmentStrings(const char * src,pfc::string_base & out); +BOOL SHARED_EXPORT uGetUserName(pfc::string_base & out); +BOOL SHARED_EXPORT uGetShortPathName(const char * src,pfc::string_base & out); + +HSZ SHARED_EXPORT uDdeCreateStringHandle(DWORD ins,const char * src); +BOOL SHARED_EXPORT uDdeQueryString(DWORD ins,HSZ hsz,pfc::string_base & out); +UINT SHARED_EXPORT uDdeInitialize(LPDWORD pidInst,PFNCALLBACK pfnCallback,DWORD afCmd,DWORD ulRes); +BOOL SHARED_EXPORT uDdeAccessData_Text(HDDEDATA data,pfc::string_base & out); + +HIMAGELIST SHARED_EXPORT uImageList_LoadImage(HINSTANCE hi, const char * lpbmp, int cx, int cGrow, COLORREF crMask, UINT uType, UINT uFlags); + +#define uDdeFreeStringHandle DdeFreeStringHandle +#define uDdeCmpStringHandles DdeCmpStringHandles +#define uDdeKeepStringHandle DdeKeepStringHandle +#define uDdeUninitialize DdeUninitialize +#define uDdeNameService DdeNameService +#define uDdeFreeDataHandle DdeFreeDataHandle + + +typedef TVINSERTSTRUCTA uTVINSERTSTRUCT; + +HTREEITEM SHARED_EXPORT uTreeView_InsertItem(HWND wnd,const uTVINSERTSTRUCT * param); +LPARAM SHARED_EXPORT uTreeView_GetUserData(HWND wnd,HTREEITEM item); +bool SHARED_EXPORT uTreeView_GetText(HWND wnd,HTREEITEM item,pfc::string_base & out); + +#define uSetWindowsHookEx SetWindowsHookEx +#define uUnhookWindowsHookEx UnhookWindowsHookEx +#define uCallNextHookEx CallNextHookEx + +typedef TCITEMA uTCITEM; +int SHARED_EXPORT uTabCtrl_InsertItem(HWND wnd,t_size idx,const uTCITEM * item); +int SHARED_EXPORT uTabCtrl_SetItem(HWND wnd,t_size idx,const uTCITEM * item); + +int SHARED_EXPORT uGetKeyNameText(LONG lparam,pfc::string_base & out); + +void SHARED_EXPORT uFixAmpersandChars(const char * src,pfc::string_base & out);//for notification area icon +void SHARED_EXPORT uFixAmpersandChars_v2(const char * src,pfc::string_base & out);//for other controls + +#if FB2K_SUPPORT_CRASH_LOGS +t_size SHARED_EXPORT uPrintCrashInfo(LPEXCEPTION_POINTERS param,const char * extrainfo,char * out); +enum {uPrintCrashInfo_max_length = 1024}; + +void SHARED_EXPORT uPrintCrashInfo_Init(const char * name);//called only by the exe on startup +void SHARED_EXPORT uPrintCrashInfo_SetComponentList(const char * p_info);//called only by the exe on startup +void SHARED_EXPORT uPrintCrashInfo_AddEnvironmentInfo(const char * p_info);//called only by the exe on startup +void SHARED_EXPORT uPrintCrashInfo_SetDumpPath(const char * name);//called only by the exe on startup + + +#endif // FB2K_SUPPORT_CRASH_LOGS + +void SHARED_EXPORT uDumpCrashInfo(LPEXCEPTION_POINTERS param); + +void SHARED_EXPORT uPrintCrashInfo_OnEvent(const char * message, t_size length); + +BOOL SHARED_EXPORT uListBox_GetText(HWND listbox,UINT index,pfc::string_base & out); + +void SHARED_EXPORT uPrintfV(pfc::string_base & out,const char * fmt,va_list arglist); +static inline void uPrintf(pfc::string_base & out,const char * fmt,...) {va_list list;va_start(list,fmt);uPrintfV(out,fmt,list);va_end(list);} + + +class NOVTABLE uResource +{ +public: + virtual const void * GetPointer() = 0; + virtual unsigned GetSize() = 0; + virtual ~uResource() {} +}; + +typedef uResource* puResource; + +puResource SHARED_EXPORT uLoadResource(HMODULE hMod,const char * name,const char * type,WORD wLang = MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL) ); +puResource SHARED_EXPORT LoadResourceEx(HMODULE hMod,const TCHAR * name,const TCHAR * type,WORD wLang = MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL) ); +HRSRC SHARED_EXPORT uFindResource(HMODULE hMod,const char * name,const char * type,WORD wLang = MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL) ); + +BOOL SHARED_EXPORT uLoadString(HINSTANCE ins,UINT id,pfc::string_base & out); + +UINT SHARED_EXPORT uCharLower(UINT c); +UINT SHARED_EXPORT uCharUpper(UINT c); + +BOOL SHARED_EXPORT uGetMenuString(HMENU menu,UINT id,pfc::string_base & out,UINT flag); +BOOL SHARED_EXPORT uModifyMenu(HMENU menu,UINT id,UINT flags,UINT newitem,const char * data); +UINT SHARED_EXPORT uGetMenuItemType(HMENU menu,UINT position); + + +// New in 1.3.4 +// Load a system library safely - forcibly look in system directories, not elsewhere. +HMODULE SHARED_EXPORT LoadSystemLibrary(const TCHAR * name); +}//extern "C" + +static inline void uAddDebugEvent(const char * msg) {uPrintCrashInfo_OnEvent(msg, strlen(msg));} + +inline char * uCharNext(char * src) {return src+uCharLength(src);} +inline const char * uCharNext(const char * src) {return src+uCharLength(src);} + + +class string_utf8_from_window +{ +public: + string_utf8_from_window(HWND wnd) + { + uGetWindowText(wnd,m_data); + } + string_utf8_from_window(HWND wnd,UINT id) + { + uGetDlgItemText(wnd,id,m_data); + } + inline operator const char * () const {return m_data.get_ptr();} + inline t_size length() const {return m_data.length();} + inline bool is_empty() const {return length() == 0;} + inline const char * get_ptr() const {return m_data.get_ptr();} +private: + pfc::string8 m_data; +}; + +static pfc::string uGetWindowText(HWND wnd) { + pfc::string8 temp; + if (!uGetWindowText(wnd,temp)) return ""; + return temp.toString(); +} +static pfc::string uGetDlgItemText(HWND wnd,UINT id) { + pfc::string8 temp; + if (!uGetDlgItemText(wnd,id,temp)) return ""; + return temp.toString(); +} + +#define uMAKEINTRESOURCE(x) ((const char*)LOWORD(x)) + +//other + +#define uIsDialogMessage IsDialogMessage +#define uGetMessage GetMessage +#define uPeekMessage PeekMessage +#define uDispatchMessage DispatchMessage + +#define uCallWindowProc CallWindowProc +#define uDefWindowProc DefWindowProc +#define uGetWindowLong GetWindowLong +#define uSetWindowLong SetWindowLong + +#define uEndDialog EndDialog +#define uDestroyWindow DestroyWindow +#define uGetDlgItem GetDlgItem +#define uEnableWindow EnableWindow +#define uGetDlgItemInt GetDlgItemInt +#define uSetDlgItemInt SetDlgItemInt + +#define uSendMessage SendMessage +#define uSendDlgItemMessage SendDlgItemMessage +#define uSendMessageTimeout SendMessageTimeout +#define uSendNotifyMessage SendNotifyMessage +#define uSendMessageCallback SendMessageCallback +#define uPostMessage PostMessage +#define uPostThreadMessage PostThreadMessage + + +class uStringPrintf +{ +public: + inline explicit uStringPrintf(const char * fmt,...) + { + va_list list; + va_start(list,fmt); + uPrintfV(m_data,fmt,list); + va_end(list); + } + inline operator const char * () const {return m_data.get_ptr();} + inline t_size length() const {return m_data.length();} + inline bool is_empty() const {return length() == 0;} + inline const char * get_ptr() const {return m_data.get_ptr();} + const char * toString() const {return get_ptr();} +private: + pfc::string8_fastalloc m_data; +}; +#pragma deprecated(uStringPrintf, uPrintf, uPrintfV) + +inline LRESULT uButton_SetCheck(HWND wnd,UINT id,bool state) {return uSendDlgItemMessage(wnd,id,BM_SETCHECK,state ? BST_CHECKED : BST_UNCHECKED,0); } +inline bool uButton_GetCheck(HWND wnd,UINT id) {return uSendDlgItemMessage(wnd,id,BM_GETCHECK,0,0) == BST_CHECKED;} + + + +extern "C" { +int SHARED_EXPORT stricmp_utf8(const char * p1,const char * p2) throw(); +int SHARED_EXPORT stricmp_utf8_ex(const char * p1,t_size len1,const char * p2,t_size len2) throw(); +int SHARED_EXPORT stricmp_utf8_stringtoblock(const char * p1,const char * p2,t_size p2_bytes) throw(); +int SHARED_EXPORT stricmp_utf8_partial(const char * p1,const char * p2,t_size num = ~0) throw(); +int SHARED_EXPORT stricmp_utf8_max(const char * p1,const char * p2,t_size p1_bytes) throw(); +t_size SHARED_EXPORT uReplaceStringAdd(pfc::string_base & out,const char * src,t_size src_len,const char * s1,t_size len1,const char * s2,t_size len2,bool casesens); +t_size SHARED_EXPORT uReplaceCharAdd(pfc::string_base & out,const char * src,t_size src_len,unsigned c1,unsigned c2,bool casesens); +//all lengths in uReplaceString functions are optional, set to -1 if parameters is a simple null-terminated string +void SHARED_EXPORT uAddStringLower(pfc::string_base & out,const char * src,t_size len = ~0); +void SHARED_EXPORT uAddStringUpper(pfc::string_base & out,const char * src,t_size len = ~0); +} + +class comparator_stricmp_utf8 { +public: + static int compare(const char * p_string1,const char * p_string2) throw() {return stricmp_utf8(p_string1,p_string2);} +}; + +inline void uStringLower(pfc::string_base & out,const char * src,t_size len = ~0) {out.reset();uAddStringLower(out,src,len);} +inline void uStringUpper(pfc::string_base & out,const char * src,t_size len = ~0) {out.reset();uAddStringUpper(out,src,len);} + +inline t_size uReplaceString(pfc::string_base & out,const char * src,t_size src_len,const char * s1,t_size len1,const char * s2,t_size len2,bool casesens) +{ + out.reset(); + return uReplaceStringAdd(out,src,src_len,s1,len1,s2,len2,casesens); +} + +inline t_size uReplaceChar(pfc::string_base & out,const char * src,t_size src_len,unsigned c1,unsigned c2,bool casesens) +{ + out.reset(); + return uReplaceCharAdd(out,src,src_len,c1,c2,casesens); +} + +class string_lower +{ +public: + explicit string_lower(const char * ptr,t_size p_count = ~0) {uAddStringLower(m_data,ptr,p_count);} + inline operator const char * () const {return m_data.get_ptr();} + inline t_size length() const {return m_data.length();} + inline bool is_empty() const {return length() == 0;} + inline const char * get_ptr() const {return m_data.get_ptr();} +private: + pfc::string8 m_data; +}; + +class string_upper +{ +public: + explicit string_upper(const char * ptr,t_size p_count = ~0) {uAddStringUpper(m_data,ptr,p_count);} + inline operator const char * () const {return m_data.get_ptr();} + inline t_size length() const {return m_data.length();} + inline bool is_empty() const {return length() == 0;} + inline const char * get_ptr() const {return m_data.get_ptr();} +private: + pfc::string8 m_data; +}; + + +inline BOOL uGetLongPathNameEx(const char * name,pfc::string_base & out) +{ + if (uGetLongPathName(name,out)) return TRUE; + return uGetFullPathName(name,out); +} + +struct t_font_description +{ + enum + { + m_facename_length = LF_FACESIZE*2, + m_height_dpi = 480, + }; + + t_uint32 m_height; + t_uint32 m_weight; + t_uint8 m_italic; + t_uint8 m_charset; + char m_facename[m_facename_length]; + + bool operator==(const t_font_description & other) const {return g_equals(*this, other);} + bool operator!=(const t_font_description & other) const {return !g_equals(*this, other);} + + static bool g_equals(const t_font_description & v1, const t_font_description & v2) { + return v1.m_height == v2.m_height && v1.m_weight == v2.m_weight && v1.m_italic == v2.m_italic && v1.m_charset == v2.m_charset && pfc::strcmp_ex(v1.m_facename, m_facename_length, v2.m_facename, m_facename_length) == 0; + } + + HFONT SHARED_EXPORT create() const; + bool SHARED_EXPORT popup_dialog(HWND p_parent); + void SHARED_EXPORT from_font(HFONT p_font); + static t_font_description SHARED_EXPORT g_from_font(HFONT p_font); + static t_font_description SHARED_EXPORT g_from_logfont(LOGFONT const & lf); + static t_font_description SHARED_EXPORT g_from_system(int id = TMT_MENUFONT); + + template void to_stream(t_stream p_stream,t_abort & p_abort) const; + template void from_stream(t_stream p_stream,t_abort & p_abort); +}; + +/* relevant types not yet defined here */ template void t_font_description::to_stream(t_stream p_stream,t_abort & p_abort) const { + p_stream->write_lendian_t(m_height,p_abort); + p_stream->write_lendian_t(m_weight,p_abort); + p_stream->write_lendian_t(m_italic,p_abort); + p_stream->write_lendian_t(m_charset,p_abort); + p_stream->write_string(m_facename,PFC_TABSIZE(m_facename),p_abort); +} + +/* relevant types not yet defined here */ template void t_font_description::from_stream(t_stream p_stream,t_abort & p_abort) { + p_stream->read_lendian_t(m_height,p_abort); + p_stream->read_lendian_t(m_weight,p_abort); + p_stream->read_lendian_t(m_italic,p_abort); + p_stream->read_lendian_t(m_charset,p_abort); + pfc::string8 temp; + p_stream->read_string(temp,p_abort); + strncpy_s(m_facename,temp,PFC_TABSIZE(m_facename)); +} + + +struct t_modal_dialog_entry +{ + HWND m_wnd_to_poke; + bool m_in_use; +}; + +extern "C" { + void SHARED_EXPORT ModalDialog_Switch(t_modal_dialog_entry & p_entry); + void SHARED_EXPORT ModalDialog_PokeExisting(); + bool SHARED_EXPORT ModalDialog_CanCreateNew(); + + HWND SHARED_EXPORT FindOwningPopup(HWND p_wnd); + void SHARED_EXPORT PokeWindow(HWND p_wnd); +}; + +static bool ModalDialogPrologue() { + bool rv = ModalDialog_CanCreateNew(); + if (!rv) ModalDialog_PokeExisting(); + return rv; +} + +//! The purpose of modal_dialog_scope is to help to avoid the modal dialog recursion problem. Current toplevel modal dialog handle is stored globally, so when creation of a new modal dialog is blocked, it can be activated to indicate the reason for the task being blocked. +class modal_dialog_scope { +public: + //! This constructor initializes the modal dialog scope with specified dialog handle. + inline modal_dialog_scope(HWND p_wnd) throw() : m_initialized(false) {initialize(p_wnd);} + //! This constructor leaves the scope uninitialized (you can call initialize() later with your window handle). + inline modal_dialog_scope() throw() : m_initialized(false) {} + inline ~modal_dialog_scope() throw() {deinitialize();} + + //! Returns whether creation of a new modal dialog is allowed (false when there's another one active).\n + //! NOTE: when calling context is already inside a modal dialog that you own, you should not be checking this before creating a new modal dialog. + inline static bool can_create() throw(){return ModalDialog_CanCreateNew();} + //! Activates the top-level modal dialog existing, if one exists. + inline static void poke_existing() throw() {ModalDialog_PokeExisting();} + + //! Initializes the scope with specified window handle. + void initialize(HWND p_wnd) throw() + { + if (!m_initialized) + { + m_initialized = true; + m_entry.m_in_use = true; + m_entry.m_wnd_to_poke = p_wnd; + ModalDialog_Switch(m_entry); + } + } + + void deinitialize() throw() + { + if (m_initialized) + { + ModalDialog_Switch(m_entry); + m_initialized = false; + } + } + + + +private: + modal_dialog_scope(const modal_dialog_scope & p_scope) = delete; + const modal_dialog_scope & operator=(const modal_dialog_scope &) = delete; + + t_modal_dialog_entry m_entry; + + bool m_initialized; +}; + + + +#include "audio_math.h" +#include "win32_misc.h" + +#include "fb2kdebug.h" + +#if FB2K_LEAK_STATIC_OBJECTS +#define FB2K_STATIC_OBJECT(TYPE, NAME) static TYPE & NAME = * new TYPE; +#else +#define FB2K_STATIC_OBJECT(TYPE, NAME) static TYPE NAME; +#endif + +#endif //_SHARED_DLL__SHARED_H_ diff --git a/foobar2000/shared/shared.lib b/foobar2000/shared/shared.lib new file mode 100644 index 0000000000000000000000000000000000000000..0a61b8c86f3c5c84c4048f870328e3cb512d86bb GIT binary patch literal 38516 zcmd^ITWnoN)m~{!DWO1s5<)37Eujsew7ED=0;N8-V<%2*$BCUw+q5Ug_Ktm$<8w~W zC3ezF+VqyDX?hQ(KR*xXrw>)d1L^}p73u>*JXDAWg!+UkRG~f~gb+dqA^Lx7X7--h zvuBU@_DTPj-_pw2d(B$&&3)F)tl7EY_EKYN_KxMZt&abFYeW1!d|R&`wsmh-k4Kgh zE!#%4`!7VJ*AVU5MYQ*Vq(g5IG3~or()cPOru{o5O}tISv=8!36YC_6{|IuRJKvHt zeFYIyV_4GOm53iS_Nt`Efz9;9`--OV3wrFIlJ43{#Ps+nNpl|%F+Fxx(j4-@^wb(j zM-Ug&)U}Gv;}Y}7`R}wK5pOb`Skf0WRLHE8XDR_fOP#=ClhZiK>_d6n{+U=5_xrvDB!c~%9M)?Us z*}V+9Z&uPd=rc8uH>M|lCFwboDboVd#Plezm=;b*I&+MOseX;5!^@El&?Enrbo2}n z)04;})9D8!&7w@0=7GiZ;;$7=;1~4J4S5%fIbWqR&4NzbpsI}`H4RQ{Kw zV}B%KIzA@pSOxE(Q*TImaFmFtdPq|1AtI*pKb7+lPj1{PD}ElJHE zq8)(F7bHE6v@tEbFKGsilIanY5z`CM6@<8708LjUoksqdCXsHY$B-7LGryPg%tf?M zQ0s4!E}TGlf*$<2q;tSydit=WyHGw%cSGmyi^by5$k?8J6UFTVi3K zjbNIJ#nJu4!^vEG|oaMQCF@s>w@p7e7oDQnB#$2i5h|Y*Di)hbOsWR2Blv+Wt9+Zw-5sr-Y znaRr}+cQlSBgmT%%%c&-9+usP?xghKB4y{*i4bJgH*{wzyTR15%M(EYJDE2wJ0F-wqo;{h z5V!@*rj|{t*zRl3^v?!U#|!Hz{vx_+vArW`^_MD@@m6W-_!#$RmUA$BdN#Bp!HEW> zgGQmSw|Ag$DC+kQy#Q1xi7xTiXBBB-_sTaotk zAeCcFU_5ATZ_m|5-n)|eR&Z(@{k6Gzt%^hpmz%AEJEDY)JGS2G>4EBra-&wALwuY; zSA0&X9y|Nv?RvctG@D|W)-{z)EK!(4)v_~hAz$TZU|}k#w+73VR?rx0)TTu8T})^G z3mY~jszLCSG}l&C^&;v7M@jAr=4vN`u~M}>wX;;6uAs$o#kko@2gRCDaPX9LU!ykP z3zR>rYV%~X70eYd!`7-T*aFR| zM!DW9*Q$lW_>P|5{Tm89^|+8rCq$^!W;kviL~q~zR%vE<&yK-8qZ0=Ug#_Z#;s{f) z82ni8{+*0mEIOcbD))%lHdSeFFWYG@xZHX@^;*4MFHV>|%0<1X7Xa`5P$Md!n z)t;$F0MpKm)VIUTl^){q>5SB-OO@>+L&g45wO;^^2J<`}%GT|ZFvG@b$Af_dOakTV zj9c55Ftx|$%dM$d?m0p2D=wyzA1p8Q&eelP6T@*o)*;Q}iH%~`P76rWb1N}dX-otQ ztv&5lf2~q$G$%@vlfiUh6OXWI0WLjR0x=E2QnDBdIc#T8o|%>NVuB+jo2^E93M->_ z>*&^EfJp)gaq(2Vm~Y0RQJvXeM_I`M-odk_himf*Y0Xp8`+|C5awWR#Ilt+cv@PHI6{~WZOFzsN^+tR z1P98&d{K1<8MBk?(AeJ$8rw^)lE|s4WCkcb3>s{<_m}ETSupYUDCN9gEbMz>VvPr} zcO`|y_E*aF$y%u~ElZz`B@u0UkkZ{3%wW6^8de0N=sjU!`f81q$Ocvr*qITK<4K-Q z=SWa(i)uSrj}mC}lr*Lm4CfqJ*vJbz>pdRlxadaG4n>|>%U~&4(Q1_-n}gQ7lHyrr zU(l?z8&k@#O>kH-6k)p=L!wkp4G>tNicF>{YedoJl@`iUC0}Gc$x>+*>%pT3WHU8i z!W^S+`)UjE=FF=+o2lfb?Z91bq*Sl76(EdlH(TkTxOWF9xy41@E6Gx_n9TZHjY<*y zk6VH2UJj-uKU786a;R8*r8P8%;f8Zr**o2R8^>y+Yhtt`q4I_?mq ziAFj;tHvsGyi72)Jdfi-l#2-Z=DU&_Sg6B3H1-7V5(J< z)2`Uoa`2TF`jb;Dg|D&XEZ9t7x%gU3PVNaM2V3bF{g8_;{;FhTWl3PP&@S#z4qi+e zTYs%~Q|ic3HLP-rue5}DKd=x?wXq}+0m#Z=X5VTlYcDRPs66^`HA=^AHJ=m^PY-yXPq-A-I zpuE)nkj;q6=_a9TFUE^Wi=q_+vy3!)S5k7bOY3vfaqyHhiiacUZDEF=M0%z9=;SJm z?Lj4I1p<|$j=w6I5$wVV3C@uD5~WevPqGb$rmjqBN{CJ`OqzBjP-7PBAhkA6OB;jA zfl{Mfnnc6oR6!{vNa=ES6Lu6!n=eK0wRxq$`r7O}jQ>oMzHNg+!tg+L61V>Nk%r8|}@L}_kslxDY(L-Bb z2l9f(!FK5EE6s}~irA<&wafsWKG_<%EX~X$xk^L!V|kz^05Ic{XeqEa+U<2_8b zu+^2(QtX$9nxpNxNo>N1mM9A#UBDGjQjs}f`cbh4rqau+LL(x3&k9?HQvwom{1%#u!-ea>ldXl@zMhSRu2;#~VmauF^oCGc;s3 zaPV~ea>$eYREi_|1XIVZgGAXP?l>okxjL;9qO!&iat%0+5={v+v`saYZLSEh3nAiS zrk-+oCt(GO(Uj*R8Bba$5{*NxGHlu4V5!`aEj`+2O$p8K?bRK5d%;bE{<53sno*)% zd*DB@m*|Z{L|5-4S~X6zb3f7B6X4^09e#fV-dlGPT`^5G+#p&B`B%pf$KyorKLNiD z&_5p|+Ikm!Wx)Gj4n8M%Uyk23PeBfTAJKkz z__u&=0nQ(Q{{j3rj@(1^E@;=iL~HLR`sK6mEd%|%4qp(^YcBy4^xgwRtBXXxDZ$qX zbPezaK$5jsg>M51d9?W}!Dv^y?RqPS6h?LfYY*!j4G) zeje#XT&wVV9`vs=(I4UWGIkvPH28f3Wit98e9x-zBWe--^gQB%{&n{g{o-Nxv_O6( z=ub%NYe@G6@c#o^0ojYd{26dw2mW7?o_A)D-?J#YHtG@a{{(5j1Mde==9A#PgmfQ3 zoPS1|S3>@`z&wUBd<8x@>p<_Kp5H<}{}5$RKsw%e8ug64A3?lpq5JL&kOx&Dw*qn8 zfjV7@`u;u2<|4}TZ%ES#r1R&<&-*CL!_a>dZQ~;9aN9DX&(aFIg07_H^l`eHK1rXT zt7t9VL|>;h^fmez-AtdNmGs~AE&3ttr2>6}*3mcV7FtG^Em`-ww3)t5-=W*-I@&_r z^b%c9U!kwkr|AVcOFeW4Jxb#=PZwx`4$ujz(j1j&nkH$A?x!c|3A&bUq*XLSk5M1h zX^b||dfGv~^ci}JhUgqMX_j80({zgNrZyd<77f!VJwp%C4YZm9I!^adgC3_YI!dq7 zF}jOhr2TY;9-#_7OApgN+JlBNLJ!c(^dbz~m5|K@BYdp#op9zq z>0RP^bg0(ZA$T*>L4PKfz$KkLFX~dYxYtaYDZ~32)-h{}{UWwe!_8}DO0oqn_j?Sx z9qWSBlV~$6+bMGVR5Pa_c!g5DlMVrRCn_iEs27qLF`u!Haw>B3^tqG-M}^ zcEbQMZ0ViuXdgnIfao2yn1a}t)|(ben7QWhZA?Y)C+yL#DZ{Yqo^g{%y1k9BSEKxQg%a0S!YOUcpgN`hj0065I1u zT&^bV=MpikV<&?jj#j3#K*IOT*vw3XDTlQv%9saA$X(n)2jgnwO)pNZqWv#%N@?zV zt4LWR+_a86s5k^lV0C*g2*!oDEp)oT&hf}zM z#qKQPwAx@At1g2Zp7I$i*Qmj#P6+`19d#42us1} zn&JHs1!~Fi3}OruR;Y2W#;wkWU-re9R7vxijVYvjuM}%>hq+KfHbGV5Mkdmax4kz% za(cn4lh`bS1^i^Si2~` z?BFO7;Y#LSudrvKd!@o5)*8j-hG=;WwPOnpSw%5cv=(V6ZvM3$>yNhU>LHlvYYqg) zRvVTBJzlL9hTz1k1aPdYF5Fp?T;l=^n{3*b-Lr-eO9bboU31+a7ZQuRw99_PgP+id zuMz~yF`+Quo=TW!7#_BNRdO}J3h{M-;8-gF=2^BsvuvxMdA7YD@>CXu9|MaCmoZLH z(jiA%_pEFh@6y-GVrq+?#S^x?V2b09m=(|Lg#8{eL(5&hyKMF_L|g03G3|7I*K@OI zc@ZaWo2UA7w=H|T?Xtv$(~-8prBkYMe3LJc-EI3PiTBXf7BBJsW7*ej%d)nwGQ|7} z+fF;$y7Vju7R%yw+s>9|F|?X(zF{xhL73AMma;>}w2@_ScrziidL!FUZUTsY8-~d$ zH!Nb8tEb}G8<1e9-VBrd2W2DsfMxT-BP!LSNQ9{bv8fWdP(|4=E!-3rJi&GiLUF5A zR%o2ZbGdTbvibw$G$)+$6Lzb8E7EXyc3hQATB+tG0BxTpxlsZfHmM;UTBGV>j^S1p z=Qu3Tc4%E1=A3X~4EZRK$m)#Wl9aX1)#tJ_^@R;Fs)bj{BJ)uVO~PZ6r@$z{NXXXI zug=Ji(cC&+sYnm7#lG^@wJrSK*OBGJ%Pyx6@wXg4I9I_f34hCOon|+|HRJLX{yuns z%TMgDKr8xgEl=ZW#+r>`z%_Vy2lSp|T#nxYt$$m|bt^Xfd*G)8dM{+)w?M1kz6`&g zh360dE!Ua4nLfYb<6v=g{P$_N3jMhJxsmczqt>h)ZLMCr?;ER!hemhH;P}6c#flsO zyw$|h3jF&q@uWeJf+;jUB=yJTWy?PNFoa_b{^PV=Mq!Q%aH3+)Y{(ILW1P!FP8Z97 zp=CB}4)d?)f7Bnx1z$1s#y^+An+RX;TEb~`+P8^xUbD55ja}Q+SK+voTqat{&qT^N zeePB=ErRqvMl0DAa=DfKPxzvKxs`krwV$O8>3f#*Wx3s8(h=wHn zm=Qhf_7?gEpkL1DnY>OkYW{NHvc+BJ+mM1QL<;iwvV2r2`IsvuF6WcTL#CsYY<8E) z7L>`AW=cXY8jr7EC+Ubx2di}qA{`%hr6ZGTjqv6%lBFNBwb?D6(zz;`DpxH>y$W%JQ@yJM)e#}@pp8m94q$-a$lSa>9K3k*j&&Egd@1pg5l2ek~N2;UL zq>QtDNX^xpnx%PO8A;NQ*=lNA%4|1;6n)A_(bAkvb!zh0oNrA2w2`uGPGX&;qrvz(sAoa8kw{;(W9ox1DzY5H27&GZ4Xu#>mY%R@~!!Ixz zwhdgj#57L+e(uZRO2*_p$8Z-??i?9#`Y~e;H@f@MYLw>}Lp0k-QiJ5~E1Oen+na&6 zit#+2mj-P9cH_bQl8tNmb9RKAGN0Ul{9bG0MqZ+wAaR4B<2c=b%isGwt>?xNPo5t{NA({S-q+%IksClK zcwL^JxJKf|+^;*q+MLpAR-@H?U0_+B+kE0-0P4q#JtM`sx)FFcx$xvEg@!S}vmyF+ zoo;p^nobuT;C7|tbQN;CR^n=(_D*m-t>qg6C+;d@K;`avT`A+*waC-D2rK%|h5@A? zv(=h!-utFT5}%*!GTKCb3(bdgON119&**$C0V!pr#rwP8Qb;k+90N>0W-C9R-Fd6T zl4pZ*oaE0mXb9smBbsMKzD=Sf{D};>{O!CNPrXREA%{LlvFtFISgWaHX z{P|nR=9DpmN5_p8mf>P$Kd$A( z78G8pi>U_7-EMqyV6Vb8y;lvW+;!;ddwl|nXWHlno&lvFvz4Z=ZT2gqxYMmhSi+c+ zvS-CTVS9uYZUwRnStn?lJv&tc3N7g@+!12R+JN^w2Hl9#5!nFMj~Qz)W$xoWs~sVx z;p431&tHGOzPD3irTb4C!1^&;9(^nHA%Tl+Yx`oS1~FPuHoPv6MYt=%GrYb#z}lRG zbrb4ux5jcgly`#anFWUvsA_=}TV!hRY-Q?Ojg3TzF*kS(k-G+ccJ^q5mUM14Aajq} zK0A9)h-rE6cZBC@Ib$K7`(}Uvs~vtl=i(kYr;Phk0+ViX;_J?TJ9S@C>zwUgcc_75I?vHeY=PAvDCY~D7 z;wFYh&RCkBzHlf)v#-5qluKKlcg7K;D>WVAd93@pLOlDrjsd72GnQzIHNz|UyIpwE zO(2c5gi`h7@^FZ1-!d`)^<&0ddS>5yBD}-}CJmE+KJ?`9-Vo8g8`S}xZ?#l3@zk{| z11kT#>uEDbG?wE^S0|{xS*c{AM%TqULQBabTlteAnsF7aBP`D7mi{g|!K`+CGti7Ec47;!f*eiV_vM|gVsjKFf-D%2S= z>N8{BQp%Ih#AYR)`O%P0kWxlYK2a=3Na{-@o#1$KbWG!f*DO0h^7QQE5t9C9iUF0o zUHHypD-o8u9clpS$Bga4GlI-TIG1Xl$Jeu}5n5>Q#g6Xtm!PM%Y62^MVOE!1eyhZn zue!vFec+}eEKk3>Phmy>n}`9VAG5U#-=17U;JEG(Yn1$@=v#p_1!DXzvgO>7yM=V8 zw0*wo(sJOrzZ=w1j#K>Q*o(F8N`bmp-4Ui|FXDs?Q{UM(VD)3R*3_NS8*f8S=Uu3{ zY-?W!*O>X+me1d8AqC&KYHmR5$80(GjpQdavb@EvBhNjv`@CCE37qf}d~~xsx*%`B z=I%wCQ)-sif%nI7r6H;>-*<%S*&TU6q4NK2ZhlpwBfOMRmwg=`H1MpC1{jd~F9Sxp@H&`Jf6PsSOW8soKSe?&Br6e|GP7TCqm3i zd1m0*v3$~jm$;6RdnEVmlbj6^FWqi}r)NBsMC;hI)|b0;5|96XcjNzUG`d9Yvhs59Nu^2WlAOF(aa927X2&CcgG!z~wJdPk*=&;mOfO zfA*y#M9*0ItQ)c8R_bdv&)Jyr;%;Z{#&ZhuyoIN}tz#6ae$3Wpe0}f*i6(A(T$=jp zPU#bTH}b_KTJn=XI(zwhhi8U*NnnRx6*4~Z6ThsGzlV56)R$wJ#+Qsb$)l(3T$Fg` z7m+$0Z#=#6dr36?rO!@s=oxcfk(iFphZ=zS>(G // CLSID_AutoComplete + +#include "CEnumString.h" +using PP::CEnumString; + +HRESULT InitializeSimpleAC(HWND edit, IUnknown * vals, DWORD opts) { + pfc::com_ptr_t ac; + HRESULT hr; + hr = CoCreateInstance(CLSID_AutoComplete, NULL, CLSCTX_ALL, IID_IAutoComplete, (void**)ac.receive_ptr()); + if (FAILED(hr)) { + PFC_ASSERT(!"Should not get here - CoCreateInstance/IAutoComplete fail!"); return hr; + } + hr = ac->Init(edit, vals, NULL, NULL); + if (FAILED(hr)) { + PFC_ASSERT(!"Should not get here - ac->Init fail!"); return hr; + } + + pfc::com_ptr_t ac2; + hr = ac->QueryInterface(ac2.receive_ptr()); + if (FAILED(hr)) { + PFC_ASSERT(!"Should not get here - ac->QueryInterface fail!"); return hr; + } + hr = ac2->SetOptions(opts); + if (FAILED(hr)) { + PFC_ASSERT(!"Should not get here - ac2->SetOptions fail!"); return hr; + } + return S_OK; +} + +pfc::com_ptr_t CreateACList(pfc::const_iterator valueEnum) { + pfc::com_ptr_t acl = new CEnumString::TImpl(); + while (valueEnum.is_valid()) { + acl->AddStringU(*valueEnum); ++valueEnum; + } + return acl; +} +pfc::com_ptr_t CreateACList(pfc::const_iterator valueEnum) { + pfc::com_ptr_t acl = new CEnumString::TImpl(); + while (valueEnum.is_valid()) { + acl->AddStringU(*valueEnum); ++valueEnum; + } + return acl; +} +pfc::com_ptr_t CreateACList(pfc::const_iterator valueEnum) { + pfc::com_ptr_t acl = new CEnumString::TImpl(); + while (valueEnum.is_valid()) { + acl->AddStringU(valueEnum->ptr()); ++valueEnum; + } + return acl; +} + +pfc::com_ptr_t CreateACList() { + return new CEnumString::TImpl(); +} + +void CreateACList_AddItem(IUnknown * theList, const char * item) { + static_cast(theList)->AddStringU(item); +} + +HRESULT InitializeEditAC(HWND edit, pfc::const_iterator valueEnum, DWORD opts) { + pfc::com_ptr_t acl = CreateACList(valueEnum); + return InitializeSimpleAC(edit, acl.get_ptr(), opts); +} +HRESULT InitializeEditAC(HWND edit, const char * values, DWORD opts) { + pfc::com_ptr_t acl = new CEnumString::TImpl(); + for (const char * walk = values;;) { + const char * next = strchr(walk, '\n'); + if (next == NULL) { acl->AddStringU(walk, ~0); break; } + acl->AddStringU(walk, next - walk); + walk = next + 1; + } + return InitializeSimpleAC(edit, acl.get_ptr(), opts); +} diff --git a/libPPUI/AutoComplete.h b/libPPUI/AutoComplete.h new file mode 100644 index 0000000..c6865d8 --- /dev/null +++ b/libPPUI/AutoComplete.h @@ -0,0 +1,12 @@ +#pragma once + +#include + +HRESULT InitializeEditAC(HWND edit, pfc::const_iterator valueEnum, DWORD opts = ACO_AUTOAPPEND | ACO_AUTOSUGGEST); +HRESULT InitializeEditAC(HWND edit, const char * values, DWORD opts = ACO_AUTOAPPEND | ACO_AUTOSUGGEST); +HRESULT InitializeSimpleAC(HWND edit, IUnknown * vals, DWORD opts); +pfc::com_ptr_t CreateACList(pfc::const_iterator valueEnum); +pfc::com_ptr_t CreateACList(pfc::const_iterator valueEnum); +pfc::com_ptr_t CreateACList(pfc::const_iterator valueEnum); +pfc::com_ptr_t CreateACList(); +void CreateACList_AddItem(IUnknown * theList, const char * item); diff --git a/libPPUI/CButtonLite.h b/libPPUI/CButtonLite.h new file mode 100644 index 0000000..7d99a8a --- /dev/null +++ b/libPPUI/CButtonLite.h @@ -0,0 +1,272 @@ +#pragma once + +#include +#include +#include "WTL-PP.h" +#include "win32_op.h" + +typedef CWinTraits CButtonLiteTraits; + +class CButtonLite : public CWindowImpl { +public: + BEGIN_MSG_MAP_EX(CButtonLite) + MESSAGE_RANGE_HANDLER_EX(WM_MOUSEFIRST, WM_MOUSELAST, MousePassThru); + MSG_WM_SETTEXT(OnSetText) + MSG_WM_PAINT( OnPaint ) + MSG_WM_MOUSEMOVE(OnMouseMove) + MSG_WM_LBUTTONDOWN(OnLButtonDown) + MSG_WM_SETFOCUS(OnSetFocus) + MSG_WM_KILLFOCUS(OnKillFocus) + MSG_WM_KEYDOWN(OnKeyDown) + MSG_WM_KEYUP(OnKeyUp) + MSG_WM_CHAR(OnChar) + MSG_WM_ENABLE(OnEnable) + MESSAGE_HANDLER_EX(WM_GETDLGCODE, OnGetDlgCode) + MSG_WM_SETFONT(OnSetFont) + MSG_WM_GETFONT(OnGetFont) + MSG_WM_CREATE(OnCreate) + END_MSG_MAP() + std::function ClickHandler; + + unsigned Measure() { + auto font = myGetFont(); + LOGFONT lf; + WIN32_OP_D(font.GetLogFont(lf)); + MakeBoldFont( lf ); + CFont bold; + WIN32_OP_D(bold.CreateFontIndirect(&lf)); + CWindowDC dc(*this); + auto oldFont = dc.SelectFont( bold ); + CSize size (0,0); + + { + CString measure; + measure = L"#"; + measure += m_textDrawMe; + WIN32_OP_D(dc.GetTextExtent(measure, measure.GetLength(), &size)); + } + + dc.SelectFont( oldFont ); + + return size.cx; + } + std::function< void (HWND) > TabCycleHandler; + std::function< HBRUSH (CDCHandle) > CtlColorHandler; + std::function< bool (HWND) > WantTabCheck; + CWindow WndCtlColorTarget; + + // Rationale: sometimes you want a different text to be presented to accessibility APIs than actually drawn + // For an example, a clear button looks best with a multiplication sign, but the narrator should say "clear" or so when focused + void DrawAlternateText( const wchar_t * textDrawMe ) { + m_textDrawMe = textDrawMe; + } + +protected: + CFontHandle m_font; + void OnSetFont(HFONT font, BOOL bRedraw) { + m_font = font; if (bRedraw) Invalidate(); + } + HFONT OnGetFont() { + return m_font; + } + LRESULT OnGetDlgCode(UINT, WPARAM wp, LPARAM lp) { + if ( wp == VK_TAB && TabCycleHandler != NULL) { + if ( WantTabCheck == NULL || WantTabCheck(m_hWnd) ) { + TabCycleHandler( m_hWnd ); + return DLGC_WANTTAB; + } + } + SetMsgHandled(FALSE); return 0; + } + void OnChar(UINT nChar, UINT nRepCnt, UINT nFlags) { + } + void OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags) { + switch(nChar) { + case VK_SPACE: + case VK_RETURN: + TogglePressed(true); break; + } + } + void OnKeyUp(UINT nChar, UINT nRepCnt, UINT nFlags) { + switch(nChar) { + case VK_SPACE: + case VK_RETURN: + TogglePressed(false); + OnClicked(); + break; + } + } + void OnSetFocus(CWindow wndOld) { + m_focused = true; Invalidate(); + } + void OnKillFocus(CWindow wndFocus) { + m_focused = false; Invalidate(); + } + CFontHandle myGetFont() { + auto f = GetFont(); + if ( f == NULL ) f = (HFONT) GetStockObject(DEFAULT_GUI_FONT); + return f; + } + static void MakeBoldFont(LOGFONT & lf ) { + lf.lfWeight += 300; + if (lf.lfWeight > 1000 ) lf.lfWeight = 1000; + } + + virtual void DrawBackground( CDCHandle dc, CRect rcClient ) { + HBRUSH brush = NULL; + if ( IsPressed() ) { + CTheme theme; + if (theme.OpenThemeData(*this, L"BUTTON" )) { + DrawThemeBackground(theme, dc, BP_PUSHBUTTON, PBS_PRESSED, rcClient, rcClient ); + } else { + DrawFrameControl( dc, rcClient, DFC_BUTTON, DFCS_PUSHED ); + } + } else if (m_hot) { + CTheme theme; + if (theme.OpenThemeData(*this, L"BUTTON")) { + DrawThemeBackground(theme, dc, BP_PUSHBUTTON, PBS_HOT, rcClient, rcClient); + } else { + DrawFrameControl(dc, rcClient, DFC_BUTTON, DFCS_HOT); + } + } else if ( CtlColorHandler ) { + brush = CtlColorHandler( dc ); + } else { + CWindow target = WndCtlColorTarget; + if ( target == NULL ) target = GetParent(); + brush = (HBRUSH) target.SendMessage(WM_CTLCOLORBTN, (WPARAM) dc.m_hDC, (LPARAM) this->m_hWnd ); + } + if ( brush != NULL ) { + dc.FillRect( rcClient, brush ); + dc.SetBkMode( TRANSPARENT ); + } + } + + virtual void OnPaint(CDCHandle) { + CPaintDC pdc(*this); + + CRect rcClient; + if (! GetClientRect( &rcClient ) ) return; + + auto font = myGetFont(); + /* + CFont fontOverride; + if ( IsPressed() ) { + LOGFONT lf; + font.GetLogFont( lf ); + MakeBoldFont( lf ); + fontOverride.CreateFontIndirect( & lf ); + font = fontOverride; + } + */ + HFONT oldFont = pdc.SelectFont( font ); + + DrawBackground( pdc.m_hDC, rcClient ); + + pdc.SetBkMode( TRANSPARENT ); + if ( !IsWindowEnabled() ) { + pdc.SetTextColor( ::GetSysColor(COLOR_GRAYTEXT) ); + } else if ( m_focused ) { + pdc.SetTextColor( ::GetSysColor(COLOR_HIGHLIGHT) ); + } + pdc.DrawText( m_textDrawMe, m_textDrawMe.GetLength(), &rcClient, DT_VCENTER | DT_CENTER | DT_SINGLELINE | DT_NOPREFIX ); + + pdc.SelectFont( oldFont ); + } + virtual void OnClicked() { + if ( ClickHandler ) { + ClickHandler(); + } else { + GetParent().PostMessage( WM_COMMAND, MAKEWPARAM( this->GetDlgCtrlID(), BN_CLICKED ), (LPARAM) m_hWnd ); + } + } + bool IsPressed() {return m_pressed; } +private: + int OnCreate(LPCREATESTRUCT lpCreateStruct) { + if ( lpCreateStruct->lpszName != nullptr ) this->m_textDrawMe = lpCreateStruct->lpszName; + SetMsgHandled(FALSE); return 0; + } + void OnEnable(BOOL bEnable) { + Invalidate(); SetMsgHandled(FALSE); + } + void ToggleHot( bool bHot ) { + if ( bHot != m_hot ) { + m_hot = bHot; Invalidate(); + } + } + void OnMouseMove(UINT nFlags, CPoint point) { + const DWORD maskButtons = MK_LBUTTON | MK_RBUTTON | MK_MBUTTON | MK_XBUTTON1 | MK_XBUTTON2; + if ((nFlags & maskButtons) != 0) return; + CRect rcClient; + if (!GetClientRect(rcClient)) return; + if (!rcClient.PtInRect( point ) ) return; + ToggleHot( true ); + SetCaptureEx([=](UINT cMsg, DWORD cFlags, CPoint cPoint) { + CRect rcClient; + if (!GetClientRect(rcClient)) return false; + if ( cMsg == WM_MOUSEWHEEL || cMsg == WM_MOUSEHWHEEL || (cFlags & maskButtons) != 0 || !rcClient.PtInRect(cPoint) ) { + ToggleHot(false); + SetMsgHandled( FALSE ); + return false; + } + return true; + } ); + } + void OnLButtonDown(UINT nFlags, CPoint point) { + const DWORD maskButtons = MK_LBUTTON | MK_RBUTTON | MK_MBUTTON | MK_XBUTTON1 | MK_XBUTTON2; + if ( ( nFlags & maskButtons ) != MK_LBUTTON ) return; + TogglePressed( true ); + SetCaptureEx([=] (UINT cMsg, DWORD cFlags, CPoint cPoint) { + if (cMsg == WM_MOUSEWHEEL || cMsg == WM_MOUSEHWHEEL) { + TogglePressed(false); + SetMsgHandled(FALSE); + return false; + } + if ( cMsg == WM_LBUTTONUP ) { + bool wasPressed = m_pressed; + TogglePressed(false); + if ( wasPressed ) OnClicked(); + return false; + } + CRect rcClient; + if (!GetClientRect( rcClient )) return false; + if ( (cFlags & maskButtons) != (nFlags & maskButtons ) || ! rcClient.PtInRect( cPoint ) ) { + TogglePressed(false); + SetMsgHandled( FALSE ); return false; + } + return true; + } ); + } + void TogglePressed( bool bPressed ) { + if ( bPressed != m_pressed ) { + m_pressed = bPressed; Invalidate(); + } + } + int OnSetText(LPCTSTR lpstrText) { + m_textDrawMe = lpstrText; + Invalidate(); SetMsgHandled(FALSE); + return 0; + } + typedef std::function< bool(UINT, DWORD, CPoint) > CaptureProc_t; + void SetCaptureEx( CaptureProc_t proc ) { + m_captureProc = proc; SetCapture(); + } + LRESULT MousePassThru(UINT msg, WPARAM wp, LPARAM lp) { + auto p = m_captureProc; // create local ref in case something in mid-captureproc clears it + if (p) { + CPoint pt(lp); + if (!p(msg, (DWORD)wp, pt)) { + ::ReleaseCapture(); + m_captureProc = nullptr; + } + return 0; + } + + SetMsgHandled(FALSE); + return 0; + } + + CaptureProc_t m_captureProc; + + bool m_pressed = false, m_focused = false, m_hot = false; + CString m_textDrawMe; +}; diff --git a/libPPUI/CDialogResizeHelper.cpp b/libPPUI/CDialogResizeHelper.cpp new file mode 100644 index 0000000..172105f --- /dev/null +++ b/libPPUI/CDialogResizeHelper.cpp @@ -0,0 +1,194 @@ +#include "stdafx.h" +#include "CDialogResizeHelper.h" + +#include "win32_op.h" + +static BOOL GetChildWindowRect2(HWND wnd, HWND wndChild, RECT* child) { + RECT temp; + if (wndChild == NULL) return FALSE; + if (!GetWindowRect(wndChild, &temp)) return FALSE; + if (!MapWindowPoints(NULL, wnd, (POINT*)&temp, 2)) return FALSE; + *child = temp; + return TRUE; +} + +static BOOL GetChildWindowRect(HWND wnd, UINT id, RECT* child) { + return GetChildWindowRect2(wnd, GetDlgItem(wnd, id), child); +} + + +bool CDialogResizeHelper::EvalRect(UINT id, CRect & out) const { + for( auto iter = m_runtime.begin(); iter != m_runtime.end(); ++ iter ) { + if ( iter->initData.id == id ) { + out = _EvalRect(*iter, this->CurrentSize() ); + return true; + } + } + return false; +} + +CRect CDialogResizeHelper::_EvalRect(runtime_t const & rt, CSize wndSize) const { + CRect rc = rt.origRect; + int delta_x = wndSize.cx - rt.origWindowSize.cx, + delta_y = wndSize.cy - rt.origWindowSize.cy; + + const Param & e = rt.initData; + rc.left += pfc::rint32(e.snapLeft * delta_x); + rc.right += pfc::rint32(e.snapRight * delta_x); + rc.top += pfc::rint32(e.snapTop * delta_y); + rc.bottom += pfc::rint32(e.snapBottom * delta_y); + + return rc; +} + +CWindow CDialogResizeHelper::ResolveWnd(runtime_t const & rt) const { + if ( rt.userHWND != NULL ) return rt.userHWND; + return m_thisWnd.GetDlgItem( rt.initData.id ); +} + +CSize CDialogResizeHelper::CurrentSize() const { + CRect rc; + WIN32_OP_D( m_thisWnd.GetClientRect(rc) ); + return rc.Size(); +} + +void CDialogResizeHelper::OnSize(UINT, CSize newSize) +{ + if (m_thisWnd != NULL) { + HDWP hWinPosInfo = BeginDeferWindowPos((int)(m_runtime.size() + (m_sizeGrip != NULL ? 1 : 0))); + for (auto iter = m_runtime.begin(); iter != m_runtime.end(); ++ iter) { + CRect rc = _EvalRect(*iter, newSize); + hWinPosInfo = DeferWindowPos(hWinPosInfo, ResolveWnd(*iter), 0, rc.left, rc.top, rc.Width(), rc.Height(), SWP_NOZORDER | SWP_NOOWNERZORDER | SWP_NOACTIVATE | SWP_NOCOPYBITS); + } + if (m_sizeGrip != NULL) + { + RECT rc, rc_grip; + if (m_thisWnd.GetClientRect(&rc) && m_sizeGrip.GetWindowRect(&rc_grip)) { + DWORD flags = SWP_NOZORDER | SWP_NOOWNERZORDER | SWP_NOACTIVATE | SWP_NOSIZE | SWP_NOCOPYBITS; + if (IsZoomed(m_thisWnd)) flags |= SWP_HIDEWINDOW; + else flags |= SWP_SHOWWINDOW; + hWinPosInfo = DeferWindowPos(hWinPosInfo, m_sizeGrip, NULL, rc.right - (rc_grip.right - rc_grip.left), rc.bottom - (rc_grip.bottom - rc_grip.top), 0, 0, flags); + } + } + EndDeferWindowPos(hWinPosInfo); + //RedrawWindow(m_thisWnd, NULL, NULL, RDW_UPDATENOW | RDW_ALLCHILDREN); + } + SetMsgHandled(FALSE); +} + +void CDialogResizeHelper::OnGetMinMaxInfo(LPMINMAXINFO info) const { + CRect r; + const DWORD dwStyle = m_thisWnd.GetWindowLong(GWL_STYLE); + const DWORD dwExStyle = m_thisWnd.GetWindowLong(GWL_EXSTYLE); + if (max_x && max_y) + { + r.left = 0; r.right = max_x; + r.top = 0; r.bottom = max_y; + MapDialogRect(m_thisWnd, &r); + AdjustWindowRectEx(&r, dwStyle, FALSE, dwExStyle); + info->ptMaxTrackSize.x = r.right - r.left; + info->ptMaxTrackSize.y = r.bottom - r.top; + } + if (min_x && min_y) + { + r.left = 0; r.right = min_x; + r.top = 0; r.bottom = min_y; + MapDialogRect(m_thisWnd, &r); + AdjustWindowRectEx(&r, dwStyle, FALSE, dwExStyle); + info->ptMinTrackSize.x = r.right - r.left; + info->ptMinTrackSize.y = r.bottom - r.top; + } +} + +void CDialogResizeHelper::OnInitDialog(CWindow thisWnd) { + + m_thisWnd = thisWnd; + const auto origSize = CurrentSize(); + for( auto initIter = m_initData.begin(); initIter != m_initData.end(); ++ initIter ) { + CRect rc; + if (GetChildWindowRect(m_thisWnd, initIter->id, &rc)) { + runtime_t rt; + rt.origRect = rc; + rt.initData = * initIter; + rt.origWindowSize = origSize; + m_runtime.push_back( std::move(rt) ); + } + } + AddSizeGrip(); + SetMsgHandled(FALSE); +} +void CDialogResizeHelper::OnDestroy() { + m_runtime.clear(); + m_sizeGrip = NULL; m_thisWnd = NULL; + SetMsgHandled(FALSE); +} + +void CDialogResizeHelper::AddSizeGrip() +{ + if (m_thisWnd != NULL && m_sizeGrip == NULL) + { + if (m_thisWnd.GetWindowLong(GWL_STYLE) & WS_POPUP) { + m_sizeGrip = CreateWindowEx(0, WC_SCROLLBAR, _T(""), WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | SBS_SIZEGRIP | SBS_SIZEBOXBOTTOMRIGHTALIGN, + 0, 0, CW_USEDEFAULT, CW_USEDEFAULT, + m_thisWnd, (HMENU)0, NULL, NULL); + if (m_sizeGrip != NULL) + { + RECT rc, rc_grip; + if (m_thisWnd.GetClientRect(&rc) && m_sizeGrip.GetWindowRect(&rc_grip)) { + m_sizeGrip.SetWindowPos(NULL, rc.right - (rc_grip.right - rc_grip.left), rc.bottom - (rc_grip.bottom - rc_grip.top), 0, 0, SWP_NOZORDER | SWP_NOSIZE); + } + } + } + } +} + + +void CDialogResizeHelper::InitTable(const Param* table, size_t tableSize) { + m_initData.assign( table, table+tableSize ); +} +void CDialogResizeHelper::InitTable(const ParamOld * table, size_t tableSize) { + m_initData.resize(tableSize); + for (size_t walk = 0; walk < tableSize; ++walk) { + const ParamOld in = table[walk]; + Param entry = {}; + entry.id = table[walk].id; + if (in.flags & CDialogResizeHelperCompat::X_MOVE) entry.snapLeft = entry.snapRight = 1; + else if (in.flags & CDialogResizeHelperCompat::X_SIZE) entry.snapRight = 1; + if (in.flags & CDialogResizeHelperCompat::Y_MOVE) entry.snapTop = entry.snapBottom = 1; + else if (in.flags & CDialogResizeHelperCompat::Y_SIZE) entry.snapBottom = 1; + m_initData[walk] = entry; + } +} +void CDialogResizeHelper::InitMinMax(const CRect & range) { + min_x = range.left; min_y = range.top; max_x = range.right; max_y = range.bottom; +} + +void CDialogResizeHelper::AddControl(Param const & initData, CWindow wnd) { + CRect rc; + if ( wnd == NULL ) { + PFC_ASSERT( initData.id != 0 ); + WIN32_OP_D(GetChildWindowRect(m_thisWnd, initData.id, rc)); + } else { + WIN32_OP_D(GetChildWindowRect2(m_thisWnd, wnd, rc)); + } + + runtime_t rt; + rt.initData = initData; + rt.userHWND = wnd; + rt.origRect = rc; + rt.origWindowSize = this->CurrentSize(); + m_runtime.push_back( std::move(rt) ); +} + +bool CDialogResizeHelper::RemoveControl(CWindow wnd) { + return pfc::remove_if_t( m_runtime, [wnd] (runtime_t const & rt) { + return rt.userHWND == wnd; + } ) > 0; +} + +bool CDialogResizeHelper::HaveControl(CWindow wnd) const { + for( auto i = m_runtime.begin(); i != m_runtime.end(); ++ i ) { + if ( i->userHWND == wnd ) return true; + } + return false; +} diff --git a/libPPUI/CDialogResizeHelper.h b/libPPUI/CDialogResizeHelper.h new file mode 100644 index 0000000..e81e687 --- /dev/null +++ b/libPPUI/CDialogResizeHelper.h @@ -0,0 +1,67 @@ +#pragma once + +#include "CDialogResizeHelperCompat.h" +#include + +class CDialogResizeHelper : public CMessageMap { +public: + + typedef CDialogResizeHelperCompat::param ParamOld; + + struct Param { + uint32_t id; + float snapLeft, snapTop, snapRight, snapBottom; + }; +private: + void AddSizeGrip(); +public: + inline void set_min_size(unsigned x, unsigned y) { min_x = x; min_y = y; } + inline void set_max_size(unsigned x, unsigned y) { max_x = x; max_y = y; } + + + BEGIN_MSG_MAP_EX(CDialogResizeHelper) + if (uMsg == WM_INITDIALOG) OnInitDialog(hWnd); + MSG_WM_SIZE(OnSize) + MSG_WM_DESTROY(OnDestroy) + MSG_WM_GETMINMAXINFO(OnGetMinMaxInfo) + END_MSG_MAP() + + template CDialogResizeHelper(const TParam(&src)[paramCount], CRect const& minMaxRange = CRect(0, 0, 0, 0)) { + InitTable(src, paramCount); + InitMinMax(minMaxRange); + } + + void InitTable(const Param* table, size_t tableSize); + void InitTable(const ParamOld * table, size_t tableSize); + void InitMinMax(const CRect & range); + + bool EvalRect(UINT id, CRect & out) const; + + //! initData.id may be null, if so specify a non null window handle. + void AddControl( Param const & initData, CWindow wnd = NULL ); + bool RemoveControl( CWindow wnd ); + bool HaveControl(CWindow wnd) const; +private: + struct runtime_t { + HWND userHWND = 0; + CRect origRect; + CSize origWindowSize; + Param initData = {}; + }; + + CSize CurrentSize() const; + CWindow ResolveWnd( runtime_t const & rt ) const; + + CRect _EvalRect(runtime_t const & rt, CSize wndSize) const; + void OnGetMinMaxInfo(LPMINMAXINFO lpMMI) const; + void OnSize(UINT nType, CSize size); + void OnInitDialog(CWindow thisWnd); + void OnDestroy(); + + + std::vector m_runtime; + std::vector m_initData; + + CWindow m_thisWnd, m_sizeGrip; + unsigned min_x, min_y, max_x, max_y; +}; \ No newline at end of file diff --git a/libPPUI/CDialogResizeHelperCompat.h b/libPPUI/CDialogResizeHelperCompat.h new file mode 100644 index 0000000..90bf10c --- /dev/null +++ b/libPPUI/CDialogResizeHelperCompat.h @@ -0,0 +1,16 @@ +#pragma once + +class CDialogResizeHelperCompat { +public: + struct param { + unsigned short id; + unsigned short flags; + }; + + enum { + X_MOVE = 1, X_SIZE = 2, Y_MOVE = 4, Y_SIZE = 8, + XY_MOVE = X_MOVE | Y_MOVE, XY_SIZE = X_SIZE | Y_SIZE, + X_MOVE_Y_SIZE = X_MOVE | Y_SIZE, X_SIZE_Y_MOVE = X_SIZE | Y_MOVE, + }; + +}; \ No newline at end of file diff --git a/libPPUI/CEditWithButtons.cpp b/libPPUI/CEditWithButtons.cpp new file mode 100644 index 0000000..ecc4936 --- /dev/null +++ b/libPPUI/CEditWithButtons.cpp @@ -0,0 +1,178 @@ +#include "stdafx.h" +#include "CEditWithButtons.h" + + +void CEditWithButtons::AddMoreButton(std::function f) { + AddButton(L"more", f, nullptr, L"\x2026"); +} +void CEditWithButtons::AddClearButton(const wchar_t * clearVal, bool bHandleEsc) { + std::wstring clearValCopy(clearVal); + auto handler = [this, clearValCopy] { + this->SetWindowText(clearValCopy.c_str()); + }; + auto condition = [clearValCopy](const wchar_t * txt) -> bool { + return clearValCopy != txt; + }; + // Present "clear" to accessibility APIs but actually draw a multiplication x sign + AddButton(L"clear", handler, condition, L"\x00D7"); + + if (bHandleEsc) { + this->onEscKey = handler; + } + +} + +void CEditWithButtons::AddButton(const wchar_t * str, handler_t handler, condition_t condition, const wchar_t * drawAlternateText) { + Button_t btn; + btn.handler = handler; + btn.title = str; + btn.condition = condition; + btn.visible = EvalCondition(btn, nullptr); + + if (drawAlternateText != nullptr) { + btn.titleDraw = drawAlternateText; + } + + m_buttons.push_back(std::move(btn)); + RefreshButtons(); +} + +CRect CEditWithButtons::RectOfButton(const wchar_t * text) { + for (auto i = m_buttons.begin(); i != m_buttons.end(); ++i) { + if (i->title == text && i->wnd != NULL) { + CRect rc; + if (i->wnd.GetWindowRect(rc)) return rc; + } + } + return CRect(); +} + +void CEditWithButtons::TabCycleButtons(HWND wnd) { + for (auto i = m_buttons.begin(); i != m_buttons.end(); ++i) { + if (i->wnd == wnd) { + if (IsShiftPressed()) { + // back + for (;; ) { + if (i == m_buttons.begin()) { + TabFocusThis(m_hWnd); break; + } else { + --i; + if (i->visible) { + TabFocusThis(i->wnd); + break; + } + } + } + } else { + // forward + for (;; ) { + ++i; + if (i == m_buttons.end()) { + TabFocusThis(m_hWnd); + TabFocusPrevNext(false); + break; + } else { + if (i->visible) { + TabFocusThis(i->wnd); + break; + } + } + } + } + + return; + } + } +} + + +bool CEditWithButtons::ButtonWantTab(HWND wnd) { + if (IsShiftPressed()) return true; + if (m_buttons.size() == 0) return false; // should not be possible + auto last = m_buttons.rbegin(); + if (wnd == last->wnd) return false; // not for last button + return true; +} +bool CEditWithButtons::EvalCondition(Button_t & btn, const wchar_t * newText) { + if (!btn.condition) return true; + if (newText != nullptr) return btn.condition(newText); + TCHAR text[256] = {}; + GetWindowText(text, 256); + text[255] = 0; + return btn.condition(text); +} +void CEditWithButtons::RefreshConditions(const wchar_t * newText) { + bool changed = false; + for (auto i = m_buttons.begin(); i != m_buttons.end(); ++i) { + bool status = EvalCondition(*i, newText); + if (status != i->visible) { + i->visible = status; changed = true; + } + } + if (changed) { + Layout(); + } +} + +void CEditWithButtons::Layout(CSize size, CFontHandle fontSetMe) { + if (m_buttons.size() == 0) return; + + int walk = size.cx; + + HDWP dwp = BeginDeferWindowPos((int)m_buttons.size()); + for (auto iter = m_buttons.rbegin(); iter != m_buttons.rend(); ++iter) { + if (!iter->visible) { + if (::GetFocus() == iter->wnd) { + this->SetFocus(); + } + ::DeferWindowPos(dwp, iter->wnd, NULL, 0, 0, 0, 0, SWP_NOZORDER | SWP_HIDEWINDOW | SWP_NOMOVE | SWP_NOSIZE | SWP_NOCOPYBITS); + continue; + } + + if (iter->wnd == NULL) { + auto b = std::make_shared< CButtonLite >(); + iter->buttonImpl = b; + b->Create(*this, NULL, iter->title.c_str()); + if (iter->titleDraw.length() > 0) b->DrawAlternateText(iter->titleDraw.c_str()); + CFontHandle font = fontSetMe; + if (font == NULL) font = GetFont(); + b->SetFont(font); + b->ClickHandler = iter->handler; + b->CtlColorHandler = [=](CDCHandle dc) -> HBRUSH { + return this->OnColorBtn(dc, NULL); + }; + b->TabCycleHandler = [=](HWND wnd) { + TabCycleButtons(wnd); + }; + b->WantTabCheck = [=](HWND wnd) -> bool { + return ButtonWantTab(wnd); + }; + if (!IsWindowEnabled()) b->EnableWindow(FALSE); + iter->wnd = *b; + } else if (fontSetMe) { + iter->wnd.SetFont(fontSetMe); + } + + unsigned delta = MeasureButton(*iter); + int left = walk - delta; + + if (iter->wnd != NULL) { + CRect rc; + rc.top = 0; + rc.bottom = size.cy; + rc.left = left; + rc.right = walk; + ::DeferWindowPos(dwp, iter->wnd, NULL, rc.left, rc.top, rc.Width(), rc.Height(), SWP_NOZORDER | SWP_SHOWWINDOW | SWP_NOCOPYBITS); + } + + walk = left; + } + EndDeferWindowPos(dwp); + this->SetMargins(0, size.cx - walk, EC_RIGHTMARGIN); +} + +unsigned CEditWithButtons::MeasureButton(Button_t const & button) { + if (m_fixedWidth != 0) return m_fixedWidth; + + return button.buttonImpl->Measure(); +} diff --git a/libPPUI/CEditWithButtons.h b/libPPUI/CEditWithButtons.h new file mode 100644 index 0000000..638b685 --- /dev/null +++ b/libPPUI/CEditWithButtons.h @@ -0,0 +1,213 @@ +#pragma once + +#include +#include +#include +#include +#include "WTL-PP.h" + +#include "CButtonLite.h" + +class CEditWithButtons : public CEditPPHooks { +public: + CEditWithButtons(::ATL::CMessageMap * hookMM = nullptr, int hookMMID = 0) : CEditPPHooks(hookMM, hookMMID), m_fixedWidth() {} + + enum { + MSG_CHECKCONDITIONS = WM_USER+13, + MSG_CHECKCONDITIONS_MAGIC1 = 0xaec66f0c, + MSG_CHECKCONDITIONS_MAGIC2 = 0x180c2f35, + + }; + + BEGIN_MSG_MAP_EX(CEditWithButtons) + MSG_WM_SETFONT(OnSetFont) + MSG_WM_WINDOWPOSCHANGED(OnPosChanged) + MSG_WM_CTLCOLORBTN(OnColorBtn) + MESSAGE_HANDLER_EX(WM_GETDLGCODE, OnGetDlgCode) + MSG_WM_KEYDOWN(OnKeyDown) + MSG_WM_CHAR(OnChar) + // MSG_WM_SETFOCUS( OnSetFocus ) + // MSG_WM_KILLFOCUS( OnKillFocus ) + MSG_WM_ENABLE(OnEnable) + MSG_WM_SETTEXT( OnSetText ) + MESSAGE_HANDLER_EX(WM_PAINT, CheckConditionsTrigger) + MESSAGE_HANDLER_EX(WM_CUT, CheckConditionsTrigger) + MESSAGE_HANDLER_EX(WM_PASTE, CheckConditionsTrigger) + MESSAGE_HANDLER_EX(MSG_CHECKCONDITIONS, OnCheckConditions) + CHAIN_MSG_MAP(CEditPPHooks) + END_MSG_MAP() + + void SubclassWindow( HWND wnd ) { + CEditPPHooks::SubclassWindow( wnd ); + this->ModifyStyle(0, WS_CLIPCHILDREN); + RefreshButtons(); + } + typedef std::function handler_t; + typedef std::function condition_t; + + void AddMoreButton( std::function f ); + void AddClearButton( const wchar_t * clearVal = L"", bool bHandleEsc = false); + void AddButton( const wchar_t * str, handler_t handler, condition_t condition = nullptr, const wchar_t * drawAlternateText = nullptr ); + + static unsigned DefaultFixedWidth() {return GetSystemMetrics(SM_CXVSCROLL) * 3 / 4;} + void SetFixedWidth(unsigned fw = DefaultFixedWidth() ) { + m_fixedWidth = fw; + RefreshButtons(); + } + CRect RectOfButton( const wchar_t * text ); + + void Invalidate() { + __super::Invalidate(); + for( auto i = m_buttons.begin(); i != m_buttons.end(); ++i ) { + if (i->wnd != NULL) i->wnd.Invalidate(); + } + } + void SetShellFolderAutoComplete() { + SetShellAutoComplete(SHACF_FILESYS_DIRS); + } + void SetShellFileAutoComplete() { + SetShellAutoComplete(SHACF_FILESYS_ONLY); + } + void SetShellAutoComplete(DWORD flags) { + SHAutoComplete(*this, flags); + SetHasAutoComplete(); + } + void SetHasAutoComplete(bool bValue = true) { + m_hasAutoComplete = bValue; + } + void RefreshConditions(const wchar_t * newText = nullptr); +private: + LRESULT OnCheckConditions( UINT msg, WPARAM wp, LPARAM lp ) { + if ( msg == MSG_CHECKCONDITIONS && wp == MSG_CHECKCONDITIONS_MAGIC1 && lp == MSG_CHECKCONDITIONS_MAGIC2 ) { + this->RefreshConditions(); + } else { + SetMsgHandled(FALSE); + } + return 0; + } + bool HaveConditions() { + for( auto i = m_buttons.begin(); i != m_buttons.end(); ++i ) { + if ( i->condition ) return true; + } + return false; + } + void PostCheckConditions() { + if ( HaveConditions() ) { + PostMessage( MSG_CHECKCONDITIONS, MSG_CHECKCONDITIONS_MAGIC1, MSG_CHECKCONDITIONS_MAGIC2 ); + } + } + LRESULT CheckConditionsTrigger( UINT, WPARAM, LPARAM ) { + PostCheckConditions(); + SetMsgHandled(FALSE); + return 0; + } + int OnSetText(LPCTSTR lpstrText) { + PostCheckConditions(); + SetMsgHandled(FALSE); + return 0; + } + void OnEnable(BOOL bEnable) { + for( auto i = m_buttons.begin(); i != m_buttons.end(); ++ i ) { + if ( i->wnd != NULL ) { + i->wnd.EnableWindow( bEnable ); + } + } + SetMsgHandled(FALSE); + } + void OnChar(UINT nChar, UINT nRepCnt, UINT nFlags) { + if (nChar == VK_TAB ) { + return; + } + PostCheckConditions(); + SetMsgHandled(FALSE); + } + void OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags) { + if ( nChar == VK_TAB ) { + return; + } + SetMsgHandled(FALSE); + } + bool canStealTab() { + if (m_hasAutoComplete) return false; + if (IsShiftPressed()) return false; + if (m_buttons.size() == 0) return false; + return true; + } + UINT OnGetDlgCode(UINT, WPARAM wp, LPARAM lp) { + if ( wp == VK_TAB && canStealTab() ) { + for (auto i = m_buttons.begin(); i != m_buttons.end(); ++ i ) { + if ( i->visible ) { + TabFocusThis(i->wnd); + return DLGC_WANTTAB; + } + } + } + SetMsgHandled(FALSE); return 0; + } + void OnSetFocus(HWND) { + this->ModifyStyleEx(0, WS_EX_CONTROLPARENT ); SetMsgHandled(FALSE); + } + void OnKillFocus(HWND) { + this->ModifyStyleEx(WS_EX_CONTROLPARENT, 0 ); SetMsgHandled(FALSE); + } + HBRUSH OnColorBtn(CDCHandle dc, CButton button) { + if ( (this->GetStyle() & ES_READONLY) != 0 || !this->IsWindowEnabled() ) { + return (HBRUSH) GetParent().SendMessage( WM_CTLCOLORSTATIC, (WPARAM) dc.m_hDC, (LPARAM) m_hWnd ); + } else { + return (HBRUSH) GetParent().SendMessage( WM_CTLCOLOREDIT, (WPARAM) dc.m_hDC, (LPARAM) m_hWnd ); + } + } + void OnPosChanged(LPWINDOWPOS lpWndPos) { + Layout(); SetMsgHandled(FALSE); + } + + struct Button_t { + std::wstring title, titleDraw; + handler_t handler; + std::shared_ptr buttonImpl; + CWindow wnd; + bool visible; + condition_t condition; + }; + void OnSetFont(CFontHandle font, BOOL bRedraw) { + CRect rc; + if (GetClientRect(&rc)) { + Layout(rc.Size(), font); + } + SetMsgHandled(FALSE); + } + + void RefreshButtons() { + if ( m_hWnd != NULL && m_buttons.size() > 0 ) { + Layout(); + } + } + void Layout( ) { + CRect rc; + if (GetClientRect(&rc)) { + Layout(rc.Size(), NULL); + } + } + static bool IsShiftPressed() { + return (GetKeyState(VK_SHIFT) & 0x8000) ? true : false; + } + CWindow FindDialog() { + return GetParent(); + } + void TabFocusThis(HWND wnd) { + FindDialog().PostMessage(WM_NEXTDLGCTL, (WPARAM) wnd, TRUE ); + } + void TabFocusPrevNext(bool bPrev) { + FindDialog().PostMessage(WM_NEXTDLGCTL, bPrev ? TRUE : FALSE, FALSE); + } + void TabCycleButtons(HWND wnd); + + bool ButtonWantTab( HWND wnd ); + bool EvalCondition( Button_t & btn, const wchar_t * newText ); + void Layout(CSize size, CFontHandle fontSetMe); + unsigned MeasureButton(Button_t const & button ); + + unsigned m_fixedWidth; + std::list< Button_t > m_buttons; + bool m_hasAutoComplete = false; +}; diff --git a/libPPUI/CEnumString.h b/libPPUI/CEnumString.h new file mode 100644 index 0000000..eb456c3 --- /dev/null +++ b/libPPUI/CEnumString.h @@ -0,0 +1,99 @@ +#pragma once +#include +#include "pp-COM-macros.h" + +namespace PP { + + class CEnumString : public IEnumString { + public: + typedef pfc::chain_list_v2_t > t_data; + typedef std::shared_ptr shared_t; + + CEnumString(t_data && in) { + m_shared = std::make_shared(std::move(in)); + Reset(); + } + CEnumString(const t_data & in) { + m_shared = std::make_shared(in); + Reset(); + } + CEnumString() { + m_shared = std::make_shared< t_data >(); + } + + void SetStrings(t_data && data) { + *m_shared = std::move(data); + Reset(); + } + + static pfc::array_t stringToBuffer(const char * in) { + pfc::array_t arr; + arr.set_size(pfc::stringcvt::estimate_utf8_to_wide(in)); + pfc::stringcvt::convert_utf8_to_wide_unchecked(arr.get_ptr(), in); + return arr; + } + + void AddString(const TCHAR * in) { + m_shared->insert_last()->set_data_fromptr(in, _tcslen(in) + 1); + Reset(); + } + void AddStringU(const char * in, t_size len) { + pfc::array_t & arr = *m_shared->insert_last(); + arr.set_size(pfc::stringcvt::estimate_utf8_to_wide(in, len)); + pfc::stringcvt::convert_utf8_to_wide(arr.get_ptr(), arr.get_size(), in, len); + Reset(); + } + void AddStringU(const char * in) { + *m_shared->insert_last() = stringToBuffer(in); + Reset(); + } + void ResetStrings() { + m_shared->remove_all(); + Reset(); + } + + typedef ImplementCOMRefCounter TImpl; + COM_QI_BEGIN() + COM_QI_ENTRY(IUnknown) + COM_QI_ENTRY(IEnumString) + COM_QI_END() + + HRESULT STDMETHODCALLTYPE Next(ULONG celt, LPOLESTR *rgelt, ULONG *pceltFetched) { + if (rgelt == NULL) return E_INVALIDARG; + ULONG done = 0; + while (done < celt && m_walk.is_valid()) { + rgelt[done] = CoStrDup(m_walk->get_ptr()); + ++m_walk; ++done; + } + if (pceltFetched != NULL) *pceltFetched = done; + return done == celt ? S_OK : S_FALSE; + } + + static TCHAR * CoStrDup(const TCHAR * in) { + const size_t lenBytes = (_tcslen(in) + 1) * sizeof(TCHAR); + TCHAR * out = reinterpret_cast(CoTaskMemAlloc(lenBytes)); + if (out) memcpy(out, in, lenBytes); + return out; + } + + HRESULT STDMETHODCALLTYPE Skip(ULONG celt) { + while (celt > 0) { + if (m_walk.is_empty()) return S_FALSE; + --celt; ++m_walk; + } + return S_OK; + } + + HRESULT STDMETHODCALLTYPE Reset() { + m_walk = m_shared->first(); + return S_OK; + } + + HRESULT STDMETHODCALLTYPE Clone(IEnumString **ppenum) { + *ppenum = new TImpl(*this); return S_OK; + } + private: + shared_t m_shared; + t_data::const_iterator m_walk; + }; +} diff --git a/libPPUI/CFlashWindow.h b/libPPUI/CFlashWindow.h new file mode 100644 index 0000000..2c8bfa2 --- /dev/null +++ b/libPPUI/CFlashWindow.h @@ -0,0 +1,69 @@ +#pragma once + +#include "win32_op.h" + +typedef CWinTraits CFlashWindowTraits; + +class CFlashWindow : public CWindowImpl { +public: + void Activate(CWindow parent) { + ShowAbove(parent); + m_tickCount = 0; + SetTimer(KTimerID, 500); + } + void Deactivate() throw() { + ShowWindow(SW_HIDE); KillTimer(KTimerID); + } + + void ShowAbove(CWindow parent) { + if (m_hWnd == NULL) { + WIN32_OP( Create(NULL) != NULL ); + } + CRect rect; + WIN32_OP_D( parent.GetWindowRect(rect) ); + WIN32_OP_D( SetWindowPos(NULL,rect,SWP_NOZORDER | SWP_NOACTIVATE | SWP_SHOWWINDOW) ); + m_parent = parent; + } + + void CleanUp() throw() { + if (m_hWnd != NULL) DestroyWindow(); + } + + BEGIN_MSG_MAP_EX(CFlashWindow) + MSG_WM_CREATE(OnCreate) + MSG_WM_TIMER(OnTimer) + MSG_WM_DESTROY(OnDestroy) + END_MSG_MAP() + + DECLARE_WND_CLASS_EX(TEXT("{2E124D52-131F-4004-A569-2316615BE63F}"),0,COLOR_HIGHLIGHT); +private: + void OnDestroy() throw() { + KillTimer(KTimerID); + } + enum { + KTimerID = 0x47f42dd0 + }; + void OnTimer(WPARAM id) { + if (id == KTimerID) { + switch(++m_tickCount) { + case 1: + ShowWindow(SW_HIDE); + break; + case 2: + ShowAbove(m_parent); + break; + case 3: + ShowWindow(SW_HIDE); + KillTimer(KTimerID); + break; + } + } + } + LRESULT OnCreate(LPCREATESTRUCT) throw() { + SetLayeredWindowAttributes(*this,0,128,LWA_ALPHA); + return 0; + } + CWindow m_parent; + uint32_t m_tickCount; +}; + diff --git a/libPPUI/CHeaderCtrlEx.h b/libPPUI/CHeaderCtrlEx.h new file mode 100644 index 0000000..9b13569 --- /dev/null +++ b/libPPUI/CHeaderCtrlEx.h @@ -0,0 +1,15 @@ +#pragma once + +class CHeaderCtrlEx : public CHeaderCtrl { +public: + CHeaderCtrlEx(HWND wnd = NULL) : CHeaderCtrl(wnd) {} + CHeaderCtrlEx const & operator=(HWND wnd) { m_hWnd = wnd; return *this; } + + // Column sort marker operations + // If they appear to have no effect, you're probably missing Common Controls 6 manifest, see link-CommonControls6.h + DWORD GetItemFormat(int iItem); + void SetItemFormat(int iItem, DWORD flags); + void SetItemSort(int iItem, int direction); + void SetSingleItemSort(int iItem, int direction); + void ClearSort(); +}; diff --git a/libPPUI/CIconOverlayWindow.h b/libPPUI/CIconOverlayWindow.h new file mode 100644 index 0000000..d79514b --- /dev/null +++ b/libPPUI/CIconOverlayWindow.h @@ -0,0 +1,47 @@ +#pragma once + +#include "win32_op.h" + +typedef CWinTraits _COverlayWindowTraits; + +class CIconOverlayWindow : public CWindowImpl { +public: + DECLARE_WND_CLASS_EX(TEXT("{384298D0-4370-4f9b-9C36-49FC1A396DC7}"),0,(-1)); + + void AttachIcon(HICON p_icon) {m_icon = p_icon;} + bool HaveIcon() const {return m_icon != NULL;} + + enum { + ColorKey = 0xc0ffee + }; + + BEGIN_MSG_MAP_EX(CIconOverlayWindow) + MESSAGE_HANDLER(WM_CREATE,OnCreate); + MESSAGE_HANDLER(WM_PAINT,OnPaint); + MESSAGE_HANDLER(WM_ERASEBKGND,OnEraseBkgnd); + END_MSG_MAP() +private: + LRESULT OnCreate(UINT,WPARAM,LPARAM,BOOL&) { + ::SetLayeredWindowAttributes(*this,ColorKey,0,LWA_COLORKEY); + return 0; + } + LRESULT OnEraseBkgnd(UINT,WPARAM p_wp,LPARAM,BOOL& bHandled) { + CRect rcClient; + WIN32_OP_D( GetClientRect(rcClient) ); + CDCHandle((HDC)p_wp).FillSolidRect(rcClient,ColorKey); + return 1; + } + LRESULT OnPaint(UINT,WPARAM,LPARAM,BOOL& bHandled) { + if (m_icon != NULL) { + CPaintDC dc(*this); + CRect client; + WIN32_OP_D( GetClientRect(&client) ); + dc.DrawIconEx(0,0,m_icon,client.right,client.bottom); + //CDCHandle(ps.hdc).DrawIcon(0,0,m_icon); + } else { + bHandled = FALSE; + } + return 0; + } + CIcon m_icon; +}; diff --git a/libPPUI/CListAccessible.cpp b/libPPUI/CListAccessible.cpp new file mode 100644 index 0000000..c9e2e8b --- /dev/null +++ b/libPPUI/CListAccessible.cpp @@ -0,0 +1,744 @@ +#include "stdafx.h" +#include "CListAccessible.h" + +#include "pp-COM-macros.h" + +static size_t IndexFromChildId(LONG id) {return (size_t)(id-1);} +static LONG ChildIdFromIndex(size_t index) {return (LONG)(index+1);} + +class IEnumVARIANT_selection : public ImplementCOMRefCounter { +public: + IEnumVARIANT_selection(const LONG * data,size_t dataCount, ULONG pos = 0) : m_pos(pos) { + m_data.set_data_fromptr(data,dataCount); + } + + COM_QI_BEGIN() + COM_QI_ENTRY(IEnumVARIANT) + COM_QI_ENTRY(IUnknown) + COM_QI_END() + + // IEnumVARIANT methods + STDMETHOD(Next)(ULONG celt, VARIANT *rgVar, ULONG *pCeltFetched); + STDMETHOD(Skip)(ULONG celt); + STDMETHOD(Reset)(); + STDMETHOD(Clone)(IEnumVARIANT **ppEnum); +private: + pfc::array_t m_data; + ULONG m_pos; +}; + + +HRESULT IEnumVARIANT_selection::Next(ULONG celt, VARIANT *rgVar, ULONG *pceltFetched) { + if (rgVar == NULL) return E_INVALIDARG; + + if (pceltFetched) *pceltFetched = 0; + + ULONG n; + for (n = 0; n < celt; n++) VariantInit(&rgVar[n]); + + for (n = 0; n < celt && m_pos+n < m_data.get_size(); n++) { + rgVar[n].vt = VT_I4; + rgVar[n].lVal = m_data[m_pos+n]; + } + + if (pceltFetched) *pceltFetched = n; + + m_pos += n; + + return n == celt ? S_OK : S_FALSE; +} + +HRESULT IEnumVARIANT_selection::Skip(ULONG celt) { + m_pos += celt; + if (m_pos > m_data.get_size()) { + m_pos = (ULONG)m_data.get_size(); + return S_FALSE; + } + return S_OK; +} + +HRESULT IEnumVARIANT_selection::Reset() { + m_pos = 0; + return S_OK; +} + +HRESULT IEnumVARIANT_selection::Clone(IEnumVARIANT **ppEnum) +{ + if (ppEnum == NULL) + return E_INVALIDARG; + + IEnumVARIANT * var; + try { + var = new IEnumVARIANT_selection(m_data.get_ptr(), m_data.get_size(), m_pos); + } catch(std::bad_alloc) {return E_OUTOFMEMORY;} + + var->AddRef(); + *ppEnum = var; + + return S_OK; +} + +namespace { + class WeakRef { + public: + WeakRef( CListAccessible * ptr_, std::shared_ptr ks_ ) : ptr(ptr_), ks(ks_) {} + + CListAccessible * operator->() const { return ptr; } + + bool IsEmpty() const { + return * ks; + } + private: + CListAccessible * ptr; + std::shared_ptr ks; + }; +} + +class IAccessible_CListControl : public ImplementCOMRefCounter { +public: + IAccessible_CListControl(WeakRef owner) : m_owner(owner) {} + COM_QI_BEGIN() + COM_QI_ENTRY(IUnknown) + COM_QI_ENTRY(IDispatch) + COM_QI_ENTRY(IAccessible) + COM_QI_END() + + //IDispatch + STDMETHOD(GetTypeInfoCount)(UINT * pcTInfo); + STDMETHOD(GetTypeInfo)(UINT iTInfo, LCID lcid, ITypeInfo ** ppTInfo); + STDMETHOD(GetIDsOfNames)(REFIID riid, LPOLESTR *rgszNames, UINT cNames, LCID lcid, DISPID *rgDispId); + STDMETHOD(Invoke)(DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS * pDispParams, VARIANT * pVarResult, EXCEPINFO * pExcepInfo, UINT * puArgErr); + //IAccessible + STDMETHOD(get_accParent)(IDispatch **ppdispParent); // required + STDMETHOD(get_accChildCount)(long *pcountChildren); + STDMETHOD(get_accChild)(VARIANT varChild, IDispatch **ppdispChild); + STDMETHOD(get_accName)(VARIANT varChild, BSTR *pszName); // required + STDMETHOD(get_accValue)(VARIANT varChild, BSTR *pszValue); + STDMETHOD(get_accDescription)(VARIANT varChild, BSTR *pszDescription); + STDMETHOD(get_accRole)(VARIANT varChild, VARIANT *pvarRole); // required + STDMETHOD(get_accState)(VARIANT varChild, VARIANT *pvarState); // required + STDMETHOD(get_accHelp)(VARIANT varChild, BSTR *pszHelp); + STDMETHOD(get_accHelpTopic)(BSTR *pszHelpFile, VARIANT varChild, long *pidTopic); + STDMETHOD(get_accKeyboardShortcut)(VARIANT varChild, BSTR *pszKeyboardShortcut); + STDMETHOD(get_accFocus)(VARIANT *pvarChild); + STDMETHOD(get_accSelection)(VARIANT *pvarChildren); + STDMETHOD(get_accDefaultAction)(VARIANT varChild, BSTR *pszDefaultAction); + STDMETHOD(accSelect)(long flagsSelect, VARIANT varChild); + STDMETHOD(accLocation)(long *pxLeft, long *pyTop, long *pcxWidth, long *pcyHeight, VARIANT varChild); // required + STDMETHOD(accNavigate)(long navDir, VARIANT varStart, VARIANT *pvarEndUpAt); + STDMETHOD(accHitTest)(long xLeft, long yTop, VARIANT *pvarChild); + STDMETHOD(accDoDefaultAction)(VARIANT varChild); + STDMETHOD(put_accName)(VARIANT varChild, BSTR szName); + STDMETHOD(put_accValue)(VARIANT varChild, BSTR szValue); +private: + const WeakRef m_owner; +}; + +void CListAccessible::AccInitialize(CWindow wnd) { + m_wnd = wnd; +} +void CListAccessible::AccCleanup() { + if (m_interface.is_valid()) { + NotifyWinEvent(EVENT_OBJECT_DESTROY, m_wnd, OBJID_CLIENT, CHILDID_SELF); + m_interface.release(); + } + m_wnd = NULL; +} + +LRESULT CListAccessible::AccGetObject(WPARAM wp,LPARAM lp) { + const WPARAM dwFlags = wp; + const LPARAM dwObjId = lp; + + if (dwObjId == OBJID_CLIENT) + { + if (m_interface.is_empty()) + { + try { + m_interface = new IAccessible_CListControl( WeakRef(this, m_killSwitch) ); + } catch(...) { + //bah + return DefWindowProc(m_wnd,WM_GETOBJECT,wp,lp); + } + NotifyWinEvent(EVENT_OBJECT_CREATE, m_wnd, OBJID_CLIENT, CHILDID_SELF); + } + return LresultFromObject(IID_IAccessible, dwFlags, m_interface.get_ptr()); + } + else return DefWindowProc(m_wnd,WM_GETOBJECT,wp,lp); +} + +void CListAccessible::AccItemNamesChanged(pfc::bit_array const & mask) { + this->AccRefreshItems(mask, EVENT_OBJECT_NAMECHANGE); +} +void CListAccessible::AccReloadItems(pfc::bit_array const & mask) { + this->AccRefreshItems(mask, EVENT_OBJECT_NAMECHANGE); +} +void CListAccessible::AccStateChange(pfc::bit_array const & mask) { + this->AccRefreshItems(mask, EVENT_OBJECT_STATECHANGE); +} +void CListAccessible::AccStateChange(size_t index) { + this->AccStateChange(pfc::bit_array_one(index)); +} +void CListAccessible::AccRefreshItems(pfc::bit_array const & affected, UINT what) { + if (m_wnd == NULL || m_interface.is_empty()) return; + //if (GetFocus() != m_hWnd) return; + const size_t total = AccGetItemCount(); + for (size_t walk = affected.find_first(true, 0, total); walk < total; walk = affected.find_next(true, walk, total)) { + NotifyWinEvent(what, m_wnd, OBJID_CLIENT, ChildIdFromIndex(walk)); + } +} +void CListAccessible::AccItemLayoutChanged() { + if (m_wnd == NULL || m_interface.is_empty()) return; + NotifyWinEvent(EVENT_OBJECT_REORDER, m_wnd, OBJID_CLIENT, CHILDID_SELF); +} +void CListAccessible::AccFocusItemChanged(size_t index) { + if (m_wnd == NULL || m_interface.is_empty()) return; + if (GetFocus() != m_wnd) return; + NotifyWinEvent(EVENT_OBJECT_FOCUS, m_wnd, OBJID_CLIENT, index != SIZE_MAX ? ChildIdFromIndex(index) : CHILDID_SELF); +} +void CListAccessible::AccFocusOtherChanged(size_t index) { + if (m_wnd == NULL || m_interface.is_empty()) return; + if (GetFocus() != m_wnd) return; + const size_t itemCount = this->AccGetItemCount(); + index += itemCount; + NotifyWinEvent(EVENT_OBJECT_FOCUS, m_wnd, OBJID_CLIENT, index != SIZE_MAX ? ChildIdFromIndex(index) : CHILDID_SELF); +} +void CListAccessible::AccSelectionChanged(const pfc::bit_array & affected, const pfc::bit_array & state) { + if (m_wnd == NULL || m_interface.is_empty()) return; + //if (GetFocus() != m_wnd) return; + const size_t itemCount = this->AccGetItemCount(); + + const size_t limit = 20; + if (affected.calc_count(true, 0, itemCount, limit) == limit) { + NotifyWinEvent(EVENT_OBJECT_SELECTIONWITHIN, m_wnd, OBJID_CLIENT, CHILDID_SELF); + } else for(size_t walk = affected.find_first(true,0,itemCount); walk < itemCount; walk = affected.find_next(true,walk,itemCount)) { + NotifyWinEvent(state[walk] ? EVENT_OBJECT_SELECTIONADD : EVENT_OBJECT_SELECTIONREMOVE, m_wnd, OBJID_CLIENT, ChildIdFromIndex(walk)); + } +} +void CListAccessible::AccLocationChange() { + if (m_wnd == NULL || m_interface.is_empty()) return; + NotifyWinEvent(EVENT_OBJECT_LOCATIONCHANGE,m_wnd,OBJID_CLIENT, CHILDID_SELF); +} + +HRESULT IAccessible_CListControl::GetTypeInfoCount(UINT * pcTInfo) { + return E_NOTIMPL; +} + +HRESULT IAccessible_CListControl::GetTypeInfo(UINT iTInfo, LCID lcid, ITypeInfo ** ppTInfo) { + return E_NOTIMPL; +} + +HRESULT IAccessible_CListControl::GetIDsOfNames(REFIID riid, LPOLESTR *rgszNames, UINT cNames, LCID lcid, DISPID *rgDispId) { + return E_NOTIMPL; +} + +HRESULT IAccessible_CListControl::Invoke(DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS * pDispParams, VARIANT * pVarResult, EXCEPINFO * pExcepInfo, UINT * puArgErr) { + return E_NOTIMPL; +} + +HRESULT IAccessible_CListControl::get_accParent(IDispatch **ppdispParent) // required +{ + if (ppdispParent == NULL) return E_INVALIDARG; + if (m_owner.IsEmpty()) return E_FAIL; + + *ppdispParent = NULL; + HRESULT hResult = AccessibleObjectFromWindow(m_owner->AccGetWnd(), OBJID_WINDOW, IID_IDispatch, (void**)ppdispParent); + return (hResult == S_OK) ? S_OK : S_FALSE; +} + +HRESULT IAccessible_CListControl::get_accChildCount(long *pcountChildren) { + if (pcountChildren == NULL) return E_INVALIDARG; + if (m_owner.IsEmpty()) return E_FAIL; + *pcountChildren = (long)( m_owner->AccGetItemCount() + m_owner->AccGetOtherCount() ); + return S_OK; +} +HRESULT IAccessible_CListControl::get_accChild(VARIANT varChild, IDispatch **ppdispChild) { + if (varChild.vt != VT_I4) return E_INVALIDARG; + if (ppdispChild == NULL) return E_INVALIDARG; + if (varChild.lVal == CHILDID_SELF) { + *ppdispChild = this; AddRef(); + return S_OK; + } else { + if (m_owner.IsEmpty()) return E_FAIL; + const size_t index = IndexFromChildId(varChild.lVal); + const size_t itemCount = m_owner->AccGetItemCount(); + if (index < itemCount) { + return S_FALSE; + } else if (index < itemCount + m_owner->AccGetOtherCount()) { + CWindow wnd = m_owner->AccGetOtherChildWnd(index - itemCount); + if (wnd == NULL) return S_FALSE; + if (AccessibleObjectFromWindow(wnd, OBJID_WINDOW, IID_IDispatch, (void**)ppdispChild) != S_OK) return S_FALSE; + return S_OK; + } else { + return E_INVALIDARG; + } + } +} + + +HRESULT IAccessible_CListControl::get_accName(VARIANT varChild, BSTR *pszName) // required +{ + if (varChild.vt != VT_I4) return E_INVALIDARG; + if (pszName == NULL) return E_INVALIDARG; + if (m_owner.IsEmpty()) return E_FAIL; + pfc::string8_fastalloc name; + if (varChild.lVal == CHILDID_SELF) { + m_owner->AccGetName(name); + } else { + const size_t index = IndexFromChildId(varChild.lVal); + const size_t itemCount = m_owner->AccGetItemCount(); + if (index < itemCount) { + m_owner->AccGetItemName(index,name); + } else if (index < itemCount + m_owner->AccGetOtherCount()) { + m_owner->AccGetOtherName(index - itemCount, name); + } else { + return E_INVALIDARG; + } + } + *pszName = SysAllocString(pfc::stringcvt::string_wide_from_utf8(name)); + return S_OK; +} +HRESULT IAccessible_CListControl::get_accValue(VARIANT varChild, BSTR *pszValue) { + if (varChild.vt != VT_I4) + return E_INVALIDARG; + if (pszValue == NULL) + return E_INVALIDARG; + + return S_FALSE; +} +HRESULT IAccessible_CListControl::get_accDescription(VARIANT varChild, BSTR *pszDescription) { + if (varChild.vt != VT_I4) return E_INVALIDARG; + if (pszDescription == NULL) return E_INVALIDARG; + if (varChild.lVal == CHILDID_SELF) return S_FALSE; + if (m_owner.IsEmpty()) return E_FAIL; + + *pszDescription = NULL; + + const size_t itemCount = m_owner->AccGetItemCount(); + const size_t index = IndexFromChildId(varChild.lVal); + + pfc::string_formatter temp; + if (index < itemCount) { + if (!m_owner->AccGetItemDescription(index, temp)) return S_FALSE; + } else if (index < itemCount + m_owner->AccGetOtherCount()) { + if (!m_owner->AccGetOtherDescription(index - itemCount, temp)) return S_FALSE; + } else { + return E_INVALIDARG; + } + *pszDescription = SysAllocString(pfc::stringcvt::string_os_from_utf8(temp)); + return S_OK; +} + +HRESULT IAccessible_CListControl::get_accRole(VARIANT varChild, VARIANT *pvarRole) // required +{ + if (varChild.vt != VT_I4) return E_INVALIDARG; + if (pvarRole == NULL) return E_INVALIDARG; + if (m_owner.IsEmpty()) return E_FAIL; + VariantClear(pvarRole); + pvarRole->vt = VT_I4; + if (varChild.lVal == CHILDID_SELF) { + pvarRole->lVal = ROLE_SYSTEM_LIST; + } else { + const size_t itemCount = m_owner->AccGetItemCount(); + const size_t index = IndexFromChildId(varChild.lVal); + if (index < itemCount) { + pvarRole->lVal = m_owner->AccGetItemRole( index ); + } else if (index < itemCount + m_owner->AccGetOtherCount()) { + pvarRole->lVal = m_owner->AccGetOtherRole(index - itemCount); + } else { + return E_INVALIDARG; + } + } + return S_OK; +} + +HRESULT IAccessible_CListControl::get_accState(VARIANT varChild, VARIANT *pvarState) // required +{ + if (varChild.vt != VT_I4) return E_INVALIDARG; + if (pvarState == NULL) return E_INVALIDARG; + if (m_owner.IsEmpty()) return E_FAIL; + + VariantClear(pvarState); + pvarState->vt = VT_I4; + + if (varChild.lVal == CHILDID_SELF) { + pvarState->lVal = /*STATE_SYSTEM_NORMAL*/ 0; + if (GetFocus() == m_owner->AccGetWnd()) + pvarState->lVal |= STATE_SYSTEM_FOCUSED; + } + else + { + const size_t index = IndexFromChildId(varChild.lVal); + const size_t itemCount = m_owner->AccGetItemCount(); + if (index < itemCount) { + pvarState->lVal = STATE_SYSTEM_MULTISELECTABLE | STATE_SYSTEM_SELECTABLE; + if (GetFocus() == m_owner->AccGetWnd()) { + pvarState->lVal |= STATE_SYSTEM_FOCUSABLE; + if (m_owner->AccGetFocusItem() == index) pvarState->lVal |= STATE_SYSTEM_FOCUSED; + } + if (m_owner->AccIsItemSelected(index)) pvarState->lVal |= STATE_SYSTEM_SELECTED; + if (!m_owner->AccIsItemVisible(index)) pvarState->lVal |= STATE_SYSTEM_OFFSCREEN | STATE_SYSTEM_INVISIBLE; + if (m_owner->AccIsItemChecked(index)) pvarState->lVal |= STATE_SYSTEM_CHECKED; + } else if (index < itemCount + m_owner->AccGetOtherCount()) { + const size_t indexO = index - itemCount; + pvarState->lVal = 0; + if (m_owner->AccIsOtherFocusable(indexO) && GetFocus() == m_owner->AccGetWnd()) { + pvarState->lVal |= STATE_SYSTEM_FOCUSABLE; + if (m_owner->AccGetFocusOther() == indexO) pvarState->lVal |= STATE_SYSTEM_FOCUSED; + } + if (!m_owner->AccIsOtherVisible(indexO)) pvarState->lVal |= STATE_SYSTEM_OFFSCREEN | STATE_SYSTEM_INVISIBLE; + } else { + return E_INVALIDARG; + } + } + return S_OK; +} +HRESULT IAccessible_CListControl::get_accHelp(VARIANT varChild, BSTR *pszHelp) +{ + if (varChild.vt != VT_I4) + return E_INVALIDARG; + if (pszHelp == NULL) + return E_INVALIDARG; + return S_FALSE; +} +HRESULT IAccessible_CListControl::get_accHelpTopic(BSTR *pszHelpFile, VARIANT varChild, long *pidTopic) +{ + if (varChild.vt != VT_I4) + return E_INVALIDARG; + if (pszHelpFile == NULL) + return E_INVALIDARG; + if (pidTopic == NULL) + return E_INVALIDARG; + return S_FALSE; +} +HRESULT IAccessible_CListControl::get_accKeyboardShortcut(VARIANT varChild, BSTR *pszKeyboardShortcut) +{ + if (pszKeyboardShortcut == NULL) + return E_INVALIDARG; + return S_FALSE; +} +HRESULT IAccessible_CListControl::get_accFocus(VARIANT *pvarChild) +{ + if (pvarChild == NULL) return E_INVALIDARG; + if (m_owner.IsEmpty()) return E_FAIL; + VariantClear(pvarChild); + if (GetFocus() != m_owner->AccGetWnd()) + pvarChild->vt = VT_EMPTY; + else { + pvarChild->vt = VT_I4; + size_t index = m_owner->AccGetFocusItem(); + if (index != ~0) { + pvarChild->lVal = ChildIdFromIndex(index); + } else { + index = m_owner->AccGetFocusOther(); + if (index != ~0) { + pvarChild->lVal = ChildIdFromIndex(index + m_owner->AccGetItemCount()); + } else { + pvarChild->lVal = CHILDID_SELF; + } + } + } + return S_OK; +} +HRESULT IAccessible_CListControl::get_accSelection(VARIANT *pvarChildren) +{ + if (pvarChildren == NULL) return E_INVALIDARG; + if (m_owner.IsEmpty()) return E_FAIL; + VariantClear(pvarChildren); + try { + + const size_t itemCount = m_owner->AccGetItemCount(); + size_t selCount = 0; + pfc::bit_array_bittable mask(itemCount); + for(size_t walk = 0; walk < itemCount; ++walk) { + bool state = m_owner->AccIsItemSelected(walk); + mask.set(walk,state); + if (state) selCount++; + } + + if (selCount == 0) { + pvarChildren->vt = VT_EMPTY; + } else if (selCount == 1) { + pvarChildren->vt = VT_I4; + pvarChildren->lVal = ChildIdFromIndex(mask.find_first(true, 0, itemCount)); + } else { + pfc::array_t data; data.set_size(selCount); size_t dataWalk = 0; + for(size_t walk = mask.find_first(true,0,itemCount); walk < itemCount; walk = mask.find_next(true,walk,itemCount)) { + data[dataWalk++] = ChildIdFromIndex(walk); + } + IEnumVARIANT * ptr = new IEnumVARIANT_selection(data.get_ptr(),data.get_size()); + ptr->AddRef(); + pvarChildren->vt = VT_UNKNOWN; + pvarChildren->punkVal = ptr; + } + } catch(std::bad_alloc) { + return E_OUTOFMEMORY; + } + return S_OK; +} +HRESULT IAccessible_CListControl::get_accDefaultAction(VARIANT varChild, BSTR *pszDefaultAction) +{ + if (varChild.vt != VT_I4) return E_INVALIDARG; + if (pszDefaultAction == NULL) return E_INVALIDARG; + if (m_owner.IsEmpty()) return E_FAIL; + *pszDefaultAction = NULL; + if (varChild.lVal == CHILDID_SELF) { + return S_FALSE; + } else { + pfc::string_formatter temp; + const size_t index = IndexFromChildId(varChild.lVal); + const size_t itemCount = m_owner->AccGetItemCount(); + if (index < itemCount) { + if (!m_owner->AccGetItemDefaultAction(temp)) return S_FALSE; + } else if (index < itemCount + m_owner->AccGetOtherCount()) { + if (!m_owner->AccGetOtherDefaultAction(index - itemCount, temp)) return false; + } else { + return E_INVALIDARG; + } + + *pszDefaultAction = SysAllocString(pfc::stringcvt::string_os_from_utf8(temp)); + return S_OK; + } +} +HRESULT IAccessible_CListControl::accSelect(long flagsSelect, VARIANT varChild) +{ + if (varChild.vt != VT_EMPTY && varChild.vt != VT_I4) return E_INVALIDARG; + if (m_owner.IsEmpty()) return E_FAIL; + if (varChild.vt == VT_EMPTY || varChild.lVal == CHILDID_SELF) + { + switch (flagsSelect) + { + case SELFLAG_TAKEFOCUS: + m_owner->AccGetWnd().SetFocus(); + return S_OK; + default: + return DISP_E_MEMBERNOTFOUND; + } + } + else + { + const size_t count = m_owner->AccGetItemCount(); + const size_t index = IndexFromChildId(varChild.lVal); + if (index < count) { + if (flagsSelect & SELFLAG_TAKESELECTION) + { + if (flagsSelect & (SELFLAG_ADDSELECTION | SELFLAG_REMOVESELECTION | SELFLAG_EXTENDSELECTION)) + return E_INVALIDARG; + m_owner->AccSetSelection(pfc::bit_array_true(), pfc::bit_array_one(index)); + } + else if (flagsSelect & SELFLAG_EXTENDSELECTION) + { + if (flagsSelect & SELFLAG_TAKESELECTION) + return E_INVALIDARG; + size_t focus = m_owner->AccGetFocusItem(); + if (focus == ~0) return S_FALSE; + bool state; + if (flagsSelect & SELFLAG_ADDSELECTION) + { + if (flagsSelect & SELFLAG_REMOVESELECTION) + return E_INVALIDARG; + state = true; + } + else if (flagsSelect & SELFLAG_REMOVESELECTION) + { + if (flagsSelect & SELFLAG_ADDSELECTION) + return E_INVALIDARG; + state = false; + } + else + { + state = m_owner->AccIsItemSelected(focus); + } + m_owner->AccSetSelection(pfc::bit_array_range(min(index, focus), max(index, focus)-min(index, focus)+1), pfc::bit_array_val(state)); + } + else if (flagsSelect & SELFLAG_ADDSELECTION) + { + if (flagsSelect & (SELFLAG_REMOVESELECTION | SELFLAG_EXTENDSELECTION)) + return E_INVALIDARG; + m_owner->AccSetSelection(pfc::bit_array_one(index), pfc::bit_array_true()); + } + else if (flagsSelect & SELFLAG_REMOVESELECTION) + { + if (flagsSelect & (SELFLAG_ADDSELECTION | SELFLAG_EXTENDSELECTION)) + return E_INVALIDARG; + m_owner->AccSetSelection(pfc::bit_array_one(index), pfc::bit_array_false()); + } + + if (flagsSelect & SELFLAG_TAKEFOCUS) { + m_owner->AccSetFocusItem(index); + } + } else if (index < count + m_owner->AccGetOtherCount()) { + const size_t indexO = index - count; + if (flagsSelect & SELFLAG_TAKEFOCUS) { + m_owner->AccSetFocusOther(indexO); + } + } else { + return E_INVALIDARG; + } + return S_OK; + } +} +HRESULT IAccessible_CListControl::accLocation(long *pxLeft, long *pyTop, long *pcxWidth, long *pcyHeight, VARIANT varChild) // required +{ + if (varChild.vt != VT_I4) return E_INVALIDARG; + if (pxLeft == NULL || pyTop == NULL || pcxWidth == NULL || pcyHeight == NULL) return E_INVALIDARG; + if (m_owner.IsEmpty()) return E_FAIL; + CRect rc; + const CWindow ownerWnd = m_owner->AccGetWnd(); + if (varChild.lVal == CHILDID_SELF) { + if (!ownerWnd.GetClientRect(&rc)) return E_FAIL; + } else { + const size_t index = IndexFromChildId(varChild.lVal); + const size_t itemCount = m_owner->AccGetItemCount(); + if (index < itemCount) { + if (!m_owner->AccGetItemRect(index,rc)) return S_FALSE; + } else if (index < itemCount + m_owner->AccGetOtherCount()) { + if (!m_owner->AccGetOtherRect(index - itemCount, rc)) return S_FALSE; + } else { + return E_INVALIDARG; + } + } + + if (!ownerWnd.ClientToScreen(rc)) return E_FAIL; + *pxLeft = rc.left; + *pyTop = rc.top; + *pcxWidth = rc.right-rc.left; + *pcyHeight = rc.bottom-rc.top; + return S_OK; +} +HRESULT IAccessible_CListControl::accNavigate(long navDir, VARIANT varStart, VARIANT *pvarEndUpAt) +{ + if (varStart.vt != VT_I4 && varStart.vt != VT_EMPTY) return E_INVALIDARG; + if (pvarEndUpAt == NULL) return E_INVALIDARG; + if (m_owner.IsEmpty()) return E_FAIL; + VariantClear(pvarEndUpAt); + pvarEndUpAt->vt = VT_EMPTY; + if (varStart.vt == VT_EMPTY || varStart.lVal == CHILDID_SELF) + { + switch (navDir) + { + case NAVDIR_LEFT: + case NAVDIR_RIGHT: + case NAVDIR_UP: + case NAVDIR_DOWN: + case NAVDIR_PREVIOUS: + case NAVDIR_NEXT: + // leave empty + break; + case NAVDIR_FIRSTCHILD: + if (m_owner->AccGetItemCount() > 0) + { + pvarEndUpAt->vt = VT_I4; + pvarEndUpAt->lVal = ChildIdFromIndex(0); + } + break; + case NAVDIR_LASTCHILD: + if (m_owner->AccGetItemCount() > 0) + { + pvarEndUpAt->vt = VT_I4; + pvarEndUpAt->lVal = ChildIdFromIndex(m_owner->AccGetItemCount()-1); + } + break; + } + } + else + { + const size_t index = IndexFromChildId(varStart.lVal); + const size_t itemCount = m_owner->AccGetItemCount(); + if (index >= itemCount) { + if (index < itemCount + m_owner->AccGetOtherCount()) return S_FALSE; + else return E_INVALIDARG; + } + switch (navDir) + { + case NAVDIR_LEFT: + case NAVDIR_RIGHT: + case NAVDIR_FIRSTCHILD: + case NAVDIR_LASTCHILD: + // leave empty + break; + case NAVDIR_UP: + case NAVDIR_PREVIOUS: + if (index > 0) + { + pvarEndUpAt->vt = VT_I4; + pvarEndUpAt->lVal = ChildIdFromIndex(index-1); + } + break; + case NAVDIR_DOWN: + case NAVDIR_NEXT: + if (index+1 < itemCount) + { + pvarEndUpAt->vt = VT_I4; + pvarEndUpAt->lVal = ChildIdFromIndex(index+1); + } + break; + } + } + return (pvarEndUpAt->vt != VT_EMPTY) ? S_OK : S_FALSE; +} +HRESULT IAccessible_CListControl::accHitTest(long xLeft, long yTop, VARIANT *pvarChild) +{ + if (pvarChild == NULL) return E_INVALIDARG; + if (m_owner.IsEmpty()) return E_FAIL; + VariantClear(pvarChild); + CPoint pt (xLeft, yTop); + const CWindow ownerWnd = m_owner->AccGetWnd(); + if (!ownerWnd.ScreenToClient(&pt)) return E_FAIL; + CRect rcClient; + if (!ownerWnd.GetClientRect(&rcClient)) return E_FAIL; + if (PtInRect(&rcClient, pt)) { + size_t index = m_owner->AccItemHitTest(pt); + if (index != ~0) { + pvarChild->vt = VT_I4; + pvarChild->lVal = ChildIdFromIndex(index); + } else { + index = m_owner->AccOtherHitTest(pt); + if (index != ~0) { + pvarChild->vt = VT_I4; + pvarChild->lVal = ChildIdFromIndex(m_owner->AccGetItemCount() + index); + CWindow wnd = m_owner->AccGetOtherChildWnd(index); + if (wnd != NULL) { + IDispatch * obj; + if (AccessibleObjectFromWindow(wnd,OBJID_WINDOW, IID_IDispatch, (void**)&obj) == S_OK) { + pvarChild->vt = VT_DISPATCH; + pvarChild->pdispVal = obj; + } + } + } else { + pvarChild->vt = VT_I4; + pvarChild->lVal = CHILDID_SELF; + } + } + } else { + pvarChild->vt = VT_EMPTY; + } + return S_OK; +} +HRESULT IAccessible_CListControl::accDoDefaultAction(VARIANT varChild) +{ + if (varChild.vt != VT_I4) return E_INVALIDARG; + if (m_owner.IsEmpty()) return E_FAIL; + if (varChild.lVal == CHILDID_SELF) return S_FALSE; + const size_t index = IndexFromChildId(varChild.lVal); + const size_t itemCount = m_owner->AccGetItemCount(); + if (index < itemCount) { + if (!m_owner->AccExecuteItemDefaultAction(index)) return S_FALSE; + } else if (index < itemCount + m_owner->AccGetOtherCount()) { + if (!m_owner->AccExecuteOtherDefaultAction(index - itemCount)) return S_FALSE; + } else { + return E_INVALIDARG; + } + return S_OK; +} +HRESULT IAccessible_CListControl::put_accName(VARIANT varChild, BSTR szName) { + return DISP_E_MEMBERNOTFOUND; +} +HRESULT IAccessible_CListControl::put_accValue(VARIANT varChild, BSTR szValue) { + return DISP_E_MEMBERNOTFOUND; +} + +void CListAccessible::AccGetName(pfc::string_base & out) const { + auto str = pfc::getWindowText(m_wnd); + if ( str.length() > 0 ) out = str; + else out = "List Control"; +} diff --git a/libPPUI/CListAccessible.h b/libPPUI/CListAccessible.h new file mode 100644 index 0000000..a1dad3d --- /dev/null +++ b/libPPUI/CListAccessible.h @@ -0,0 +1,292 @@ +#pragma once + +#include + +#pragma comment(lib, "oleacc.lib") + +#include "CListControl-Cell.h" +#include "CListControlWithSelection.h" + +//! Internal class interfacing with Windows accessibility APIs. \n +//! This class is not tied to any specific control and requires most of its methods to be overridden. \n +//! With CListControl you want to use CListControlAccImpl<> template instead of using CListAccessible directly. +class CListAccessible { +public: + void AccInitialize(CWindow wnd); + void AccCleanup(); + LRESULT AccGetObject(WPARAM wp,LPARAM lp); + CWindow AccGetWnd() const {return m_wnd;} + virtual size_t AccGetItemCount() const {return 0;} + virtual LONG AccGetItemRole( size_t index ) const {return ROLE_SYSTEM_LISTITEM;} + virtual void AccGetItemName(size_t index, pfc::string_base & out) const {out = "";} + virtual void AccGetName(pfc::string_base & out) const; + virtual size_t AccGetFocusItem() const {return ~0;} + virtual bool AccIsItemSelected(size_t index) const {return false;} + virtual bool AccIsItemChecked( size_t index ) const { return false; } + virtual bool AccIsItemVisible(size_t index) const {return false;} + virtual bool AccGetItemDefaultAction(pfc::string_base & out) const {return false;} + virtual bool AccExecuteItemDefaultAction(size_t index) {return false;} + virtual void AccSetSelection(pfc::bit_array const & affected, pfc::bit_array const & state) {} + virtual void AccSetFocusItem(size_t index) {} + virtual bool AccGetItemRect(size_t index, CRect & out) const {return false;} + virtual bool AccGetItemDescription(size_t index, pfc::string_base & out) const {return false;} + virtual size_t AccItemHitTest(CPoint const & pt) const {return ~0;} + + virtual DWORD AccGetOtherRole(size_t index) {return 0;} + virtual size_t AccGetOtherCount() const {return 0;} + virtual void AccGetOtherName(size_t index, pfc::string_base & out) const {out = "";} + virtual size_t AccGetFocusOther() const {return ~0;} + virtual void AccSetFocusOther(size_t index) {} + virtual bool AccIsOtherVisible(size_t index) const {return false;} + virtual bool AccGetOtherDescription(size_t index, pfc::string_base & out) const {return false;} + virtual size_t AccOtherHitTest(CPoint const & pt) const {return ~0;} + virtual bool AccIsOtherFocusable(size_t index) const {return false;} + virtual bool AccGetOtherDefaultAction(size_t index, pfc::string_base & out) const {return false;} + virtual bool AccExecuteOtherDefaultAction(size_t index) {return false;} + virtual bool AccGetOtherRect(size_t index, CRect & out) const {return false;} + virtual CWindow AccGetOtherChildWnd(size_t index) const {return NULL;} + + void AccItemNamesChanged( pfc::bit_array const & mask); + void AccReloadItems(pfc::bit_array const & mask ); + void AccRefreshItems(pfc::bit_array const & mask, UINT what); + void AccStateChange(pfc::bit_array const & mask); + void AccStateChange(size_t index); + void AccItemLayoutChanged(); + void AccFocusItemChanged(size_t index); + void AccFocusOtherChanged(size_t index); + void AccSelectionChanged(const pfc::bit_array & affected, const pfc::bit_array & status); + void AccLocationChange(); +protected: + ~CListAccessible() { * m_killSwitch = true; } +private: + CWindow m_wnd; + std::shared_ptr m_killSwitch = std::make_shared(); + pfc::com_ptr_t m_interface; +}; + +//! Basic wrapper implementing CListAccessible methods on top of CListControl. Leaves many methods to be overridden by calle +template class CListControlAccImpl : public TBaseClass, protected CListAccessible { +public: + template CListControlAccImpl( arg_t && ... arg ) : TBaseClass(std::forward(arg) ... ) {} + + BEGIN_MSG_MAP_EX(CListControlAccImpl) + MESSAGE_HANDLER(WM_GETOBJECT,OnGetObject) + MESSAGE_HANDLER(WM_DESTROY,OnDestroyPassThru) + MESSAGE_HANDLER(WM_CREATE,OnCreatePassThru) + CHAIN_MSG_MAP(TBaseClass) + END_MSG_MAP() +public: + void ReloadData() { + TBaseClass::ReloadData(); + AccItemLayoutChanged(); + } + void ReloadItems(pfc::bit_array const & mask) { + TBaseClass::ReloadItems(mask); + AccReloadItems(mask); + } +protected: + size_t AccGetItemCount() const {return this->GetItemCount();} + size_t AccGetFocusItem() const {return this->GetFocusItem();} + bool AccIsItemSelected(size_t index) const {return this->IsItemSelected(index);} + bool AccIsItemVisible(size_t index) const { + if (index >= AccGetItemCount()) return false; + return this->IsRectVisible(this->GetItemRect(index)); + } + bool AccExecuteItemDefaultAction(size_t index) {if (index < AccGetItemCount()) {this->ExecuteDefaultAction(index);return true;} else return false;} + bool AccGetItemRect(size_t index, CRect & out) const { + if (index >= AccGetItemCount()) return false; + out = this->GetItemRect(index); + return true; + } + size_t AccItemHitTest(CPoint const & pt) const { + size_t item; + if (!this->ItemFromPoint(pt,item)) return ~0; + return item; + } + + void OnViewOriginChange(CPoint p_delta) override { + TBaseClass::OnViewOriginChange(p_delta); + AccLocationChange(); + } + + /* overrideme, optional + void AccGetName(pfc::string_base & out) const; + bool AccGetItemDefaultAction(pfc::string_base & out) const; + */ + + LONG AccRoleAt(size_t idx, size_t sub) const { + auto cell = this->GetCellType(idx, sub); + if (cell == nullptr) return 0; + return cell->AccRole(); + } + bool isCellText(size_t idx, size_t sub) const { + switch (AccRoleAt(idx, sub)) { + case ROLE_SYSTEM_TEXT: + case ROLE_SYSTEM_STATICTEXT: + case ROLE_SYSTEM_LISTITEM: + return true; + default: + return false; + } + } + bool useCellForDescription(size_t idx, size_t sub) const { + return sub > 0 && isCellText(idx, sub); + } + bool AccGetItemDescription(size_t index, pfc::string_base& out) const { + pfc::string_formatter ret, temp, temp2; + const size_t total = this->GetColumnCount(); + for (size_t walk = 0; walk < total; ) { + if (useCellForDescription(index, walk) && this->GetSubItemText(index, walk, temp) && temp.length() > 0) { + if (ret.length() > 0) ret << "; "; + this->GetColumnText(walk, temp2); + if (temp2.length() > 0) ret << temp2 << ": "; + ret << temp; + } + size_t d = this->GetSubItemSpan(index, walk); + if (d < 1) d = 1; + walk += d; + } + bool rv = (ret.length() > 0); + if (rv) out = ret; + return ret; + } + + void AccGetItemNameAlt(size_t index, pfc::string_base & out) const { + pfc::string_formatter ret, temp; + const size_t total = this->GetColumnCount(); + for (size_t walk = 0; walk < total; ) { + if (this->isCellText(index, walk) && this->GetSubItemText(index, walk, temp) && temp.length() > 0) { + if (ret.length() > 0) ret << "; "; + ret << temp; + } + size_t d = this->GetSubItemSpan(index, walk); + if (d < 1) d = 1; + walk += d; + } + out = ret; + } + + // Item name by default taken from column 0, override this if you supply another + void AccGetItemName(size_t index, pfc::string_base & out) const { + this->GetSubItemText(index, 0, out); + } + + void AccSetSelection(pfc::bit_array const & affected, pfc::bit_array const & state) override { + this->SetSelection(affected, state); +} + void AccSetFocusItem(size_t index) override { + this->SetFocusItem(index); + } + + void OnFocusChangedGroup(int iGroup) override { + TBaseClass::OnFocusChangedGroup(iGroup); + AccFocusOtherChanged((size_t)iGroup); + } + void OnFocusChanged(size_t f) override { + TBaseClass::OnFocusChanged(f); + AccFocusItemChanged(f); + } + + void OnSelectionChanged(pfc::bit_array const & affected, pfc::bit_array const & status) override { + TBaseClass::OnSelectionChanged(affected, status); + AccSelectionChanged(affected, status); + } + + CWindow AccGetOtherChildWnd(size_t index) const {return index == 0 ? CWindow(this->GetHeaderCtrl()) : CWindow(NULL) ;} + virtual DWORD AccGetOtherRole(size_t index) {return index > 0 ? ROLE_SYSTEM_GROUPING : ROLE_SYSTEM_WINDOW;}//FIXME? + virtual bool AccGetOtherDescription(size_t index, pfc::string_base & out) const {return false;}//FIXME?? + + size_t AccGetOtherCount() const {return 1 + this->GetGroupCount();} + void AccGetOtherName(size_t index, pfc::string_base & out) const override { + if (index == 0) out = "Columns Header"; + else if (!this->GetGroupHeaderText((int)index, out)) out = ""; + } + size_t AccGetFocusOther() const override { + int focus = this->GetGroupFocus(); + if (focus > 0) return (size_t) focus; + else return SIZE_MAX; + } + void AccSetFocusOther(size_t index) override { + if (index > 0) this->SetGroupFocus((int)index); + } + bool AccIsOtherVisible(size_t index) const override { + if (index == 0) return true; + CRect rc; + if (!this->GetGroupHeaderRect((int)index,rc)) return false; + return IsRectVisible(rc); + } + + size_t AccOtherHitTest(CPoint const & pt) const { + { + CPoint s(pt); + if (this->ClientToScreen(&s)) { + CRect rc; + auto hdr = this->GetHeaderCtrl(); + if (hdr != NULL && hdr.GetWindowRect(rc)) { + if (rc.PtInRect(s)) { + return 0; + } + } + } + } + int group; + if (this->GroupHeaderFromPoint(pt,group)) return (size_t) group; + return ~0; + } + bool AccIsOtherFocusable(size_t index) const {return index > 0;} + bool AccGetOtherDefaultAction(size_t index, pfc::string_base & out) const {return false;} + bool AccExecuteOtherDefaultAction(size_t index) {return false;} + bool AccGetOtherRect(size_t index, CRect & out) const { + if (index == 0) { + CRect rc, client; + + auto hdr = this->GetHeaderCtrl(); + if ( hdr == NULL ) return false; + if (!hdr.GetWindowRect(rc)) return false; + if (!this->ScreenToClient(rc)) return false; + if (!this->GetClientRect(client)) return false; + return !! out.IntersectRect(rc, client); + } else { + return this->GetGroupHeaderRect((int)index, out); + } + } + LONG AccGetItemRole( size_t index ) const override { + auto type = this->GetCellType( index, 0 ); + if ( type != nullptr ) { + return type->AccRole(); + } + return ROLE_SYSTEM_LISTITEM; + } + void SetCellCheckState(size_t item, size_t subItem, bool value) override { + __super::SetCellCheckState(item, subItem, value); + this->AccStateChange(item); + } + bool AccIsItemChecked( size_t index ) const override { + auto type = this->GetCellType( index, 0 ); + if ( type != nullptr && type->IsToggle() ) { + return this->GetCellCheckState( index, 0 ); + } + return false; + } +private: + bool IsRectVisible(CRect const & rc) const { + return !!CRect().IntersectRect(this->GetClientRectHook(),rc); + } + LRESULT OnCreatePassThru(UINT,WPARAM,LPARAM,BOOL& bHandled) { + bHandled = FALSE; + try { AccInitialize(*this); } catch(...) {AccCleanup();} + return 0; + } + LRESULT OnDestroyPassThru(UINT,WPARAM,LPARAM,BOOL& bHandled) { + bHandled = FALSE; + AccCleanup(); + return 0; + } + LRESULT OnGetObject(UINT,WPARAM wp, LPARAM lp, BOOL & ) { + return this->AccGetObject(wp,lp); + } + +}; + +class CListControlWithSelectionImpl; +typedef CListControlAccImpl< CListControlWithSelectionImpl > CListControlWithSelectionAccImpl; diff --git a/libPPUI/CListControl-Cell.h b/libPPUI/CListControl-Cell.h new file mode 100644 index 0000000..8fe163f --- /dev/null +++ b/libPPUI/CListControl-Cell.h @@ -0,0 +1,39 @@ +#pragma once +#include // HTHEME + +class CListCell { +public: + typedef uint32_t cellState_t; + enum { + cellState_none = 0, + cellState_hot = 1 << 0, + cellState_pressed = 1 << 1, + cellState_disabled = 1 << 2, + }; + + struct DrawContentArg_t { + DWORD hdrFormat; + cellState_t cellState = 0; + CRect subItemRect; + CDCHandle dc; + const wchar_t * text = nullptr; + bool allowColors = true; + CRect rcHot; + CRect rcText; + HTHEME theme = NULL; + uint32_t colorHighlight = 0; + CWindow thisWnd; + }; + virtual void DrawContent( DrawContentArg_t const & arg ) = 0; + virtual const char * Theme() { return nullptr; } + virtual bool ApplyTextStyle( LOGFONT & font, double scale, uint32_t state ); + virtual bool IsInteractive() { return false; } + virtual bool SuppressRowSelect() { return false; } + virtual bool IsToggle() { return false; } + virtual bool IsRadioToggle() { return false; } + virtual bool AllowTypeFind() { return true; } + virtual CRect HotRect( CRect rc ) { return rc; } + virtual HCURSOR HotCursor() { return NULL; } + virtual bool AllowDrawThemeText() { return false; } + virtual LONG AccRole(); +}; diff --git a/libPPUI/CListControl-Cells-Compat.h b/libPPUI/CListControl-Cells-Compat.h new file mode 100644 index 0000000..ca2379b --- /dev/null +++ b/libPPUI/CListControl-Cells-Compat.h @@ -0,0 +1,13 @@ +#pragma once +#include "CListControl-Cells.h" + +// Wrapper for old code using cell type enum constants + +#define cell_text &PFC_SINGLETON(CListCell_Text) +#define cell_multitext &PFC_SINGLETON(CListCell_MultiText) +#define cell_hyperlink &PFC_SINGLETON(CListCell_Hyperlink) +#define cell_button &PFC_SINGLETON(CListCell_Button) +#define cell_button_lite &PFC_SINGLETON(CListCell_ButtonLite) +#define cell_button_glyph &PFC_SINGLETON(CListCell_ButtonGlyph) +#define cell_checkbox &PFC_SINGLETON(CListCell_Checkbox) +#define cell_radiocheckbox &PFC_SINGLETON(CListCell_RadioCheckbox) diff --git a/libPPUI/CListControl-Cells.cpp b/libPPUI/CListControl-Cells.cpp new file mode 100644 index 0000000..89cfd7d --- /dev/null +++ b/libPPUI/CListControl-Cells.cpp @@ -0,0 +1,298 @@ +#include "stdafx.h" +#include "CListControl.h" +#include "CListControlHeaderImpl.h" +#include "CListControl-Cells.h" +#include "PaintUtils.h" +#include "GDIUtils.h" +#include + +LONG CListCell::AccRole() { + return ROLE_SYSTEM_LISTITEM; +} + +void RenderCheckbox( HTHEME theme, CDCHandle dc, CRect rcCheckBox, unsigned stateFlags, bool bRadio ) { + + const int part = bRadio ? BP_RADIOBUTTON : BP_CHECKBOX; + + const bool bDisabled = (stateFlags & CListCell::cellState_disabled) != 0; + const bool bPressed = (stateFlags & CListCell::cellState_pressed ) != 0; + const bool bHot = ( stateFlags & CListCell::cellState_hot ) != 0; + + if (theme != NULL && IsThemePartDefined(theme, part, 0)) { + int state = 0; + if (bDisabled) { + state = bPressed ? CBS_CHECKEDDISABLED : CBS_DISABLED; + } else if ( bHot ) { + state = bPressed ? CBS_CHECKEDHOT : CBS_HOT; + } else { + state = bPressed ? CBS_CHECKEDNORMAL : CBS_NORMAL; + } + + CSize size; + if (SUCCEEDED(GetThemePartSize(theme, dc, part, state, rcCheckBox, TS_TRUE, &size))) { + if (size.cx <= rcCheckBox.Width() && size.cy <= rcCheckBox.Height()) { + CRect rc = rcCheckBox; + rc.left += ( rc.Width() - size.cx ) / 2; + rc.top += ( rc.Height() - size.cy ) / 2; + rc.right = rc.left + size.cx; + rc.bottom = rc.top + size.cy; + DrawThemeBackground(theme, dc, part, state, rc, &rc); + return; + } + } + } + int stateEx = bRadio ? DFCS_BUTTONRADIO : DFCS_BUTTONCHECK; + if ( bPressed ) stateEx |= DFCS_CHECKED; + if ( bDisabled ) stateEx |= DFCS_INACTIVE; + else if ( bHot ) stateEx |= DFCS_HOT; + DrawFrameControl(dc, rcCheckBox, DFC_BUTTON, stateEx); +} + +void RenderButton( HTHEME theme, CDCHandle dc, CRect rcButton, CRect rcUpdate, uint32_t cellState ) { + + const int part = BP_PUSHBUTTON; + + enum { + stNormal = PBS_NORMAL, + stHot = PBS_HOT, + stDisabled = PBS_DISABLED, + stPressed = PBS_PRESSED, + }; + + int state = 0; + if (cellState & CListCell::cellState_disabled) state = stDisabled; + if ( cellState & CListCell::cellState_pressed ) state = stPressed; + else if ( cellState & CListCell::cellState_hot ) state = stHot; + else state = stNormal; + + CRect rcClient = rcButton; + + if (theme != NULL && IsThemePartDefined(theme, part, 0)) { + DrawThemeBackground(theme, dc, part, state, rcClient, &rcUpdate); + } else { + int stateEx = DFCS_BUTTONPUSH; + switch (state) { + case stPressed: stateEx |= DFCS_PUSHED; break; + case stDisabled: stateEx |= DFCS_INACTIVE; break; + } + DrawFrameControl(dc, rcClient, DFC_BUTTON, stateEx); + } +} + +bool CListCell::ApplyTextStyle( LOGFONT & font, double scale, uint32_t ) { + if ( scale != 1.0 ) { + font.lfHeight = pfc::rint32( font.lfHeight * scale ); + return true; + } else { + return false; + } +} + +void CListCell_Text::DrawContent( DrawContentArg_t const & arg ) { + const auto fgWas = arg.dc.GetTextColor(); + CDCHandle dc = arg.dc; + if (arg.cellState & cellState_disabled) { + dc.SetTextColor(GetSysColor(COLOR_GRAYTEXT)); + } + + + CRect clip = arg.rcText; + + const t_uint32 format = PaintUtils::DrawText_TranslateHeaderAlignment(arg.hdrFormat); + dc.DrawText( arg.text, (int)wcslen(arg.text), clip, format | DT_NOPREFIX | DT_END_ELLIPSIS | DT_SINGLELINE | DT_VCENTER ); + + dc.SetTextColor(fgWas); +} + +void CListCell_TextColors::DrawContent( DrawContentArg_t const & arg ) { + CDCHandle dc = arg.dc; + + CRect clip = arg.rcText; + + const uint32_t fgWas = dc.GetTextColor(); + + const t_uint32 format = PaintUtils::DrawText_TranslateHeaderAlignment(arg.hdrFormat); + const t_uint32 bk = dc.GetBkColor(); + const t_uint32 fg = fgWas; + const t_uint32 hl = (arg.allowColors ? arg.colorHighlight : fg); + const t_uint32 colors[3] = { PaintUtils::BlendColor(bk, fg, 33), fg, hl }; + + PaintUtils::TextOutColorsEx(dc, arg.text, clip, format, colors); + + dc.SetTextColor(fgWas); +} + +void CListCell_MultiText::DrawContent( DrawContentArg_t const & arg ) { + CDCHandle dc = arg.dc; + + const int textLen = (int) wcslen( arg.text ); + + CRect clip = arg.rcText; + + const t_uint32 format = PaintUtils::DrawText_TranslateHeaderAlignment(arg.hdrFormat) | DT_NOPREFIX | DT_VCENTER ; + + CRect rcDraw = clip; + dc.DrawText(arg.text, textLen, rcDraw, format | DT_CALCRECT); + auto txSize = rcDraw.Size(); + rcDraw = clip; + if ( txSize.cy < rcDraw.Height() ) { + int sub = rcDraw.Height() - txSize.cy; + rcDraw.top += sub/2; + rcDraw.bottom = rcDraw.top + txSize.cy; + } + dc.DrawText(arg.text, textLen, rcDraw, format); +} + +bool CListCell_Hyperlink::ApplyTextStyle( LOGFONT & font, double scale, uint32_t state ) { + bool rv = __super::ApplyTextStyle(font, scale, state); + + if ( state & cellState_hot ) { + font.lfUnderline = TRUE; + rv = true; + } + + return rv; +} + +HCURSOR CListCell_Hyperlink::HotCursor() { + return LoadCursor(NULL, IDC_HAND); +} + +LONG CListCell_Hyperlink::AccRole() { + return ROLE_SYSTEM_LINK; +} + +void CListCell_Hyperlink::DrawContent( DrawContentArg_t const & arg ) { + + CDCHandle dc = arg.dc; + + const uint32_t fgWas = dc.GetTextColor(); + + const t_uint32 format = PaintUtils::DrawText_TranslateHeaderAlignment(arg.hdrFormat); + if (arg.allowColors) dc.SetTextColor( arg.colorHighlight ); + const t_uint32 bk = dc.GetBkColor(); + + CRect rc = arg.rcText; + dc.DrawText(arg.text, (int) wcslen(arg.text), rc, format | DT_NOPREFIX | DT_END_ELLIPSIS | DT_SINGLELINE | DT_VCENTER ); + + dc.SetTextColor(fgWas); +} + +LONG CListCell_Button::AccRole() { + return ROLE_SYSTEM_PUSHBUTTON; +} + +void CListCell_Button::DrawContent( DrawContentArg_t const & arg ) { + + CDCHandle dc = arg.dc; + + const bool bPressed = (arg.cellState & cellState_pressed) != 0; + const bool bHot = (arg.cellState & cellState_hot) != 0; + + + if ( !m_lite || bHot || bPressed ) { + RenderButton( arg.theme, dc, arg.rcHot, arg.rcHot, arg.cellState ); + } + + CRect clip = arg.rcText; + + dc.DrawText(arg.text, (int) wcslen(arg.text), clip, DT_NOPREFIX | DT_SINGLELINE | DT_VCENTER | DT_CENTER); +} + +bool CListCell_ButtonGlyph::ApplyTextStyle( LOGFONT & font, double scale, uint32_t state ) { + return __super::ApplyTextStyle(font, scale * 1.3, state); +} + +static CRect CheckBoxRect(CRect rc) { + if (rc.Width() > rc.Height()) { + rc.right = rc.left + rc.Height(); + } + return rc; +} + +LONG CListCell_Checkbox::AccRole() { + return m_radio ? ROLE_SYSTEM_RADIOBUTTON : ROLE_SYSTEM_CHECKBUTTON; +} + +CRect CListCell_Checkbox::HotRect( CRect rc ) { + return CheckBoxRect( rc ); +} + +void CListCell_Checkbox::DrawContent( DrawContentArg_t const & arg ) { + + CDCHandle dc = arg.dc; + + const bool bPressed = (arg.cellState & cellState_pressed) != 0; + const bool bHot = (arg.cellState & cellState_hot) != 0; + + + CRect clip = arg.rcText; + + const uint32_t fgWas = dc.GetTextColor(); + + if (arg.subItemRect.Width() > arg.subItemRect.Height() ) { + CRect rcCheckbox = arg.subItemRect; + rcCheckbox.right = rcCheckbox.left + rcCheckbox.Height(); + RenderCheckbox(arg.theme, dc, rcCheckbox, arg.cellState, m_radio ); + CRect rcText = arg.subItemRect; + rcText.left = rcCheckbox.right; + if (arg.cellState & cellState_disabled) { + dc.SetTextColor(GetSysColor(COLOR_GRAYTEXT)); + } + dc.DrawText(arg.text, (int) wcslen(arg.text), rcText, DT_NOPREFIX | DT_SINGLELINE | DT_VCENTER | DT_LEFT); + } else { + RenderCheckbox(arg.theme, dc, arg.subItemRect, arg.cellState, m_radio ); + } + + dc.SetTextColor(fgWas); +} + +void CListCell_Text_FixedColor::DrawContent(DrawContentArg_t const & arg) { + if (arg.allowColors) { + SetTextColorScope scope(arg.dc, m_col); + __super::DrawContent(arg); + } else { + __super::DrawContent(arg); + } +} + +void CListCell_Combo::DrawContent(DrawContentArg_t const & arg) { + CDCHandle dc = arg.dc; + + const bool bDisabled = (arg.cellState & CListCell::cellState_disabled) != 0; + const bool bPressed = (arg.cellState & cellState_pressed) != 0; + const bool bHot = (arg.cellState & cellState_hot) != 0; + + const int part = CP_DROPDOWNBUTTONRIGHT; + + const HTHEME theme = arg.theme; + + if (theme != NULL && IsThemePartDefined(theme, part, 0)) { + int state = CBXSR_NORMAL; + if (bDisabled) { + state = CBXSR_DISABLED; + } else if (bPressed) { + state = CBXSR_PRESSED; + } else if (bHot) { + state = CBXSR_HOT; + } + + CSize size; + CRect rcCombo = arg.subItemRect; + CRect rcText = arg.rcText; + int w = rcCombo.Height()*3/4; + if (w < rcCombo.Width()) { + rcCombo.left = rcCombo.right - w; + DrawThemeBackground(theme, dc, part, state, rcCombo, &rcCombo); + if (rcCombo.left < rcText.right ) rcText.right = rcCombo.left; + } + + DrawContentArg_t arg2 = arg; + arg2.rcText = rcText; + PFC_SINGLETON(CListCell_Text).DrawContent(arg2); + } +} + +LONG CListCell_Combo::AccRole() { + return ROLE_SYSTEM_DROPLIST; +} \ No newline at end of file diff --git a/libPPUI/CListControl-Cells.h b/libPPUI/CListControl-Cells.h new file mode 100644 index 0000000..39d8e86 --- /dev/null +++ b/libPPUI/CListControl-Cells.h @@ -0,0 +1,94 @@ +#pragma once + +#include "CListControl-Cell.h" + + +class CListCell_Interactive : public CListCell { +public: + bool IsInteractive() override { return true; } +}; + +class CListCell_Text : public CListCell { +public: + void DrawContent( DrawContentArg_t const & ) override; + bool AllowDrawThemeText() override { return true; } +}; + +class CListCell_TextColors : public CListCell_Text { +public: + void DrawContent( DrawContentArg_t const & ) override; +}; + +class CListCell_MultiText : public CListCell { +public: + void DrawContent( DrawContentArg_t const & ) override; +}; + +class CListCell_Hyperlink : public CListCell_Interactive { +public: + void DrawContent( DrawContentArg_t const & ) override; + bool ApplyTextStyle( LOGFONT & font, double scale, uint32_t state ) override; + HCURSOR HotCursor() override; + LONG AccRole() override; + bool SuppressRowSelect() override { return true; } +}; + +class CListCell_Button : public CListCell_Interactive { +public: + void DrawContent( DrawContentArg_t const & ) override; + const char * Theme() override { return "BUTTON"; } + bool AllowTypeFind() override { return false; } + LONG AccRole() override; + bool SuppressRowSelect() override { return true; } + +protected: + bool m_lite = false; +}; + +class CListCell_ButtonLite : public CListCell_Button { +public: + CListCell_ButtonLite() { m_lite = true; } +}; + +class CListCell_ButtonGlyph : public CListCell_ButtonLite { +public: + bool ApplyTextStyle( LOGFONT & font, double scale, uint32_t state ) override; +}; + +class CListCell_Checkbox : public CListCell_Interactive { +public: + void DrawContent( DrawContentArg_t const & ) override; + const char * Theme() override { return "BUTTON"; } + bool IsToggle() override { return true; } + CRect HotRect( CRect rc ) override; + bool IsRadioToggle() override { return m_radio; } + LONG AccRole() override; + +protected: + bool m_radio = false; +}; + +class CListCell_RadioCheckbox : public CListCell_Checkbox { +public: + CListCell_RadioCheckbox() { m_radio = true; } + + static CListCell_RadioCheckbox instance; +}; + +class CListCell_Combo : public CListCell_Interactive { +public: + void DrawContent(DrawContentArg_t const &) override; + const char * Theme() override { return "COMBOBOX"; } + LONG AccRole() override; +}; + +void RenderButton( HTHEME theme, CDCHandle dc, CRect rcButton, CRect rcUpdate, uint32_t cellState ); +void RenderCheckbox( HTHEME theme, CDCHandle dc, CRect rcCheckBox, unsigned stateFlags, bool bRadio ); + + +class CListCell_Text_FixedColor : public CListCell_Text { + const COLORREF m_col; +public: + CListCell_Text_FixedColor(COLORREF col) : m_col(col) {} + void DrawContent(DrawContentArg_t const & arg) override; +}; diff --git a/libPPUI/CListControl.cpp b/libPPUI/CListControl.cpp new file mode 100644 index 0000000..2acda60 --- /dev/null +++ b/libPPUI/CListControl.cpp @@ -0,0 +1,989 @@ +#include "stdafx.h" +#include "CListControl.h" +#include "PaintUtils.h" +#include "CListControlUserOptions.h" +#include "GDIUtils.h" + +CListControlUserOptions * CListControlUserOptions::instance = nullptr; + +CRect CListControlImpl::GetClientRectHook() const { + CRect temp; if (!GetClientRect(temp)) temp.SetRectEmpty(); return temp; +} + +bool CListControlImpl::UserEnabledSmoothScroll() const { + auto i = CListControlUserOptions::instance; + if ( i != nullptr ) return i->useSmoothScroll(); + return false; +} + +LRESULT CListControlImpl::SetFocusPassThru(UINT,WPARAM,LPARAM,BOOL& bHandled) { + SetFocus(); + bHandled = FALSE; + return 0; +} + +void CListControlImpl::EnsureVisibleRectAbs(const CRect & p_rect) { + const CRect rcView = GetVisibleRectAbs(); + const CRect rcItem = p_rect; + int deltaX = 0, deltaY = 0; + + const bool centerOnItem = m_ensureVisibleUser; + + if (rcItem.top < rcView.top || rcItem.bottom > rcView.bottom) { + if (rcItem.Height() > rcView.Height()) { + deltaY = rcItem.top - rcView.top; + } else { + if (centerOnItem) { + deltaY = rcItem.CenterPoint().y - rcView.CenterPoint().y; + } else { + if (rcItem.bottom > rcView.bottom) deltaY = rcItem.bottom - rcView.bottom; + else deltaY = rcItem.top - rcView.top; + + } + } + } + if (rcItem.left < rcView.left || rcItem.right > rcView.right) { + if (rcItem.Width() > rcView.Width()) { + if (rcItem.left > rcView.left || rcItem.right < rcView.right) deltaX = rcItem.left - rcView.left; + } else { + if (centerOnItem) { + deltaX = rcItem.CenterPoint().x - rcView.CenterPoint().x; + } else { + if (rcItem.right > rcView.right) deltaX = rcItem.right - rcView.right; + else deltaX = rcItem.left - rcView.left; + } + } + } + + if (deltaX != 0 || deltaY != 0) { + MoveViewOriginDelta(CPoint(deltaX,deltaY)); + } +} +void CListControlImpl::EnsureItemVisible(t_size p_item, bool bUser) { + m_ensureVisibleUser = bUser; + EnsureVisibleRectAbs(GetItemRectAbs(p_item)); + m_ensureVisibleUser = false; +} +void CListControlImpl::EnsureHeaderVisible(int p_group) { + CRect rect; + if (GetGroupHeaderRectAbs(p_group,rect)) EnsureVisibleRectAbs(rect); +} + +void CListControlImpl::RefreshSlider(bool p_vertical) { + const CRect viewArea = GetViewAreaRectAbs(); + const CRect rcVisible = GetVisibleRectAbs(); + SCROLLINFO si = {}; + si.cbSize = sizeof(si); + si.fMask = SIF_PAGE|SIF_RANGE|SIF_POS; + + + if (AllowScrollbar(p_vertical)) { + if (p_vertical) { + si.nPage = rcVisible.Height(); + si.nMin = viewArea.top; + si.nMax = viewArea.bottom - 1; + si.nPos = rcVisible.top; + } else { + si.nPage = rcVisible.Width(); + si.nMin = viewArea.left; + si.nMax = viewArea.right - 1; + si.nPos = rcVisible.left; + } + } + + SetScrollInfo(p_vertical ? SB_VERT : SB_HORZ, &si); +} + +void CListControlImpl::RefreshSliders() { + //PROBLEM: while lots of data can be reused across those, it has to be recalculated inbetween because view area etc may change when scroll info changes + RefreshSlider(false); RefreshSlider(true); +} + +int CListControlImpl::GetScrollThumbPos(int which) { + SCROLLINFO si = {}; + si.cbSize = sizeof(si); + si.fMask = SIF_TRACKPOS; + GetScrollInfo(which,&si); + return si.nTrackPos; +} + +namespace { + class ResolveGroupHelper { + public: + ResolveGroupHelper(const CListControlImpl & p_control) : m_control(p_control) {} + int operator[](t_size p_index) const {return m_control.GetItemGroup(p_index);} + private: + const CListControlImpl & m_control; + }; +} + +bool CListControlImpl::ResolveGroupRange(int p_id,t_size & p_base,t_size & p_count) const { + + return pfc::binarySearch<>::runGroup(ResolveGroupHelper(*this),0,GetItemCount(),p_id,p_base,p_count); + + + //return pfc::bsearch_range_t(GetItemCount(),ResolveGroupHelper(*this),pfc::compare_t,p_id,p_base,p_count); +} + +static int HandleScroll(WORD p_code,int p_offset,int p_page, int p_line, int p_bottom, int p_thumbpos) { + switch(p_code) { + case SB_LINEUP: + return p_offset - p_line; + case SB_LINEDOWN: + return p_offset + p_line; + case SB_BOTTOM: + return p_bottom - p_page; + case SB_TOP: + return 0; + case SB_PAGEUP: + return p_offset - p_page; + case SB_PAGEDOWN: + return p_offset + p_page; + case SB_THUMBPOSITION: + return p_thumbpos; + case SB_THUMBTRACK: + return p_thumbpos; + default: + return p_offset; + } +} + +static CPoint ClipPointToRect(CPoint const & p_pt,CRect const & p_rect) { + return CPoint(pfc::clip_t(p_pt.x,p_rect.left,p_rect.right),pfc::clip_t(p_pt.y,p_rect.top,p_rect.bottom)); +} + +void CListControlImpl::MoveViewOriginNoClip(CPoint p_target) { + UpdateWindow(); + const CPoint old = m_viewOrigin; + m_viewOrigin = p_target; + + if (m_viewOrigin != old) { + if (m_viewOrigin.x != old.x) SetScrollPos(SB_HORZ,m_viewOrigin.x); + if (m_viewOrigin.y != old.y) SetScrollPos(SB_VERT,m_viewOrigin.y); + + const CPoint delta = old - m_viewOrigin; + if (FixedOverlayPresent()) Invalidate(); + else { + DWORD flags = SW_INVALIDATE | SW_ERASE; + const DWORD smoothScrollMS = 50; + if (this->UserEnabledSmoothScroll() && this->CanSmoothScroll()) { + flags |= SW_SMOOTHSCROLL | (smoothScrollMS << 16); + } + + ScrollWindowEx(delta.x,delta.y,GetClientRectHook(),NULL,0,0,flags ); + } + + OnViewOriginChange(m_viewOrigin - old); + } +} + +CPoint CListControlImpl::ClipViewOrigin(CPoint p_origin) const { + return ClipPointToRect(p_origin,GetValidViewOriginArea()); +} +void CListControlImpl::MoveViewOrigin(CPoint p_target) { + MoveViewOriginNoClip(ClipViewOrigin(p_target)); +} + +#ifndef SPI_GETWHEELSCROLLCHARS +#define SPI_GETWHEELSCROLLCHARS 0x006C +#endif +int CListControlImpl::HandleWheel(int & p_accum,int p_delta, bool bHoriz) { + if ( m_suppressMouseWheel ) return 0; + UINT scrollLines = 1; + SystemParametersInfo(bHoriz ? SPI_GETWHEELSCROLLCHARS : SPI_GETWHEELSCROLLLINES,0,&scrollLines,0); + if (scrollLines == ~0) { + p_accum = 0; + int rv = -pfc::sgn_t(p_delta); + CRect client = GetClientRectHook(); + if (bHoriz) rv *= client.Width(); + else rv *= client.Height(); + return rv; + } + + const int itemHeight = GetItemHeight(); + const int extraScale = 10000; + + p_accum += p_delta * extraScale; + if ((int)scrollLines < 1) scrollLines = 1; + int multiplier = (WHEEL_DELTA * extraScale) / (scrollLines * itemHeight); + if (multiplier<1) multiplier = 1; + + int delta = pfc::rint32( (double) p_accum / (double) multiplier ); + p_accum -= delta * multiplier; + return -delta; + + /* + if (p_accum<=-multiplier || p_accum>=multiplier) { + int direction; + int ov = p_accum; + if (ov<0) { + direction = -1; + ov = -ov; + p_accum = - ((-p_accum)%multiplier); + } else { + p_accum %= multiplier; + direction = 1; + } + + return - (direction * (ov + multiplier - 1) ) / multiplier; + } else { + return 0; + } + */ +} + +LRESULT CListControlImpl::OnVWheel(UINT,WPARAM p_wp,LPARAM p_lp,BOOL&) { + const CRect client = GetClientRectHook(), view = this->GetViewAreaRectAbs(); + int deltaPixels = HandleWheel(m_wheelAccumY,(short)HIWORD(p_wp), false); + + const bool canVScroll = client.Height() < view.Height(); + const bool canHScroll = client.Width() < view.Width(); + + CPoint ptDelta; + if ( canVScroll && canHScroll && GetHotkeyModifierFlags() == MOD_SHIFT) { + ptDelta = CPoint(deltaPixels, 0); // default to horizontal scroll if shift is pressed + } else if (canVScroll) { + ptDelta = CPoint(0,deltaPixels); + } else if (canHScroll) { + ptDelta = CPoint(deltaPixels,0); + } + + if ( ptDelta != CPoint(0,0) ) { + MoveViewOriginDelta(ptDelta); + } + return 0; +} +LRESULT CListControlImpl::OnHWheel(UINT,WPARAM p_wp,LPARAM p_lp,BOOL&) { + const CRect client = GetClientRectHook(); + int deltaPixels = HandleWheel(m_wheelAccumX,(short)HIWORD(p_wp), true); + MoveViewOriginDelta(CPoint(-deltaPixels,0)); + return 0; +} + +LRESULT CListControlImpl::OnVScroll(UINT,WPARAM p_wp,LPARAM,BOOL&) { + int target = HandleScroll(LOWORD(p_wp),m_viewOrigin.y,GetVisibleRectAbs().Height(),GetItemHeight(),GetViewAreaRectAbs().bottom,GetScrollThumbPos(SB_VERT)); + MoveViewOrigin(CPoint(m_viewOrigin.x,target)); + return 0; +} +LRESULT CListControlImpl::OnHScroll(UINT,WPARAM p_wp,LPARAM,BOOL&) { + int target = HandleScroll(LOWORD(p_wp),m_viewOrigin.x,GetVisibleRectAbs().Width(),GetItemHeight() /*fixme*/,GetViewAreaRectAbs().right,GetScrollThumbPos(SB_HORZ)); + MoveViewOrigin(CPoint(target,m_viewOrigin.y)); + return 0; +} + +LRESULT CListControlImpl::OnGesture(UINT,WPARAM,LPARAM lParam,BOOL& bHandled) { + if (!this->m_gestureAPI.IsAvailable()) { + bHandled = FALSE; + return 0; + } + HGESTUREINFO hGesture = (HGESTUREINFO) lParam; + GESTUREINFO gestureInfo = {sizeof(gestureInfo)}; + if (m_gestureAPI.GetGestureInfo(hGesture, &gestureInfo)) { + //console::formatter() << "WM_GESTURE " << pfc::format_hex( gestureInfo.dwFlags ) << " " << (int)gestureInfo.dwID << " X:" << gestureInfo.ptsLocation.x << " Y:" << gestureInfo.ptsLocation.y << " arg:" << (__int64) gestureInfo.ullArguments; + CPoint pt( gestureInfo.ptsLocation.x, gestureInfo.ptsLocation.y ); + switch(gestureInfo.dwID) { + case GID_BEGIN: + m_gesturePoint = pt; + break; + case GID_END: + break; + case GID_PAN: + MoveViewOriginDelta( this->m_gesturePoint - pt); + m_gesturePoint = pt; + break; + } + } + + m_gestureAPI.CloseGestureInfoHandle(hGesture); + bHandled = TRUE; + return 0; +} + +LRESULT CListControlImpl::OnSize(UINT,WPARAM,LPARAM p_lp,BOOL&) { + OnSizeAsync_Trigger(); + RefreshSliders(); + return 0; +} + + +void CListControlImpl::RenderBackground( CDCHandle dc, CRect const & rc ) { + PaintUtils::FillRectSimple(dc,rc,GetSysColorHook(colorBackground)); +} + +void CListControlImpl::PaintContent(CRect rcPaint, HDC dc) { + CDCHandle renderDC(dc); + + CMemoryDC bufferDC(renderDC,rcPaint); + renderDC = bufferDC; + this->RenderBackground(renderDC, rcPaint); + + { + const CPoint pt = GetViewOffset(); + OffsetWindowOrgScope offsetScope(renderDC, pt); + CRect renderRect = rcPaint; renderRect.OffsetRect(pt); + RenderRect(renderRect,renderDC); + } +} + +void CListControlImpl::OnPrintClient(HDC dc, UINT uFlags) { + CRect rcClient; this->GetClientRect( rcClient ); + PaintContent( rcClient, dc ); +} + +LRESULT CListControlImpl::OnPaint(UINT,WPARAM,LPARAM,BOOL&) { + CPaintDC paintDC(*this); + + PaintContent( paintDC.m_ps.rcPaint, paintDC.m_hDC ); + + return 0; +} + +namespace { + class comparator_rect { + public: + static int compare(const CRect & p_rect1,const CRect & p_rect2) { + if (p_rect1.bottom <= p_rect2.top) return -1; + else if (p_rect1.top >= p_rect2.bottom) return 1; + else return 0; + } + }; + + static int RectPointCompare(const CRect & p_item1,const int p_y) { + if (p_item1.bottom <= p_y) return -1; + else if (p_item1.top > p_y) return 1; + else return 0; + } + + class RectSearchHelper_Items { + public: + RectSearchHelper_Items(const CListControlImpl & p_control) : m_control(p_control) {} + CRect operator[](t_size p_index) const { + return m_control.GetItemRectAbs(p_index); + } + private: + const CListControlImpl & m_control; + }; + class RectSearchHelper_Groups { + public: + RectSearchHelper_Groups(const CListControlImpl & p_control) : m_control(p_control) {} + CRect operator[](t_size p_index) const { + CRect rect; + if (!m_control.GetGroupHeaderRectAbs((int)(p_index + 1),rect)) rect.SetRectEmpty(); + return rect; + } + private: + const CListControlImpl & m_control; + }; +} + +bool CListControlImpl::GetItemRange(const CRect & p_rect,t_size & p_base,t_size & p_count) const { + CRect temp(p_rect); temp.OffsetRect( GetViewOffset() ); + return GetItemRangeAbs(temp, p_base, p_count); +} + + + +bool CListControlImpl::GetItemRangeAbsInclHeaders(const CRect & p_rect,t_size & p_base,t_size & p_count) const { + CRect temp(p_rect); + temp.bottom += this->GetGroupHeaderHeight(); + return GetItemRangeAbs(temp, p_base, p_count); +} + +bool CListControlImpl::GetItemRangeAbs(const CRect & p_rect,t_size & p_base,t_size & p_count) const { + if (p_rect.right < 0 || p_rect.left >= GetItemWidth()) return false; + + return pfc::binarySearch::runGroup(RectSearchHelper_Items(*this),0,GetItemCount(),p_rect,p_base,p_count); +} + +void CListControlImpl::RenderRect(const CRect & p_rect,CDCHandle p_dc) { + const CRect rectAbs = p_rect; + + t_size base, count; + if (GetItemRangeAbs(rectAbs,base,count)) { + for(t_size walk = 0; walk < count; ++walk) { + CRect rcUpdate, rcItem = GetItemRectAbs(base+walk); + if (rcUpdate.IntersectRect(rcItem,p_rect)) { + DCStateScope dcState(p_dc); + if (p_dc.IntersectClipRect(rcUpdate) != NULLREGION) { + try { + RenderItem(base+walk,rcItem,rcUpdate,p_dc); + } catch(std::exception const & e) { + (void) e; + // console::complain("List Control: Item rendering failure", e); + } + } + } + } + } + + if (pfc::binarySearch::runGroup(RectSearchHelper_Groups(*this),0,GetGroupCount(),rectAbs,base,count)) { + for(t_size walk = 0; walk < count; ++walk) { + CRect rcHeader, rcUpdate; + const int id = (int)(base+walk+1); + if (GetGroupHeaderRectAbs(id,rcHeader) && rcUpdate.IntersectRect(rcHeader,p_rect)) { + DCStateScope dcState(p_dc); + if (p_dc.IntersectClipRect(rcUpdate) != NULLREGION) { + try { + RenderGroupHeader(id,rcHeader,rcUpdate,p_dc); + } catch(std::exception const & e) { + (void) e; + // console::complain("List Control: Group header rendering failure", e); + } + } + } + } + } + + RenderOverlay(p_rect,p_dc); +} + +CRect CListControlImpl::GetItemRect(t_size p_item) const { + CRect rcItem = GetItemRectAbs(p_item); + rcItem.OffsetRect( - GetViewOffset() ); + return rcItem; +} +bool CListControlImpl::GetGroupHeaderRect(int p_group,CRect & p_rect) const { + if (!GetGroupHeaderRectAbs(p_group,p_rect)) return false; + p_rect.OffsetRect( - GetViewOffset() ); + return true; +} + +int CListControlImpl::GetViewAreaHeight() const { + const t_size itemCount = GetItemCount(); + int subAreaBase = 0; + if (itemCount > 0) { + subAreaBase = GetItemRectAbs(itemCount - 1).bottom; + } + return subAreaBase; +} + +CRect CListControlImpl::GetItemRectAbs(t_size p_item) const { + CRect rcItem; + const int itemHeight = GetItemHeight(), itemWidth = GetItemWidth(), groupHeight = GetGroupHeaderHeight(), itemGroup = GetItemGroup(p_item); + rcItem.top = (int)p_item * itemHeight + groupHeight * itemGroup; + rcItem.bottom = rcItem.top + itemHeight; + rcItem.left = 0; + rcItem.right = rcItem.left + itemWidth; + return rcItem; +} +bool CListControlImpl::GetGroupHeaderRectAbs(int p_group,CRect & p_rect) const { + if (p_group == 0) return false; + t_size itemBase, itemCount; + if (!ResolveGroupRange(p_group,itemBase,itemCount)) return false; + const int itemHeight = GetItemHeight(), itemWidth = GetItemWidth(), groupHeight = GetGroupHeaderHeight(); + p_rect.bottom = (int) itemBase * itemHeight + groupHeight * p_group; + p_rect.top = p_rect.bottom - groupHeight; + p_rect.left = 0; + p_rect.right = p_rect.left + itemWidth; + return true; +} + +CRect CListControlImpl::GetViewAreaRectAbs() const { + return CRect(0,0,GetViewAreaWidth(),GetViewAreaHeight()); +} + +CRect CListControlImpl::GetViewAreaRect() const { + CRect rc = GetViewAreaRectAbs(); + rc.OffsetRect( - GetViewOffset() ); + CRect ret; ret.IntersectRect(rc,GetClientRectHook()); + return ret; +} + +t_size CListControlImpl::GetGroupCount() const { + const t_size itemCount = GetItemCount(); + if (itemCount > 0) { + return (t_size) GetItemGroup(itemCount-1); + } else { + return 0; + } +} + +void CListControlImpl::UpdateGroupHeader(int p_id) { + CRect rect; + if (GetGroupHeaderRect(p_id,rect)) { + InvalidateRect(rect); + } +} +static void AddUpdateRect(HRGN p_rgn,CRect const & p_rect) { + CRgn temp; temp.CreateRectRgnIndirect(p_rect); + CRgnHandle(p_rgn).CombineRgn(temp,RGN_OR); +} + +void CListControlImpl::OnItemsReordered( const size_t * order, size_t count ) { + PFC_ASSERT( count == GetItemCount() ); + ReloadItems( pfc::bit_array_order_changed(order) ); +} +void CListControlImpl::UpdateItems(const pfc::bit_array & p_mask) { + t_size base,count; + if (GetItemRangeAbs(GetVisibleRectAbs(),base,count)) { + const t_size max = base+count; + CRgn updateRgn; updateRgn.CreateRectRgn(0,0,0,0); + bool found = false; + for(t_size walk = p_mask.find_first(true,base,max); walk < max; walk = p_mask.find_next(true,walk,max)) { + found = true; + AddUpdateRect(updateRgn,GetItemRect(walk)); + } + if (found) { + InvalidateRgn(updateRgn); + } + } +} + +void CListControlImpl::UpdateItemsAndHeaders(const pfc::bit_array & p_mask) { + t_size base,count; + int groupWalk = 0; + if (GetItemRangeAbsInclHeaders(GetVisibleRectAbs(),base,count)) { + const t_size max = base+count; + CRgn updateRgn; updateRgn.CreateRectRgn(0,0,0,0); + bool found = false; + for(t_size walk = p_mask.find_first(true,base,max); walk < max; walk = p_mask.find_next(true,walk,max)) { + found = true; + const int groupId = GetItemGroup(walk); + if (groupId != groupWalk) { + if (groupId > 0) { + CRect rect; + if (GetGroupHeaderRect(groupId,rect)) { + AddUpdateRect(updateRgn,rect); + } + } + groupWalk = groupId; + } + AddUpdateRect(updateRgn,GetItemRect(walk)); + } + if (found) { + InvalidateRgn(updateRgn); + } + } +} + + +CRect CListControlImpl::GetValidViewOriginArea() const { + const CRect rcView = GetViewAreaRectAbs(); + const CRect rcClient = GetClientRectHook(); + CRect rcArea = rcView; + rcArea.right -= pfc::min_t(rcView.Width(),rcClient.Width()); + rcArea.bottom -= pfc::min_t(rcView.Height(),rcClient.Height()); + return rcArea; +} + +void CListControlImpl::OnViewAreaChanged(CPoint p_originOverride) { + const CPoint oldViewOrigin = m_viewOrigin; + m_viewOrigin = ClipPointToRect(p_originOverride,GetValidViewOriginArea()); + + RefreshSliders(); + + Invalidate(); + + if (oldViewOrigin != m_viewOrigin) { + OnViewOriginChange(m_viewOrigin - oldViewOrigin); + } +} + +bool CListControlImpl::ItemFromPointAbs(CPoint const & p_pt,t_size & p_item) const { + if (p_pt.x < 0 || p_pt.x >= GetItemWidth()) return false; + t_size dummy; + return GetItemRangeAbs(CRect(p_pt,p_pt + CPoint(1,1)),p_item,dummy); +} + +bool CListControlImpl::GroupHeaderFromPointAbs(CPoint const & p_pt,int & p_group) const { + if (p_pt.x < 0 || p_pt.x >= GetItemWidth()) return false; + t_size result; + + if (!pfc::binarySearch::run(RectSearchHelper_Groups(*this),0,GetGroupCount(),CRect(p_pt,p_pt + CSize(1,1)),result)) return false; + + + //if (!pfc::bsearch_t(GetGroupCount(),RectSearchHelper_Groups(*this),RectCompare,CRect(p_pt,p_pt),result)) return false; + p_group = (int) (result + 1); + return true; +} + +void CListControlImpl::OnThemeChanged() { + m_themeCache.remove_all(); +} + +CTheme & CListControlImpl::themeFor(const char * what) { + bool bNew; + auto & ret = this->m_themeCache.find_or_add_ex( what, bNew ); + if (bNew) ret.OpenThemeData(*this, pfc::stringcvt::string_wide_from_utf8(what)); + return ret; +} + +LRESULT CListControlImpl::OnCreatePassThru(UINT,WPARAM,LPARAM,BOOL& bHandled) { + ::SetWindowTheme(*this, _T("explorer"), NULL); + OnViewAreaChanged(); + + if (m_gestureAPI.IsAvailable()) { + GESTURECONFIG config = {GID_PAN, GC_PAN_WITH_SINGLE_FINGER_VERTICALLY|GC_PAN_WITH_INERTIA, GC_PAN_WITH_SINGLE_FINGER_HORIZONTALLY | GC_PAN_WITH_GUTTER}; + m_gestureAPI.SetGestureConfig( *this, 0, 1, &config, sizeof(GESTURECONFIG)); + } + + bHandled = FALSE; + return 0; +} +bool CListControlImpl::IsSameItemOrHeaderAbs(const CPoint & p_point1, const CPoint & p_point2) const { + t_size item1, item2; int group1, group2; + if (ItemFromPointAbs(p_point1, item1)) { + if (ItemFromPointAbs(p_point2,item2)) { + return item1 == item2; + } else { + return false; + } + } + if (GroupHeaderFromPointAbs(p_point1, group1)) { + if (GroupHeaderFromPointAbs(p_point2, group2)) { + return group1 == group2; + } else { + return false; + } + } + return false; +} + +void CListControlImpl::OnSizeAsync_Trigger() { + if (!m_sizeAsyncPending) { + if (PostMessage(MSG_SIZE_ASYNC,0,0)) { + m_sizeAsyncPending = true; + } else { + PFC_ASSERT(!"Shouldn't get here!"); + //should not happen + ListHandleResize(); + } + } +} + +void CListControlImpl::ListHandleResize() { + MoveViewOriginDelta(CPoint(0,0)); + m_sizeAsyncPending = false; +} + +void CListControlImpl::AddGroupHeaderToUpdateRgn(HRGN p_rgn, int id) const { + if (id > 0) { + CRect rcHeader; + if (GetGroupHeaderRect(id,rcHeader)) AddUpdateRect(p_rgn,rcHeader); + } +} +void CListControlImpl::AddItemToUpdateRgn(HRGN p_rgn, t_size p_index) const { + if (p_index < this->GetItemCount()) { + AddUpdateRect(p_rgn,GetItemRect(p_index)); + } +} + +COLORREF CListControlImpl::GetSysColorHook(int colorIndex) const { + return GetSysColor(colorIndex); +} + +LRESULT CListControlImpl::OnEraseBkgnd(UINT,WPARAM wp,LPARAM,BOOL&) { +#ifndef CListControl_ScrollWindowFix + const CRect rcClient = GetClientRectHook(); + PaintUtils::FillRectSimple((HDC)wp,rcClient,GetColor(ui_color_background)); +#endif + return 1; +} + +t_size CListControlImpl::InsertIndexFromPointEx(const CPoint & pt, bool & bInside) const { + bInside = false; + t_size insertMark = ~0; + t_size itemIdx; int groupId; + CPoint test(pt); test += GetViewOffset(); + test.x = GetItemWidth() / 2; + if (test.y >= GetViewAreaHeight()) { + return GetItemCount(); + } else if (ItemFromPointAbs(test,itemIdx)) { + const CRect rc = GetItemRectAbs(itemIdx); + if (test.y > rc.top + MulDiv(rc.Height(),2,3)) itemIdx++; + else if (test.y >= rc.top + MulDiv(rc.Height(),1,3)) bInside = true; + return itemIdx; + } else if (GroupHeaderFromPointAbs(test,groupId)) { + t_size base,count; + if (ResolveGroupRange(groupId,base,count)) { + return base; + } + } + return ~0; +} +t_size CListControlImpl::InsertIndexFromPoint(const CPoint & pt) const { + bool dummy; return InsertIndexFromPointEx(pt,dummy); +} + +COLORREF CListControlImpl::BlendGridColor( COLORREF bk ) { + return BlendGridColor( bk, PaintUtils::DetermineTextColor( bk ) ); +} + +COLORREF CListControlImpl::BlendGridColor( COLORREF bk, COLORREF tx ) { + return PaintUtils::BlendColor(bk, tx, 10); +} + +COLORREF CListControlImpl::GridColor() { + return BlendGridColor( GetSysColorHook(colorBackground), GetSysColorHook(colorText) ); +} + +void CListControlImpl::RenderItemBackground(CDCHandle p_dc,const CRect & p_itemRect,size_t p_item, uint32_t bkColor) { + switch( this->m_rowStyle ) { + case rowStylePlaylistDelimited: + PaintUtils::RenderItemBackground(p_dc,p_itemRect,p_item+GetItemGroup(p_item),bkColor); + { + auto blend = BlendGridColor(bkColor); + CDCPen pen(p_dc, blend); + SelectObjectScope scope(p_dc, pen); + + p_dc.MoveTo( p_itemRect.right-1, p_itemRect.top ); + p_dc.LineTo( p_itemRect.right-1, p_itemRect.bottom ); + } + break; + case rowStylePlaylist: + PaintUtils::RenderItemBackground(p_dc,p_itemRect,p_item+GetItemGroup(p_item),bkColor); + break; + case rowStyleGrid: + PaintUtils::FillRectSimple(p_dc, p_itemRect, bkColor ); + { + auto blend = BlendGridColor(bkColor); + CDCBrush brush(p_dc, blend); + p_dc.FrameRect(&p_itemRect, brush); + + } + break; + case rowStyleFlat: + PaintUtils::FillRectSimple(p_dc, p_itemRect, bkColor ); + break; + } +} + +void CListControlImpl::RenderGroupHeaderBackground(CDCHandle p_dc,const CRect & p_headerRect,int p_group) { + const t_uint32 bkColor = GetSysColorHook(colorBackground); + t_size index = 0; + t_size base, count; + if (p_group > 0 && ResolveGroupRange(p_group,base,count)) { + index = base + (t_size) p_group - 1; + } + switch( this->m_rowStyle ) { + default: + PaintUtils::FillRectSimple( p_dc, p_headerRect, bkColor ); + break; + case rowStylePlaylistDelimited: + case rowStylePlaylist: + PaintUtils::RenderItemBackground(p_dc,p_headerRect,index,bkColor); + break; + } +} + +void CListControlImpl::RenderItem(t_size p_item,const CRect & p_itemRect,const CRect & p_updateRect,CDCHandle p_dc) { + this->RenderItemBackground(p_dc, p_itemRect, p_item, GetSysColorHook(colorBackground) ); + + DCStateScope backup(p_dc); + p_dc.SetBkMode(TRANSPARENT); + p_dc.SetBkColor(GetSysColorHook(colorBackground)); + p_dc.SetTextColor(GetSysColorHook(colorText)); + + RenderItemText(p_item,p_itemRect,p_updateRect,p_dc, true); +} + +void CListControlImpl::RenderGroupHeader(int p_group,const CRect & p_headerRect,const CRect & p_updateRect,CDCHandle p_dc) { + this->RenderGroupHeaderBackground(p_dc, p_headerRect, p_group ); + + DCStateScope backup(p_dc); + p_dc.SetBkMode(TRANSPARENT); + p_dc.SetBkColor(GetSysColorHook(colorBackground)); + p_dc.SetTextColor(GetSysColorHook(colorHighlight)); + + RenderGroupHeaderText(p_group,p_headerRect,p_updateRect,p_dc); +} + + +CListControlFontOps::CListControlFontOps() : m_font((HFONT)::GetStockObject(DEFAULT_GUI_FONT)), m_itemHeight(), m_groupHeaderHeight() { + UpdateGroupHeaderFont(); + CalculateHeights(); +} + +void CListControlFontOps::UpdateGroupHeaderFont() { + try { + m_groupHeaderFont = NULL; + LOGFONT lf = {}; + WIN32_OP_D( m_font.GetLogFont(lf) ); + lf.lfHeight = pfc::rint32( (double) lf.lfHeight * GroupHeaderFontScale() ); + lf.lfWeight = GroupHeaderFontWeight(lf.lfWeight); + WIN32_OP_D( m_groupHeaderFont.CreateFontIndirect(&lf) != NULL ); + } catch(std::exception const & e) { + (void) e; + // console::print(e.what()); + m_groupHeaderFont = (HFONT)::GetStockObject(DEFAULT_GUI_FONT); + } +} + +void CListControlFontOps::CalculateHeights() { + const t_uint32 spacing = MulDiv(4, m_dpi.cy, 96); + m_itemHeight = GetFontHeight( m_font ) + spacing; + m_groupHeaderHeight = GetFontHeight( m_groupHeaderFont ) + spacing; +} + +void CListControlFontOps::SetFont(HFONT font,bool bUpdateView) { + m_font = font; + UpdateGroupHeaderFont(); CalculateHeights(); + OnSetFont(bUpdateView); + if (bUpdateView && m_hWnd != NULL) OnViewAreaChanged(); + +} + +LRESULT CListControlFontOps::OnSetFont(UINT,WPARAM wp,LPARAM,BOOL&) { + SetFont((HFONT)wp); + return 0; +} + +LRESULT CListControlFontOps::OnGetFont(UINT,WPARAM,LPARAM,BOOL&) { + return (LRESULT)(HFONT)m_font; +} + +void CListControlImpl::SetCaptureEx(CaptureProc_t proc) { + this->m_captureProc = proc; SetCapture(); +} + +LRESULT CListControlImpl::MousePassThru(UINT msg, WPARAM wp, LPARAM lp) { + auto p = m_captureProc; // create local ref in case something in mid-captureproc clears it + if ( p ) { + CPoint pt(lp); + if (!p(msg, (DWORD) wp, pt ) ) { + ReleaseCapture(); + m_captureProc = nullptr; + } + return 0; + } + + SetMsgHandled(FALSE); + return 0; +} + +LRESULT CListControlImpl::OnGetDlgCode(UINT, WPARAM wp, LPARAM) { + switch(wp) { + case VK_RETURN: + return m_dlgWantEnter ? DLGC_WANTMESSAGE : 0; + default: + SetMsgHandled(FALSE); + return 0; + } +} + +void CListControlImpl::CreateInDialog(CWindow wndDialog, UINT replaceControlID ) { + CWindow lstReplace = wndDialog.GetDlgItem(replaceControlID); + PFC_ASSERT( lstReplace != NULL ); + auto status = lstReplace.SendMessage(WM_GETDLGCODE, VK_RETURN ); + m_dlgWantEnter = (status & DLGC_WANTMESSAGE); + CRect rc; + CWindow wndPrev = wndDialog.GetNextDlgTabItem(lstReplace, TRUE); + WIN32_OP_D( lstReplace.GetWindowRect(&rc) ); + WIN32_OP_D( wndDialog.ScreenToClient(rc) ); + WIN32_OP_D( lstReplace.DestroyWindow() ); + WIN32_OP_D( this->Create(wndDialog, &rc, 0, 0, WS_EX_STATICEDGE, replaceControlID) ); + if (wndPrev != NULL ) this->SetWindowPos(wndPrev, 0,0,0,0, SWP_NOSIZE | SWP_NOMOVE ); + this->SetFont(wndDialog.GetFont()); +} + + +void CListControlImpl::defer(std::function f) { + m_deferred.push_back( f ); + if (!m_defferredMsgPending) { + if ( PostMessage(MSG_EXEC_DEFERRED) ) m_defferredMsgPending = true; + } +} + +LRESULT CListControlImpl::OnExecDeferred(UINT, WPARAM, LPARAM) { + + for ( ;; ) { + auto i = m_deferred.begin(); + if ( i == m_deferred.end() ) break; + auto op = std::move(*i); + m_deferred.erase(i); // erase first, execute later - avoid erratic behavior if op alters the list + op(); + } + + m_defferredMsgPending = false; + return 0; +} + +// ======================================================================================== +// Mouse wheel vs drag&drop hacks +// Install MouseHookProc for the duration of DoDragDrop and handle the input from there +// ======================================================================================== +static HHOOK g_hook = NULL; +static CListControlImpl * g_dragDropInstance = nullptr; +LRESULT CALLBACK CListControlImpl::MouseHookProc(int nCode, WPARAM wParam, LPARAM lParam) { + if (nCode == HC_ACTION && g_dragDropInstance != nullptr) { + switch (wParam) { + case WM_MOUSEWHEEL: + case WM_MOUSEHWHEEL: + g_dragDropInstance->MouseWheelFromHook((UINT)wParam, lParam); + break; + } + } + return CallNextHookEx(g_hook, nCode, wParam, lParam); +} + +bool CListControlImpl::MouseWheelFromHook(UINT msg, LPARAM data) { + MOUSEHOOKSTRUCTEX const * mhs = reinterpret_cast ( data ); + if ( ::WindowFromPoint(mhs->pt) != m_hWnd ) return false; + LRESULT dummyResult = 0; + WPARAM wp = mhs->mouseData; + LPARAM lp = MAKELPARAM( mhs->pt.x, mhs->pt.y ); + // If we get here, m_suppressMouseWheel should be true per our DoDragDrop() + pfc::vartoggle_t scope(m_suppressMouseWheel, false); + this->ProcessWindowMessage( m_hWnd, msg, wp, lp, dummyResult ); + return true; +} + +HRESULT CListControlImpl::DoDragDrop(LPDATAOBJECT pDataObj, LPDROPSOURCE pDropSource, DWORD dwOKEffects, LPDWORD pdwEffect) { + HRESULT ret = E_FAIL; + // Should not get here with non null g_dragDropInstance - means we have a recursive call + PFC_ASSERT(g_dragDropInstance == nullptr); + if ( g_dragDropInstance == nullptr ) { + // futureproofing: kill mouse wheel message processing if we get them delivered the regular way while this is in progress + pfc::vartoggle_t scope(m_suppressMouseWheel, true); + g_dragDropInstance = this; + g_hook = SetWindowsHookEx(WH_MOUSE, MouseHookProc, NULL, GetCurrentThreadId()); + try { + ret = ::DoDragDrop(pDataObj, pDropSource, dwOKEffects, pdwEffect); + } catch (...) { + } + g_dragDropInstance = nullptr; + UnhookWindowsHookEx(pfc::replace_null_t(g_hook)); + } + return ret; +} + +void CListControlImpl::OnKillFocus(CWindow) { + if (m_captureProc) { + ReleaseCapture(); + m_captureProc = nullptr; + } + SetMsgHandled(FALSE); +} + +void CListControlImpl::OnWindowPosChanged(LPWINDOWPOS arg) { + if ( arg->flags & SWP_HIDEWINDOW ) { + if (m_captureProc) { + ReleaseCapture(); + m_captureProc = nullptr; + } + } + SetMsgHandled(FALSE); +} + +void CListControlHeaderImpl::RenderItemBackground(CDCHandle p_dc,const CRect & p_itemRect,size_t item, uint32_t bkColor) { + if ( ! this->DelimitColumns() ) { + __super::RenderItemBackground(p_dc, p_itemRect, item, bkColor); + } else { + auto cnt = this->GetColumnCount(); + uint32_t x = 0; + for( size_t walk = 0; walk < cnt; ) { + auto span = this->GetSubItemSpan( item, walk ); + PFC_ASSERT( span > 0 ); + uint32_t width = 0; + for( size_t walk2 = 0; walk2 < span; ++ walk2 ) { + width += this->GetSubItemWidth( walk + walk2 ); + } + CRect rc = p_itemRect; + rc.left = x; + x += width; + rc.right = x; + __super::RenderItemBackground(p_dc, rc, item, bkColor); + walk += span; + } + } +} diff --git a/libPPUI/CListControl.h b/libPPUI/CListControl.h new file mode 100644 index 0000000..8cecb4f --- /dev/null +++ b/libPPUI/CListControl.h @@ -0,0 +1,328 @@ +#pragma once + +// ================================================================================ +// Main CListControl implementation +// +// For ready-to-use CListControl specializations, +// see CListControlSimple.h and CListControlOwnerData.h +// ================================================================================ + + +#pragma comment(lib, "uxtheme.lib") + +#include +#include +#include +#include +#include +#include "CMiddleDragImpl.h" +#include "wtl-pp.h" +#include "gesture.h" +#include "gdiplus_helpers.h" + +#define CListControl_ScrollWindowFix + +#ifdef CListControl_ScrollWindowFix +#define WS_EX_COMPOSITED_CListControl 0 +#else +#define WS_EX_COMPOSITED_CListControl WS_EX_COMPOSITED +#endif + + +typedef std::function< bool ( UINT, DWORD, CPoint ) > CaptureProc_t; + +typedef CWinTraits CListControlTraits; + +class CListControlImpl : public CWindowImpl { +public: + CListControlImpl() {} + + DECLARE_WND_CLASS_EX(TEXT("{4B94B650-C2D8-40de-A0AD-E8FADF62D56C}"),CS_DBLCLKS,COLOR_WINDOW); + + // Wrapper around CWindowImpl::Create(). + // Creates CListControl replacing another dialog control with the specified ID. + // Note that m_dlgWantEnter is set to false by this method, as it's typically unwanted in dialogs. + void CreateInDialog( CWindow wndDialog, UINT replaceControlID ); + + enum { + MSG_SIZE_ASYNC = WM_USER + 13, + MSG_EXEC_DEFERRED, + UserMsgBase + }; + + BEGIN_MSG_MAP_EX(CListControlImpl) + MESSAGE_RANGE_HANDLER_EX(WM_MOUSEFIRST, WM_MOUSELAST, MousePassThru); + MESSAGE_HANDLER_EX(MSG_EXEC_DEFERRED, OnExecDeferred); + MESSAGE_HANDLER(WM_PAINT,OnPaint); + MSG_WM_PRINTCLIENT(OnPrintClient); + MESSAGE_HANDLER(WM_VSCROLL,OnVScroll); + MESSAGE_HANDLER(WM_HSCROLL,OnHScroll); + MESSAGE_HANDLER(WM_SIZE,OnSize); + MESSAGE_HANDLER(WM_MOUSEHWHEEL,OnHWheel); + MESSAGE_HANDLER(WM_MOUSEWHEEL,OnVWheel); + MESSAGE_HANDLER(WM_LBUTTONDOWN,SetFocusPassThru); + MESSAGE_HANDLER(WM_RBUTTONDOWN,SetFocusPassThru); + MESSAGE_HANDLER(WM_MBUTTONDOWN,SetFocusPassThru); + MESSAGE_HANDLER(WM_LBUTTONDBLCLK,SetFocusPassThru); + MESSAGE_HANDLER(WM_RBUTTONDBLCLK,SetFocusPassThru); + MESSAGE_HANDLER(WM_MBUTTONDBLCLK,SetFocusPassThru); + MESSAGE_HANDLER(WM_CREATE,OnCreatePassThru); + MESSAGE_HANDLER(WM_ERASEBKGND,OnEraseBkgnd); + MESSAGE_HANDLER(MSG_SIZE_ASYNC,OnSizeAsync); + MESSAGE_HANDLER(WM_GESTURE, OnGesture) + MSG_WM_THEMECHANGED(OnThemeChanged) + MSG_WM_KILLFOCUS(OnKillFocus) + MSG_WM_WINDOWPOSCHANGED(OnWindowPosChanged) + MESSAGE_HANDLER_EX( WM_GETDLGCODE, OnGetDlgCode ) + END_MSG_MAP() + + virtual void ReloadData() { OnViewAreaChanged(); } + virtual void ReloadItems( pfc::bit_array const & mask ) { UpdateItems( mask); } + + //! Hookable function called in response to reordering of items. Redraws the view and updates internal data to reflect the change. + virtual void OnItemsReordered( const size_t * order, size_t count ); + //! Hookable function called in response to removal of items. Redraws the view and updates internal data to reflect the change. + virtual void OnItemsRemoved( pfc::bit_array const & mask, size_t oldCount ) { ReloadData(); } + //! Hookable function called in response to insertion of items. Redraws the view and updates internal data to reflect the change. + virtual void OnItemsInserted( size_t at, size_t count, bool bSelect ) { ReloadData(); } + + void ReloadItem(size_t i) { ReloadItems( pfc::bit_array_one(i) ); } + void OnViewAreaChanged() {OnViewAreaChanged(GetViewOrigin());} + void OnViewAreaChanged(CPoint p_originOverride); + void UpdateGroupHeader(int p_id); + void UpdateItems(const pfc::bit_array & p_mask); + void UpdateItemsAndHeaders(const pfc::bit_array & p_mask); + void UpdateItem(t_size p_item) {UpdateItems(pfc::bit_array_one(p_item));} + void UpdateItemsAll() {Invalidate();} + void EnsureItemVisible(t_size p_item, bool bUser = false); + void EnsureHeaderVisible(int p_group); + virtual void EnsureVisibleRectAbs(const CRect & p_rect); + CRect GetItemRect(t_size p_item) const; + bool GetGroupHeaderRect(int p_group,CRect & p_rect) const; + CRect GetItemRectAbs(t_size p_item) const; + bool GetGroupHeaderRectAbs(int p_group,CRect & p_rect) const; + CPoint GetViewOrigin() const {return m_viewOrigin;} + CPoint GetViewOffset() const {return GetViewOrigin() - GetClientOrigin();} + int GetViewAreaWidth() const {return GetItemWidth();} + int GetViewAreaHeight() const; + CRect GetViewAreaRectAbs() const; + CRect GetViewAreaRect() const; + CRect GetValidViewOriginArea() const; + t_size GetGroupCount() const; + bool GetItemRangeAbs(const CRect & p_rect,t_size & p_base,t_size & p_count) const; + bool GetItemRangeAbsInclHeaders(const CRect & p_rect,t_size & p_base,t_size & p_count) const; + bool GetItemRange(const CRect & p_rect,t_size & p_base,t_size & p_count) const; + void MoveViewOriginNoClip(CPoint p_target); + void MoveViewOrigin(CPoint p_target); + CPoint ClipViewOrigin(CPoint p_origin) const; + void MoveViewOriginDelta(CPoint p_delta) {MoveViewOrigin( GetViewOrigin() + p_delta );} + void MoveViewOriginDeltaNoClip(CPoint p_delta) {MoveViewOriginNoClip( GetViewOrigin() + p_delta );} + bool ItemFromPoint(CPoint const & p_pt,t_size & p_item) const {return ItemFromPointAbs(p_pt + GetViewOffset(),p_item);} + bool GroupHeaderFromPoint(CPoint const & p_pt,int & p_group) const {return GroupHeaderFromPointAbs(p_pt + GetViewOffset(),p_group);} + bool ItemFromPointAbs(CPoint const & p_pt,t_size & p_item) const; + bool GroupHeaderFromPointAbs(CPoint const & p_pt,int & p_group) const; + + bool ResolveGroupRange(int p_id,t_size & p_base,t_size & p_count) const; + + virtual int GetGroupHeaderHeight() const {return 0;} + virtual int GetItemHeight() const {return 0;} + virtual int GetItemWidth() const {return 0;} + virtual t_size GetItemCount() const {return 0;} + virtual int GetItemGroup(t_size p_item) const {return 0;} + //override optionally + virtual void RenderItem(t_size p_item,const CRect & p_itemRect,const CRect & p_updateRect,CDCHandle p_dc); + //override optionally + virtual void RenderGroupHeader(int p_group,const CRect & p_headerRect,const CRect & p_updateRect,CDCHandle p_dc); + + //called by default RenderItem implementation + virtual void RenderItemText(t_size p_item,const CRect & p_itemRect,const CRect & p_updateRect,CDCHandle p_dc, bool allowColors) {} + //called by default RenderItem implementation + virtual void RenderGroupHeaderText(int p_group,const CRect & p_headerRect,const CRect & p_updateRect,CDCHandle p_dc) {} + + virtual void RenderItemBackground(CDCHandle p_dc,const CRect & p_itemRect,size_t item, uint32_t bkColor); + virtual void RenderGroupHeaderBackground(CDCHandle p_dc,const CRect & p_headerRect,int iGroup); + + virtual void RenderBackground( CDCHandle dc, CRect const & rc ); + + virtual void OnViewOriginChange(CPoint p_delta) {} + virtual void RenderOverlay(const CRect & p_updaterect,CDCHandle p_dc) {} + virtual bool FixedOverlayPresent() {return false;} + + virtual CRect GetClientRectHook() const; + + enum { + colorText = COLOR_WINDOWTEXT, + colorBackground = COLOR_WINDOW, + colorHighlight = COLOR_HOTLIGHT, + colorSelection = COLOR_HIGHLIGHT, + }; + + virtual COLORREF GetSysColorHook( int colorIndex ) const; + + //! Called by CListControlWithSelectionBase. + virtual void OnItemClicked(t_size item, CPoint pt) {} + //! Called by CListControlWithSelectionBase. + virtual void OnGroupHeaderClicked(int groupId, CPoint pt) {} + + //! Return true to indicate that some area of the control has a special purpose and clicks there should not trigger changes in focus/selection. + virtual bool OnClickedSpecialHitTest(CPoint pt) { return false; } + virtual bool OnClickedSpecial(DWORD status, CPoint pt) {return false;} + + virtual bool AllowScrollbar(bool vertical) const {return true;} + + CPoint GetClientOrigin() const {return GetClientRectHook().TopLeft();} + CRect GetVisibleRectAbs() const { + CRect view = GetClientRectHook(); + view.OffsetRect( GetViewOrigin() - view.TopLeft() ); + return view; + } + + bool IsSameItemOrHeaderAbs(const CPoint & p_point1, const CPoint & p_point2) const; + + void AddItemToUpdateRgn(HRGN p_rgn, t_size p_index) const; + void AddGroupHeaderToUpdateRgn(HRGN p_rgn, int id) const; + + t_size InsertIndexFromPoint(const CPoint & p_pt) const; + //! Translate point to insert location for drag and drop. \n + //! Made virtual so it can be specialized to allow only specific drop locations. + virtual t_size InsertIndexFromPointEx(const CPoint & pt, bool & bInside) const; + + virtual void ListHandleResize(); + + //! Can smooth-scroll *now* ? Used to suppress smooth scroll on temporary basis due to specific user operations in progress + virtual bool CanSmoothScroll() const { return true; } + //! Is smooth scroll enabled by user? + virtual bool UserEnabledSmoothScroll() const; + virtual bool ToggleSelectedItemsHook(pfc::bit_array const & mask) { return false; } + + void SetCaptureEx(CaptureProc_t proc); + void SetCaptureMsgHandled(BOOL v) { this->SetMsgHandled(v); } + + SIZE GetDPI() const { return this->m_dpi;} + + // Should this control take enter key in dialogs or not? + // Default to true for compatibility with existing code - but when used in dialogs, you'll want it set to false to hit [OK] with enter. + // Note that CreateInDialog() sets this to false. Change it later if you want enter key presses to reach this control in a dialog. + bool m_dlgWantEnter = true; + + bool WantReturn() const { return m_dlgWantEnter; } + void SetWantReturn(bool v) { m_dlgWantEnter = v; } + + enum { + rowStyleGrid = 0, + rowStyleFlat, + rowStylePlaylist, + rowStylePlaylistDelimited, + }; + void SetPlaylistStyle() {SetRowStyle(rowStylePlaylist);} + void SetRowStyle(unsigned v) { this->m_rowStyle = v; if (m_hWnd) Invalidate(); } + void SetFlatStyle() {SetRowStyle(rowStyleFlat);} + unsigned m_rowStyle = rowStylePlaylistDelimited; + bool DelimitColumns() const { return m_rowStyle == rowStyleGrid || m_rowStyle == rowStylePlaylistDelimited; } + + static COLORREF BlendGridColor( COLORREF bk, COLORREF tx ); + static COLORREF BlendGridColor( COLORREF bk ); + COLORREF GridColor(); + +private: + void RenderRect(const CRect & p_rect,CDCHandle p_dc); + int HandleWheel(int & p_accum,int p_delta, bool bHoriz); + + void OnKillFocus(CWindow); + void OnWindowPosChanged(LPWINDOWPOS); + void PaintContent(CRect rcPaint, HDC dc); + void OnPrintClient(HDC dc, UINT uFlags); + LRESULT OnPaint(UINT,WPARAM,LPARAM,BOOL&); + LRESULT OnVScroll(UINT,WPARAM,LPARAM,BOOL&); + LRESULT OnHScroll(UINT,WPARAM,LPARAM,BOOL&); + LRESULT OnSize(UINT,WPARAM,LPARAM,BOOL&); + LRESULT OnSizeAsync(UINT,WPARAM,LPARAM,BOOL&) {ListHandleResize();return 0;} + LRESULT OnVWheel(UINT,WPARAM,LPARAM,BOOL&); + LRESULT OnHWheel(UINT,WPARAM,LPARAM,BOOL&); + LRESULT OnGesture(UINT,WPARAM,LPARAM,BOOL&); + LRESULT SetFocusPassThru(UINT,WPARAM,LPARAM,BOOL&); + LRESULT OnCreatePassThru(UINT,WPARAM,LPARAM,BOOL&); + LRESULT OnEraseBkgnd(UINT,WPARAM,LPARAM,BOOL&); + LRESULT MousePassThru(UINT, WPARAM, LPARAM); + LRESULT OnGetDlgCode(UINT, WPARAM, LPARAM); + + void OnThemeChanged(); + int GetScrollThumbPos(int which); + void RefreshSliders(); + void RefreshSlider(bool p_vertical); + + void OnSizeAsync_Trigger(); + static LRESULT CALLBACK MouseHookProc(int nCode, WPARAM wParam, LPARAM lParam); + bool MouseWheelFromHook(UINT msg, LPARAM data); + + bool m_suppressMouseWheel = false; + int m_wheelAccumX = 0, m_wheelAccumY = 0; + CPoint m_viewOrigin = CPoint(0,0); + bool m_sizeAsyncPending = false; + CPoint m_gesturePoint; + +protected: + pfc::map_t m_themeCache; + CTheme & themeFor( const char * what ); + CTheme & theme() { return themeFor("LISTVIEW");} + + const SIZE m_dpi = QueryScreenDPIEx(); + CGestureAPI m_gestureAPI; + bool m_ensureVisibleUser = false; + CaptureProc_t m_captureProc; + void defer( std::function f ); + LRESULT OnExecDeferred(UINT, WPARAM, LPARAM); + + + // Overlays our stuff on top of generic DoDragDrop call. + // Currently catches mouse wheel messages in mid-drag&drop and handles them in our view. + HRESULT DoDragDrop(LPDATAOBJECT pDataObj, LPDROPSOURCE pDropSource, DWORD dwOKEffects, LPDWORD pdwEffect); + +private: + bool m_defferredMsgPending = false; + std::list > m_deferred; +}; + +class CListControlFontOps : public CListControlImpl { +private: + typedef CListControlImpl TParent; +public: + CListControlFontOps(); + BEGIN_MSG_MAP_EX(CListControlFontOps) + MESSAGE_HANDLER(WM_GETFONT,OnGetFont); + MESSAGE_HANDLER(WM_SETFONT,OnSetFont); + CHAIN_MSG_MAP(TParent); + END_MSG_MAP() + CFontHandle GetFont() const { return m_font; } + void SetFont(HFONT font, bool bUpdateView = true); +protected: + CFontHandle GetGroupHeaderFont() const {return (HFONT)m_groupHeaderFont;} + virtual double GroupHeaderFontScale() const { return 1.25; } + virtual int GroupHeaderFontWeight(int origVal) const { + //return pfc::min_t(FW_BLACK, origVal + 200); + return origVal; + } + + //! Overridden implementations should always forward the call to the base class. + virtual void OnSetFont(bool bUpdatingView) {} + + int GetGroupHeaderHeight() const {return m_groupHeaderHeight;} + int GetItemHeight() const {return m_itemHeight;} + +private: + LRESULT OnSetFont(UINT,WPARAM,LPARAM,BOOL&); + LRESULT OnGetFont(UINT,WPARAM,LPARAM,BOOL&); + void UpdateGroupHeaderFont(); + void CalculateHeights(); + int m_itemHeight, m_groupHeaderHeight; + CFontHandle m_font; + CFont m_groupHeaderFont; +}; + +#include "CListControlHeaderImpl.h" +#include "CListControlTruncationTooltipImpl.h" + + + +typedef CMiddleDragImpl CListControl; + diff --git a/libPPUI/CListControlComplete.h b/libPPUI/CListControlComplete.h new file mode 100644 index 0000000..ea8968e --- /dev/null +++ b/libPPUI/CListControlComplete.h @@ -0,0 +1,22 @@ +#pragma once + +// ================================================================================ +// CListControlComplete +// ================================================================================ +// Simplified declaration of the base class that most CListControl users will need. +// The other base classes are used directly mainly by old code predating libPPUI. +// ================================================================================ + +#include "CListControlWithSelection.h" +#include "CListControl_EditImpl.h" +#include "CListAccessible.h" + +// ================================================================================ +// CListControlWithSelectionImpl = list control with selection/focus +// CListControl_EditImpl = inplace editbox implementation +// CListControlAccImpl = accessibility API implementation (screen reader interop) +// ================================================================================ +typedef CListControlAccImpl > CListControlComplete; + +// CListControlReadOnly : no inplace edit functionality (CListControl_EditImpl) +typedef CListControlAccImpl CListControlReadOnly; diff --git a/libPPUI/CListControlHeaderImpl.cpp b/libPPUI/CListControlHeaderImpl.cpp new file mode 100644 index 0000000..3709164 --- /dev/null +++ b/libPPUI/CListControlHeaderImpl.cpp @@ -0,0 +1,1078 @@ +#include "stdafx.h" +#include "CListControl.h" +#include "CListControlHeaderImpl.h" // redundant but makes intelisense quit showing false errors +#include "CListControl-Cells.h" +#include "PaintUtils.h" +#include "GDIUtils.h" +#include "win32_utility.h" + +enum { + lineBelowHeaderCY = 1 +}; + +static bool testDrawLineBelowHeader() { + // Win10 + return GetOSVersionCode() >= 0xA00; +} + +void CListControlHeaderImpl::InitializeHeaderCtrl(DWORD flags) { + PFC_ASSERT(!IsHeaderEnabled()); + WIN32_OP_D( m_header.Create(*this,NULL,NULL,WS_CHILD | flags) != NULL ); + m_header.SetFont( GetFont() ); + + if (testDrawLineBelowHeader()) { + WIN32_OP_D( m_headerLine.Create( *this, NULL, NULL, WS_CHILD ) != NULL ); + } + + UpdateHeaderLayout(); +} + +void CListControlHeaderImpl::UpdateHeaderLayout() { + CRect client; WIN32_OP_D( GetClientRect(client) ); + m_clientWidth = client.Width(); + if (IsHeaderEnabled()) { + auto rc = client; + rc.left -= GetViewOffset().x; + WINDOWPOS wPos = {}; + HDLAYOUT layout = {&rc, &wPos}; + if (m_header.Layout(&layout)) { + m_header.SetWindowPos(wPos.hwndInsertAfter,wPos.x,wPos.y,wPos.cx,wPos.cy,wPos.flags | SWP_SHOWWINDOW); + if (m_headerLine != NULL) m_headerLine.SetWindowPos(m_header, wPos.x, wPos.y + wPos.cy, wPos.cx, lineBelowHeaderCY, wPos.flags | SWP_SHOWWINDOW); + } else { + m_header.ShowWindow(SW_HIDE); + if (m_headerLine != NULL) m_headerLine.ShowWindow(SW_HIDE); + } + } +} + +int CListControlHeaderImpl::GetItemWidth() const { + if (IsHeaderEnabled()) return m_itemWidth; + else return m_clientWidth; +} + +LRESULT CListControlHeaderImpl::OnSizePassThru(UINT,WPARAM,LPARAM p_lp) { + UpdateHeaderLayout(); + + ProcessAutoWidth(); + + SetMsgHandled(FALSE); + return 0; +} + +void CListControlHeaderImpl::OnViewOriginChange(CPoint p_delta) { + TParent::OnViewOriginChange(p_delta); + if (p_delta.x != 0) UpdateHeaderLayout(); +} + +void CListControlHeaderImpl::SetHeaderFont(HFONT font) { + if (IsHeaderEnabled()) { + m_header.SetFont(font); UpdateHeaderLayout(); + } +} + +LRESULT CListControlHeaderImpl::OnDividerDoubleClick(int,LPNMHDR hdr,BOOL&) { + const NMHEADER * info = (const NMHEADER *) hdr; + if (info->iButton == 0) { + AutoColumnWidth((t_size)info->iItem); + } + return 0; +} + +LRESULT CListControlHeaderImpl::OnHeaderItemClick(int,LPNMHDR p_hdr,BOOL&) { + const NMHEADER * info = (const NMHEADER *) p_hdr; + if (info->iButton == 0) { + OnColumnHeaderClick((t_uint32)info->iItem); + } + return 0; +} + +LRESULT CListControlHeaderImpl::OnHeaderItemChanged(int,LPNMHDR p_hdr,BOOL&) { + const NMHEADER * info = (const NMHEADER*) p_hdr; + if (info->pitem->mask & (HDI_WIDTH | HDI_ORDER)) { + if(!m_ownColumnsChange) ProcessColumnsChange(); + } + return 0; +} + +LRESULT CListControlHeaderImpl::OnHeaderEndDrag(int,LPNMHDR hdr,BOOL&) { + NMHEADER * info = (NMHEADER*) hdr; + return OnColumnHeaderDrag(info->iItem,info->pitem->iOrder) ? TRUE : FALSE; +} + +bool CListControlHeaderImpl::OnColumnHeaderDrag(t_size index, t_size newPos) { + index = GetSubItemOrder(index); + const t_size count = this->GetColumnCount(); + if ( count == 0 ) return false; + std::vector perm; perm.resize(count); pfc::create_move_items_permutation(&perm[0],count, pfc::bit_array_one(index), (int) newPos - (int) index ); + std::vector order, newOrder; order.resize(count); newOrder.resize(count); + WIN32_OP_D(m_header.GetOrderArray((int)count, &order[0])); + for(t_size walk = 0; walk < count; ++walk) newOrder[walk] = order[perm[walk]]; + WIN32_OP_D(m_header.SetOrderArray((int)count, &newOrder[0])); + OnColumnsChanged(); + return true; +} +t_size CListControlHeaderImpl::SubItemFromPointAbs(CPoint pt) const { + + auto order = GetColumnOrderArray(); + const t_size colCount = order.size(); + + size_t item; + if (! ItemFromPointAbs(pt, item ) ) item = pfc_infinite; + + long xWalk = 0; + for(t_size _walk = 0; _walk < colCount; ) { + const t_size walk = (t_size) order[_walk]; + + size_t span = 1; + if (item != pfc_infinite) span = this->GetSubItemSpan(item, walk); + + PFC_ASSERT( span == 1 || _walk == walk ); + + if ( walk + span > colCount ) span = colCount - walk; + + long width = 0; + for( size_t sub = 0; sub < span; ++ sub ) { + width += (long)this->GetSubItemWidth(walk + sub); + } + + if (xWalk + width > pt.x) return walk; + xWalk += width; + _walk += span; + } + return pfc_infinite; +} + +bool CListControlHeaderImpl::OnClickedSpecial(DWORD status, CPoint pt) { + + const DWORD maskButtons = MK_LBUTTON | MK_RBUTTON | MK_MBUTTON | MK_XBUTTON1 | MK_XBUTTON2; + + if ( (status & maskButtons) != MK_LBUTTON ) return false; + + if (!GetCellTypeSupported()) return false; + + size_t item; size_t subItem; + if (! this->GetItemAtPointAbsEx( pt + GetViewOffset(), item, subItem ) ) { + return false; + } + + bool bCapture = false; + auto cellType = GetCellType(item, subItem); + if ( !CellTypeReactsToMouseOver( cellType ) ) return false; + auto rcHot = CellHotRect( item, subItem, cellType ); + if (!rcHot.PtInRect( pt )) return false; + + SetPressedItem(item, subItem); + SetCaptureEx([=](UINT, DWORD newStatus, CPoint pt) { + + { + CRect rc = this->GetClientRectHook(); + if (!rc.PtInRect(pt)) { + ClearPressedItem(); return false; + } + } + + size_t newItem, newSubItem; + if (!this->GetItemAtPointAbsEx(pt + GetViewOffset(), newItem, newSubItem) || newItem != item || newSubItem != subItem) { + ClearPressedItem(); return false; + } + + DWORD buttons = newStatus & maskButtons; + if (buttons == 0) { + // button released? + this->defer( [=] { + OnSubItemClicked(item, subItem, pt); + } ); + ClearPressedItem(); return false; + } + if (buttons != MK_LBUTTON) { + // another button pressed? + ClearPressedItem(); return false; + } + + return true; + }); + return true; +} + +bool CListControlHeaderImpl::CellTypeUsesSpecialHitTests( cellType_t ct ) { + if ( ct == nullptr ) return false; + return ct->SuppressRowSelect(); +} + +bool CListControlHeaderImpl::OnClickedSpecialHitTest(CPoint pt) { + if ( ! GetCellTypeSupported() ) return false; + auto ct = GetCellTypeAtPointAbs( pt + GetViewOffset() ); + return CellTypeUsesSpecialHitTests(ct); +} + +void CListControlHeaderImpl::OnItemClicked(t_size item, CPoint pt) { + t_size subItem = SubItemFromPointAbs(pt + GetViewOffset()); + if (subItem != ~0) { + if ( this->GetCellTypeSupported() ) { + auto ct = this->GetCellType(item, subItem ); + // we don't handle hyperlink & button clicks thru here + if (CellTypeUsesSpecialHitTests(ct)) return; + } + OnSubItemClicked(item, subItem, pt); + } +} + +std::vector CListControlHeaderImpl::GetColumnOrderArray() const { + const size_t cCount = this->GetColumnCount(); + std::vector order; + if ( cCount > 0 ) { + order.resize(cCount); + if (IsHeaderEnabled()) { + WIN32_OP_D(m_header.GetOrderArray((int)cCount, &order[0])); + } else { + for (size_t c = 0; c < cCount; ++c) order[c] = (int)c; + } + } + return order; +} + +void CListControlHeaderImpl::RenderItemText(t_size item,const CRect & itemRect,const CRect & updateRect,CDCHandle dc, bool allowColors) { + + t_uint32 xWalk = itemRect.left; + CRect subItemRect(itemRect); + auto order = GetColumnOrderArray(); + const size_t cCount = order.size(); + SelectObjectScope fontScope(dc,GetFont()); + for(t_size _walk = 0; _walk < cCount; ) { + const t_size walk = order[_walk]; + + size_t span = GetSubItemSpan(item, walk); + + PFC_ASSERT( walk == _walk || span == 1 ); + + t_uint32 width = GetSubItemWidth(walk); + + if ( span > 1 ) { + if ( walk + span > cCount ) span = cCount - walk; + for( size_t extraWalk = 1; extraWalk < span; ++ extraWalk ) { + width += GetSubItemWidth(walk + extraWalk); + } + } + + subItemRect.left = xWalk; subItemRect.right = xWalk + width; + CRect subUpdate; + if (subUpdate.IntersectRect(subItemRect, updateRect)) { + DCStateScope scope(dc); + if (dc.IntersectClipRect(subItemRect) != NULLREGION) { + RenderSubItemText(item,walk,subItemRect,subUpdate,dc, allowColors); + } + } + xWalk += width; + + _walk += span; + } +} + +t_size CListControlHeaderImpl::GetSubItemOrder(t_size subItem) const { + if ( ! IsHeaderEnabled( ) ) return subItem; + HDITEM hditem = {}; + hditem.mask = HDI_ORDER; + WIN32_OP_D( m_header.GetItem( (int) subItem, &hditem ) ); + return (t_size) hditem.iOrder; +} + +size_t CListControlHeaderImpl::GetSubItemSpan(size_t row, size_t column) const { + return 1; +} + +uint32_t CListControlHeaderImpl::GetSubItemWidth(t_size subItem) const { + if ( ! IsHeaderEnabled( ) ) { + // Should be overridden for custom columns layout + PFC_ASSERT( GetColumnCount() == 1 ); + PFC_ASSERT( subItem == 0 ); + return GetItemWidth(); + } + + if ( subItem < m_colRuntime.size() ) return m_colRuntime[subItem].m_widthPixels; + PFC_ASSERT( !"bad column idx"); + return 0; +} + +int CListControlHeaderImpl::GetHeaderItemWidth( int which ) { + HDITEM hditem = {}; + hditem.mask = HDI_WIDTH; + WIN32_OP_D( m_header.GetItem( which, &hditem) ); + return hditem.cxy; +} + +void CListControlHeaderImpl::OnColumnsChanged() { + if ( IsHeaderEnabled() ) { + for( size_t walk = 0; walk < m_colRuntime.size(); ++ walk ) { + m_colRuntime[walk].m_widthPixels = GetHeaderItemWidth( (int) walk ); + } + RecalcItemWidth(); + } + this->OnViewAreaChanged(); +} + +void CListControlHeaderImpl::ResetColumns(bool update) { + m_colRuntime.clear(); + m_itemWidth = 0; + PFC_ASSERT(IsHeaderEnabled()); + for(;;) { + int count = m_header.GetItemCount(); + if (count <= 0) break; + m_header.DeleteItem(count - 1); + } + if (update) OnColumnsChanged(); +} + +void CListControlHeaderImpl::SetColumn( size_t which, const char * label, DWORD fmtFlags, bool updateView) { + PFC_ASSERT( IsHeaderEnabled() ); + pfc::stringcvt::string_os_from_utf8 labelOS(label); + + HDITEM item = {}; + item.mask = HDI_TEXT | HDI_FORMAT; + item.fmt = fmtFlags | HDF_STRING; + item.pszText = const_cast(labelOS.get_ptr()); + m_header.SetItem( (int) which, &item ); + + if (which < m_colRuntime.size()) m_colRuntime[which].m_text = label; + + if (updateView) OnColumnsChanged(); +} + +void CListControlHeaderImpl::GetColumnText(size_t which, pfc::string_base & out) const { + if (which < m_colRuntime.size()) { + out = m_colRuntime[which].m_text.c_str(); + } else { + out = ""; + } +} + +void CListControlHeaderImpl::ResizeColumn(t_size index, t_uint32 widthPixels, bool updateView) { + PFC_ASSERT( IsHeaderEnabled() ); + PFC_ASSERT( index < m_colRuntime.size() ); + HDITEM item = {}; + item.mask = HDI_WIDTH; + item.cxy = widthPixels; + { pfc::vartoggle_t scope(m_ownColumnsChange, true); m_header.SetItem( (int) index, &item ); } + m_colRuntime[index].m_widthPixels = widthPixels; + RecalcItemWidth(); + if (updateView) OnColumnsChanged(); +} + +void CListControlHeaderImpl::DeleteColumns( pfc::bit_array const & mask, bool updateView ) { + int nDeleted = 0; + const size_t oldCount = GetColumnCount(); + mask.for_each(true, 0, oldCount, [&] (size_t idx) { + int iDelete = (int) idx - nDeleted; + bool bDeleted = m_header.DeleteItem( iDelete ); + PFC_ASSERT( bDeleted ); + if ( bDeleted ) ++ nDeleted; + } ); + + pfc::remove_mask_t( m_colRuntime, mask ); + + ColumnWidthFix(); + + if (updateView) { + OnColumnsChanged(); + } + +} + +bool CListControlHeaderImpl::DeleteColumn(size_t index, bool update) { + PFC_ASSERT( IsHeaderEnabled() ); + + if (!m_header.DeleteItem( (int) index )) return false; + + pfc::remove_mask_t( m_colRuntime, pfc::bit_array_one( index ) ); + + ColumnWidthFix(); + + if (update) { + OnColumnsChanged(); + } + + return true; +} + +void CListControlHeaderImpl::SetSortIndicator( size_t whichColumn, bool isUp ) { + HeaderControl_SetSortIndicator( GetHeaderCtrl(), (int) whichColumn, isUp ); +} + +void CListControlHeaderImpl::ClearSortIndicator() { + HeaderControl_SetSortIndicator(GetHeaderCtrl(), -1, false); +} + +bool CListControlHeaderImpl::HaveAutoWidthContentColumns() const { + for( auto i = m_colRuntime.begin(); i != m_colRuntime.end(); ++ i ) { + if ( i->autoWidthContent() ) return true; + } + return false; +} +bool CListControlHeaderImpl::HaveAutoWidthColumns() const { + for( auto i = m_colRuntime.begin(); i != m_colRuntime.end(); ++ i ) { + if ( i->autoWidth() ) return true; + } + return false; +} +void CListControlHeaderImpl::AddColumnEx( const char * label, uint32_t widthPixelsAt96DPI, DWORD fmtFlags, bool update ) { + uint32_t w = widthPixelsAt96DPI; + if ( w <= columnWidthMax ) { + w = MulDiv( w, m_dpi.cx, 96 ); + } + AddColumn( label, w, fmtFlags, update ); +} + +void CListControlHeaderImpl::AddColumnDLU( const char * label, uint32_t widthDLU, DWORD fmtFlags, bool update ) { + uint32_t w = widthDLU; + if ( w <= columnWidthMax ) { + w = ::MapDialogWidth( GetParent(), w ); + } + AddColumn( label, w, fmtFlags, update ); +} +void CListControlHeaderImpl::AddColumnF( const char * label, float widthF, DWORD fmtFlags, bool update) { + uint32_t w = columnWidthMax; + if ( widthF >= 0 ) { + w = pfc::rint32( widthF * m_dpi.cx / 96.0f ); + } + AddColumn( label, w, fmtFlags, update ); +} +void CListControlHeaderImpl::AddColumn(const char * label, uint32_t width, DWORD fmtFlags,bool update) { + if (! IsHeaderEnabled( ) ) InitializeHeaderCtrl(); + + pfc::stringcvt::string_os_from_utf8 labelOS(label); + HDITEM item = {}; + item.mask = HDI_TEXT | HDI_FORMAT; + if ( width != UINT32_MAX ) { + item.cxy = width; + item.mask |= HDI_WIDTH; + } + + item.pszText = const_cast(labelOS.get_ptr()); + item.fmt = HDF_STRING | fmtFlags; + int iColumn; + WIN32_OP_D( (iColumn = m_header.InsertItem(m_header.GetItemCount(),&item) ) >= 0 ); + colRuntime_t rt; + rt.m_text = label; + rt.m_userWidth = width; + if ( width <= columnWidthMax ) { + m_itemWidth += width; + rt.m_widthPixels = width; + } + m_colRuntime.push_back( std::move(rt) ); + + if (update) OnColumnsChanged(); + + ProcessAutoWidth(); +} + +float CListControlHeaderImpl::GetColumnWidthF(size_t subItem) const { + auto w = GetSubItemWidth(subItem); + return (float) w * 96.0f / (float)m_dpi.cx; +} + +void CListControlHeaderImpl::RenderBackground(CDCHandle dc, CRect const& rc) { + __super::RenderBackground(dc,rc); +#if 0 + if ( m_drawLineBelowHeader && IsHeaderEnabled()) { + CRect rcHeader; + if (m_header.GetWindowRect(rcHeader)) { + // Draw a grid line below header + int y = rcHeader.Height(); + if ( y >= rc.top && y < rc.bottom ) { + CDCPen pen(dc, GridColor()); + SelectObjectScope scope(dc, pen); + dc.MoveTo(rc.left, y); + dc.LineTo(rc.right, y); + } + } + } +#endif +} + +CRect CListControlHeaderImpl::GetClientRectHook() const { + CRect rcClient = __super::GetClientRectHook(); + if (m_header != NULL) { + PFC_ASSERT( m_header.IsWindow() ); + CRect rcHeader; + if (m_header.GetWindowRect(rcHeader)) { + int h = rcHeader.Height(); + if ( m_headerLine != NULL ) h += lineBelowHeaderCY; + rcClient.top = pfc::min_t(rcClient.bottom,rcClient.top + h); + } + } + return rcClient; +} + +CRect CListControlHeaderImpl::GetItemTextRectHook(size_t, size_t, CRect const & rc) const { + return GetItemTextRect( rc ); +} + +CRect CListControlHeaderImpl::GetItemTextRect(const CRect & itemRect) const { + CRect rc ( itemRect ); + rc.DeflateRect(GetColumnSpacing(),0); + return rc; +} + +void CListControlHeaderImpl::SetColumnSort(t_size which, bool isUp) { + HeaderControl_SetSortIndicator(m_header,(int)which,isUp); +} + +void CListControlHeaderImpl::SetColumnFormat(t_size which, DWORD format) { + HDITEM item = {}; + item.mask = HDI_FORMAT; + item.fmt = HDF_STRING | format; + WIN32_OP_D( m_header.SetItem((int)which,&item) ); +} + +DWORD CListControlHeaderImpl::GetColumnFormat(t_size which) const { + if (!IsHeaderEnabled()) return HDF_LEFT; + HDITEM hditem = {}; + hditem.mask = HDI_FORMAT; + WIN32_OP_D( m_header.GetItem( (int) which, &hditem) ); + return hditem.fmt; +} + +BOOL CListControlHeaderImpl::OnSetCursor(CWindow wnd, UINT nHitTest, UINT message) { +#if 0 + // no longer meaningful since SetCapture on mouse over was added to track hot status + if ( message != 0 && GetCellTypeSupported() ) { + CPoint pt( (LPARAM) GetMessagePos() ); + WIN32_OP_D( ScreenToClient( &pt ) ); + if ( GetCellTypeAtPointAbs( pt + GetViewOffset() ) == cell_hyperlink ) { + SetCursor(LoadCursor(NULL, IDC_HAND)); return TRUE; + } + } +#endif + SetMsgHandled(FALSE); return FALSE; +} + +bool CListControlHeaderImpl::GetItemAtPointAbsEx(CPoint pt, size_t & outItem, size_t & outSubItem) const { + size_t item, subItem; + if (ItemFromPointAbs(pt, item)) { + subItem = SubItemFromPointAbs(pt); + if (subItem != pfc_infinite) { + outItem = item; outSubItem = subItem; return true; + } + } + return false; +} + +CListControlHeaderImpl::cellType_t CListControlHeaderImpl::GetCellTypeAtPointAbs(CPoint pt) const { + size_t item, subItem; + if ( GetItemAtPointAbsEx( pt, item, subItem) ) { + return GetCellType( item, subItem ); + } + return nullptr; +} + +void CListControlHeaderImpl::RenderSubItemTextInternal(t_size subItem, const CRect & subItemRect, CDCHandle dc, const char * text, bool allowColors) { + pfc::stringcvt::string_os_from_utf8 cvt(text); + CRect clip = GetItemTextRect(subItemRect); + const t_uint32 format = PaintUtils::DrawText_TranslateHeaderAlignment(GetColumnFormat(subItem)); + if (true) { + const t_uint32 bk = dc.GetBkColor(); + const t_uint32 fg = dc.GetTextColor(); + const t_uint32 hl = (allowColors ? GetSysColorHook(colorHighlight) : fg); + const t_uint32 colors[3] = {PaintUtils::BlendColor(bk, fg, 33), fg, hl}; + + PaintUtils::TextOutColorsEx(dc, cvt, clip, format, colors); + + dc.SetTextColor(fg); + } else { + dc.DrawText(cvt,(int)cvt.length(),clip,DT_NOPREFIX | DT_END_ELLIPSIS | DT_SINGLELINE | DT_VCENTER | format ); + } +} +CListCell * CListControlHeaderImpl::GetCellType( size_t item, size_t subItem ) const { + return &PFC_SINGLETON(CListCell_Text); +} +void CListControlHeaderImpl::RenderSubItemText(t_size item, t_size subItem,const CRect & subItemRect,const CRect & updateRect,CDCHandle dc, bool allowColors) { + const auto cellType = GetCellType( item, subItem ); + if ( cellType == nullptr ) { + PFC_ASSERT( !"Should not get here" ); + return; + } + pfc::string_formatter label; + const bool bHaveText = GetSubItemText(item,subItem,label); + if (! bHaveText ) { + label = ""; //sanity + } + + pfc::stringcvt::string_os_from_utf8_fast labelOS ( label ); + CListCell::DrawContentArg_t arg; + arg.hdrFormat = GetColumnFormat( subItem ); + arg.subItemRect = subItemRect; + arg.dc = dc; + arg.text = labelOS.get_ptr(); + arg.allowColors = allowColors; + bool bPressed; + if ( cellType->IsToggle() ) bPressed = this->GetCellCheckState(item, subItem); + else bPressed = (item == m_pressedItem) && (subItem == m_pressedSubItem); + bool bHot = (item == m_hotItem) && ( subItem == m_hotSubItem ); + if ( bPressed ) arg.cellState |= CListCell::cellState_pressed; + if ( bHot ) arg.cellState|= CListCell::cellState_hot; + arg.rcText = GetItemTextRectHook(item, subItem, subItemRect); + arg.rcHot = CellHotRect(item, subItem, cellType, subItemRect); + + auto strTheme = cellType->Theme(); + if ( strTheme != nullptr ) { + arg.theme = themeFor( strTheme ).m_theme; + } + arg.colorHighlight = GetSysColorHook(colorHighlight); + + arg.thisWnd = m_hWnd; + + if (this->IsSubItemGrayed( item, subItem ) ) { + arg.cellState |= CListCell::cellState_disabled; + } + + CFontHandle fontRestore; + CFont fontOverride; + + LOGFONT data; + if (dc.GetCurrentFont().GetLogFont(&data)) { + if ( cellType->ApplyTextStyle( data, CellTextScale(item, subItem ), arg.cellState ) ) { + if (fontOverride.CreateFontIndirect( & data )) { + fontRestore = dc.SelectFont( fontOverride ); + } + } + } + cellType->DrawContent( arg ); + + if ( fontRestore != NULL ) dc.SelectFont( fontRestore ); +} + +void CListControlHeaderImpl::RenderGroupHeaderText(int id,const CRect & headerRect,const CRect & updateRect,CDCHandle dc) { + pfc::string_formatter label; + if (GetGroupHeaderText(id,label)) { + SelectObjectScope fontScope(dc,GetGroupHeaderFont()); + pfc::stringcvt::string_os_from_utf8 cvt(label); + CRect contentRect(GetItemTextRect(headerRect)); + dc.DrawText(cvt,(int)cvt.length(),contentRect,DT_NOPREFIX | DT_END_ELLIPSIS | DT_SINGLELINE | DT_VCENTER | DT_LEFT ); + SIZE txSize; + const int lineSpacing = contentRect.Height() / 2; + if (dc.GetTextExtent(cvt,(int)cvt.length(),&txSize)) { + if (txSize.cx + lineSpacing < contentRect.Width()) { + const CPoint center = contentRect.CenterPoint(); + const CPoint pt1(contentRect.left + txSize.cx + lineSpacing, center.y), pt2(contentRect.right, center.y); + const COLORREF lineColor = PaintUtils::BlendColor(dc.GetTextColor(),dc.GetBkColor(),25); + +#ifndef CListControl_ScrollWindowFix +#error FIXME CMemoryDC needed +#endif + PaintUtils::DrawSmoothedLine(dc, pt1, pt2, lineColor, 1.0 * (double)m_dpi.cy / 96.0); + } + } + } +} + +uint32_t CListControlHeaderImpl::GetOptimalColumnWidthFixed(const char * fixedText) const { + CWindowDC dc(*this); + SelectObjectScope fontScope(dc, GetFont()); + GetOptimalWidth_Cache cache; + cache.m_dc = dc; + cache.m_stringTemp = fixedText; + return cache.GetStringTempWidth() + this->GetColumnSpacing() * 2; +} + +t_uint32 CListControlHeaderImpl::GetOptimalSubItemWidth(t_size item, t_size subItem, GetOptimalWidth_Cache & cache) const { + const t_uint32 base = this->GetColumnSpacing() * 2; + if (GetSubItemText(item,subItem,cache.m_stringTemp)) { + return base + cache.GetStringTempWidth(); + } else { + return base; + } +} + +t_uint32 CListControlHeaderImpl::GetOptimalWidth_Cache::GetStringTempWidth() { + if (m_stringTemp.replace_string_ex(m_stringTempUnfuckAmpersands, "&", "&&") > 0) { + m_convertTemp.convert(m_stringTempUnfuckAmpersands); + } else { + m_convertTemp.convert(m_stringTemp); + } + return PaintUtils::TextOutColors_CalcWidth(m_dc, m_convertTemp); +} + +t_uint32 CListControlHeaderImpl::GetOptimalColumnWidth(t_size which, GetOptimalWidth_Cache & cache) const { + const t_size totalItems = GetItemCount(); + t_uint32 val = 0; + for(t_size item = 0; item < totalItems; ++item) { + pfc::max_acc( val, GetOptimalSubItemWidth( item, which, cache ) ); + } + return val; +} + +t_uint32 CListControlHeaderImpl::GetOptimalSubItemWidthSimple(t_size item, t_size subItem) const { + CWindowDC dc(*this); + SelectObjectScope fontScope(dc, GetFont() ); + GetOptimalWidth_Cache cache; + cache.m_dc = dc; + return GetOptimalSubItemWidth(item, subItem, cache); +} + +LRESULT CListControlHeaderImpl::OnKeyDown(UINT,WPARAM wp,LPARAM,BOOL& bHandled) { + switch(wp) { + case VK_ADD: + if (IsKeyPressed(VK_CONTROL)) { + AutoColumnWidths(); + return 0; + } + break; + } + bHandled = FALSE; + return 0; +} + +uint32_t CListControlHeaderImpl::GetOptimalColumnWidth(size_t colIndex) const { + CWindowDC dc(*this); + SelectObjectScope fontScope(dc, GetFont()); + GetOptimalWidth_Cache cache; + cache.m_dc = dc; + uint32_t ret = 0; + const auto itemCount = GetItemCount(); + for (t_size walk = 0; walk < itemCount; ++walk) { + pfc::max_acc(ret, GetOptimalSubItemWidth(walk, colIndex, cache)); + } + return ret; +} + +void CListControlHeaderImpl::AutoColumnWidths(const pfc::bit_array & mask, bool expandLast) { + PFC_ASSERT( IsHeaderEnabled() ); + if (!IsHeaderEnabled()) return; + const t_size itemCount = GetItemCount(); + if (itemCount == 0) return; + const t_size columnCount = (t_size) m_header.GetItemCount(); + if (columnCount == 0) return; + pfc::array_t widths; widths.set_size(columnCount); widths.fill_null(); + { + CWindowDC dc(*this); + SelectObjectScope fontScope(dc,GetFont()); + GetOptimalWidth_Cache cache; + cache.m_dc = dc; + for(t_size walk = 0; walk < itemCount; ++walk) { + for(t_size colWalk = mask.find_first(true,0,columnCount); colWalk < columnCount; colWalk = mask.find_next(true,colWalk,columnCount)) { + pfc::max_acc(widths[colWalk], GetOptimalSubItemWidth(walk,colWalk,cache)); + } + } + } + + if (expandLast) { + uint32_t usedWidth = 0; size_t lastCol = SIZE_MAX; + pfc::array_t order; order.set_size(columnCount); + WIN32_OP_D( m_header.GetOrderArray((int)columnCount,order.get_ptr()) ); + for(size_t _walk = 0; _walk < columnCount; ++_walk) { + const size_t colWalk = (size_t) order[_walk]; + PFC_ASSERT( colWalk < columnCount ); + if (mask[colWalk]) { + lastCol = colWalk; + usedWidth += widths[colWalk]; + } else { + usedWidth += GetSubItemWidth(colWalk); + } + } + if (lastCol != SIZE_MAX) { + t_uint32 clientWidth = this->GetClientRectHook().Width(); + if (clientWidth > 0) --clientWidth; // $!@# scrollbar hack + if (usedWidth < clientWidth) { + widths[lastCol] += clientWidth - usedWidth; + } + } + } + for(t_size colWalk = mask.find_first(true,0,columnCount); colWalk < columnCount; colWalk = mask.find_next(true,colWalk,columnCount)) { + ResizeColumn(colWalk,widths[colWalk],false); + } + ProcessColumnsChange(); +} + +t_uint32 CListControlHeaderImpl::GetOptimalGroupHeaderWidth(int which) const { + CWindowDC dc(*this); + SelectObjectScope fontScope(dc,GetGroupHeaderFont()); + GetOptimalWidth_Cache cache; cache.m_dc = dc; + const t_uint32 base = this->GetColumnSpacing() * 2; + if (GetGroupHeaderText(which,cache.m_stringTemp)) { + return base + cache.GetStringTempWidth(); + } else { + return base; + } +} + +size_t CListControlHeaderImpl::GetColumnCount() const { + if ( ! IsHeaderEnabled() ) return 1; +#if PFC_DEBUG + int iHeaderCount = m_header.GetItemCount(); + PFC_ASSERT( m_colRuntime.size() == (size_t) iHeaderCount ); +#endif + return m_colRuntime.size(); +} + +void CListControlHeaderImpl::ColumnWidthFix() { + if ( this->HaveAutoWidthColumns() ) { + ProcessAutoWidth(); + } else { + RecalcItemWidth(); + } +} + +void CListControlHeaderImpl::ProcessAutoWidth() { + if ( HaveAutoWidthColumns() ) { + const int clientWidth = this->GetClientRectHook().Width(); + + if ( ! this->HaveAutoWidthContentColumns( ) && clientWidth == m_itemWidth) return; + + const size_t count = GetColumnCount(); + uint32_t totalNonAuto = 0; + size_t numAutoWidth = 0; + for(size_t walk = 0; walk < count; ++ walk ) { + if ( m_colRuntime[walk].autoWidth() ) { + ++ numAutoWidth; + } else { + totalNonAuto += GetSubItemWidth(walk); + } + } + int toDivide = clientWidth - totalNonAuto; + if ( toDivide < 0 ) toDivide = 0; + + size_t numLeft = numAutoWidth; + auto worker = [&] ( size_t iCol ) { + auto & rt = m_colRuntime[iCol]; + int lo = this->GetOptimalColumnWidthFixed( rt.m_text.c_str() ); + if ( rt.autoWidthContent() ) { + int lo2 = this->GetOptimalColumnWidth( iCol ); + if ( lo < lo2 ) lo = lo2; + } + int width = (int)(toDivide / numLeft); + if ( width < lo ) width = lo; + + HDITEM item = {}; + item.mask = HDI_WIDTH; + item.cxy = width; + WIN32_OP_D( m_header.SetItem( iCol, &item ) ); + rt.m_widthPixels = width; + + if ( toDivide > width ) { + toDivide -= width; + } else { + toDivide = 0; + } + -- numLeft; + + }; + for( size_t iCol = 0; iCol < count; ++ iCol ) { + if (m_colRuntime[iCol].autoWidthContent() ) worker(iCol); + } + for( size_t iCol = 0; iCol < count; ++ iCol ) { + if ( m_colRuntime[iCol].autoWidthPlain() ) worker(iCol); + } + + RecalcItemWidth(); + OnColumnsChanged(); + m_header.Invalidate(); + } +} + +void CListControlHeaderImpl::RecalcItemWidth() { + int total = 0; + const t_size count = GetColumnCount(); + for(t_size walk = 0; walk < count; ++walk) total += GetSubItemWidth(walk); + m_itemWidth = total; +} + +CRect CListControlHeaderImpl::GetSubItemRectAbs(t_size item,t_size subItem) const { + CRect rc = GetItemRectAbs(item); + auto order = GetColumnOrderArray(); + const t_size colCount = order.size(); + for(t_size _walk = 0; _walk < colCount; ++_walk) { + const t_size walk = (t_size) order[_walk]; + + t_size width = this->GetSubItemWidth(walk); + if (subItem == walk) { + + size_t span = GetSubItemSpan(item, walk); + if ( walk + span > colCount ) span = colCount - walk; + for( size_t extra = 1; extra < span; ++ extra ) { + width += GetSubItemWidth( walk + extra); + } + + rc.right = rc.left + (long)width; + + return rc; + } else { + rc.left += (long)width; + } + } + throw pfc::exception_invalid_params(); +} + +CRect CListControlHeaderImpl::GetSubItemRect(t_size item,t_size subItem) const { + CRect rc = GetSubItemRectAbs(item,subItem); rc.OffsetRect(-GetViewOffset()); return rc; +} + +void CListControlHeaderImpl::SetHotItem(size_t row, size_t column) { + if ( m_hotItem != row || m_hotSubItem != column ) { + if (m_hotItem != pfc_infinite) InvalidateRect(GetSubItemRect(m_hotItem, m_hotSubItem)); + m_hotItem = row; m_hotSubItem = column; + if (m_hotItem != pfc_infinite) InvalidateRect(GetSubItemRect(m_hotItem, m_hotSubItem)); + } +} + +void CListControlHeaderImpl::SetPressedItem(size_t row, size_t column) { + if (m_pressedItem != row || m_pressedSubItem != column) { + if (m_pressedItem != pfc_infinite) InvalidateRect(GetSubItemRect(m_pressedItem, m_pressedSubItem)); + m_pressedItem = row; m_pressedSubItem = column; + if (m_pressedItem != pfc_infinite) InvalidateRect(GetSubItemRect(m_pressedItem, m_pressedSubItem)); + } +} + +void CListControlHeaderImpl::SetCellCheckState(size_t item, size_t subItem, bool value) { + ReloadItem(item); (void)subItem; (void)value; + // Subclass deals with keeping track of state +} + +bool CListControlHeaderImpl::ToggleSelectedItemsHook(const pfc::bit_array & mask) { + if (this->GetCellTypeSupported() ) { + bool handled = false; + bool setTo = true; + + mask.walk(GetItemCount(), [&](size_t idx) { + auto ct = this->GetCellType(idx, 0); + if ( ct != nullptr && ct->IsToggle() ) { + if ( ct->IsRadioToggle() ) { + if (!handled) { + handled = true; + setTo = !this->GetCellCheckState(idx, 0); + this->SetCellCheckState(idx, 0, setTo); + } + } else { + if (!handled) { + handled = true; + setTo = ! this->GetCellCheckState(idx,0); + } + this->SetCellCheckState(idx,0,setTo); + } + } + }); + + if (handled) return true; + } + return __super::ToggleSelectedItemsHook(mask); +} + +void CListControlHeaderImpl::OnSubItemClicked(t_size item, t_size subItem, CPoint pt) { + auto ct = GetCellType(item, subItem); + if ( ct != nullptr && ct->IsToggle() ) { + if ( ct->HotRect(GetSubItemRect(item, subItem)).PtInRect(pt) ) { + this->SetCellCheckState( item, subItem, ! GetCellCheckState( item, subItem ) ); + } + } +} + + +bool CListControlHeaderImpl::AllowTypeFindInCell(size_t item, size_t subItem) const { + auto cell = GetCellType( item, subItem ); + if ( cell == nullptr ) return false; + return cell->AllowTypeFind(); +} + +bool CListControlHeaderImpl::CellTypeReactsToMouseOver(cellType_t ct) { + return ct != nullptr && ct->IsInteractive(); +} + +CRect CListControlHeaderImpl::CellHotRect( size_t, size_t, cellType_t ct, CRect rcCell) { + if ( ct != nullptr ) { + return ct->HotRect(rcCell); + } + return rcCell; +} +CRect CListControlHeaderImpl::CellHotRect(size_t item, size_t subItem, cellType_t ct) { + return CellHotRect( item, subItem, ct, GetSubItemRect( item, subItem ) ); +} +void CListControlHeaderImpl::OnMouseMove(UINT nFlags, CPoint pt) { + const DWORD maskButtons = MK_LBUTTON | MK_RBUTTON | MK_MBUTTON | MK_XBUTTON1 | MK_XBUTTON2; + if (GetCellTypeSupported() && (nFlags & maskButtons) == 0 ) { + size_t item; size_t subItem; + if (this->GetItemAtPointAbsEx(pt + GetViewOffset(), item, subItem)) { + auto ct = this->GetCellType( item, subItem ); + if (CellTypeReactsToMouseOver(ct) ) { + auto rc = CellHotRect( item, subItem, ct ); + if ( PtInRect( rc, pt ) ) { + { + auto c = ct->HotCursor(); + if ( c != NULL ) SetCursor(c); + } + SetHotItem(item, subItem); + SetCaptureEx([=](UINT msg, DWORD newStatus, CPoint pt) { + if ((newStatus & maskButtons) != 0 || msg == WM_MOUSEWHEEL || msg == WM_MOUSEHWHEEL ) { + // A button has been pressed or wheel has been moved + this->ClearHotItem(); + SetCaptureMsgHandled(FALSE); + return false; + } + + if ( ! PtInRect( rc, pt ) ) { + // Left the rect + this->ClearHotItem(); + SetCaptureMsgHandled(FALSE); + return false; + } + + return true; + }); + } + } + } + } + SetMsgHandled(FALSE); +} + +bool CListControlHeaderImpl::AllowScrollbar(bool vertical) const { + if ( vertical ) { + // vertical + return true; + } else { + // horizontal + if (! IsHeaderEnabled( ) ) return false; // no header? + return true; + } +} + +void CListControlHeaderImpl::OnDestroy() { + m_colRuntime.clear(); + m_header = NULL; + m_headerLine = NULL; + SetMsgHandled(FALSE); +} + + +uint32_t CListControlHeaderImpl::GetColumnsBlankWidth( size_t colExclude ) const { + auto client = this->GetClientRectHook().Width(); + int item = GetItemWidth(); + if (colExclude) item -= GetSubItemWidth(colExclude); + if ( item < 0 ) item = 0; + if ( item < client ) return (uint32_t)( client - item ); + else return 0; +} + +void CListControlHeaderImpl::SizeColumnToContent( size_t which, uint32_t minWidth ) { + auto width = this->GetOptimalColumnWidth( which ); + if ( width < minWidth ) width = minWidth; + this->ResizeColumn( which, width ); +} + +void CListControlHeaderImpl::SizeColumnToContentFillBlank( size_t which ) { + this->SizeColumnToContent( which, this->GetColumnsBlankWidth(which) ); +} + +HBRUSH CListControlHeaderImpl::OnCtlColorStatic(CDCHandle dc, CStatic wndStatic) { + if ( wndStatic == m_headerLine ) { + COLORREF col = GridColor(); + dc.SetDCBrushColor( col ); + return (HBRUSH) GetStockObject(DC_BRUSH); + } + SetMsgHandled(FALSE); + return NULL; +} + +void CListControlHeaderImpl::ReloadData() { + __super::ReloadData(); + if ( this->HaveAutoWidthContentColumns( ) ) { + this->ColumnWidthFix(); + } +} diff --git a/libPPUI/CListControlHeaderImpl.h b/libPPUI/CListControlHeaderImpl.h new file mode 100644 index 0000000..3938a89 --- /dev/null +++ b/libPPUI/CListControlHeaderImpl.h @@ -0,0 +1,224 @@ +#pragma once + +class CListCell; + +class CListControlHeaderImpl : public CListControlFontOps { +private: + typedef CListControlFontOps TParent; +public: + CListControlHeaderImpl() {} + + BEGIN_MSG_MAP_EX(CListControlHeaderImpl) + MSG_WM_CTLCOLORSTATIC(OnCtlColorStatic); + MESSAGE_HANDLER(WM_KEYDOWN,OnKeyDown); + MESSAGE_HANDLER(WM_SYSKEYDOWN,OnKeyDown); + MESSAGE_HANDLER_EX(WM_SIZE,OnSizePassThru); + NOTIFY_CODE_HANDLER(HDN_ITEMCHANGED,OnHeaderItemChanged); + NOTIFY_CODE_HANDLER(HDN_ENDDRAG,OnHeaderEndDrag); + NOTIFY_CODE_HANDLER(HDN_ITEMCLICK,OnHeaderItemClick); + NOTIFY_CODE_HANDLER(HDN_DIVIDERDBLCLICK,OnDividerDoubleClick); + MSG_WM_SETCURSOR(OnSetCursor); + MSG_WM_MOUSEMOVE(OnMouseMove) + MSG_WM_DESTROY(OnDestroy) + MSG_WM_ENABLE(OnEnable) + CHAIN_MSG_MAP(TParent) + END_MSG_MAP() + + typedef uint32_t cellState_t; + typedef CListCell * cellType_t; + + int GetHeaderItemWidth( int which ); + void InitializeHeaderCtrl(DWORD flags = HDS_FULLDRAG); + void InitializeHeaderCtrlSortable() {InitializeHeaderCtrl(HDS_FULLDRAG | HDS_BUTTONS);} + CHeaderCtrl GetHeaderCtrl() const {return m_header;} + void SetSortIndicator( size_t whichColumn, bool isUp ); + void ClearSortIndicator(); + + bool IsHeaderEnabled() const {return m_header.m_hWnd != NULL;} + void ResetColumns(bool update = true); + + enum { + columnWidthMax = (uint32_t)INT32_MAX, + columnWidthAuto = UINT32_MAX, + columnWidthAutoUseContent = UINT32_MAX-1, + }; + void AddColumn(const char * label, uint32_t widthPixels, DWORD fmtFlags = HDF_LEFT,bool update = true); + //! Extended AddColumn, specifies width in pixels @ 96DPI instead of screen-specific pixels + void AddColumnEx( const char * label, uint32_t widthPixelsAt96DPI, DWORD fmtFlags = HDF_LEFT, bool update = true ); + //! Extended AddColumn, specifies width in Dialog Length Units (DLU), assumes parent of this list to be a dialog window. + void AddColumnDLU( const char * label, uint32_t widthDLU, DWORD fmtFlags = HDF_LEFT, bool update = true ); + //! Extended AddColumn, specifies width as floating-point value of pixels at 96DPI. \n + //! For DPI-safe storage of user's column widths. + void AddColumnF( const char * label, float widthF, DWORD fmtFlags = HDF_LEFT, bool update = true ); + void AddColumnAutoWidth( const char * label, DWORD fmtFlags = HDF_LEFT, bool bUpdate = true) { AddColumn(label, columnWidthAuto, fmtFlags, bUpdate); } + bool DeleteColumn(size_t index, bool updateView = true); + void DeleteColumns( pfc::bit_array const & mask, bool updateView = true); + void ResizeColumn(t_size index, t_uint32 widthPixels, bool updateView = true); + void SetColumn( size_t which, const char * title, DWORD fmtFlags = HDF_LEFT, bool updateView = true); + void GetColumnText(size_t which, pfc::string_base & out) const; + + uint32_t GetOptimalColumnWidth( size_t index ) const; + uint32_t GetOptimalColumnWidthFixed( const char * fixedText) const; + uint32_t GetColumnsBlankWidth( size_t colExclude = SIZE_MAX ) const; + void SizeColumnToContent( size_t which, uint32_t minWidth ); + void SizeColumnToContentFillBlank( size_t which ); + + //! If creating a custom headerless multi column scheme, override these to manipulate your columns + virtual size_t GetColumnCount() const; + virtual uint32_t GetSubItemWidth(size_t subItem) const; + //! Returns column width as a floating-point value of pixels at 96DPI. \n + //! For DPI-safe storage of user's column widths. + float GetColumnWidthF( size_t subItem ) const; + //! Indicate how many columns a specific row/column cell spans\n + //! This makes sense only if the columns can't be user-reordered + virtual size_t GetSubItemSpan(size_t row, size_t column) const; + + t_size GetSubItemOrder(t_size subItem) const; + int GetItemWidth() const override; +protected: + CRect GetClientRectHook() const override; + void RenderBackground(CDCHandle dc, CRect const& rc) override; + + struct GetOptimalWidth_Cache { + //! For temporary use. + pfc::string8_fastalloc m_stringTemp, m_stringTempUnfuckAmpersands; + //! For temporary use. + pfc::stringcvt::string_wide_from_utf8_t m_convertTemp; + //! Our DC for measuring text. Correct font pre-selected. + CDCHandle m_dc; + + t_uint32 GetStringTempWidth(); + }; + + void UpdateHeaderLayout(); + void OnViewOriginChange(CPoint p_delta); + void SetHeaderFont(HFONT font); + void RenderItemText(t_size item,const CRect & itemRect,const CRect & updateRect,CDCHandle dc, bool allowColors); + void RenderGroupHeaderText(int id,const CRect & headerRect,const CRect & updateRect,CDCHandle dc); + + //! Converts an item/subitem rect to a rect in which the text should be rendered, removing spacing to the left/right of the text. + virtual CRect GetItemTextRectHook(size_t item, size_t subItem, CRect const & itemRect) const; + CRect GetItemTextRect(CRect const & itemRect) const; + //! Override for custom spacing to the left/right of the text in each column. + virtual t_uint32 GetColumnSpacing() const {return MulDiv(4,m_dpi.cx,96);} + //! Override for column-header-click sorting. + virtual void OnColumnHeaderClick(t_size index) {} + //! Override to supply item labels. + virtual bool GetSubItemText(t_size item, t_size subItem, pfc::string_base & out) const {return false;} + //! Override if you support groups. + virtual bool GetGroupHeaderText(int id, pfc::string_base & out) const {return false;} + //! Override optionally. + virtual void RenderSubItemText(t_size item, t_size subItem,const CRect & subItemRect,const CRect & updateRect,CDCHandle dc, bool allowColors); + + virtual void OnColumnsChanged(); + + virtual t_uint32 GetOptimalSubItemWidth(t_size item, t_size subItem, GetOptimalWidth_Cache & cache) const; + + virtual t_uint32 GetOptimalGroupHeaderWidth(int which) const; + + + bool GetItemAtPointAbsEx( CPoint pt, size_t & outItem, size_t & outSubItem ) const; + cellType_t GetCellTypeAtPointAbs( CPoint pt ) const; + virtual cellType_t GetCellType( size_t item, size_t subItem ) const; + virtual bool AllowTypeFindInCell( size_t item, size_t subItem ) const; + virtual bool GetCellTypeSupported() const { return false; } // optimization hint, some expensive checks can be suppressed if cell types are not used for this view + virtual bool GetCellCheckState( size_t item, size_t subItem ) const { return false; } + virtual void SetCellCheckState(size_t item, size_t subItem, bool value); + virtual bool ToggleSelectedItemsHook(const pfc::bit_array & mask); + + void RenderSubItemTextInternal(size_t subItem, const CRect & subItemRect, CDCHandle dc, const char * text, bool allowColors); + + t_uint32 GetOptimalColumnWidth(t_size which, GetOptimalWidth_Cache & cache) const; + t_uint32 GetOptimalSubItemWidthSimple(t_size item, t_size subItem) const; + + void AutoColumnWidths(const pfc::bit_array & mask,bool expandLast = false); + void AutoColumnWidths() {AutoColumnWidths(pfc::bit_array_true());} + void AutoColumnWidth(t_size which) {AutoColumnWidths(pfc::bit_array_one(which));} + + virtual bool OnColumnHeaderDrag(t_size index, t_size newOrder); + + void OnItemClicked(t_size item, CPoint pt) override; + virtual void OnSubItemClicked(t_size item, t_size subItem,CPoint pt); + bool OnClickedSpecialHitTest(CPoint pt) override; + bool OnClickedSpecial(DWORD status, CPoint pt) override; + static bool CellTypeUsesSpecialHitTests( cellType_t ct ); + + + CRect GetSubItemRectAbs(t_size item,t_size subItem) const; + CRect GetSubItemRect(t_size item,t_size subItem) const; + + t_size SubItemFromPointAbs(CPoint pt) const; + + static bool CellTypeReactsToMouseOver( cellType_t ct ); + virtual CRect CellHotRect( size_t item, size_t subItem, cellType_t ct, CRect rcCell ); + CRect CellHotRect( size_t item, size_t subItem, cellType_t ct ); + virtual double CellTextScale(size_t item, size_t subItem) { return 1; } + virtual bool IsSubItemGrayed(size_t item, size_t subItem) { return !this->IsWindowEnabled(); } + + // HDF_* constants for this column, override when not using list header control. Used to control text alignment. + virtual DWORD GetColumnFormat(t_size which) const; + void SetColumnFormat(t_size which,DWORD format); + void SetColumnSort(t_size which, bool isUp); + + std::vector GetColumnOrderArray() const; + + bool AllowScrollbar(bool vertical) const override; + + void RenderItemBackground(CDCHandle p_dc,const CRect & p_itemRect,size_t item, uint32_t bkColor) override; + + void ColumnWidthFix(); // Call RecalcItemWidth() / ProcessAutoWidth() + + void ReloadData() override; +private: + void OnEnable(BOOL) { Invalidate(); } + HBRUSH OnCtlColorStatic(CDCHandle dc, CStatic wndStatic); + void ProcessColumnsChange() { OnColumnsChanged();} + LRESULT OnSizePassThru(UINT,WPARAM,LPARAM); + LRESULT OnHeaderItemClick(int,LPNMHDR,BOOL&); + LRESULT OnDividerDoubleClick(int,LPNMHDR,BOOL&); + LRESULT OnHeaderItemChanged(int,LPNMHDR,BOOL&); + LRESULT OnHeaderEndDrag(int,LPNMHDR,BOOL&); + LRESULT OnKeyDown(UINT,WPARAM,LPARAM,BOOL&); + void OnDestroy(); + BOOL OnSetCursor(CWindow wnd, UINT nHitTest, UINT message); + void OnMouseMove(UINT nFlags, CPoint point); + + void RecalcItemWidth(); // FIXED width math + void ProcessAutoWidth(); // DYNAMIC width math + + bool m_ownColumnsChange = false; + + int m_itemWidth = 0; + int m_clientWidth = 0; + CHeaderCtrl m_header; + CStatic m_headerLine; + + bool HaveAutoWidthColumns() const; + bool HaveAutoWidthContentColumns() const; + + struct colRuntime_t { + bool m_autoWidth = false; + bool m_autoWidthContent = false; + int m_widthPixels = 0; + uint32_t m_userWidth = 0; + std::string m_text; + + bool autoWidth() const { return m_userWidth > columnWidthMax; } + bool autoWidthContent() const { return m_userWidth == columnWidthAutoUseContent; } + bool autoWidthPlain() const { return m_userWidth == columnWidthAuto; } + }; + + std::vector m_colRuntime; + + + //for group headers + GdiplusScope m_gdiPlusScope; + + void SetPressedItem(size_t row, size_t column); + void ClearPressedItem() {SetPressedItem(SIZE_MAX, SIZE_MAX);} + void SetHotItem( size_t row, size_t column ); + void ClearHotItem() { SetHotItem(SIZE_MAX, SIZE_MAX); } + + size_t m_pressedItem = SIZE_MAX, m_pressedSubItem = SIZE_MAX; + size_t m_hotItem = SIZE_MAX, m_hotSubItem = SIZE_MAX; +}; diff --git a/libPPUI/CListControlOwnerData.h b/libPPUI/CListControlOwnerData.h new file mode 100644 index 0000000..ad3ded9 --- /dev/null +++ b/libPPUI/CListControlOwnerData.h @@ -0,0 +1,206 @@ +#pragma once + +// ================================================================================ +// CListControlOwnerData +// ================================================================================ +// Forwards all list data retrieval and manipulation calls to a host object. +// Not intended for subclassing, just implement IListControlOwnerDataSource methods +// in your class. +// ================================================================================ + +#include "CListControlComplete.h" + +class CListControlOwnerData; + +class IListControlOwnerDataSource { +public: + typedef const CListControlOwnerData * ctx_t; + + + virtual size_t listGetItemCount( ctx_t ) = 0; + virtual pfc::string8 listGetSubItemText( ctx_t, size_t item, size_t subItem ) = 0; + virtual bool listCanReorderItems( ctx_t ) { return false; } + virtual bool listReorderItems( ctx_t, const size_t*, size_t) {return false;} + virtual bool listRemoveItems( ctx_t, pfc::bit_array const & ) {return false;} + virtual void listItemAction(ctx_t, size_t) {} + virtual void listSubItemClicked( ctx_t, size_t, size_t ) {} + virtual bool listCanSelectItem( ctx_t, size_t ) { return true; } + virtual pfc::string8 listGetEditField(ctx_t ctx, size_t item, size_t subItem, size_t & lineCount) { + lineCount = 1; return listGetSubItemText( ctx, item, subItem); + } + virtual void listSetEditField(ctx_t ctx, size_t item, size_t subItem, const char * val) {} + virtual uint32_t listGetEditFlags(ctx_t ctx, size_t item, size_t subItem) {return 0;} + typedef InPlaceEdit::CTableEditHelperV2::autoComplete_t autoComplete_t; + typedef InPlaceEdit::CTableEditHelperV2::combo_t combo_t; + virtual autoComplete_t listGetAutoComplete(ctx_t, size_t item, size_t subItem) {return autoComplete_t();} + virtual combo_t listGetCombo(ctx_t, size_t item, size_t subItem) { return combo_t(); } + + virtual bool listIsColumnEditable( ctx_t, size_t ) { return false; } + virtual bool listKeyDown(ctx_t, UINT nChar, UINT nRepCnt, UINT nFlags) { return false; } + virtual bool listKeyUp(ctx_t, UINT nChar, UINT nRepCnt, UINT nFlags) { return false; } + + // Allow type-find in this view? + // Called prior to a typefind pass attempt, you can either deny entirely, or prepare any necessary data and allow it. + virtual bool listAllowTypeFind( ctx_t ) { return true; } + // Allow type-find in a specific item/column? + virtual bool listAllowTypeFindHere( ctx_t, size_t item, size_t subItem ) { return true ;} + + virtual void listColumnHeaderClick(ctx_t, size_t subItem) {} + + virtual void listBeforeDrawItemText( ctx_t, size_t item, CDCHandle dc ) {} + virtual bool listIsSubItemGrayed(ctx_t, size_t item, size_t subItem) { return false; } + + virtual void listSelChanged(ctx_t) {} + virtual void listFocusChanged(ctx_t) {} + + virtual void listColumnsChanged(ctx_t) {} + + virtual bool listEditCanAdvanceHere(ctx_t, size_t item, size_t subItem, uint32_t whatHappened) {(void) item; (void) subItem, (void) whatHappened; return true;} +}; + +class IListControlOwnerDataCells { +public: + typedef const CListControlOwnerData * cellsCtx_t; + virtual CListControl::cellType_t listCellType( cellsCtx_t, size_t item, size_t subItem ) = 0; + virtual size_t listCellSpan( cellsCtx_t, size_t item, size_t subItem ) {return 1;} + virtual bool listCellCheckState( cellsCtx_t, size_t item, size_t subItem ) {return false; } + virtual void listCellSetCheckState( cellsCtx_t, size_t item, size_t subItem, bool state ) {} +}; + +class CListControlOwnerData : public CListControlComplete { + IListControlOwnerDataSource * const m_host; +public: + + CListControlOwnerData( IListControlOwnerDataSource * h) : m_host(h) {} + + BEGIN_MSG_MAP_EX(CListControlOwnerData) + MSG_WM_KEYDOWN(OnKeyDown) + MSG_WM_KEYUP(OnKeyUp) + MSG_WM_SYSKEYDOWN(OnKeyDown) + MSG_WM_SYSKEYUP(OnKeyUp) + CHAIN_MSG_MAP(CListControlComplete) + END_MSG_MAP() + + using CListControl_EditImpl::TableEdit_Abort; + using CListControl_EditImpl::TableEdit_Start; + using CListControl_EditImpl::TableEdit_IsActive; + + bool CanSelectItem( size_t idx ) const override { + return m_host->listCanSelectItem( this, idx ); + } + size_t GetItemCount() const override { + return m_host->listGetItemCount( this ); + } + bool GetSubItemText(size_t item, size_t subItem, pfc::string_base & out) const override { + out = m_host->listGetSubItemText( this, item, subItem ); + return true; + } + void OnSubItemClicked( size_t item, size_t subItem, CPoint pt ) override { + __super::OnSubItemClicked(item, subItem, pt); // needed to toggle checkboxes etc + m_host->listSubItemClicked( this, item, subItem ); + } + + uint32_t QueryDragDropTypes() const override { + return (m_host->listCanReorderItems(this)) ? dragDrop_reorder : 0; + } + + void RequestReorder( const size_t * order, size_t count) override { + if ( ! m_host->listReorderItems( this, order, count )) return; + this->OnItemsReordered( order, count ); + } + void RequestRemoveSelection() override { + auto mask = this->GetSelectionMask(); + size_t oldCount = GetItemCount(); + if ( ! m_host->listRemoveItems( this, mask ) ) return; + this->OnItemsRemoved( mask, oldCount ); + } + void ExecuteDefaultAction( size_t idx ) override { + m_host->listItemAction( this, idx ); + } + size_t EvalTypeFind() override { + if (! m_host->listAllowTypeFind(this) ) return SIZE_MAX; + return __super::EvalTypeFind(); + } + bool AllowTypeFindInCell( size_t item, size_t subItem ) const { + return __super::AllowTypeFindInCell( item, subItem ) && m_host->listAllowTypeFindHere( this, item, subItem ); + } + +protected: + void OnFocusChanged(size_t newFocus) override { + __super::OnFocusChanged(newFocus); + m_host->listFocusChanged(this); + } + void OnSelectionChanged(pfc::bit_array const & affected, pfc::bit_array const & status) { + __super::OnSelectionChanged(affected, status); + m_host->listSelChanged(this); + } + + void RenderItemText(t_size p_item,const CRect & p_itemRect,const CRect & p_updateRect,CDCHandle p_dc, bool allowColors) { + m_host->listBeforeDrawItemText(this, p_item, p_dc ); + __super::RenderItemText(p_item, p_itemRect, p_updateRect, p_dc, allowColors); + } + void TableEdit_SetField(t_size item, t_size subItem, const char * value) override { + m_host->listSetEditField(this, item, subItem, value); + } + void TableEdit_GetField(t_size item, t_size subItem, pfc::string_base & out, t_size & lineCount) override { + lineCount = 1; + out = m_host->listGetEditField(this, item, subItem, lineCount); + } + + t_uint32 TableEdit_GetEditFlags(t_size item, t_size subItem) const override { + return m_host->listGetEditFlags( this, item, subItem ); + } + + combo_t TableEdit_GetCombo(size_t item, size_t subItem) override { + return m_host->listGetCombo(this, item, subItem); + } + autoComplete_t TableEdit_GetAutoCompleteEx(t_size item, t_size subItem) override { + return m_host->listGetAutoComplete( this, item, subItem ); + } + bool TableEdit_IsColumnEditable(t_size subItem) const override { + return m_host->listIsColumnEditable( this, subItem ); + } + void OnColumnHeaderClick(t_size index) override { + m_host->listColumnHeaderClick(this, index); + } + void OnColumnsChanged() override { + __super::OnColumnsChanged(); + m_host->listColumnsChanged(this); + } + bool IsSubItemGrayed(size_t item, size_t subItem) override { + return __super::IsSubItemGrayed(item, subItem) || m_host->listIsSubItemGrayed(this, item, subItem); + } +private: + bool TableEdit_CanAdvanceHere( size_t item, size_t subItem, uint32_t whatHappened ) const override { + return m_host->listEditCanAdvanceHere(this, item, subItem, whatHappened); + } + void OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags) { + bool handled = m_host->listKeyDown(this, nChar, nRepCnt, nFlags); + SetMsgHandled( !! handled ); + } + void OnKeyUp(UINT nChar, UINT nRepCnt, UINT nFlags) { + bool handled = m_host->listKeyUp(this, nChar, nRepCnt, nFlags); + SetMsgHandled( !! handled ); + } +}; + +class CListControlOwnerDataCells : public CListControlOwnerData { + IListControlOwnerDataCells * const m_cells; +public: + CListControlOwnerDataCells( IListControlOwnerDataSource * source, IListControlOwnerDataCells * cells ) : CListControlOwnerData(source), m_cells(cells) {} + + bool GetCellTypeSupported() const override {return true; } + bool GetCellCheckState( size_t item, size_t subItem ) const override { + return m_cells->listCellCheckState( this, item, subItem ); + } + void SetCellCheckState( size_t item, size_t subItem, bool value ) override { + m_cells->listCellSetCheckState( this, item, subItem, value ); + __super::SetCellCheckState(item, subItem, value); + } + cellType_t GetCellType( size_t item, size_t subItem ) const override { + return m_cells->listCellType( this, item, subItem ); + } + size_t GetSubItemSpan(size_t row, size_t column) const override { + return m_cells->listCellSpan( this, row, column ); + } +}; \ No newline at end of file diff --git a/libPPUI/CListControlSimple.h b/libPPUI/CListControlSimple.h new file mode 100644 index 0000000..4d42336 --- /dev/null +++ b/libPPUI/CListControlSimple.h @@ -0,0 +1,137 @@ +#pragma once + +// ================================================================================ +// CListControlSimple +// Simplified CListControl interface; a ready-to-use class that can be instantiated +// without subclassing. +// Use when you don't need advanced features such as buttons or editing. +// Maintains its own data. +// ================================================================================ + +#include "CListControlComplete.h" + +#include +#include +#include +#include + + +class CListControlSimple : public CListControlReadOnly { +public: + // Events + std::function onReordered; // if not set, list reordering is disabled + std::function onRemoved; // if not set, list item removal is disabled + std::function onItemAction; + std::function onSelChange; + + size_t GetItemCount() const override { + return m_lines.size(); + } + void SetItemCount( size_t count ) { + m_lines.resize( count ); + ReloadData(); + } + void SetItemText(size_t item, size_t subItem, const char * text, bool bRedraw = true) { + if ( item < m_lines.size() ) { + m_lines[item].text[subItem] = text; + if ( bRedraw ) ReloadItem( item ); + } + } + bool GetSubItemText(size_t item, size_t subItem, pfc::string_base & out) const override { + if ( item < m_lines.size() ) { + auto & l = m_lines[item].text; + auto iter = l.find( subItem ); + if ( iter != l.end() ) { + out = iter->second.c_str(); + return true; + } + } + return false; + } + + uint32_t QueryDragDropTypes() const override { + return (onReordered != nullptr) ? dragDrop_reorder : 0; + } + + void RequestReorder( const size_t * order, size_t count) override { + if ( onReordered == nullptr ) return; + pfc::reorder_t( m_lines, order, count ); + this->OnItemsReordered( order, count ); + onReordered(); + } + void RequestRemoveSelection() override { + if (onRemoved == nullptr) return; + auto mask = this->GetSelectionMask(); + size_t oldCount = m_lines.size(); + pfc::remove_mask_t( m_lines, mask ); + this->OnItemsRemoved( mask, oldCount ); + onRemoved(); + } + void ExecuteDefaultAction( size_t idx ) override { + if (onItemAction != nullptr) onItemAction(idx); + } + + void SetItemUserData( size_t item, size_t user ) { + if ( item < m_lines.size() ) { + m_lines[item].user = user; + } + } + size_t GetItemUserData( size_t item ) const { + size_t ret = 0; + if ( item < m_lines.size() ) { + ret = m_lines[item].user; + } + return ret; + } + void RemoveAllItems() { + RemoveItems(pfc::bit_array_true()); + } + void RemoveItems( pfc::bit_array const & mask ) { + const auto oldCount = m_lines.size(); + pfc::remove_mask_t( m_lines, mask ); + this->OnItemsRemoved( mask, oldCount ); + } + void RemoveItem( size_t which ) { + RemoveItems( pfc::bit_array_one( which ) ); + } + + size_t InsertItem( size_t insertAt, const char * textCol0 = nullptr ) { + if ( insertAt > m_lines.size() ) { + insertAt = m_lines.size(); + } + { + line_t data; + if ( textCol0 != nullptr ) data.text[0] = textCol0; + m_lines.insert( m_lines.begin() + insertAt, std::move(data) ); + } + this->OnItemsInserted( insertAt, 1, false ); + return insertAt; + } + size_t AddItem( const char * textCol0 = nullptr ) { + return InsertItem( SIZE_MAX, textCol0 ); + } + size_t InsertItems( size_t insertAt, size_t count ) { + if ( insertAt > m_lines.size() ) { + insertAt = m_lines.size(); + } + + { + line_t val; + m_lines.insert( m_lines.begin(), count, val ); + } + + this->OnItemsInserted( insertAt, count, false ); + return insertAt; + } +protected: + void OnSelectionChanged(pfc::bit_array const & affected, pfc::bit_array const & status) { + __super::OnSelectionChanged(affected, status); + if ( onSelChange ) onSelChange(); + } +private: + struct line_t { + std::map text; + size_t user = 0; + }; + std::vector m_lines; +}; diff --git a/libPPUI/CListControlTruncationTooltipImpl.cpp b/libPPUI/CListControlTruncationTooltipImpl.cpp new file mode 100644 index 0000000..62727a4 --- /dev/null +++ b/libPPUI/CListControlTruncationTooltipImpl.cpp @@ -0,0 +1,209 @@ +#include "stdafx.h" +#include "CListControl.h" +#include "PaintUtils.h" + +LRESULT CListControlTruncationTooltipImpl::OnTTShow(int,LPNMHDR,BOOL&) { + SetTimer(KTooltipTimer,KTooltipTimerDelay); + return 0; +} +LRESULT CListControlTruncationTooltipImpl::OnTTPop(int,LPNMHDR,BOOL&) { + KillTimer(KTooltipTimer); + return 0; +} +LRESULT CListControlTruncationTooltipImpl::OnTTGetDispInfo(int,LPNMHDR p_hdr,BOOL&) { + LPNMTTDISPINFO info = (LPNMTTDISPINFO)p_hdr; + + info->lpszText = const_cast(this->m_tooltipText.get_ptr()); + info->hinst = 0; + info->uFlags = 0; + + return 0; +} + +LRESULT CListControlTruncationTooltipImpl::OnDestroyPassThru(UINT,WPARAM,LPARAM,BOOL& bHandled) { + if (m_tooltip.m_hWnd != NULL) m_tooltip.DestroyWindow(); + KillTimer(KTooltipTimer); + bHandled = FALSE; return 0; +} + +CListControlTruncationTooltipImpl::CListControlTruncationTooltipImpl() + : m_toolinfo() + , m_tooltipRect(0,0,0,0) +{ +} + + + +void CListControlTruncationTooltipImpl::TooltipRemove() { + m_tooltipRect = CRect(0,0,0,0); + if (m_tooltip.m_hWnd != NULL) { + m_tooltip.TrackActivate(&m_toolinfo,FALSE); + } +} + +void CListControlTruncationTooltipImpl::TooltipRemoveCheck() { + CPoint pt = GetCursorPos(); + if (ScreenToClient(&pt)) { + TooltipRemoveCheck( MAKELPARAM( pt.x, pt.y ) ); + } +} +void CListControlTruncationTooltipImpl::TooltipRemoveCheck(LPARAM pos) { + if (!m_tooltipRect.IsRectEmpty()) { + CPoint pt(pos); + if (!GetClientRectHook().PtInRect(pt)) { + TooltipRemove(); + } else { + ClientToScreen(&pt); + if (!m_tooltipRect.PtInRect(pt)) { + TooltipRemove(); + } + } + } +} + +LRESULT CListControlTruncationTooltipImpl::OnTimer(UINT,WPARAM wp,LPARAM,BOOL& bHandled) { + switch(wp) { + case KTooltipTimer: + TooltipRemoveCheck(); + return 0; + default: + bHandled = FALSE; + return 0; + } +} + +LRESULT CListControlTruncationTooltipImpl::OnMouseMovePassThru(UINT,WPARAM,LPARAM lp,BOOL& bHandled) { + TooltipRemoveCheck(lp); + { + TRACKMOUSEEVENT ev = {sizeof(ev)}; + ev.dwFlags = TME_HOVER; + ev.hwndTrack = *this; + ev.dwHoverTime = HOVER_DEFAULT; + TrackMouseEvent(&ev); + } + bHandled = FALSE; + return 0; +} + + +bool CListControlTruncationTooltipImpl::IsRectPartiallyObscuredAbs(CRect const & r) const { + CRect cl = this->GetClientRectHook(); cl.OffsetRect( this->GetViewOffset() ); + return r.right > cl.right || r.top < cl.top || r.bottom > cl.bottom; +} + +bool CListControlTruncationTooltipImpl::IsRectFullyVisibleAbs(CRect const & r) { + CRect cl = this->GetClientRectHook(); cl.OffsetRect( this->GetViewOffset() ); + return r.left >= cl.left && r.right <= cl.right && r.top >= cl.top && r.bottom <= cl.bottom; +} + +bool CListControlTruncationTooltipImpl::GetTooltipData(CPoint pt, pfc::string_base & outText, CRect & outRC, CFontHandle & outFont) const { + t_size item; int group; + if (ItemFromPointAbs(pt, item)) { + const CRect itemRectAbs = this->GetItemRectAbs(item); + /*if (this->IsHeaderEnabled()) */{ + t_uint32 cbase = 0; + auto orderArray = this->GetColumnOrderArray(); + for (t_size _cwalk = 0; _cwalk < orderArray.size(); ++_cwalk) { + const t_size cwalk = orderArray[_cwalk]; + //const TColumnRuntime & col = m_columns[cwalk]; + + const t_uint32 width = GetSubItemWidth(cwalk); + if ((t_uint32)pt.x < cbase + width) { + t_uint32 estWidth = GetOptimalSubItemWidthSimple(item, cwalk); + CRect rc = itemRectAbs; rc.left = cbase; rc.right = cbase + estWidth; + if (estWidth > width || (IsRectPartiallyObscuredAbs(rc) && rc.PtInRect(pt))) { + pfc::string_formatter label, ccTemp; + if (GetSubItemText(item, cwalk, label)) { + PaintUtils::TextOutColors_StripCodes(ccTemp, label); + outFont = GetFont(); outRC = rc; outText = ccTemp; + return true; + } + } + break; + } + cbase += width; + } + } + } else if (GroupHeaderFromPointAbs(pt, group)) { + CRect rc; + if (GetGroupHeaderRectAbs(group, rc) && rc.PtInRect(pt)) { + const t_uint32 estWidth = GetOptimalGroupHeaderWidth(group); + CRect rcText = rc; rcText.right = rcText.left + estWidth; + if (estWidth > (t_uint32)rc.Width() || (IsRectPartiallyObscuredAbs(rcText) && rcText.PtInRect(pt))) { + pfc::string_formatter label; + if (GetGroupHeaderText(group, label)) { + outFont = GetGroupHeaderFont(); outRC = rc; outText = label; + return true; + } + } + } + } + return false; +} +LRESULT CListControlTruncationTooltipImpl::OnHover(UINT,WPARAM wp,LPARAM lp,BOOL&) { + if (!m_tooltipRect.IsRectEmpty()) { + return 0; + } + if (wp & (MK_LBUTTON | MK_RBUTTON | MK_MBUTTON)) return 0; + const CPoint viewOffset = GetViewOffset(); + CPoint pt ( lp ); pt += viewOffset; + + CFontHandle font; + CRect rc; + pfc::string8 text; + if ( this->GetTooltipData(pt, text, rc, font) ) { + this->m_tooltipFont = font; + // Gets stuck if the text is very long! + if (text.length() < 4096) { + TooltipActivateAbs(text, rc); + } + } + return 0; +} + +void CListControlTruncationTooltipImpl::TooltipActivateAbs(const char * label, const CRect & rect) { + CRect temp(rect); + temp.OffsetRect( - GetViewOffset() ); + ClientToScreen(temp); + TooltipActivate(label,temp); +} +void CListControlTruncationTooltipImpl::TooltipActivate(const char * label, const CRect & rect) { + if (rect.IsRectEmpty()) return; + if (m_tooltip.m_hWnd == NULL) { + try { + InitTooltip(); + } catch(std::exception const & e) { + (void) e; + // console::complain("Tooltip initialization failure", e); + return; + } + } + + m_tooltipText.convert( EscapeTooltipText( label ) ); + + m_tooltipRect = rect; + + TooltipUpdateFont(); + m_tooltip.TrackPosition(rect.left,rect.top); + m_tooltip.TrackActivate(&m_toolinfo,TRUE); +} + +void CListControlTruncationTooltipImpl::TooltipUpdateFont() { + if (m_tooltip.m_hWnd != NULL) { + if (m_tooltipFont) { + m_tooltip.SetFont(m_tooltipFont); + } + } +} + +void CListControlTruncationTooltipImpl::InitTooltip() { + m_tooltipRect = CRect(0,0,0,0); + WIN32_OP( m_tooltip.Create(NULL,NULL,NULL,WS_POPUP,WS_EX_TRANSPARENT) ); + m_toolinfo.cbSize = sizeof(m_toolinfo); + m_toolinfo.uFlags = TTF_TRACK | TTF_IDISHWND | TTF_ABSOLUTE | TTF_TRANSPARENT; + m_toolinfo.hwnd = *this; + m_toolinfo.uId = 0; + m_toolinfo.lpszText = LPSTR_TEXTCALLBACK; + m_toolinfo.hinst = GetThisModuleHandle(); + WIN32_OP_D( m_tooltip.AddTool(&m_toolinfo) ); +} diff --git a/libPPUI/CListControlTruncationTooltipImpl.h b/libPPUI/CListControlTruncationTooltipImpl.h new file mode 100644 index 0000000..b55d2f4 --- /dev/null +++ b/libPPUI/CListControlTruncationTooltipImpl.h @@ -0,0 +1,51 @@ +#pragma once + +class CListControlTruncationTooltipImpl : public CListControlHeaderImpl { +private: + typedef CListControlHeaderImpl TParent; +public: + CListControlTruncationTooltipImpl(); + + BEGIN_MSG_MAP_EX(CListControlTruncationTooltipImpl) + MESSAGE_HANDLER(WM_MOUSEHOVER,OnHover); + MESSAGE_HANDLER(WM_MOUSEMOVE,OnMouseMovePassThru); + MESSAGE_HANDLER(WM_TIMER,OnTimer); + MESSAGE_HANDLER(WM_DESTROY,OnDestroyPassThru); + CHAIN_MSG_MAP(TParent) + NOTIFY_CODE_HANDLER(TTN_GETDISPINFO,OnTTGetDispInfo); + NOTIFY_CODE_HANDLER(TTN_POP,OnTTPop); + NOTIFY_CODE_HANDLER(TTN_SHOW,OnTTShow); + END_MSG_MAP() + + void OnViewOriginChange(CPoint p_delta) {TParent::OnViewOriginChange(p_delta);TooltipRemove();} + void TooltipRemove(); +protected: + virtual bool GetTooltipData( CPoint ptAbs, pfc::string_base & text, CRect & rc, CFontHandle & font) const; +private: + enum { + KTooltipTimer = 0x51dbee9e, + KTooltipTimerDelay = 50, + }; + LRESULT OnHover(UINT,WPARAM,LPARAM,BOOL&); + LRESULT OnMouseMovePassThru(UINT,WPARAM,LPARAM,BOOL&); + LRESULT OnTimer(UINT,WPARAM,LPARAM,BOOL&); + LRESULT OnTTGetDispInfo(int,LPNMHDR,BOOL&); + LRESULT OnTTShow(int,LPNMHDR,BOOL&); + LRESULT OnTTPop(int,LPNMHDR,BOOL&); + LRESULT OnDestroyPassThru(UINT,WPARAM,LPARAM,BOOL&); + + void InitTooltip(); + void TooltipActivateAbs(const char * label, const CRect & rect); + void TooltipActivate(const char * label, const CRect & rect); + void TooltipRemoveCheck(LPARAM pos); + void TooltipRemoveCheck(); + void TooltipUpdateFont(); + void OnSetFont(bool) {TooltipUpdateFont();} + bool IsRectFullyVisibleAbs(CRect const & r); + bool IsRectPartiallyObscuredAbs(CRect const & r) const; + CRect m_tooltipRect; + CToolTipCtrl m_tooltip; + TOOLINFO m_toolinfo; + pfc::stringcvt::string_os_from_utf8 m_tooltipText; + CFontHandle m_tooltipFont; +}; diff --git a/libPPUI/CListControlUserOptions.h b/libPPUI/CListControlUserOptions.h new file mode 100644 index 0000000..1e07db5 --- /dev/null +++ b/libPPUI/CListControlUserOptions.h @@ -0,0 +1,9 @@ +#pragma once + +class CListControlUserOptions { +public: + CListControlUserOptions() { instance = this; } + virtual bool useSmoothScroll() = 0; + + static CListControlUserOptions * instance; +}; \ No newline at end of file diff --git a/libPPUI/CListControlWithSelection.cpp b/libPPUI/CListControlWithSelection.cpp new file mode 100644 index 0000000..3a2ec31 --- /dev/null +++ b/libPPUI/CListControlWithSelection.cpp @@ -0,0 +1,1587 @@ +#include "stdafx.h" +#include +#include +#include "ppresources.h" +#include "CListControlWithSelection.h" +#include "PaintUtils.h" +#include "IDataObjectUtils.h" +#include "SmartStrStr.h" +#include "CListControl-Cells.h" + +namespace { + class bit_array_selection_CListControl : public pfc::bit_array { + public: + bit_array_selection_CListControl(CListControlWithSelectionBase const & list) : m_list(list) {} + bool get(t_size n) const {return m_list.IsItemSelected(n);} + private: + CListControlWithSelectionBase const & m_list; + }; +} + +bool CListControlWithSelectionBase::SelectAll() { + SetSelection(pfc::bit_array_true(), pfc::bit_array_true() ); + return true; +} +void CListControlWithSelectionBase::SelectNone() { + SetSelection(pfc::bit_array_true(), pfc::bit_array_false() ); +} + +void CListControlWithSelectionBase::SelectSingle(size_t which) { + this->SetFocusItem( which ); + SetSelection(pfc::bit_array_true(), pfc::bit_array_one( which ) ); +} + +LRESULT CListControlWithSelectionBase::OnFocus(UINT msg,WPARAM,LPARAM,BOOL& bHandled) { + UpdateItems(pfc::bit_array_or(bit_array_selection_CListControl(*this), pfc::bit_array_one(GetFocusItem()))); + bHandled = FALSE; + return 0; +} + +void CListControlWithSelectionBase::OnKeyDown_SetIndexDeltaPageHelper(int p_delta, int p_keys) { + const CRect rcClient = GetClientRectHook(); + int itemHeight = GetItemHeight(); + if (itemHeight > 0) { + int delta = rcClient.Height() / itemHeight; + if (delta < 1) delta = 1; + OnKeyDown_SetIndexDeltaHelper(delta * p_delta,p_keys); + } +} + +void CListControlWithSelectionBase::OnKeyDown_HomeEndHelper(bool isEnd, int p_keys) { + if (!isEnd) { + //SPECIAL FIX - ensure first group header visibility. + MoveViewOrigin(CPoint(GetViewOrigin().x,0)); + } + + const t_size itemCount = GetItemCount(); + if (itemCount == 0) return;//don't bother + + if ((p_keys & (MK_SHIFT|MK_CONTROL)) == (MK_SHIFT|MK_CONTROL)) { + RequestMoveSelection( isEnd ? (int)itemCount : -(int)itemCount ); + } else { + OnKeyDown_SetIndexHelper(isEnd ? (int)(itemCount-1) : 0,p_keys); + } +} +void CListControlWithSelectionBase::OnKeyDown_SetIndexHelper(int p_index, int p_keys) { + const t_size count = GetItemCount(); + if (count > 0) { + t_size idx = (t_size)pfc::clip_t(p_index,0,(int)(count - 1)); + const t_size oldFocus = GetFocusItem(); + if (p_keys & MK_CONTROL) { + SetFocusItem(idx); + } else if ((p_keys & MK_SHIFT) != 0 && this->AllowRangeSelect() ) { + t_size selStart = GetSelectionStart(); + if (selStart == pfc_infinite) selStart = oldFocus; + if (selStart == pfc_infinite) selStart = idx; + t_size selFirst, selCount; + selFirst = pfc::min_t(selStart,idx); + selCount = pfc::max_t(selStart,idx) + 1 - selFirst; + SetSelection(pfc::bit_array_true(), pfc::bit_array_range(selFirst,selCount)); + SetFocusItem(idx); + SetSelectionStart(selStart); + } else { + SetFocusItem(idx); + SetSelection(pfc::bit_array_true(), pfc::bit_array_one(idx)); + } + } +} + +void CListControlWithSelectionBase::SelectGroupHelper(int p_group,int p_keys) { + t_size base, count; + if (ResolveGroupRange(p_group,base,count)) { + if (p_keys & MK_CONTROL) { + SetGroupFocusByItem(base); + } /*else if (p_keys & MK_SHIFT) { + } */else { + SetGroupFocusByItem(base); + SetSelection(pfc::bit_array_true(), pfc::bit_array_range(base,count)); + } + } +} + +void CListControlWithSelectionBase::OnKeyDown_SetIndexDeltaLineHelper(int p_delta, int p_keys) { + if ((p_keys & (MK_SHIFT | MK_CONTROL)) == (MK_SHIFT|MK_CONTROL)) { + this->RequestMoveSelection(p_delta); + return; + } + const t_size total = GetItemCount(); + t_size current = GetFocusItem(); + const int focusGroup = this->GetGroupFocus(); + if (focusGroup >= 0) { + t_size dummy; + if (!ResolveGroupRange(focusGroup,current,dummy)) current = pfc_infinite; + } + + if (current == pfc_infinite) { + OnKeyDown_SetIndexDeltaHelper(p_delta,p_keys); + return; + } + + const int currentGroup = GetItemGroup(current); + if (GroupFocusActive()) { + if (p_delta < 0) { + int targetGroup = currentGroup - 1; + t_size base, count; + if (ResolveGroupRange(targetGroup, base, count)) { + OnKeyDown_SetIndexHelper((int)(base + count - 1), p_keys); + } + } else if (p_delta > 0) { + t_size base, count; + if (ResolveGroupRange(currentGroup,base,count)) { + OnKeyDown_SetIndexHelper((int) base, p_keys); + } + } + } else { + if ((p_keys & MK_SHIFT) != 0) { + OnKeyDown_SetIndexDeltaHelper(p_delta,p_keys); + } else if (p_delta < 0) { + if (currentGroup == 0 || (current > 0 && currentGroup == GetItemGroup(current - 1))) { + OnKeyDown_SetIndexDeltaHelper(p_delta,p_keys); + } else { + SelectGroupHelper(currentGroup, p_keys); + } + } else if (p_delta > 0) { + if (current + 1 >= total || currentGroup == GetItemGroup(current + 1)) { + OnKeyDown_SetIndexDeltaHelper(p_delta,p_keys); + } else { + SelectGroupHelper(GetItemGroup(current + 1), p_keys); + } + } + } +} + +LRESULT CListControlWithSelectionBase::OnLButtonDblClk(UINT,WPARAM p_wp,LPARAM p_lp,BOOL& bHandled) { + CPoint pt(p_lp); + if (OnClickedSpecialHitTest(pt)) { + return OnButtonDown(WM_LBUTTONDOWN, p_wp, p_lp, bHandled); + } + t_size item; int groupId; + if (ItemFromPoint(pt,item)) { + ExecuteDefaultAction(item); + return 0; + } else if (GroupHeaderFromPoint(pt,groupId)) { + t_size count; + if (ResolveGroupRange(groupId,item,count)) { + ExecuteDefaultActionGroup(item,count); + } + return 0; + } else if (ExecuteCanvasDefaultAction(pt)) { + return 0; + } else { + return OnButtonDown(WM_LBUTTONDOWN,p_wp,p_lp,bHandled); + } +} +void CListControlWithSelectionBase::ExecuteDefaultActionByFocus() { + const int groupId = this->GetGroupFocus(); + if (groupId >= 0) { + t_size item,count; + if (ResolveGroupRange(groupId,item,count)) { + ExecuteDefaultActionGroup(item,count); + } + } else { + t_size index = this->GetFocusItem(); + if (index != ~0) this->ExecuteDefaultAction(index); + } +} + +t_size CListControlWithSelectionBase::GetSingleSel() const { + t_size total = GetItemCount(); + t_size first = ~0; + for(t_size walk = 0; walk < total; ++walk) { + if (IsItemSelected(walk)) { + if (first == ~0) first = walk; + else return ~0; + } + } + return first; +} + +t_size CListControlWithSelectionBase::GetSelectedCount(pfc::bit_array const & mask,t_size max) const { + const t_size itemCount = this->GetItemCount(); + t_size found = 0; + for(t_size walk = mask.find_first(true,0,itemCount); walk < itemCount && found < max; walk = mask.find_next(true,walk,itemCount)) { + if (IsItemSelected(walk)) ++found; + } + return found; +} +LRESULT CListControlWithSelectionBase::OnButtonDown(UINT p_msg,WPARAM p_wp,LPARAM p_lp,BOOL&) { + pfc::vartoggle_t l_noEnsureVisible(m_noEnsureVisible,true); + if (m_selectDragMode) { + AbortSelectDragMode(); + return 0; + } + + CPoint pt(p_lp); + + if (OnClickedSpecial( (DWORD) p_wp, pt)) { + return 0; + } + + + int groupId; t_size item; + const bool isRightClick = (p_msg == WM_RBUTTONDOWN || p_msg == WM_RBUTTONDBLCLK); + const bool gotCtrl = (p_wp & MK_CONTROL) != 0 && !isRightClick; + const bool gotShift = (p_wp & MK_SHIFT) != 0 && !isRightClick; + + + + const bool bCanSelect = !OnClickedSpecialHitTest( pt ); + + const bool instaDrag = false; + const bool ddSupported = IsDragDropSupported(); + + if (GroupHeaderFromPoint(pt,groupId)) { + t_size base,count; + if (AllowRangeSelect() && ResolveGroupRange(groupId,base,count)) { + SetGroupFocusByItem(base); + pfc::bit_array_range groupRange(base,count); + bool instaDragOverride = false; + if (gotCtrl) { + ToggleRangeSelection(groupRange); + } else if (gotShift) { + SetSelection(groupRange, pfc::bit_array_true()); + } else { + if (GetSelectedCount(groupRange) == count) instaDragOverride = true; + else SetSelection(pfc::bit_array_true(),groupRange); + } + if (ddSupported && (instaDrag || instaDragOverride)) { + PrepareDragDrop(pt,isRightClick); + } else { + InitSelectDragMode(pt, isRightClick); + } + } + } else if (ItemFromPoint(pt,item)) { + const t_size oldFocus = GetFocusItem(); + const t_size selStartBefore = GetSelectionStart(); + if ( bCanSelect ) SetFocusItem(item); + if (gotShift && AllowRangeSelect() ) { + if (bCanSelect) { + t_size selStart = selStartBefore; + if (selStart == pfc_infinite) selStart = oldFocus; + if (selStart == pfc_infinite) selStart = item; + SetSelectionStart(selStart); + t_size selFirst, selCount; + selFirst = pfc::min_t(selStart, item); + selCount = pfc::max_t(selStart, item) + 1 - selFirst; + pfc::bit_array_range rangeMask(selFirst, selCount); + pfc::bit_array_true trueMask; + SetSelection(gotCtrl ? pfc::implicit_cast(rangeMask) : pfc::implicit_cast(trueMask), rangeMask); + //if (!instaDrag) InitSelectDragMode(pt, isRightClick); + } + } else { + if (gotCtrl) { + if (bCanSelect) SetSelection(pfc::bit_array_one(item), pfc::bit_array_val(!IsItemSelected(item))); + if (!instaDrag) InitSelectDragMode(pt, isRightClick); + } else { + if (!IsItemSelected(item)) { + if (bCanSelect) SetSelection(pfc::bit_array_true(), pfc::bit_array_one(item)); + if (ddSupported && instaDrag) { + PrepareDragDrop(pt,isRightClick); + } else { + InitSelectDragMode(pt, isRightClick); + } + } else { + if (ddSupported) { + PrepareDragDrop(pt,isRightClick); + } else { + InitSelectDragMode(pt, isRightClick); + } + } + } + } + } else { + if (!gotShift && !gotCtrl && bCanSelect) SelectNone(); + InitSelectDragMode(pt, isRightClick); + } + return 0; +} + + +void CListControlWithSelectionBase::ToggleRangeSelection(pfc::bit_array const & mask) { + SetSelection(mask, pfc::bit_array_val(GetSelectedCount(mask,1) == 0)); +} +void CListControlWithSelectionBase::ToggleGroupSelection(int p_group) { + t_size base, count; + if (ResolveGroupRange(p_group,base,count)) ToggleRangeSelection(pfc::bit_array_range(base,count)); +} + +LRESULT CListControlWithSelectionBase::OnRButtonUp(UINT,WPARAM p_wp,LPARAM p_lp,BOOL& bHandled) { + bHandled = FALSE; + AbortPrepareDragDropMode(); + AbortSelectDragMode(); + return 0; +} + +LRESULT CListControlWithSelectionBase::OnMouseMove(UINT,WPARAM p_wp,LPARAM p_lp,BOOL&) { + if (m_prepareDragDropMode) { + if (CPoint(p_lp) != m_prepareDragDropOrigin) { + AbortPrepareDragDropMode(); + if (!m_ownDDActive) { + pfc::vartoggle_t ownDD(m_ownDDActive,true); + RunDragDrop(m_prepareDragDropOrigin + GetViewOffset(),m_prepareDragDropModeRightClick); + } + } + } else if (m_selectDragMode) { + HandleDragSel(CPoint(p_lp)); + } + return 0; +} +LRESULT CListControlWithSelectionBase::OnLButtonUp(UINT,WPARAM p_wp,LPARAM p_lp,BOOL&) { + const bool wasPreparingDD = m_prepareDragDropMode; + AbortPrepareDragDropMode(); + CPoint pt(p_lp); + const bool gotCtrl = (p_wp & MK_CONTROL) != 0; + const bool gotShift = (p_wp & MK_SHIFT) != 0; + bool click = false; + bool processSel = wasPreparingDD; + if (m_selectDragMode) { + processSel = !m_selectDragMoved; + AbortSelectDragMode(); + } + if (processSel) { + click = true; + if (!OnClickedSpecialHitTest(pt) ) { + int groupId; t_size item; + if (GroupHeaderFromPoint(pt, groupId)) { + t_size base, count; + if (ResolveGroupRange(groupId, base, count)) { + if (gotCtrl) { + } else { + SetSelection(pfc::bit_array_true(), pfc::bit_array_range(base, count)); + } + } + } else if (ItemFromPoint(pt, item)) { + const t_size selStartBefore = GetSelectionStart(); + if (gotCtrl) { + } else if (gotShift) { + SetSelectionStart(selStartBefore); + } else { + SetSelection(pfc::bit_array_true(), pfc::bit_array_one(item)); + } + } + } + } + if (click && !gotCtrl && !gotShift) { + int groupId; t_size item; + if (GroupHeaderFromPoint(pt,groupId)) { + OnGroupHeaderClicked(groupId,pt); + } else if (ItemFromPoint(pt,item)) { + OnItemClicked(item,pt); + } + } + return 0; +} + +void CListControlWithSelectionBase::OnKeyDown_SetIndexDeltaHelper(int p_delta, int p_keys) { + t_size focus = pfc_infinite; + if (this->GroupFocusActive()) { + t_size base,count; + if (this->ResolveGroupRange(this->GetGroupFocus(),base,count)) { + focus = base; + } + } else { + focus = GetFocusItem(); + } + int target = 0; + if (focus != pfc_infinite) target = (int) focus + p_delta; + OnKeyDown_SetIndexHelper(target,p_keys); +} + + +static int _get_keyflags() { + int ret = 0; + if (IsKeyPressed(VK_CONTROL)) ret |= MK_CONTROL; + if (IsKeyPressed(VK_SHIFT)) ret |= MK_SHIFT; + return ret; +} + +LRESULT CListControlWithSelectionBase::OnKeyDown(UINT p_msg,WPARAM p_wp,LPARAM p_lp,BOOL& bHandled) { + switch(p_wp) { + case VK_NEXT: + OnKeyDown_SetIndexDeltaPageHelper(1,_get_keyflags()); + return 0; + case VK_PRIOR: + OnKeyDown_SetIndexDeltaPageHelper(-1,_get_keyflags()); + return 0; + case VK_DOWN: + OnKeyDown_SetIndexDeltaLineHelper(1,_get_keyflags()); + return 0; + case VK_UP: + OnKeyDown_SetIndexDeltaLineHelper(-1,_get_keyflags()); + return 0; + case VK_HOME: + OnKeyDown_HomeEndHelper(false,_get_keyflags()); + return 0; + case VK_END: + OnKeyDown_HomeEndHelper(true,_get_keyflags()); + return 0; + case VK_SPACE: + if (!TypeFindCheck()) { + ToggleSelectedItems(); + } + return 0; + case VK_RETURN: + ExecuteDefaultActionByFocus(); + return 0; + case VK_DELETE: + if (GetHotkeyModifierFlags() == 0) { + RequestRemoveSelection(); + return 0; + } + break; + case 'A': + if (GetHotkeyModifierFlags() == MOD_CONTROL) { + if (SelectAll()) { + return 0; + } + // otherwise unhandled + } + break; + } + + bHandled = FALSE; + return 0; +} + +void CListControlWithSelectionBase::ToggleSelectedItems() { + if (ToggleSelectedItemsHook(bit_array_selection_CListControl(*this))) return; + if (GroupFocusActive()) { + ToggleGroupSelection(this->GetGroupFocus()); + } else { + const t_size focus = GetFocusItem(); + if (focus != pfc_infinite) { + ToggleRangeSelection(pfc::bit_array_range(focus, 1)); + } + } +} + +LRESULT CListControlWithSelectionBase::OnTimer(UINT,WPARAM p_wp,LPARAM,BOOL& bHandled) { + switch((DWORD)p_wp) { + case (DWORD)KSelectionTimerID: + if (m_selectDragMode) { + CPoint pt(GetCursorPos()); ScreenToClient(&pt); + const CRect client = GetClientRectHook(); + CPoint delta(0,0); + if (pt.x < client.left) { + delta.x = pt.x - client.left; + } else if (pt.x > client.right) { + delta.x = pt.x - client.right; + } + if (pt.y < client.top) { + delta.y = pt.y - client.top; + } else if (pt.y > client.bottom) { + delta.y = pt.y - client.bottom; + } + + MoveViewOriginDelta(delta); + HandleDragSel(pt); + } + return 0; + case TDDScrollControl::KTimerID: + HandleDDScroll(); + return 0; + default: + bHandled = FALSE; + return 0; + } +} + +bool CListControlWithSelectionBase::MoveSelectionProbe(int delta) { + pfc::array_t order; order.set_size(GetItemCount()); + { + bit_array_selection_CListControl sel(*this); + pfc::create_move_items_permutation(order.get_ptr(), order.get_size(), sel, delta); + } + for( size_t w = 0; w < order.get_size(); ++w ) if ( order[w] != w ) return true; + return false; +} +void CListControlWithSelectionBase::RequestMoveSelection(int delta) { + pfc::array_t order; order.set_size(GetItemCount()); + { + bit_array_selection_CListControl sel(*this); + pfc::create_move_items_permutation(order.get_ptr(), order.get_size(), sel, delta); + } + + this->RequestReorder(order.get_ptr(), order.get_size()); + + if (delta < 0) { + size_t idx = GetFirstSelected(); + if (idx != pfc_infinite) EnsureItemVisible(idx); + } else { + size_t idx = GetLastSelected(); + if (idx != pfc_infinite) EnsureItemVisible(idx); + } +} + +void CListControlWithSelectionBase::ToggleSelection(pfc::bit_array const & mask) { + const t_size count = GetItemCount(); + pfc::bit_array_bittable table(count); + for(t_size walk = mask.find_first(true,0,count); walk < count; walk = mask.find_next(true,walk,count)) { + table.set(walk,!IsItemSelected(walk)); + } + this->SetSelection(mask,table); +} + +static HRGN FrameRectRgn(const CRect & rect) { + CRect exterior(rect); exterior.InflateRect(1,1); + CRgn rgn; rgn.CreateRectRgnIndirect(exterior); + CRect interior(rect); interior.DeflateRect(1,1); + if (!interior.IsRectEmpty()) { + CRgn rgn2; rgn2.CreateRectRgnIndirect(interior); + rgn.CombineRgn(rgn2,RGN_DIFF); + } + return rgn.Detach(); +} + +void CListControlWithSelectionBase::HandleDragSel(const CPoint & p_pt) { + CPoint pt(p_pt); pt += GetViewOffset(); + if (pt != m_selectDragCurrentAbs) { + + if (!this->AllowRangeSelect()) { + // simplified + m_selectDragCurrentAbs = pt; + if (pt != m_selectDragOriginAbs) m_selectDragMoved = true; + return; + } + + CRect rcOld(m_selectDragOriginAbs,m_selectDragCurrentAbs); rcOld.NormalizeRect(); + m_selectDragCurrentAbs = pt; + CRect rcNew(m_selectDragOriginAbs,m_selectDragCurrentAbs); rcNew.NormalizeRect(); + + + { + CRgn rgn = FrameRectRgn(rcNew); + CRgn rgn2 = FrameRectRgn(rcOld); + rgn.CombineRgn(rgn2,RGN_OR); + rgn.OffsetRgn( - GetViewOffset() ); + InvalidateRgn(rgn); + } + + if (pt != m_selectDragOriginAbs) m_selectDragMoved = true; + + if (m_selectDragChanged || !IsSameItemOrHeaderAbs(pt,m_selectDragOriginAbs)) { + m_selectDragChanged = true; + const int keys = _get_keyflags(); + t_size base,count, baseOld, countOld; + if (!GetItemRangeAbs(rcNew,base,count)) base = count = 0; + if (!GetItemRangeAbs(rcOld,baseOld,countOld)) baseOld = countOld = 0; + { + pfc::bit_array_range rangeNew(base,count), rangeOld(baseOld,countOld); + if (keys & MK_CONTROL) { + ToggleSelection(pfc::bit_array_xor(rangeNew,rangeOld)); + } else if (keys & MK_SHIFT) { + SetSelection(pfc::bit_array_or(rangeNew,rangeOld),rangeNew); + } else { + SetSelection(pfc::bit_array_true(),rangeNew); + } + } + int groupId; + if (ItemFromPointAbs(pt,base)) { + const CRect rcVisible = GetVisibleRectAbs(), rcItem = GetItemRectAbs(base); + if (rcItem.top >= rcVisible.top && rcItem.bottom <= rcVisible.bottom) { + SetFocusItem(base); + } + } else if (GroupHeaderFromPointAbs(pt,groupId)) { + const CRect rcVisible = GetVisibleRectAbs(); + CRect rcGroup; + if (GetGroupHeaderRectAbs(groupId,rcGroup)) { + if (rcGroup.top >= rcVisible.top && rcGroup.bottom <= rcVisible.bottom) { + SetGroupFocus(groupId); + } + } + } + } + } +} + +void CListControlWithSelectionBase::InitSelectDragMode(const CPoint & p_pt,bool p_rightClick) { + // Perform the bookkeeping even if multiple selection is disabled, detection of clicks relies on it + SetTimer(KSelectionTimerID,KSelectionTimerPeriod); + m_selectDragMode = true; + m_selectDragOriginAbs = m_selectDragCurrentAbs = p_pt + GetViewOffset(); + m_selectDragChanged = false; m_selectDragMoved = false; + SetCapture(); +} + +void CListControlWithSelectionBase::AbortSelectDragMode(bool p_lostCapture) { + if (m_selectDragMode) { + m_selectDragMode = false; + CRect rcSelect(m_selectDragOriginAbs,m_selectDragCurrentAbs); rcSelect.NormalizeRect(); + rcSelect.OffsetRect( - GetViewOffset() ); + if (!p_lostCapture) ::SetCapture(NULL); + rcSelect.InflateRect(1,1); + InvalidateRect(rcSelect); + KillTimer(KSelectionTimerID); + } +} + + +LRESULT CListControlWithSelectionBase::OnCaptureChanged(UINT,WPARAM,LPARAM,BOOL&) { + AbortPrepareDragDropMode(true); + AbortSelectDragMode(true); + return 0; +} + +void CListControlWithSelectionBase::RenderOverlay(const CRect & p_updaterect,CDCHandle p_dc) { + if (m_selectDragMode && this->AllowRangeSelect() ) { + CRect rcSelect(m_selectDragOriginAbs,m_selectDragCurrentAbs); + rcSelect.NormalizeRect(); + PaintUtils::FocusRect(p_dc,rcSelect); + } + if (m_dropMark != pfc_infinite) { + RenderDropMarkerClipped(p_dc, p_updaterect, m_dropMark, m_dropMarkInside); + } + TParent::RenderOverlay(p_updaterect,p_dc); +} + +void CListControlWithSelectionBase::SetDropMark(size_t mark, bool inside) { + if (mark != m_dropMark || inside != m_dropMarkInside) { + CRgn updateRgn; updateRgn.CreateRectRgn(0, 0, 0, 0); + AddDropMarkToUpdateRgn(updateRgn, m_dropMark, m_dropMarkInside); + m_dropMark = mark; + m_dropMarkInside = inside; + AddDropMarkToUpdateRgn(updateRgn, m_dropMark, m_dropMarkInside); + RedrawWindow(NULL, updateRgn); + } +} + +static int transformDDScroll(int p_value,int p_width, int p_dpi) { + if (p_dpi <= 0) p_dpi = 96; + const double dpiMul = 96.0 / (double) p_dpi; + double val = (double)(p_width - p_value); + val *= dpiMul; + val = pow(val,1.1) * 0.33; + val /= dpiMul; + return pfc::rint32(val); +} + +void CListControlWithSelectionBase::HandleDDScroll() { + if (m_ddScroll.m_timerActive) { + const CPoint position( GetCursorPos() ); + CRect client = GetClientRectHook(); + CPoint delta (0,0); + if (ClientToScreen(client)) { + const CSize DPI = QueryScreenDPIEx(); + const int scrollZoneWidthBase = GetItemHeight() * 2; + const int scrollZoneHeight = pfc::min_t( scrollZoneWidthBase, client.Height() / 4 ); + const int scrollZoneWidth = pfc::min_t( scrollZoneWidthBase, client.Width() / 4 ); + + if (position.y >= client.top && position.y < client.top + scrollZoneHeight) { + delta.y -= transformDDScroll(position.y - client.top, scrollZoneHeight, DPI.cy); + } else if (position.y >= client.bottom - scrollZoneHeight && position.y < client.bottom) { + delta.y += transformDDScroll(client.bottom - position.y, scrollZoneHeight, DPI.cy); + } + + if (position.x >= client.left && position.x < client.left + scrollZoneWidth) { + delta.x -= transformDDScroll(position.x - client.left, scrollZoneWidth, DPI.cx); + } else if (position.x >= client.right - scrollZoneWidth && position.x < client.right) { + delta.x += transformDDScroll(client.right - position.x, scrollZoneWidth, DPI.cx); + } + } + + if (delta != CPoint(0,0)) MoveViewOriginDelta(delta); + } +} + +void CListControlWithSelectionBase::ToggleDDScroll(bool p_state) { + if (p_state != m_ddScroll.m_timerActive) { + if (p_state) { + SetTimer(m_ddScroll.KTimerID,m_ddScroll.KTimerPeriod); + } else { + KillTimer(m_ddScroll.KTimerID); + } + m_ddScroll.m_timerActive = p_state; + } +} + +void CListControlWithSelectionBase::PrepareDragDrop(const CPoint & p_point,bool p_isRightClick) { + m_prepareDragDropMode = true; + m_prepareDragDropOrigin = p_point; + m_prepareDragDropModeRightClick = p_isRightClick; + SetCapture(); +} +void CListControlWithSelectionBase::AbortPrepareDragDropMode(bool p_lostCapture) { + if (m_prepareDragDropMode) { + m_prepareDragDropMode = false; + if (!p_lostCapture) ::SetCapture(NULL); + } +} + + +void CListControlWithSelectionBase::RenderItem(t_size p_item,const CRect & p_itemRect,const CRect & p_updateRect,CDCHandle p_dc) { + //console::formatter() << "RenderItem: " << p_item; + const bool weHaveFocus = ::GetFocus() == m_hWnd; + const bool isSelected = this->IsItemSelected(p_item); + + const t_uint32 bkColor = GetSysColorHook(colorBackground); + const t_uint32 hlColor = GetSysColorHook(colorSelection); + const t_uint32 bkColorUsed = isSelected ? (weHaveFocus ? hlColor : PaintUtils::BlendColor(hlColor,bkColor)) : bkColor; + + bool alternateTextColor = false, dtt = false; + auto & m_theme = theme(); + if (m_theme != NULL && isSelected && hlColor == GetSysColor(COLOR_HIGHLIGHT) && /*bkColor == GetSysColor(COLOR_WINDOW) && */ IsThemePartDefined(m_theme, LVP_LISTITEM, 0)) { + //PaintUtils::RenderItemBackground(p_dc,p_itemRect,p_item+GetItemGroup(p_item),bkColor); + DrawThemeBackground(m_theme, p_dc, LVP_LISTITEM, weHaveFocus ? LISS_SELECTED : LISS_SELECTEDNOTFOCUS , p_itemRect, p_updateRect); + dtt = true; + } else { + this->RenderItemBackground(p_dc, p_itemRect, p_item, bkColorUsed ); + // PaintUtils::RenderItemBackground(p_dc,p_itemRect,p_item+GetItemGroup(p_item),bkColorUsed); + if (isSelected) alternateTextColor = true; + } + + { + DCStateScope backup(p_dc); + p_dc.SetBkMode(TRANSPARENT); + p_dc.SetBkColor(bkColorUsed); + p_dc.SetTextColor(alternateTextColor ? PaintUtils::DetermineTextColor(bkColorUsed) : this->GetSysColorHook(colorText)); + pfc::vartoggle_t toggle(m_drawThemeText, dtt); + RenderItemText(p_item,p_itemRect,p_updateRect,p_dc, !alternateTextColor); + } + + if (IsItemFocused(p_item) && weHaveFocus) { + PaintUtils::FocusRect2(p_dc,p_itemRect, bkColorUsed); + } +} +void CListControlWithSelectionBase::RenderSubItemText(t_size item, t_size subItem,const CRect & subItemRect,const CRect & updateRect,CDCHandle dc, bool allowColors) { + auto ct = GetCellType(item, subItem); + if ( ct == nullptr ) return; + + if (m_drawThemeText && ct->AllowDrawThemeText() && !this->IsSubItemGrayed(item, subItem)) for(;;) { + pfc::string_formatter label; + if (!GetSubItemText(item,subItem,label)) return; + const bool weHaveFocus = ::GetFocus() == m_hWnd; + const bool isSelected = this->IsItemSelected(item); + pfc::stringcvt::string_os_from_utf8 cvt(label); + if (PaintUtils::TextContainsCodes(cvt)) break; + CRect clip = GetItemTextRect(subItemRect); + const t_uint32 format = PaintUtils::DrawText_TranslateHeaderAlignment(GetColumnFormat(subItem)); + DrawThemeText(theme(), dc, LVP_LISTITEM, weHaveFocus ? LISS_SELECTED : LISS_SELECTEDNOTFOCUS, cvt, (int)cvt.length(), DT_NOPREFIX | DT_END_ELLIPSIS | DT_SINGLELINE | DT_VCENTER | format, 0, clip); + return; + } + __super::RenderSubItemText(item, subItem, subItemRect, updateRect, dc, allowColors); +} +void CListControlWithSelectionBase::RenderGroupHeader(int p_group,const CRect & p_headerRect,const CRect & p_updateRect,CDCHandle p_dc) { + TParent::RenderGroupHeader(p_group,p_headerRect,p_updateRect,p_dc); + if (IsGroupHeaderFocused(p_group)) { + PaintUtils::FocusRect(p_dc,p_headerRect); + } +} +CRect CListControlWithSelectionBase::DropMarkerUpdateRect(t_size index,bool bInside) const { + if (index != ~0) { + CRect rect; + if (bInside) { + rect = GetItemRect(index); + rect.InflateRect(DropMarkerMargin()); + } else { + rect = DropMarkerRect(DropMarkerOffset(index)); + } + return rect; + } else { + return CRect(0,0,0,0); + } +} +void CListControlWithSelectionBase::AddDropMarkToUpdateRgn(HRGN p_rgn, t_size p_index, bool bInside) const { + CRect rect = DropMarkerUpdateRect(p_index,bInside); + if (!rect.IsRectEmpty()) PaintUtils::AddRectToRgn(p_rgn,rect); +} + +CRect CListControlWithSelectionBase::DropMarkerRect(int offset) const { + const int delta = MulDiv(5,m_dpi.cy,96); + CRect rc(0,offset - delta,GetViewAreaWidth(), offset + delta); + rc.InflateRect(DropMarkerMargin()); + return rc; +} + +int CListControlWithSelectionBase::DropMarkerOffset(t_size marker) const { + return (marker > 0 ? this->GetItemRectAbs(marker-1).bottom : 0) - GetViewOffset().y; +} +bool CListControlWithSelectionBase::RenderDropMarkerClipped(CDCHandle dc, const CRect & update, t_size item, bool bInside) { + CRect markerRect = DropMarkerUpdateRect(item,bInside); markerRect.OffsetRect( GetViewOffset() ); + CRect affected; + if (affected.IntersectRect(markerRect,update)) { + DCStateScope state(dc); + if (dc.IntersectClipRect(affected)) { + RenderDropMarker(dc,item,bInside); + return true; + } + } + return false; +} +void CListControlWithSelectionBase::RenderDropMarker(CDCHandle dc, t_size item, bool bInside) { + if (item != ~0) { + if (bInside) { + CPen pen; MakeDropMarkerPen(pen); + SelectObjectScope penScope(dc,pen); + const CRect rc = GetItemRectAbs(item); + dc.MoveTo(rc.left,rc.top); + dc.LineTo(rc.right,rc.top); + dc.LineTo(rc.right,rc.bottom); + dc.LineTo(rc.left,rc.bottom); + dc.LineTo(rc.left,rc.top); + } else { + RenderDropMarkerByOffset(DropMarkerOffset(item) + GetViewOffset().y,dc); + } + } +} + +SIZE CListControlWithSelectionBase::DropMarkerMargin() const { + const int penDeltaX = MulDiv(5 /* we don't know how to translate CreatePen units... */,m_dpi.cx,96); + const int penDeltaY = MulDiv(5 /* we don't know how to translate CreatePen units... */,m_dpi.cy,96); + SIZE s = {penDeltaX,penDeltaY}; + return s; +} +void CListControlWithSelectionBase::MakeDropMarkerPen(CPen & out) const { + WIN32_OP_D( out.CreatePen(PS_SOLID,3,GetSysColorHook(colorText)) != NULL ); +} + +void CListControlWithSelectionBase::RenderDropMarkerByOffset(int offset,CDCHandle p_dc) { + CPen pen; MakeDropMarkerPen(pen); + const int delta = MulDiv(5,m_dpi.cy,96); + SelectObjectScope penScope(p_dc,pen); + const int width = GetViewAreaWidth(); + if (width > 0) { + p_dc.MoveTo(0,offset); + p_dc.LineTo(width-1,offset); + p_dc.MoveTo(0,offset-delta); + p_dc.LineTo(0,offset+delta); + p_dc.MoveTo(width-1,offset-delta); + p_dc.LineTo(width-1,offset+delta); + } +} + +void CListControlWithSelectionBase::FocusToUpdateRgn(HRGN rgn) { + t_size focusItem = GetFocusItem(); + if (focusItem != ~0) AddItemToUpdateRgn(rgn,focusItem); + else { + int focusGroup = GetGroupFocus(); + if (focusGroup >= 0) AddGroupHeaderToUpdateRgn(rgn,focusGroup); + } +} + +void CListControlWithSelectionImpl::ReloadData() { + if ( GetItemCount() != m_selection.get_size() ) { + this->SelHandleReset(); + } + __super::ReloadData(); +} + +void CListControlWithSelectionImpl::SetGroupFocus(int group) { + t_size base,total; + if (this->ResolveGroupRange(group,base,total)) { + SetGroupFocusByItem(base); + } +} + +int CListControlWithSelectionImpl::GetGroupFocus() const { + return (m_groupFocus && m_focus < GetItemCount()) ? GetItemGroup(m_focus) : -1; +} + +void CListControlWithSelectionImpl::SetSelectionImpl(pfc::bit_array const & affected, pfc::bit_array const & status) { + const t_size total = m_selection.get_size(); + pfc::bit_array_flatIndexList toUpdate; + + // Only fire UpdateItems for stuff that's both on-screen and actually changed + // Firing for whole affected mask will repaint everything when selecting one item + t_size base, count; + if (!GetItemRangeAbs(GetVisibleRectAbs(), base, count)) { base = count = 0; } + + affected.walk( total, [&] (size_t idx) { + if ( m_selection[idx] != status[idx] && this->CanSelectItem(idx) ) { + m_selection[idx] = status[idx]; + if ( idx >= base && idx < base+count ) toUpdate.add(idx); + } + } ); + + if ( toUpdate.get_count() > 0 ) { + UpdateItems(toUpdate); + } + + this->OnSelectionChanged( affected, status ); +} + +void CListControlWithSelectionImpl::SetSelection(pfc::bit_array const & affected, pfc::bit_array const & status) { + RefreshSelectionSize(); + + + if ( m_selectionSupport == selectionSupportNone ) return; + + if ( m_selectionSupport == selectionSupportSingle ) { + size_t single = SIZE_MAX; + bool selNone = true; + const size_t total = m_selection.get_size(); + for( size_t walk = 0; walk < total; ++ walk ) { + if ( affected.get(walk) ) { + if ( status.get(walk) && single == SIZE_MAX ) { + single = walk; + } + } else if ( IsItemSelected( walk ) ) { + selNone = false; + } + } + if ( single < total ) { + SetSelectionImpl( pfc::bit_array_true(), pfc::bit_array_one( single ) ); + } else if ( selNone ) { + this->SetSelectionImpl( pfc::bit_array_true(), pfc::bit_array_false() ); + } + } else { + SetSelectionImpl( affected, status ); + } + +} + +void CListControlWithSelectionImpl::RefreshSelectionSize() { + RefreshSelectionSize(GetItemCount()); +} +void CListControlWithSelectionImpl::RefreshSelectionSize(t_size total) { + const t_size oldSize = m_selection.get_size(); + if (total != oldSize) { + m_selection.set_size(total); + for(t_size walk = oldSize; walk < total; ++walk) m_selection[walk] = false; + } +} + +void CListControlWithSelectionImpl::SetGroupFocusByItem(t_size item) { + CRgn update; update.CreateRectRgn(0,0,0,0); + FocusToUpdateRgn(update); + m_groupFocus = true; m_focus = item; + FocusToUpdateRgn(update); + InvalidateRgn(update); + + const int iGroup = GetItemGroup(item); + CRect header; + if (GetGroupHeaderRectAbs(iGroup,header)) EnsureVisibleRectAbs(header); + + this->OnFocusChangedGroup( iGroup ); +} + +void CListControlWithSelectionImpl::SetFocusItem(t_size index) { + CRgn update; update.CreateRectRgn(0,0,0,0); + FocusToUpdateRgn(update); + m_groupFocus = false; m_focus = index; + FocusToUpdateRgn(update); + InvalidateRgn(update); + + EnsureVisibleRectAbs(GetItemRectAbs(index)); + + SetSelectionStart(index); + + this->OnFocusChanged( index ); +} + +static void UpdateIndexOnReorder(t_size & index, const t_size * order, t_size count) { + index = pfc::permutation_find_reverse(order,count,index); +} +static void UpdateIndexOnRemoval(t_size & index, const pfc::bit_array & mask, t_size oldCount, t_size newCount) { + if (index >= oldCount || newCount == 0) {index = ~0; return;} + for(t_size walk = mask.find_first(true,0,oldCount); walk < newCount; ++walk) { + if (walk < index) --index; + else break; + } + if (index >= newCount) index = newCount - 1; +} +static void UpdateIndexOnInsert(t_size & index, t_size base, t_size count) { + if (index != ~0 && index >= base) index += count; +} + +void CListControlWithSelectionImpl::SelHandleReorder(const t_size * order, t_size count) { + RefreshSelectionSize(); + UpdateIndexOnReorder(m_focus,order,count); + UpdateIndexOnReorder(m_selectionStart,order,count); + pfc::array_t newSel; newSel.set_size(m_selection.get_size()); + for(t_size walk = 0; walk < m_selection.get_size(); ++walk) newSel[walk] = m_selection[order[walk]]; + m_selection = newSel; +} + +void CListControlWithSelectionImpl::SelHandleReset() { + RefreshSelectionSize(GetItemCount()); + for(t_size walk = 0; walk < m_selection.get_size(); walk++) m_selection[walk] = false; + m_focus = ~0; + m_groupFocus = false; + +} +void CListControlWithSelectionImpl::SelHandleRemoval(const pfc::bit_array & mask, t_size oldCount) { + RefreshSelectionSize(oldCount); + const t_size newCount = GetItemCount(); + UpdateIndexOnRemoval(m_focus,mask,oldCount,newCount); + UpdateIndexOnRemoval(m_selectionStart,mask,oldCount,newCount); + pfc::remove_mask_t(m_selection,mask); +} + +void CListControlWithSelectionImpl::SelHandleInsertion(t_size base, t_size count, bool select) { + UpdateIndexOnInsert(m_focus,base,count); + UpdateIndexOnInsert(m_selectionStart,base,count); + RefreshSelectionSize(GetItemCount() - count); + + // To behave sanely in single-select mode, we'd have to alter selection of other items from here + // Let caller worry and outright deny select requests in modes other than multisel + if ( m_selectionSupport != selectionSupportMulti ) select = false; + + m_selection.insert_multi(select,base,count); +} + + +LRESULT CListControlWithSelectionBase::OnGetDlgCode(UINT,WPARAM,LPARAM p_lp,BOOL& bHandled) { + if (p_lp == 0) { + return DLGC_WANTALLKEYS | DLGC_WANTCHARS | DLGC_WANTARROWS; + } else { + const MSG * pmsg = reinterpret_cast(p_lp); + switch(pmsg->message) { + case WM_KEYDOWN: + case WM_KEYUP: + switch(pmsg->wParam) { + case VK_ESCAPE: + case VK_TAB: + bHandled = FALSE; + return 0; + default: + return DLGC_WANTMESSAGE; + } + case WM_CHAR: + return DLGC_WANTMESSAGE; + default: + bHandled = FALSE; + return 0; + } + } +} + + +bool CListControlWithSelectionBase::GetFocusRect(CRect & p_rect) { + if (!GetFocusRectAbs(p_rect)) return false; + p_rect.OffsetRect( - GetViewOffset() ); + return true; +} +bool CListControlWithSelectionBase::GetFocusRectAbs(CRect & p_rect) { + t_size item = this->GetFocusItem(); + if (item != ~0) { + p_rect = this->GetItemRectAbs(item); + return true; + } + int group = this->GetGroupFocus(); + if (group >= 0) { + return GetGroupHeaderRectAbs(group,p_rect); + } + + return false; +} + +CPoint CListControlWithSelectionBase::GetContextMenuPoint(CPoint ptGot) { + CPoint pt; + if (ptGot.x == -1 && ptGot.y == -1) { + CRect rect; + if (!GetFocusRectAbs(rect)) return 0; + EnsureVisibleRectAbs(rect); + pt = rect.CenterPoint() - GetViewOffset(); + ClientToScreen(&pt); + } else { + pt = ptGot; + } + return pt; +} + +CPoint CListControlWithSelectionBase::GetContextMenuPoint(LPARAM lp) { + CPoint pt; + if (lp == -1) { + CRect rect; + if (!GetFocusRectAbs(rect)) return 0; + EnsureVisibleRectAbs(rect); + pt = rect.CenterPoint() - GetViewOffset(); + ClientToScreen(&pt); + } else { + pt = lp; + } + return pt; +} + +bool CListControlWithSelectionBase::MakeDropReorderPermutation(pfc::array_t & out, CPoint ptDrop) const { + t_size insertMark = InsertIndexFromPoint(ptDrop); + /*if (insertMark != this->GetFocusItem())*/ { + const t_size count = GetItemCount(); + if (insertMark > count) insertMark = count; + { + t_size selBefore = 0; + for(t_size walk = 0; walk < insertMark; ++walk) { + if (IsItemSelected(walk)) selBefore++; + } + insertMark -= selBefore; + } + { + pfc::array_t permutation, selected, nonselected; + const t_size selcount = this->GetSelectedCount(); + selected.set_size(selcount); nonselected.set_size(count - selcount); + permutation.set_size(count); + if (insertMark > nonselected.get_size()) insertMark = nonselected.get_size(); + for(t_size walk = 0, swalk = 0, nwalk = 0; walk < count; ++walk) { + if (IsItemSelected(walk)) { + selected[swalk++] = walk; + } else { + nonselected[nwalk++] = walk; + } + } + for(t_size walk = 0; walk < insertMark; ++walk) { + permutation[walk] = nonselected[walk]; + } + for(t_size walk = 0; walk < selected.get_size(); ++walk) { + permutation[insertMark + walk] = selected[walk]; + } + for(t_size walk = insertMark; walk < nonselected.get_size(); ++walk) { + permutation[selected.get_size() + walk] = nonselected[walk]; + } + for(t_size walk = 0; walk < permutation.get_size(); ++walk) { + if (permutation[walk] != walk) { + out = permutation; + return true; + } + } + } + } + return false; +} + +void CListControlWithSelectionBase::EnsureVisibleRectAbs(const CRect & p_rect) { + if (!m_noEnsureVisible) TParent::EnsureVisibleRectAbs(p_rect); +} + +bool CListControlWithSelectionBase::TypeFindCheck(DWORD ts) const { + if (m_typeFindTS == 0) return false; + return ts - m_typeFindTS < 1000; +} + +void CListControlWithSelectionBase::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags) { + if (nChar < 32) { + m_typeFindTS = 0; + return; + } + + const DWORD ts = GetTickCount(); + if (!TypeFindCheck(ts)) m_typeFind.reset(); + + if (nChar == ' ' && m_typeFind.is_empty()) { + m_typeFindTS = 0; + return; + } + + m_typeFindTS = ts; + if (m_typeFindTS == 0) m_typeFindTS = ~0; + char temp[10] = {}; + pfc::utf8_encode_char(nChar, temp); + m_typeFind += temp; + RunTypeFind(); +} + +static unsigned detectRepetition( pfc::string8 const & str ) { + size_t count = 0; + size_t walk = 0; + uint32_t first; + + while( walk < str.length() ) { + uint32_t current; + auto delta = pfc::utf8_decode_char( str.c_str() + walk, current, str.length() - walk ); + if ( delta == 0 ) break; + walk += delta; + + if ( count == 0 ) first = current; + else if ( first != current ) return 0; + + ++ count; + } + + if ( count > 1 ) return first; + return 0; +} + +size_t CListControlWithSelectionBase::EvalTypeFind() { + if ( GetItemCount() == 0 ) return SIZE_MAX; + + static SmartStrStr tool; + + const size_t itemCount = GetItemCount(); + const size_t colCount = GetColumnCount(); + pfc::string_formatter temp; + t_size searchBase = this->GetFocusItem(); + if (searchBase >= itemCount) searchBase = 0; + + size_t partial = SIZE_MAX; + size_t repetition = SIZE_MAX; + bool useRepetition = false; + pfc::string8 strRepetitionChar; + unsigned repChar = detectRepetition( m_typeFind ); + if ( repChar != 0 ) { + useRepetition = true; + strRepetitionChar.add_char( repChar ); + } + + for(t_size walk = 0; walk < itemCount; ++walk) { + t_size index = (walk + searchBase) % itemCount; + for(size_t cWalk = 0; cWalk < colCount; ++cWalk) { + + temp.reset(); + + if (AllowTypeFindInCell( index, cWalk )) { + this->GetSubItemText(index, cWalk, temp); + } + + if ( temp.length() == 0 ) { + continue; + } + if (partial == SIZE_MAX) { + size_t matchAt; + if (tool.strStrEnd( temp, m_typeFind, & matchAt ) != nullptr) { + if ( matchAt == 0 ) return index; + partial = index; + } + } else { + if ( tool.matchHere( temp, m_typeFind ) ) return index; + } + if (useRepetition && index != searchBase) { + if ( tool.matchHere( temp, strRepetitionChar ) ) { + useRepetition = false; + repetition = index; + } + } + } + } + if (partial < itemCount) return partial; + if (repetition < itemCount) return repetition; + return SIZE_MAX; +} + +void CListControlWithSelectionBase::RunTypeFind() { + size_t index = EvalTypeFind(); + if (index < GetItemCount() ) { + this->SetFocusItem( index ); + this->SetSelection(pfc::bit_array_true(), pfc::bit_array_one(index) ); + } else { + MessageBeep(0); + } +} + +CRect CListControlWithSelectionBase::GetWholeSelectionRectAbs() const { + CRect rcTotal; + const size_t count = GetItemCount(); + for( size_t w = 0; w < count; ++w ) { + if ( IsItemSelected( w ) ) { + CRect rcItem = GetItemRectAbs(w); + if ( rcTotal.IsRectNull( ) ) rcTotal = rcItem; + else rcTotal|=rcItem; + } + } + return rcTotal; +} + +size_t CListControlWithSelectionBase::GetFirstSelected() const { + const size_t count = GetItemCount(); + for( size_t w = 0; w < count; ++w ) { + if ( IsItemSelected(w) ) return w; + } + return pfc_infinite; +} +size_t CListControlWithSelectionBase::GetLastSelected() const { + const size_t count = GetItemCount(); + for( size_t w = count - 1; (t_ssize) w >= 0; --w ) { + if ( IsItemSelected(w) ) return w; + } + return pfc_infinite; +} + +namespace { + class CDropTargetImpl : public ImplementCOMRefCounter { + public: + COM_QI_BEGIN() + COM_QI_ENTRY(IUnknown) + COM_QI_ENTRY(IDropTarget) + COM_QI_END() + + bool valid = true; + std::function Track; + std::function HookAccept; + std::function HookDrop; + std::function HookLeave; + + DWORD m_effect = DROPEFFECT_NONE; + + HRESULT STDMETHODCALLTYPE DragEnter(IDataObject *pDataObj, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect) { + if (pDataObj == NULL || pdwEffect == NULL) return E_INVALIDARG; + if (!valid) return E_FAIL; + if ( HookAccept ) { + m_effect = HookAccept(pDataObj); + } else { + m_effect = DROPEFFECT_MOVE; + } + *pdwEffect = m_effect; + return S_OK; + } + HRESULT STDMETHODCALLTYPE DragOver(DWORD grfKeyState, POINTL pt, DWORD *pdwEffect) { + if (pdwEffect == NULL) return E_INVALIDARG; + if (!valid) return E_FAIL; + if ( m_effect != DROPEFFECT_NONE ) Track(CPoint(pt.x, pt.y)); + *pdwEffect = m_effect; + return S_OK; + } + HRESULT STDMETHODCALLTYPE DragLeave() { + if (HookLeave) HookLeave(); + return S_OK; + } + HRESULT STDMETHODCALLTYPE Drop(IDataObject *pDataObj, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect) { + if ( HookDrop && m_effect != DROPEFFECT_NONE ) { + HookDrop( pDataObj, CPoint(pt.x, pt.y) ); + } + return S_OK; + } + }; + + class CDropSourceImpl : public ImplementCOMRefCounter { + public: + CPoint droppedAt; + bool droppedAtValid = false; + bool allowReorder = false; + + bool allowDragOutside = false; + CWindow wndOrigin; + + COM_QI_BEGIN() + COM_QI_ENTRY(IUnknown) + COM_QI_ENTRY(IDropSource) + COM_QI_END() + + HRESULT STDMETHODCALLTYPE GiveFeedback(DWORD dwEffect) { + m_effect = dwEffect; + return DRAGDROP_S_USEDEFAULTCURSORS; + } + + HRESULT STDMETHODCALLTYPE QueryContinueDrag(BOOL fEscapePressed, DWORD grfKeyState) { + + if (fEscapePressed || (grfKeyState & MK_RBUTTON) != 0) { + return DRAGDROP_S_CANCEL; + } else if (!(grfKeyState & MK_LBUTTON)) { + if (m_effect == DROPEFFECT_NONE) return DRAGDROP_S_CANCEL; + + const CPoint pt(GetCursorPos()); + bool bInside = false; + if (wndOrigin) { + CRect rc; + WIN32_OP_D(wndOrigin.GetWindowRect(rc)); + bInside = rc.PtInRect(pt); + } + if (!allowDragOutside && !bInside) return DRAGDROP_S_CANCEL; + + if ( allowReorder && bInside) { + droppedAt = pt; + droppedAtValid = true; + return DRAGDROP_S_CANCEL; + } + return DRAGDROP_S_DROP; + + } else { + return S_OK; + } + } + private: + DWORD m_effect = 0; + }; +} + +void CListControlWithSelectionBase::RunDragDrop(const CPoint & p_origin, bool p_isRightClick) { + + uint32_t flags = this->QueryDragDropTypes(); + if ( flags == 0 ) { + PFC_ASSERT(!"How did we get here?"); + return; + } + if ( flags == dragDrop_reorder ) { + if ( p_isRightClick ) return; + CPoint ptDrop; + if ( RunReorderDragDrop( p_origin, ptDrop ) ) { + pfc::array_t order; + if (MakeDropReorderPermutation(order, ptDrop)) { + this->RequestReorder(order.get_ptr(), order.get_size()); + } + } + return; + } + + auto obj = this->MakeDataObject(); + if (obj.is_empty()) { + PFC_ASSERT(!"How did we get here? No IDataObject"); + return; + } + + pfc::com_ptr_t source = new CDropSourceImpl(); + source->wndOrigin = *this; + source->allowDragOutside = true; + source->allowReorder = (flags & dragDrop_reorder) != 0; + + DWORD outEffect = DROPEFFECT_NONE; + HRESULT status = DoDragDrop(obj.get_ptr(), source.get_ptr(), DragDropSourceEffects() , &outEffect); + + if ( source->droppedAtValid ) { + CPoint ptDrop = source->droppedAt; + WIN32_OP_D(this->ScreenToClient(&ptDrop)); + pfc::array_t order; + if (MakeDropReorderPermutation(order, ptDrop)) { + this->RequestReorder(order.get_ptr(), order.get_size()); + } + } else if (status == DRAGDROP_S_DROP) { + DragDropSourceSucceeded(outEffect); + } +} + +pfc::com_ptr_t CListControlWithSelectionBase::MakeDataObject() { + // return dummy IDataObject, presume derived transmits drag and drop payload by other means + using namespace IDataObjectUtils; + return new ImplementCOMRefCounter< CDataObjectBase >(); +} + +bool CListControlWithSelectionBase::RunReorderDragDrop(CPoint ptOrigin, CPoint & ptDrop) { + pfc::com_ptr_t source = new CDropSourceImpl(); + pfc::com_ptr_t target = new CDropTargetImpl(); + + source->wndOrigin = *this; + source->allowDragOutside = false; + source->allowReorder = true; + + target->Track = [this](CPoint pt) { + WIN32_OP_D(this->ScreenToClient(&pt)); + size_t idx = this->InsertIndexFromPoint(pt); + this->SetDropMark(idx, false); + }; + + if ( FAILED(RegisterDragDrop(*this, target.get_ptr())) ) { + // OleInitialize not called? + PFC_ASSERT( !"Should not get here" ); + return false; + } + + pfc::onLeaving scope([=] { target->valid = false; RevokeDragDrop(*this); }); + + using namespace IDataObjectUtils; + pfc::com_ptr_t dataobject = new ImplementCOMRefCounter< CDataObjectBase >(); + DWORD outeffect = 0; + + ToggleDDScroll(true); + DoDragDrop(dataobject.get_ptr(), source.get_ptr(), DROPEFFECT_MOVE, &outeffect); + ClearDropMark(); + ToggleDDScroll(false); + if (source->droppedAtValid ) { + CPoint pt = source->droppedAt; + WIN32_OP_D( this->ScreenToClient( &pt ) ); + ptDrop = pt; + return true; + } + return false; +} + +int CListControlWithSelectionBase::OnCreatePassThru(LPCREATESTRUCT lpCreateStruct) { + const uint32_t flags = this->QueryDragDropTypes(); + if ( flags & dragDrop_external ) { + + pfc::com_ptr_t target = new CDropTargetImpl(); + + std::shared_ptr showDropMark = std::make_shared(); + + target->HookAccept = [this, flags, showDropMark] ( IDataObject * obj ) { + *showDropMark = true; + if (this->m_ownDDActive) { + // Do not generate OnDrop for reorderings + if (flags & dragDrop_reorder) return (DWORD)DROPEFFECT_MOVE; + } + return this->DragDropAccept(obj, *showDropMark); + }; + target->HookDrop = [this, flags] ( IDataObject * obj, CPoint pt ) { + this->ClearDropMark(); + if ( this->m_ownDDActive ) { + // Do not generate OnDrop for reorderings + if ( flags & dragDrop_reorder ) return; + } + this->OnDrop( obj, pt ); + }; + target->HookLeave = [this] { + this->ClearDropMark(); + }; + + target->Track = [this, showDropMark](CPoint pt) { + if ( *showDropMark ) { + WIN32_OP_D(this->ScreenToClient(&pt)); + size_t idx = this->InsertIndexFromPoint(pt); + this->SetDropMark(idx, false); + } else { + this->ClearDropMark(); + } + }; + + RegisterDragDrop(*this, target.get_ptr() ); + } + SetMsgHandled(FALSE); return 0; +} + +void CListControlWithSelectionBase::OnDestroyPassThru() { + AbortSelectDragMode(); + ToggleDDScroll(false); + RevokeDragDrop(*this); + SetMsgHandled(FALSE); +} + +size_t CListControlWithSelectionBase::GetPasteTarget(const CPoint * ptPaste) const { + size_t target = ~0; + if (ptPaste != nullptr) { + CPoint pt(*ptPaste); WIN32_OP_D(ScreenToClient(&pt)); + int iGroup; + if (GroupHeaderFromPoint(pt, iGroup)) { + t_size base, count; + if (ResolveGroupRange(iGroup, base, count)) { + target = base; + } + } else if (ItemFromPoint(pt, target)) { + auto rc = GetItemRect(target); + auto height = rc.Height(); + if (height > 0) { + double posInItem = (double)(pt.y - rc.top) / (double)height; + if (posInItem >= 0.5) ++target; + } + } + } else if (GroupFocusActive()) { + t_size base, count; + if (ResolveGroupRange(GetGroupFocus(), base, count)) { + target = base; + } + } else { + target = GetFocusItem(); + } + return target; +} + + +pfc::bit_array_table CListControlWithSelectionImpl::GetSelectionMaskRef() { + return pfc::bit_array_table(this->GetSelectionArray(), this->GetItemCount()); +} +pfc::bit_array_bittable CListControlWithSelectionImpl::GetSelectionMask() { + pfc::bit_array_bittable ret; + const auto count = GetItemCount(); + ret.resize( GetItemCount() ); + for( size_t walk = 0; walk < count; ++ walk ) { + ret.set(walk, IsItemSelected(walk)); + } + return ret; +} + +void CListControlWithSelectionImpl::OnItemsReordered( const size_t * order, size_t count) { + PFC_ASSERT( count == GetItemCount() ); + + SelHandleReorder( order, count ); + __super::OnItemsReordered(order, count); +} + +void CListControlWithSelectionImpl::OnItemsRemoved( pfc::bit_array const & mask, size_t oldCount) { + SelHandleRemoval(mask, oldCount); + __super::OnItemsRemoved( mask, oldCount ); +} + +void CListControlWithSelectionImpl::OnItemsInserted( size_t at, size_t count, bool bSelect ) { + SelHandleInsertion( at, count, bSelect); + __super::OnItemsInserted( at, count, bSelect ); +} + +bool CListControlWithSelectionImpl::SelectAll() { + if ( m_selectionSupport != selectionSupportMulti ) return false; + return __super::SelectAll(); +} diff --git a/libPPUI/CListControlWithSelection.h b/libPPUI/CListControlWithSelection.h new file mode 100644 index 0000000..f666069 --- /dev/null +++ b/libPPUI/CListControlWithSelection.h @@ -0,0 +1,265 @@ +#pragma once + +#include "CListControl.h" + +//! Implementation of focus/selection handling. Leaves maintaining focus/selection info to the derived class. \n +//! Most classes should derive from CListControlWithSelectionImpl instead. +class CListControlWithSelectionBase : public CListControl { +public: + typedef CListControl TParent; + CListControlWithSelectionBase() : m_selectDragMode(), m_prepareDragDropMode(), m_prepareDragDropModeRightClick(), m_ownDDActive(), m_noEnsureVisible(), m_drawThemeText(), m_typeFindTS() {} + BEGIN_MSG_MAP_EX(CListControlWithSelectionBase) + MSG_WM_CREATE(OnCreatePassThru); + MSG_WM_DESTROY(OnDestroyPassThru); + CHAIN_MSG_MAP(TParent) + MESSAGE_HANDLER(WM_LBUTTONDBLCLK,OnLButtonDblClk) + MESSAGE_HANDLER(WM_LBUTTONDOWN,OnButtonDown) + MESSAGE_HANDLER(WM_RBUTTONDOWN,OnButtonDown) + MESSAGE_HANDLER(WM_RBUTTONDBLCLK,OnButtonDown) + MESSAGE_HANDLER(WM_RBUTTONUP,OnRButtonUp) + MESSAGE_HANDLER(WM_MOUSEMOVE,OnMouseMove) + MESSAGE_HANDLER(WM_LBUTTONUP,OnLButtonUp) + MESSAGE_HANDLER(WM_KEYDOWN,OnKeyDown); + MESSAGE_HANDLER(WM_SYSKEYDOWN,OnKeyDown); + MESSAGE_HANDLER(WM_SETFOCUS,OnFocus); + MESSAGE_HANDLER(WM_KILLFOCUS,OnFocus); + MESSAGE_HANDLER(WM_TIMER,OnTimer); + MESSAGE_HANDLER(WM_CAPTURECHANGED,OnCaptureChanged); + MESSAGE_HANDLER(WM_GETDLGCODE,OnGetDlgCode); + MSG_WM_CHAR(OnChar) + END_MSG_MAP() + + virtual void SetFocusItem(t_size index) = 0; + virtual t_size GetFocusItem() const = 0; + virtual void SetGroupFocus(int group) = 0; + virtual void SetGroupFocusByItem(t_size item) = 0; + virtual int GetGroupFocus() const = 0; + virtual bool IsItemSelected(t_size index) const = 0; + virtual void SetSelection(pfc::bit_array const & affected,pfc::bit_array const & status) = 0; + void SelectSingle(size_t which); + virtual bool SelectAll(); + void SelectNone(); + virtual void RequestMoveSelection(int delta); + bool MoveSelectionProbe(int delta); + virtual void RequestReorder( size_t const * order, size_t count ) = 0; + virtual void RequestRemoveSelection() = 0; + virtual void ExecuteDefaultAction(t_size index) = 0; + virtual void ExecuteDefaultActionGroup(t_size base, t_size count) {} + virtual bool ExecuteCanvasDefaultAction(CPoint pt) { return false; } + + virtual t_size GetSelectionStart() const = 0; + virtual void SetSelectionStart(t_size val) = 0; + //! Full hook for drag-drop loop + virtual void RunDragDrop(const CPoint & p_origin,bool p_isRightClick); + + //! Should RunDragDrop() be called at all? + virtual bool IsDragDropSupported() {return QueryDragDropTypes() != 0;} + + //! Notification, mandatory to call by SetFocusItem() implementation. \n + //! If overridden by subclass, must call parent. + virtual void OnFocusChanged(size_t newFocus) {} + virtual void OnFocusChangedGroup(int inGroup) {} + //! Notification, mandatory to call by SetSelection() implementation. \n + //! If overridden by subclass, must call parent. + virtual void OnSelectionChanged(pfc::bit_array const & affected, pfc::bit_array const & status) {} + + enum { + dragDrop_reorder = 1 << 0, + dragDrop_external = 1 << 1, + }; + + virtual uint32_t QueryDragDropTypes() const { return 0; } + virtual DWORD DragDropAccept(IDataObject * obj, bool & showDropMark) { return DROPEFFECT_NONE; } + virtual pfc::com_ptr_t MakeDataObject(); + virtual void OnDrop(IDataObject * obj, CPoint pt ) {} + virtual DWORD DragDropSourceEffects() { return DROPEFFECT_MOVE | DROPEFFECT_COPY;} + virtual void DragDropSourceSucceeded( DWORD effect ) {} + + bool GroupFocusActive() const {return GetGroupFocus() >= 0;} + + void RenderOverlay(const CRect & p_updaterect,CDCHandle p_dc); + + bool IsItemFocused(t_size index) const {return GetFocusItem() == index;} + bool IsGroupHeaderFocused(int p_id) const {return GetGroupFocus() == p_id;} + void ToggleSelection(pfc::bit_array const & mask); + + size_t GetSelectedCount(pfc::bit_array const & mask,size_t max = SIZE_MAX) const; + size_t GetSelectedCount() const {return GetSelectedCount(pfc::bit_array_true());} + size_t GetSingleSel() const; + size_t GetFirstSelected() const; + size_t GetLastSelected() const; + + //! Execute default action per focus or selection depending on what's focused/selected + virtual void ExecuteDefaultActionByFocus(); + + void FocusToUpdateRgn(HRGN rgn); + + + //! Self-contained minimal drag and drop implementation for reordering list items only; dummy IDataObject presented. \n + //! Call from your override of RunDragDrop(), if p_isRightClick is false / left button clicked, never with right button clicked. \n + //! On success, use MakeDropReorderPermutation() to fetch the permutation to apply to your content. + bool RunReorderDragDrop(CPoint ptOrigin, CPoint & ptDrop); + + bool MakeDropReorderPermutation(pfc::array_t & out, CPoint ptDrop) const; + + size_t GetPasteTarget( const CPoint * ptPaste = nullptr ) const; + + CPoint GetContextMenuPoint(LPARAM lp); + CPoint GetContextMenuPoint(CPoint ptGot); + +protected: + void ToggleDDScroll(bool p_state); + void AbortSelectDragMode() {AbortSelectDragMode(false);} + void RenderDropMarkerByOffset(int offset,CDCHandle p_dc); + void RenderDropMarker(CDCHandle dc, t_size item, bool bInside); + bool RenderDropMarkerClipped(CDCHandle dc, const CRect & update, t_size item, bool bInside); + CRect DropMarkerRect(int offset) const; + int DropMarkerOffset(t_size marker) const; + void AddDropMarkToUpdateRgn(HRGN p_rgn, t_size p_index, bool bInside = false) const; + CRect DropMarkerUpdateRect(t_size index,bool bInside) const; + bool GetFocusRect(CRect & p_rect); + bool GetFocusRectAbs(CRect & p_rect); + bool IsOwnDDActive() const {return m_ownDDActive;} + + SIZE DropMarkerMargin() const; + void MakeDropMarkerPen(CPen & out) const; + + virtual void EnsureVisibleRectAbs(const CRect & p_rect); + virtual size_t EvalTypeFind(); + + virtual bool AllowRangeSelect() const { return true; } + + CRect GetWholeSelectionRectAbs() const; + + size_t GetDropMark( ) const { return m_dropMark; } + bool IsDropMarkInside( ) const { return m_dropMarkInside; } + void SetDropMark( size_t idx, bool bInside ); + void ClearDropMark() { SetDropMark(pfc_infinite, false); } +private: + int OnCreatePassThru(LPCREATESTRUCT lpCreateStruct); + void OnDestroyPassThru(); + + struct TDDScrollControl { + bool m_timerActive = false; + + enum {KTimerID = 0x35bb25af,KTimerPeriod = 25}; + }; + + enum { + KSelectionTimerID = 0xad8abd04, + KSelectionTimerPeriod = 50, + }; + + LRESULT OnFocus(UINT,WPARAM,LPARAM,BOOL&); + LRESULT OnKeyDown(UINT,WPARAM,LPARAM,BOOL&); + LRESULT OnLButtonDblClk(UINT,WPARAM,LPARAM,BOOL&); + LRESULT OnButtonDown(UINT,WPARAM,LPARAM,BOOL&); + LRESULT OnRButtonUp(UINT,WPARAM,LPARAM,BOOL&); + LRESULT OnMouseMove(UINT,WPARAM,LPARAM,BOOL&); + LRESULT OnLButtonUp(UINT,WPARAM,LPARAM,BOOL&); + LRESULT OnTimer(UINT,WPARAM,LPARAM,BOOL&); + LRESULT OnCaptureChanged(UINT,WPARAM,LPARAM,BOOL&); + LRESULT OnGetDlgCode(UINT,WPARAM,LPARAM,BOOL&); + void OnChar(UINT nChar, UINT nRepCnt, UINT nFlags); + void RunTypeFind(); + + void OnKeyDown_HomeEndHelper(bool isEnd, int p_keys); + void OnKeyDown_SetIndexHelper(int p_index, int p_keys); + void OnKeyDown_SetIndexDeltaHelper(int p_delta, int p_keys); + void OnKeyDown_SetIndexDeltaLineHelper(int p_delta, int p_keys); + void OnKeyDown_SetIndexDeltaPageHelper(int p_delta, int p_keys); + void SelectGroupHelper(int p_group,int p_keys); + void HandleDragSel(const CPoint & p_pt); + void AbortSelectDragMode(bool p_lostCapture); + void InitSelectDragMode(const CPoint & p_pt,bool p_rightClick = false); + + void ToggleRangeSelection(pfc::bit_array const & mask); + void ToggleGroupSelection(int p_group); + + void HandleDDScroll(); + + void PrepareDragDrop(const CPoint & p_point,bool p_isRightClick); + void AbortPrepareDragDropMode(bool p_lostCapture = false); + + bool TypeFindCheck(DWORD ts = GetTickCount()) const; + + +protected: + // Spacebar handler + void ToggleSelectedItems(); + + void RenderItem(t_size p_item,const CRect & p_itemRect,const CRect & p_updateRect,CDCHandle p_dc); + void RenderGroupHeader(int p_group,const CRect & p_headerRect,const CRect & p_updateRect,CDCHandle p_dc); + void RenderSubItemText(t_size item, t_size subItem,const CRect & subItemRect,const CRect & updateRect,CDCHandle dc, bool allowColors); +private: + bool m_selectDragMode; + CPoint m_selectDragOriginAbs, m_selectDragCurrentAbs; + bool m_selectDragChanged, m_selectDragMoved; + TDDScrollControl m_ddScroll; + + bool m_prepareDragDropMode, m_prepareDragDropModeRightClick; + bool m_noEnsureVisible; + CPoint m_prepareDragDropOrigin; + + bool m_ownDDActive; + bool m_drawThemeText; + pfc::string8 m_typeFind; DWORD m_typeFindTS; + + size_t m_dropMark = SIZE_MAX; bool m_dropMarkInside = false; +}; + +//! CListControlWithSelectionImpl implements virtual methods of CListControlWithSelectionBase, +//! maintaining focus/selection info for you. +class CListControlWithSelectionImpl : public CListControlWithSelectionBase { +public: + + enum { selectionSupportNone = 0, selectionSupportSingle, selectionSupportMulti }; + unsigned m_selectionSupport = selectionSupportMulti; + + void SetSelectionModeNone() { m_selectionSupport = selectionSupportNone; } + void SetSelectionModeSingle() { m_selectionSupport = selectionSupportSingle; } + void SetSelectionModeMulti() { m_selectionSupport = selectionSupportMulti; } + + + CListControlWithSelectionImpl() {} + void SetFocusItem(t_size index); + t_size GetFocusItem() const {return m_groupFocus ? SIZE_MAX : m_focus;} + void SetGroupFocus(int group); + void SetGroupFocusByItem(t_size item); + int GetGroupFocus() const; + bool IsItemSelected(t_size index) const {return index < m_selection.get_size() ? m_selection[index] : false;} + void SetSelection(pfc::bit_array const & affected,pfc::bit_array const & status); + virtual bool CanSelectItem( size_t index ) const { return true; } + t_size GetSelectionStart() const {return m_selectionStart;} + void SetSelectionStart(t_size val) {m_selectionStart = val;} + + void SelHandleReorder(const t_size * order, t_size count); + void SelHandleRemoval(const pfc::bit_array & mask, t_size oldCount); + void SelHandleInsertion(t_size base, t_size count, bool select); + void SelHandleReset(); + + void ReloadData() override; + size_t _DebugGetItemCountSel() const { return m_selection.get_size(); } + + virtual void OnItemsReordered( const size_t* order, size_t count ) override; + virtual void OnItemsRemoved( pfc::bit_array const & mask, size_t oldCount ) override; + virtual void OnItemsInserted( size_t at, size_t count, bool bSelect ) override; + + pfc::bit_array_bittable GetSelectionMask(); // returns an standalone object holding a copy of the state + + bool SelectAll() override; + +protected: + const bool * GetSelectionArray() {RefreshSelectionSize();return m_selection.get_ptr();} + pfc::bit_array_table GetSelectionMaskRef(); // returns a *temporary* object referencing internal data structures + + bool AllowRangeSelect() const override { return m_selectionSupport == selectionSupportMulti; } + +private: + void SetSelectionImpl(pfc::bit_array const & affected,pfc::bit_array const & status); + void RefreshSelectionSize(); + void RefreshSelectionSize(t_size size); + pfc::array_t m_selection; + size_t m_focus = SIZE_MAX, m_selectionStart = SIZE_MAX; + bool m_groupFocus = false; +}; diff --git a/libPPUI/CListControl_EditImpl.h b/libPPUI/CListControl_EditImpl.h new file mode 100644 index 0000000..26b5961 --- /dev/null +++ b/libPPUI/CListControl_EditImpl.h @@ -0,0 +1,62 @@ +#pragma once + +#include "InPlaceEditTable.h" +#include "GDIUtils.h" // MakeTempBrush() + +//! Implements inplace-edit functionality on top of TParent class. Must derive from CListControlHeaderImpl. +template class CListControl_EditImpl : public T_CListControl_EditImpl_Parent, protected InPlaceEdit::CTableEditHelperV2 { +public: + BEGIN_MSG_MAP_EX(CListControl_EditImpl) + CHAIN_MSG_MAP(T_CListControl_EditImpl_Parent) + MESSAGE_HANDLER(WM_CTLCOLOREDIT,OnCtlColor); + MESSAGE_HANDLER(WM_CTLCOLORSTATIC,OnCtlColor); + END_MSG_MAP() + + // IMPLEMENT ME + // virtual void TableEdit_SetField(t_size item, t_size subItem, const char * value) = 0; +protected: + RECT TableEdit_GetItemRect(t_size item, t_size subItem) const override { + return this->GetSubItemRect(item,subItem); + } + void TableEdit_GetField(t_size item, t_size subItem, pfc::string_base & out, t_size & lineCount) override { + lineCount = 1; + if (!this->GetSubItemText(item,subItem,out)) { + PFC_ASSERT(!"Should not get here."); out = ""; + } + } + HWND TableEdit_GetParentWnd() const override {return this->m_hWnd;} + t_size TableEdit_GetItemCount() const override {return this->GetItemCount();} + t_size TableEdit_GetColumnCount() const override {return this->GetColumnCount();} + void TableEdit_SetItemFocus(t_size item, t_size subItem) override { + this->TooltipRemove(); + this->SetFocusItem(item); this->SetSelection(pfc::bit_array_true(), pfc::bit_array_one(item)); + auto rcView = this->GetVisibleRectAbs(); + auto rcEdit = this->GetSubItemRectAbs(item,subItem); + CRect rcTest; + if (!rcTest.IntersectRect(rcView, rcEdit)) { + // Only scroll to subitem if entirely invisible + this->EnsureVisibleRectAbs( rcEdit ); + } + } + void TableEdit_GetColumnOrder(t_size * out, t_size count) const override { + PFC_ASSERT( count == this->GetColumnCount() ); + if ( this->IsHeaderEnabled() ) { + pfc::array_t temp; temp.set_size(count); + WIN32_OP_D( this->GetHeaderCtrl().GetOrderArray(temp.get_size(), temp.get_ptr()) ); + for(t_size walk = 0; walk < count; ++walk) out[walk] = (t_size) temp[walk]; + } else { + for(t_size walk = 0; walk < count; ++walk) out[walk] = walk; + } + } + + void TableEdit_OnColorsChanged() {} +private: + LRESULT OnCtlColor(UINT,WPARAM wp,LPARAM lp,BOOL&) { + CDCHandle dc((HDC)wp); + const COLORREF bkgnd = this->GetSysColorHook(CListControlImpl::colorBackground); + dc.SetTextColor(this->GetSysColorHook(CListControlImpl::colorText)); + dc.SetBkColor(bkgnd); + + return (LRESULT) MakeTempBrush(dc, bkgnd); + } +}; diff --git a/libPPUI/CListViewCtrlEx.h b/libPPUI/CListViewCtrlEx.h new file mode 100644 index 0000000..82e5d69 --- /dev/null +++ b/libPPUI/CListViewCtrlEx.h @@ -0,0 +1,34 @@ +#pragma once + +class CListViewCtrlEx : public CListViewCtrl { +public: + CListViewCtrlEx( HWND wnd = NULL ) : CListViewCtrl(wnd) {} + CListViewCtrlEx const & operator=( HWND wnd ) { m_hWnd = wnd; return *this; } + unsigned InsertColumnEx(unsigned index, const wchar_t * name, unsigned widthDLU); + unsigned AddColumnEx( const wchar_t * name, unsigned widthDLU ); + void FixContextMenuPoint( CPoint & pt ); + unsigned GetColunnCount(); + + unsigned InsertString( unsigned index, const wchar_t * str ); + unsigned InsertString8( unsigned index, const char * str ); + unsigned AddString( const wchar_t * str ); + unsigned AddString8(const char * str); + void SetItemText(unsigned item, unsigned subItem, const wchar_t * str ); + void SetItemText8(unsigned item, unsigned subItem, const char * str ); + + void AutoSizeColumn( int iCol ) { SetColumnWidth(iCol, LVSCW_AUTOSIZE) ;} + int AddGroup(int iGroupID, const wchar_t * header); +}; + +// BOOL HandleLVKeyDownMod() +#define LVN_KEYDOWN_MOD_HANDLER(id, key, mod, func) \ + if (uMsg == WM_NOTIFY && LVN_KEYDOWN == ((LPNMHDR)lParam)->code && id == ((LPNMHDR)lParam)->idFrom && ((LPNMLVKEYDOWN)lParam)->wVKey == (key) && GetHotkeyModifierFlags() == (mod)) \ + { \ + SetMsgHandled(TRUE); \ + lResult = func()?1:0; \ + if(IsMsgHandled()) \ + return TRUE; \ + } + +// BOOL HandleLVCopy() +#define LVN_COPY_HANDLER(id, func) LVN_KEYDOWN_MOD_HANDLER(id, 'C', MOD_CONTROL, func) diff --git a/libPPUI/CMiddleDragImpl.cpp b/libPPUI/CMiddleDragImpl.cpp new file mode 100644 index 0000000..424da28 --- /dev/null +++ b/libPPUI/CMiddleDragImpl.cpp @@ -0,0 +1,40 @@ +#include "stdafx.h" +#include "CMiddleDragImpl.h" + +double CMiddleDragCommon::myPow(double p_val, double p_exp) { + if (p_val < 0) { + return -pow(-p_val, p_exp); + } else { + return pow(p_val, p_exp); + } +} + +double CMiddleDragCommon::ProcessMiddleDragDeltaInternal(double p_delta) { + p_delta *= (double)KTimerPeriod / 25; /*originally calculated for 25ms timer interval*/ + return myPow(p_delta * 0.05, 2.0); +} + +double CMiddleDragCommon::radiusHelper(double p_x, double p_y) { + return sqrt(p_x * p_x + p_y * p_y); +} +int CMiddleDragCommon::mySGN(LONG v) { + if (v > 0) return 1; + else if (v < 0) return -1; + else return 0; +} + +int32_t CMiddleDragCommon::Round(double val, double & acc) { + val += acc; + int32_t ret = (int32_t)floor(val + 0.5); + acc = val - ret; + return ret; +} + +LONG CMiddleDragCommon::LineToPixelsHelper(LONG & p_overflow, LONG p_pixels, LONG p_dpi, LONG p_lineWidth) { + const int lineWidth = MulDiv(p_lineWidth, p_dpi, 96); + if (lineWidth == 0) return 0; + p_overflow += p_pixels; + LONG ret = p_overflow / lineWidth; + p_overflow -= ret * lineWidth; + return ret; +} diff --git a/libPPUI/CMiddleDragImpl.h b/libPPUI/CMiddleDragImpl.h new file mode 100644 index 0000000..bafce5c --- /dev/null +++ b/libPPUI/CMiddleDragImpl.h @@ -0,0 +1,280 @@ +#pragma once + +#include "CIconOverlayWindow.h" +#include +#include "ppresources.h" +#include "win32_utility.h" + +class CMiddleDragCommon { +public: + enum { + KTimerID = 0x389675f8, + KTimerPeriod = 15, + }; + static double myPow(double p_val, double p_exp); + static double ProcessMiddleDragDeltaInternal(double p_delta); + static double radiusHelper(double p_x, double p_y); + static int mySGN(LONG v); + static int32_t Round(double val, double & acc); + static LONG LineToPixelsHelper(LONG & p_overflow, LONG p_pixels, LONG p_dpi, LONG p_lineWidth); +}; + +template +class CMiddleDragImpl : public TBase, protected CMiddleDragCommon { +private: + typedef CMiddleDragImpl TSelf; +public: + template CMiddleDragImpl( arg_t && ... arg ) : TBase(std::forward(arg) ... ) {} + + BEGIN_MSG_MAP_EX(TSelf) + MESSAGE_HANDLER(WM_TIMER,OnTimer); + MESSAGE_HANDLER(WM_CAPTURECHANGED,OnCaptureChanged); + MESSAGE_HANDLER(WM_MBUTTONDOWN,OnMButtonDown); + MESSAGE_HANDLER(WM_MBUTTONDBLCLK,OnMButtonDown); + MESSAGE_HANDLER(WM_MBUTTONUP,OnMButtonUp); + MESSAGE_HANDLER(WM_LBUTTONDOWN,OnMouseAction); + MESSAGE_HANDLER(WM_RBUTTONDOWN,OnMouseAction); + MESSAGE_HANDLER(WM_LBUTTONDBLCLK,OnMouseAction); + MESSAGE_HANDLER(WM_RBUTTONDBLCLK,OnMouseAction); + MESSAGE_HANDLER(WM_LBUTTONUP,OnMouseAction); + MESSAGE_HANDLER(WM_RBUTTONUP,OnMouseAction); + MESSAGE_HANDLER(WM_XBUTTONDOWN,OnMouseAction); + MESSAGE_HANDLER(WM_XBUTTONDBLCLK,OnMouseAction); + MESSAGE_HANDLER(WM_XBUTTONUP,OnMouseAction); + MESSAGE_HANDLER(WM_MOUSEMOVE,OnMouseMove); + MESSAGE_HANDLER(WM_DESTROY,OnDestroyPassThru); + CHAIN_MSG_MAP(TBase); + END_MSG_MAP() +protected: + bool CanSmoothScroll() const { + return !m_active; + } +private: + bool m_active = false, m_dragged = false; + CPoint m_base; + CIconOverlayWindow m_overlay; + double m_accX = 0, m_accY = 0; + + LRESULT OnDestroyPassThru(UINT,WPARAM,LPARAM,BOOL& bHandled) { + if (m_overlay != NULL) m_overlay.DestroyWindow(); + bHandled = FALSE; + return 0; + } + + LRESULT OnMButtonUp(UINT,WPARAM,LPARAM p_lp,BOOL& bHandled) { + if (m_active /*&& m_dragged*/) { + EndDrag(); + } + return 0; + } + + LRESULT OnMButtonDown(UINT,WPARAM,LPARAM p_lp,BOOL& bHandled) { + if (m_active) { + EndDrag(); + return 0; + } + EndDrag(); + this->SetFocus(); + CPoint pt(p_lp); + WIN32_OP_D( this->ClientToScreen(&pt) ); + StartDrag(pt); + return 0; + } + + LRESULT OnMouseMove(UINT,WPARAM,LPARAM p_lp,BOOL& bHandled) { + if (m_active) { + if (!m_dragged) { + CPoint pt(p_lp); + WIN32_OP_D( this->ClientToScreen(&pt) ); + if (pt != m_base) { + m_dragged = true; + } + } + bHandled = TRUE; + } else { + bHandled = FALSE; + } + return 0; + } + + LRESULT OnMouseAction(UINT,WPARAM,LPARAM,BOOL& bHandled) { + EndDrag(); + bHandled = FALSE; + return 0; + } + + void StartDrag(CPoint const & p_point) { + ::SetCapture(NULL); + + if (m_overlay == NULL) { + PFC_ASSERT( this->m_hWnd != NULL ); + if (m_overlay.Create(*this) == NULL) {PFC_ASSERT(!"Should not get here!"); return;} + HANDLE temp; + WIN32_OP_D( (temp = LoadImage(GetThisModuleHandle(),MAKEINTRESOURCE(CPPUIResources::get_IDI_SCROLL()),IMAGE_ICON,32,32,LR_DEFAULTCOLOR)) != NULL ); + m_overlay.AttachIcon((HICON) temp); + } + + //todo sanity checks - don't drag when the entire content is visible, perhaps use a different icon when only vertical or horizontal drag is possible + SetCursor(::LoadCursor(NULL,IDC_SIZEALL)); + m_active = true; + m_dragged = false; + m_base = p_point; + m_accX = m_accY = 0; + this->SetCapture(); + this->SetTimer(KTimerID,KTimerPeriod); + + { + CSize radius(16,16); + CPoint center (p_point); + CRect rect(center - radius, center + radius); + m_overlay.SetWindowPos(HWND_TOPMOST,rect,SWP_SHOWWINDOW | SWP_NOACTIVATE); + } + } + + void EndDrag() { + if (m_active) ::SetCapture(NULL); + } + + void HandleEndDrag() { + if (m_active) { + m_active = false; + this->KillTimer(KTimerID); + if (m_overlay != NULL) m_overlay.ShowWindow(SW_HIDE); + } + } + + LRESULT OnCaptureChanged(UINT,WPARAM,LPARAM,BOOL& bHandled) { + HandleEndDrag(); + bHandled = FALSE; + return 0; + } + + LRESULT OnTimer(UINT,WPARAM p_wp,LPARAM,BOOL& bHandled) { + switch(p_wp) { + case KTimerID: + this->MoveViewOriginDelta(ProcessMiddleDragDelta(CPoint(GetCursorPos()) - m_base)); + return 0; + default: + bHandled = FALSE; + return 0; + } + } + + CPoint ProcessMiddleDragDelta(CPoint p_delta) { + const CSize dpi = QueryScreenDPIEx(); + if (dpi.cx <= 0 || dpi.cy <= 0 || p_delta == CPoint(0, 0)) return CPoint(0, 0); + const double dpiMulX = 96.0 / (double)dpi.cx; + const double dpiMulY = 96.0 / (double)dpi.cy; + + const double deltaTotal = ProcessMiddleDragDeltaInternal(radiusHelper((double)p_delta.x * dpiMulX, (double)p_delta.y * dpiMulY)); + + double xVal = 0, yVal = 0; + + if (p_delta.x == 0) { + yVal = deltaTotal; + } else if (p_delta.y == 0) { + xVal = deltaTotal; + } else { + const double ratio = (double)p_delta.x / (double)p_delta.y; + yVal = sqrt((deltaTotal*deltaTotal) / (1.0 + (ratio*ratio))); + xVal = yVal * ratio; + } + + xVal = mySGN(p_delta.x) * fabs(xVal); + yVal = mySGN(p_delta.y) * fabs(yVal); + + + return CPoint(Round(xVal / dpiMulX, m_accX), Round(yVal / dpiMulY, m_accY)); + } + +}; + +template +class CMiddleDragWrapper : public TBase { +private: + typedef CMiddleDragWrapper TSelf; +public: + template CMiddleDragWrapper(arg_t && ... arg ) : TBase(std::forward(arg) ... ) { m_overflow = CPoint(0, 0); m_lineWidth = CSize(4, 16); } + + BEGIN_MSG_MAP_EX(TSelf) + MESSAGE_HANDLER(WM_CAPTURECHANGED,OnCaptureChanged); + CHAIN_MSG_MAP(TBase) + END_MSG_MAP() + + void MoveViewOriginDelta(CPoint p_delta) { + MoveViewOriginDeltaLines( MiddleDrag_PixelsToLines( m_overflow, p_delta ) ); + } + void MoveViewOriginDeltaLines(CPoint p_delta) { + FireScrollMessage(WM_HSCROLL,p_delta.x); + FireScrollMessage(WM_VSCROLL,p_delta.y); + } + void SetLineWidth(CSize p_width) {m_lineWidth = p_width;} + +private: + void FireScrollMessage(UINT p_msg,int p_delta) { + UINT count = (UINT)(p_delta<0?-p_delta:p_delta); + const UINT which = (p_msg == WM_HSCROLL ? SB_HORZ : SB_VERT); + SCROLLINFO si = {}; si.cbSize = sizeof(si); si.fMask = SIF_PAGE | SIF_RANGE; + if (!this->GetScrollInfo(which, &si) || si.nPage <= 0) return; + while(count >= si.nPage) { + const WPARAM code = p_delta < 0 ? SB_PAGEUP : SB_PAGEDOWN; + this->SendMessage(p_msg, code, 0); + count -= si.nPage; + } + const WPARAM code = p_delta < 0 ? SB_LINEUP : SB_LINEDOWN; + for(UINT walk = 0; walk < count; ++walk) this->SendMessage(p_msg, code, 0); + } + LRESULT OnCaptureChanged(UINT,WPARAM,LPARAM,BOOL& bHandled) { + m_overflow = CPoint(0,0); + bHandled = FALSE; + return 0; + } + + + CPoint MiddleDrag_PixelsToLines(CPoint & p_overflow,CPoint p_pixels) { + const CSize dpi = QueryScreenDPIEx(); + if (dpi.cx <= 0 || dpi.cy <= 0) return CPoint(0,0); + CPoint pt; + pt.x = CMiddleDragCommon::LineToPixelsHelper(p_overflow.x,p_pixels.x,dpi.cx,m_lineWidth.cx); + pt.y = CMiddleDragCommon::LineToPixelsHelper(p_overflow.y,p_pixels.y,dpi.cy,m_lineWidth.cy); + return pt; + } + + CSize m_lineWidth; + + CPoint m_overflow; +}; + +template +class CWindow_DummyMsgMap : public TBase , public CMessageMap { +public: + BOOL ProcessWindowMessage(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, + LRESULT& lResult, DWORD dwMsgMapID = 0) {return FALSE;} +}; + +typedef CMiddleDragImpl > > CMiddleDragImplSimple; + +template +class CContainedWindow_MsgMap : public CContainedWindowT > { +protected: + CContainedWindow_MsgMap() : CContainedWindowT >(msgMapCast(this)) {} +private: + template static CMessageMap * msgMapCast(t* arg) { return arg; } +}; + +template +class CMiddleDragImplCtrlHook : public CMiddleDragImpl > > { +}; + +template class CMiddleDragImplCtrlHookEx : public CMiddleDragImplCtrlHook { +public: + CMiddleDragImplCtrlHookEx(CMessageMap * hook, DWORD hookMapID = 0) : m_hook(*hook), m_hookMapID(hookMapID) {} + + BEGIN_MSG_MAP_EX(CMiddleDragImplCtrlHookEx) + CHAIN_MSG_MAP(CMiddleDragImplCtrlHook) + CHAIN_MSG_MAP_ALT_MEMBER(m_hook,m_hookMapID); + END_MSG_MAP() +private: + DWORD const m_hookMapID; + CMessageMap & m_hook; +}; diff --git a/libPPUI/CPopupTooltipMessage.h b/libPPUI/CPopupTooltipMessage.h new file mode 100644 index 0000000..b9a6c86 --- /dev/null +++ b/libPPUI/CPopupTooltipMessage.h @@ -0,0 +1,101 @@ +#pragma once + +#include "win32_op.h" +#include "wtl-pp.h" + +class CPopupTooltipMessage { +public: + CPopupTooltipMessage(DWORD style = TTS_BALLOON | TTS_NOPREFIX) : m_style(style | WS_POPUP), m_toolinfo(), m_shutDown() {} + void ShowFocus(const TCHAR * message, CWindow wndParent) { + Show(message, wndParent); wndParent.SetFocus(); + } + void Show(const TCHAR * message, CWindow wndParent) { + if (m_shutDown || (message == NULL && m_tooltip.m_hWnd == NULL)) return; + Initialize(); + Hide(); + + if (message != NULL) { + CRect rect; + WIN32_OP_D(wndParent.GetWindowRect(rect)); + ShowInternal(message, wndParent, rect); + } + } + void ShowEx(const TCHAR * message, CWindow wndParent, CRect rect) { + if (m_shutDown) return; + Initialize(); + Hide(); + ShowInternal(message, wndParent, rect); + } + void Hide() { + if (m_tooltip.m_hWnd != NULL && m_tooltip.GetToolCount() > 0) { + m_tooltip.TrackActivate(&m_toolinfo, FALSE); + m_tooltip.DelTool(&m_toolinfo); + } + } + + void CleanUp() { + if (m_tooltip.m_hWnd != NULL) { + m_tooltip.DestroyWindow(); + } + } + void ShutDown() { + m_shutDown = true; CleanUp(); + } +private: + void ShowInternal(const TCHAR * message, CWindow wndParent, CRect rect) { + + PFC_ASSERT(!m_shutDown); + PFC_ASSERT(message != NULL); + PFC_ASSERT(wndParent != NULL); + + if (_tcschr(message, '\n') != nullptr) { + m_tooltip.SetMaxTipWidth(rect.Width()); + } + m_toolinfo.cbSize = sizeof(m_toolinfo); + m_toolinfo.uFlags = TTF_TRACK | TTF_IDISHWND | TTF_ABSOLUTE | TTF_TRANSPARENT | TTF_CENTERTIP; + m_toolinfo.hwnd = wndParent; + m_toolinfo.uId = 0; + m_toolinfo.lpszText = const_cast(message); + m_toolinfo.hinst = NULL; //core_api::get_my_instance(); + if (m_tooltip.AddTool(&m_toolinfo)) { + m_tooltip.TrackPosition(rect.CenterPoint().x, rect.bottom); + m_tooltip.TrackActivate(&m_toolinfo, TRUE); + } + } + void Initialize() { + if (m_tooltip.m_hWnd == NULL) { + WIN32_OP(m_tooltip.Create(NULL, NULL, NULL, m_style)); + } + } + CContainedWindowSimpleT m_tooltip; + TOOLINFO m_toolinfo; + const DWORD m_style; + bool m_shutDown; +}; + + +template class CDialogWithTooltip : public CDialogImpl { +public: + BEGIN_MSG_MAP_EX(CDialogWithTooltip) + MSG_WM_DESTROY(OnDestroy) + END_MSG_MAP() + + void ShowTip(UINT id, const TCHAR * label) { + m_tip.Show(label, this->GetDlgItem(id)); + } + void ShowTip(HWND child, const TCHAR * label) { + m_tip.Show(label, child); + } + + void ShowTipF(UINT id, const TCHAR * label) { + m_tip.ShowFocus(label, this->GetDlgItem(id)); + } + void ShowTipF(HWND child, const TCHAR * label) { + m_tip.ShowFocus(label, child); + } + void HideTip() { m_tip.Hide(); } +private: + void OnDestroy() { m_tip.ShutDown(); this->SetMsgHandled(FALSE); } + CPopupTooltipMessage m_tip; +}; + diff --git a/libPPUI/CPowerRequest.cpp b/libPPUI/CPowerRequest.cpp new file mode 100644 index 0000000..66016c6 --- /dev/null +++ b/libPPUI/CPowerRequest.cpp @@ -0,0 +1,91 @@ +#include "stdafx.h" + +#include "CPowerRequest.h" + +#ifdef CPowerRequestAPI_Avail + + +// win32 API declaration duplicate - not always defined on some of the Windows versions we target +namespace winapi_substitute { + + typedef struct _REASON_CONTEXT { + ULONG Version; + DWORD Flags; + union { + struct { + HMODULE LocalizedReasonModule; + ULONG LocalizedReasonId; + ULONG ReasonStringCount; + LPWSTR *ReasonStrings; + + } Detailed; + + LPWSTR SimpleReasonString; + } Reason; + } REASON_CONTEXT, *PREASON_CONTEXT; + + // + // Power Request APIs + // + + typedef REASON_CONTEXT POWER_REQUEST_CONTEXT, *PPOWER_REQUEST_CONTEXT, *LPPOWER_REQUEST_CONTEXT; + +} + +HANDLE CPowerRequestAPI::PowerCreateRequestNamed( const wchar_t * str ) { + winapi_substitute::REASON_CONTEXT ctx = {POWER_REQUEST_CONTEXT_VERSION, POWER_REQUEST_CONTEXT_SIMPLE_STRING}; + ctx.Reason.SimpleReasonString = const_cast(str); + return this->PowerCreateRequest(&ctx); +} + +CPowerRequest::CPowerRequest(const wchar_t * Reason) : m_Request(INVALID_HANDLE_VALUE), m_bSystem(), m_bDisplay() { + if (m_API.IsValid()) { + winapi_substitute::REASON_CONTEXT ctx = {POWER_REQUEST_CONTEXT_VERSION, POWER_REQUEST_CONTEXT_SIMPLE_STRING}; + ctx.Reason.SimpleReasonString = const_cast(Reason); + m_Request = m_API.PowerCreateRequest(&ctx); + } +} + +void CPowerRequest::SetSystem(bool bSystem) { + if (bSystem == m_bSystem) return; + m_bSystem = bSystem; + if (m_Request != INVALID_HANDLE_VALUE) { + m_API.ToggleSystem( m_Request, bSystem ); + } else { + _UpdateTES(); + } +} + +void CPowerRequest::SetExecution(bool bExecution) { + if (bExecution == m_bSystem) return; + m_bSystem = bExecution; + if (m_Request != INVALID_HANDLE_VALUE) { + m_API.ToggleExecution( m_Request, bExecution ); + } else { + _UpdateTES(); + } +} + +void CPowerRequest::SetDisplay(bool bDisplay) { + if (bDisplay == m_bDisplay) return; + m_bDisplay = bDisplay; + if (m_Request != INVALID_HANDLE_VALUE) { + m_API.ToggleDisplay(m_Request, bDisplay); + } else { + _UpdateTES(); + } +} + +CPowerRequest::~CPowerRequest() { + if (m_Request != INVALID_HANDLE_VALUE) { + CloseHandle(m_Request); + } else { + if (m_bDisplay || m_bSystem) SetThreadExecutionState(ES_CONTINUOUS); + } +} + +void CPowerRequest::_UpdateTES() { + SetThreadExecutionState(ES_CONTINUOUS | (m_bSystem ? ES_SYSTEM_REQUIRED : 0 ) | (m_bDisplay ? ES_DISPLAY_REQUIRED : 0) ); +} +#endif // _WIN32 + diff --git a/libPPUI/CPowerRequest.h b/libPPUI/CPowerRequest.h new file mode 100644 index 0000000..db1b8e2 --- /dev/null +++ b/libPPUI/CPowerRequest.h @@ -0,0 +1,115 @@ +#pragma once + +#ifdef _WIN32 + +#ifdef WINAPI_FAMILY_PARTITION +#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) +#define CPowerRequestAPI_Avail +#endif +#else // no WINAPI_FAMILY_PARTITION, desktop SDK +#define CPowerRequestAPI_Avail +#endif + +#endif // _WIN32 + +#ifdef CPowerRequestAPI_Avail + +typedef HANDLE (WINAPI * pPowerCreateRequest_t) ( + __in void* Context + ); + +typedef BOOL (WINAPI * pPowerSetRequest_t) ( + __in HANDLE PowerRequest, + __in POWER_REQUEST_TYPE RequestType + ); + +typedef BOOL (WINAPI * pPowerClearRequest_t) ( + __in HANDLE PowerRequest, + __in POWER_REQUEST_TYPE RequestType + ); + +class CPowerRequestAPI { +public: + CPowerRequestAPI() : PowerCreateRequest(), PowerSetRequest(), PowerClearRequest() { + Bind(); + } + bool Bind() { + HMODULE kernel32 = GetModuleHandle(_T("kernel32.dll")); + return Bind(PowerCreateRequest, kernel32, "PowerCreateRequest") + && Bind(PowerSetRequest, kernel32, "PowerSetRequest") + && Bind(PowerClearRequest, kernel32, "PowerClearRequest") ; + } + bool IsValid() {return PowerCreateRequest != NULL;} + + void ToggleSystem(HANDLE hRequest, bool bSystem) { + Toggle(hRequest, bSystem, PowerRequestSystemRequired); + } + + void ToggleExecution(HANDLE hRequest, bool bSystem) { + const POWER_REQUEST_TYPE _PowerRequestExecutionRequired = (POWER_REQUEST_TYPE)3; + const POWER_REQUEST_TYPE RequestType = IsWin8() ? _PowerRequestExecutionRequired : PowerRequestSystemRequired; + Toggle(hRequest, bSystem, RequestType); + } + + void ToggleDisplay(HANDLE hRequest, bool bDisplay) { + Toggle(hRequest, bDisplay, PowerRequestDisplayRequired); + } + + void Toggle(HANDLE hRequest, bool bToggle, POWER_REQUEST_TYPE what) { + if (bToggle) { + PowerSetRequest(hRequest, what); + } else { + PowerClearRequest(hRequest, what); + } + + } + HANDLE PowerCreateRequestNamed( const wchar_t * str ); + + static bool IsWin8() { + auto ver = myGetOSVersion(); + return ver >= 0x602; + } + static WORD myGetOSVersion() { + const DWORD ver = GetVersion(); + return (WORD)HIBYTE(LOWORD(ver)) | ((WORD)LOBYTE(LOWORD(ver)) << 8); + } + + pPowerCreateRequest_t PowerCreateRequest; + pPowerSetRequest_t PowerSetRequest; + pPowerClearRequest_t PowerClearRequest; +private: + template static bool Bind(func_t & f, HMODULE dll, const char * name) { + f = reinterpret_cast(GetProcAddress(dll, name)); + return f != NULL; + } +}; + +class CPowerRequest { +public: + CPowerRequest(const wchar_t * Reason); + void SetSystem(bool bSystem); + void SetExecution(bool bExecution); + void SetDisplay(bool bDisplay); + ~CPowerRequest(); +private: + void _UpdateTES(); + HANDLE m_Request; + bool m_bSystem, m_bDisplay; + CPowerRequestAPI m_API; + CPowerRequest(const CPowerRequest&); + void operator=(const CPowerRequest&); +}; +#else + +class CPowerRequest { +public: + CPowerRequest(const wchar_t * Reason) {} + void SetSystem(bool bSystem) {} + void SetExecution(bool bExecution) {} + void SetDisplay(bool bDisplay) {} +private: + CPowerRequest(const CPowerRequest&); + void operator=(const CPowerRequest&); +}; + +#endif // CPowerRequestAPI_Avail diff --git a/libPPUI/CPropVariant.h b/libPPUI/CPropVariant.h new file mode 100644 index 0000000..c94d97b --- /dev/null +++ b/libPPUI/CPropVariant.h @@ -0,0 +1,54 @@ +#pragma once + +class CPropVariant : public PROPVARIANT { +public: + CPropVariant() {init();} + ~CPropVariant() {clear();} + CPropVariant( const CPropVariant & other ) { + init(); + PropVariantCopy( this, &other ); + } + const CPropVariant& operator=( const CPropVariant & other ) { + clear(); + PropVariantCopy(this, &other); + return *this; + } + + bool toInt64(int64_t & out) const { + switch( vt ) { + case VT_I1: out = (int64_t) cVal; return true; + case VT_I2: out = (int64_t) iVal; return true; + case VT_I4: out = (int64_t) lVal; return true; + case VT_I8: out = (int64_t) hVal.QuadPart; return true; + case VT_INT: out = (int64_t) intVal; return true; + default: return false; + } + } + bool toUint64(uint64_t & out) const { + switch( vt ) { + case VT_UI1: out = (uint64_t) bVal; return true; + case VT_UI2: out = (uint64_t) uiVal; return true; + case VT_UI4: out = (uint64_t) ulVal; return true; + case VT_UI8: out = (uint64_t) uhVal.QuadPart; return true; + case VT_UINT: out = (uint64_t) uintVal; return true; + default: return false; + } + } + bool toString( pfc::string_base & out ) const { + switch( vt ) { + case VT_LPSTR: + out = pfc::stringcvt::string_utf8_from_ansi( pszVal ); return true; + case VT_LPWSTR: + out = pfc::stringcvt::string_utf8_from_wide( pwszVal ); return true; + default: return false; + } + } +private: + void clear() { + PropVariantClear( this ); + } + void init() { + PROPVARIANT * pv = this; + PropVariantInit( pv ); + } +}; diff --git a/libPPUI/CWindowCreateAndDelete.h b/libPPUI/CWindowCreateAndDelete.h new file mode 100644 index 0000000..3de2d5f --- /dev/null +++ b/libPPUI/CWindowCreateAndDelete.h @@ -0,0 +1,11 @@ +#pragma once + +#include "win32_op.h" + +template +class CWindowCreateAndDelete : public TClass { +public: + template CWindowCreateAndDelete(HWND parent, arg_t && ... arg) : TClass(std::forward(arg) ...) { WIN32_OP(this->Create(parent) != NULL); } +private: + void OnFinalMessage(HWND wnd) { PFC_ASSERT_NO_EXCEPTION(TClass::OnFinalMessage(wnd)); PFC_ASSERT_NO_EXCEPTION(delete this); } +}; diff --git a/libPPUI/Controls.cpp b/libPPUI/Controls.cpp new file mode 100644 index 0000000..0c3f29a --- /dev/null +++ b/libPPUI/Controls.cpp @@ -0,0 +1,50 @@ +#include "stdafx.h" +#include +#include "Controls.h" +#include "PaintUtils.h" + +void CStaticSeparator::OnPaint(CDCHandle) { + PaintUtils::PaintSeparatorControl(*this); +} + +void CSeparator::OnPaint(CDCHandle dc) { + PaintUtils::PaintSeparatorControl(*this); +} + +CStaticMainInstruction::CStaticMainInstruction() { + SetThemePart(TEXT_MAININSTRUCTION); +} + +void CStaticThemed::OnPaint(CDCHandle) { + if (m_fallback) { + SetMsgHandled(FALSE); return; + } + if (m_theme == NULL) { + m_theme.OpenThemeData(*this, L"TextStyle"); + if (m_theme == NULL) { + m_fallback = true; SetMsgHandled(FALSE); return; + } + } + CPaintDC dc(*this); + TCHAR buffer[512] = {}; + GetWindowText(buffer, _countof(buffer)); + const int txLen = (int) pfc::strlen_max_t(buffer, _countof(buffer)); + CRect contentRect; + WIN32_OP_D(GetClientRect(contentRect)); + SelectObjectScope scopeFont(dc, GetFont()); + dc.SetTextColor(GetSysColor(COLOR_WINDOWTEXT)); + dc.SetBkMode(TRANSPARENT); + + if (txLen > 0) { + CRect rcText(contentRect); + DWORD flags = 0; + DWORD style = GetStyle(); + if (style & SS_LEFT) flags |= DT_LEFT; + else if (style & SS_RIGHT) flags |= DT_RIGHT; + else if (style & SS_CENTER) flags |= DT_CENTER; + if (style & SS_ENDELLIPSIS) flags |= DT_END_ELLIPSIS; + + HRESULT retval = DrawThemeText(m_theme, dc, m_id, 0, buffer, txLen, flags, 0, rcText); + PFC_ASSERT(SUCCEEDED(retval)); + } +} diff --git a/libPPUI/Controls.h b/libPPUI/Controls.h new file mode 100644 index 0000000..fb44d68 --- /dev/null +++ b/libPPUI/Controls.h @@ -0,0 +1,101 @@ +#pragma once + +#pragma comment(lib, "uxtheme.lib") + +#include "wtl-pp.h" +#include "win32_op.h" + +// Separator-in-dialog tool: subclass a static control on init +class CStaticSeparator : public CContainedWindowT, private CMessageMap { +public: + CStaticSeparator() : CContainedWindowT(this, 0) {} + BEGIN_MSG_MAP_EX(CSeparator) + MSG_WM_PAINT(OnPaint) + MSG_WM_SETTEXT(OnSetText) + END_MSG_MAP() +private: + int OnSetText(LPCTSTR lpstrText) { + Invalidate(); + SetMsgHandled(FALSE); + return 0; + } + void OnPaint(CDCHandle); +}; + +// CWindowRegistered with font & text functionality, for creating custom text label classes +template +class CTextControl : public CWindowRegisteredT { +public: + BEGIN_MSG_MAP_EX(CTextControl) + MSG_WM_SETFONT(OnSetFont) + MSG_WM_GETFONT(OnGetFont) + MSG_WM_SETTEXT(OnSetText) + CHAIN_MSG_MAP(__super) + END_MSG_MAP() +private: + HFONT OnGetFont() { + return m_font; + } + void OnSetFont(HFONT font, BOOL bRedraw) { + m_font = font; + if (bRedraw) this->Invalidate(); + } + int OnSetText(LPCTSTR lpstrText) { + this->Invalidate();this->SetMsgHandled(FALSE); return 0; + } + CFontHandle m_font; +}; + + +// Static control subclass with override for theme part used for rendering +class CStaticThemed : public CContainedWindowT, private CMessageMap { +public: + CStaticThemed() : CContainedWindowT(this, 0), m_id(), m_fallback() {} + BEGIN_MSG_MAP_EX(CStaticThemed) + MSG_WM_PAINT(OnPaint) + MSG_WM_THEMECHANGED(OnThemeChanged) + MSG_WM_SETTEXT(OnSetText) + END_MSG_MAP() + + void SetThemePart(int id) {m_id = id; if (m_hWnd != NULL) Invalidate();} +private: + int OnSetText(LPCTSTR lpstrText) { + Invalidate(); + SetMsgHandled(FALSE); + return 0; + } + void OnThemeChanged() { + m_theme.Release(); + m_fallback = false; + } + void OnPaint(CDCHandle); + int m_id; + CTheme m_theme; + bool m_fallback; +}; + +class CStaticMainInstruction : public CStaticThemed { +public: + CStaticMainInstruction(); +}; + + + +class CSeparator : public CTextControl { +public: + BEGIN_MSG_MAP_EX(CSeparator) + MSG_WM_PAINT(OnPaint) + MSG_WM_ENABLE(OnEnable) + CHAIN_MSG_MAP(__super) + END_MSG_MAP() + + static const TCHAR * GetClassName() { + return _T("foobar2000:separator"); + } +private: + void OnEnable(BOOL bEnable) { + Invalidate(); + } + void OnPaint(CDCHandle dc); +}; + diff --git a/libPPUI/GDIUtils.h b/libPPUI/GDIUtils.h new file mode 100644 index 0000000..5f29674 --- /dev/null +++ b/libPPUI/GDIUtils.h @@ -0,0 +1,261 @@ +#pragma once +#include "win32_op.h" + +static HBITMAP CreateDIB24(CSize size) { + struct { + BITMAPINFOHEADER bmi; + } bi = {}; + bi.bmi.biSize = sizeof(bi.bmi); + bi.bmi.biWidth = size.cx; + bi.bmi.biHeight = size.cy; + bi.bmi.biPlanes = 1; + bi.bmi.biBitCount = 24; + bi.bmi.biCompression = BI_RGB; + void * bitsPtr; + return CreateDIBSection(NULL, reinterpret_cast(&bi), DIB_RGB_COLORS,&bitsPtr,0,0); +} + +static HBITMAP CreateDIB16(CSize size) { + struct { + BITMAPINFOHEADER bmi; + } bi = {}; + bi.bmi.biSize = sizeof(bi.bmi); + bi.bmi.biWidth = size.cx; + bi.bmi.biHeight = size.cy; + bi.bmi.biPlanes = 1; + bi.bmi.biBitCount = 16; + bi.bmi.biCompression = BI_RGB; + void * bitsPtr; + return CreateDIBSection(NULL, reinterpret_cast(&bi), DIB_RGB_COLORS,&bitsPtr,0,0); +} + +static HBITMAP CreateDIB8(CSize size, const COLORREF palette[256]) { + struct { + BITMAPINFOHEADER bmi; + COLORREF colors[256]; + } bi = { }; + for(int c = 0; c < 256; ++c ) bi.colors[c] = palette[c]; + bi.bmi.biSize = sizeof(bi.bmi); + bi.bmi.biWidth = size.cx; + bi.bmi.biHeight = size.cy; + bi.bmi.biPlanes = 1; + bi.bmi.biBitCount = 8; + bi.bmi.biCompression = BI_RGB; + bi.bmi.biClrUsed = 256; + void * bitsPtr; + return CreateDIBSection(NULL, reinterpret_cast(&bi), DIB_RGB_COLORS,&bitsPtr,0,0); +} + +static void CreateScaledFont(CFont & out, CFontHandle in, double scale) { + LOGFONT lf; + WIN32_OP_D( in.GetLogFont(lf) ); + int temp = pfc::rint32(scale * lf.lfHeight); + if (temp == 0) temp = pfc::sgn_t(lf.lfHeight); + lf.lfHeight = temp; + WIN32_OP_D( out.CreateFontIndirect(&lf) != NULL ); +} + +static void CreateScaledFontEx(CFont & out, CFontHandle in, double scale, int weight) { + LOGFONT lf; + WIN32_OP_D( in.GetLogFont(lf) ); + int temp = pfc::rint32(scale * lf.lfHeight); + if (temp == 0) temp = pfc::sgn_t(lf.lfHeight); + lf.lfHeight = temp; + lf.lfWeight = weight; + WIN32_OP_D( out.CreateFontIndirect(&lf) != NULL ); +} + +static void CreatePreferencesHeaderFont(CFont & out, CWindow source) { + CreateScaledFontEx(out, source.GetFont(), 1.3, FW_BOLD); +} + +static void CreatePreferencesHeaderFont2(CFont & out, CWindow source) { + CreateScaledFontEx(out, source.GetFont(), 1.1, FW_BOLD); +} + +template +class CAltFontCtrl : public TCtrl { +public: + void Initialize(CWindow wnd, double scale, int weight) { + CreateScaledFontEx(m_font, wnd.GetFont(), scale, weight); + _initWnd(wnd); + } + void MakeHeader(CWindow wnd) { + CreatePreferencesHeaderFont(m_font, wnd); + _initWnd(wnd); + } + void MakeHeader2(CWindow wnd) { + CreatePreferencesHeaderFont2(m_font, wnd); + _initWnd(wnd); + } +private: + void _initWnd(CWindow wnd) { + this->SubclassWindow(wnd); this->SetFont(m_font); + } + CFont m_font; +}; + +class CFontScaled : public CFont { +public: + CFontScaled(HFONT _in, double scale) { + CreateScaledFont(*this, _in, scale); + } +}; + +class DCClipRgnScope { +public: + DCClipRgnScope(HDC dc) : m_dc(dc) { + m_dc.GetClipRgn(m_rgn); + } + ~DCClipRgnScope() { + m_dc.SelectClipRgn(m_rgn); + } + + HRGN OldVal() const throw() {return m_rgn;} + + PFC_CLASS_NOT_COPYABLE_EX(DCClipRgnScope) +private: + CDCHandle m_dc; + CRgn m_rgn; +}; + + +static HBRUSH MakeTempBrush(HDC dc, COLORREF color) throw() { + SetDCBrushColor(dc, color); return (HBRUSH) GetStockObject(DC_BRUSH); +} + +class CDCBrush : public CBrushHandle { +public: + CDCBrush(HDC dc, COLORREF color) throw() { + m_dc = dc; + m_oldCol = m_dc.SetDCBrushColor(color); + m_hBrush = (HBRUSH) GetStockObject(DC_BRUSH); + } + ~CDCBrush() throw() { + m_dc.SetDCBrushColor(m_oldCol); + } + PFC_CLASS_NOT_COPYABLE_EX(CDCBrush) +private: + CDCHandle m_dc; + COLORREF m_oldCol; +}; + +class CDCPen : public CPenHandle { +public: + CDCPen(HDC dc, COLORREF color) throw() { + m_dc = dc; + m_oldCol = m_dc.SetDCPenColor(color); + m_hPen = (HPEN) GetStockObject(DC_PEN); + } + ~CDCPen() throw() { + m_dc.SetDCPenColor(m_oldCol); + } +private: + CDCHandle m_dc; + COLORREF m_oldCol; +}; + + +class CBackBuffer : public CDC { +public: + CBackBuffer() : m_bitmapOld(NULL), m_curSize(0,0) { + CreateCompatibleDC(NULL); + ATLASSERT(m_hDC != NULL); + } + ~CBackBuffer() { + Dispose(); + } + void Attach(HBITMAP bmp, CSize size) { + Dispose(); + m_bitmap.Attach(bmp); m_curSize = size; + m_bitmapOld = SelectBitmap(m_bitmap); + } + void Attach(HBITMAP bmp) { + CSize size; + bool state = CBitmapHandle(bmp).GetSize(size); + ATLASSERT(state); + Attach(bmp, size); + } + BOOL Allocate(CSize size, HDC dcCompatible = NULL) { + if (m_hDC == NULL) return FALSE; + if (m_curSize == size) return TRUE; + Dispose(); + HBITMAP temp; + if (dcCompatible == NULL) { + temp = CreateDIB24(size); + } else { + temp = CreateCompatibleBitmap(dcCompatible, size.cx, size.cy); + } + if (temp == NULL) return FALSE; + Attach(temp); + return TRUE; + } + + void Dispose() { + if (m_bitmap != NULL) { + SelectBitmap(m_bitmapOld); m_bitmapOld = NULL; + m_bitmap.DeleteObject(); + } + m_curSize = CSize(0,0); + } + BOOL GetBitmapPtr(t_uint8 * & ptr, t_ssize & lineWidth) { + if (m_bitmap == NULL) return FALSE; + BITMAP bmp = {}; + if (!m_bitmap.GetBitmap(bmp)) return FALSE; + lineWidth = bmp.bmWidthBytes; + ptr = reinterpret_cast(bmp.bmBits); + return TRUE; + } + CSize GetSize() const { return m_curSize; } + + PFC_CLASS_NOT_COPYABLE_EX(CBackBuffer) +private: + CSize m_curSize; + CBitmap m_bitmap; + HBITMAP m_bitmapOld; +}; + +class CBackBufferScope : public CDCHandle { +public: + CBackBufferScope(HDC hDC, HDC hDCBB, const CRect & rcPaint) : CDCHandle(hDCBB), m_dcOrig(hDC), m_rcPaint(rcPaint) + { + GetClipRgn(m_clipRgnOld); + CRgn temp; + if (m_dcOrig.GetClipRgn(temp) == 1) { + if (m_clipRgnOld != NULL) temp.CombineRgn(m_clipRgnOld,RGN_AND); + SelectClipRgn(temp); + } + IntersectClipRect(rcPaint); + } + + ~CBackBufferScope() + { + m_dcOrig.BitBlt(m_rcPaint.left,m_rcPaint.top,m_rcPaint.Width(),m_rcPaint.Height(),m_hDC,m_rcPaint.left,m_rcPaint.top,SRCCOPY); + SelectClipRgn(m_clipRgnOld); + } +private: + const CRect m_rcPaint; + CDCHandle m_dcOrig; + CRgn m_clipRgnOld; +}; + +class SetTextColorScope { +public: + SetTextColorScope(HDC dc, COLORREF col) throw() : m_dc(dc) { + m_oldCol = SetTextColor(dc, col); + } + ~SetTextColorScope() throw() { + SetTextColor(m_dc, m_oldCol); + } + PFC_CLASS_NOT_COPYABLE_EX(SetTextColorScope) +private: + HDC m_dc; + COLORREF m_oldCol; +}; + +static CSize GetBitmapSize( HBITMAP bmp ) { + CBitmapHandle h ( bmp ); + BITMAP bm = {}; + WIN32_OP_D( h.GetBitmap(bm) ); + return CSize(bm.bmWidth, bm.bmHeight); +} diff --git a/libPPUI/IDI_SCROLL.ico b/libPPUI/IDI_SCROLL.ico new file mode 100644 index 0000000000000000000000000000000000000000..23e279b50c1f8a1d103b14e32f581a8b9bc5df7d GIT binary patch literal 766 zcmZWnF;c`Z3{*H~uJl#3my%zYpW&y4pFl@xI+*bwc2~*SXPn4hd1b94$85o~t`{?V zw9f~#C!lB83VVSq*w+Ae3`Wyp6f?Y5N-UTGZQC}iBwVk#6T$Fsi&KnL4-{)ncRswk z061fS@DL!_19o%^NbZ$3NKASrKdMt{@~cj04xfpWDhr>mj(%d9_)O_hFj92k`Mc8` zKj-_W7b$x^HrFdL@waZHj=d)$=YH&5?~l-MLa@`ioPpNmjP#_(t{t@hzPh*U{(X%u zy`SB;lY3dh$hDk*TE2d@oIYAUzqNcS{4MuE%sq09VJy!m?~#W&nVUCHE#`&xAI3tr AZ~y=R literal 0 HcmV?d00001 diff --git a/libPPUI/IDI_SCROLL.txt b/libPPUI/IDI_SCROLL.txt new file mode 100644 index 0000000..4a5dc87 --- /dev/null +++ b/libPPUI/IDI_SCROLL.txt @@ -0,0 +1,3 @@ +Icon needed by CMiddleDragImpl and things that use it - such as CListControl. +Include IDI_SCROLL.ico in your resources, with identifier set to IDI_SCROLL. +To get libPPUI to recognize your resource, include your resource.h before libPPUI headers in at least one of your source files. \ No newline at end of file diff --git a/libPPUI/IDataObjectUtils.cpp b/libPPUI/IDataObjectUtils.cpp new file mode 100644 index 0000000..be85121 --- /dev/null +++ b/libPPUI/IDataObjectUtils.cpp @@ -0,0 +1,187 @@ +#include "stdafx.h" + +#include "IDataObjectUtils.h" + +HRESULT IDataObjectUtils::DataBlockToSTGMEDIUM(const void * blockPtr, t_size blockSize, STGMEDIUM * medium, DWORD tymed, bool bHere) throw() { + try { + if (bHere) { + switch(tymed) { + case TYMED_ISTREAM: + { + if (medium->pstm == NULL) return E_INVALIDARG; + ULONG written = 0; + HRESULT state; + state = medium->pstm->Write(blockPtr, pfc::downcast_guarded(blockSize),&written); + if (FAILED(state)) return state; + if (written != blockSize) return STG_E_MEDIUMFULL; + return S_OK; + } + default: + return DV_E_TYMED; + } + } else { + if (tymed & TYMED_HGLOBAL) { + HGLOBAL hMem = HGlobalFromMemblock(blockPtr, blockSize); + if (hMem == NULL) return E_OUTOFMEMORY; + medium->tymed = TYMED_HGLOBAL; + medium->hGlobal = hMem; + medium->pUnkForRelease = NULL; + return S_OK; + } + if (tymed & TYMED_ISTREAM) { + HRESULT state; + HGLOBAL hMem = HGlobalFromMemblock(blockPtr, blockSize); + if (hMem == NULL) return E_OUTOFMEMORY; + medium->tymed = TYMED_ISTREAM; + pfc::com_ptr_t stream; + if (FAILED( state = CreateStreamOnHGlobal(hMem,TRUE,stream.receive_ptr()) ) ) { + GlobalFree(hMem); + return state; + } + { + LARGE_INTEGER wtf = {}; + if (FAILED( state = stream->Seek(wtf,STREAM_SEEK_END,NULL) ) ) { + return state; + } + } + medium->pstm = stream.detach(); + medium->pUnkForRelease = NULL; + return S_OK; + } + return DV_E_TYMED; + } + } catch(pfc::exception_not_implemented) { + return E_NOTIMPL; + } catch(std::bad_alloc) { + return E_OUTOFMEMORY; + } catch(...) { + return E_UNEXPECTED; + } +} + + +HGLOBAL IDataObjectUtils::HGlobalFromMemblock(const void * ptr,t_size size) { + HGLOBAL handle = GlobalAlloc(GMEM_MOVEABLE,size); + if (handle != NULL) { + void * destptr = GlobalLock(handle); + if (destptr == NULL) { + GlobalFree(handle); + handle = NULL; + } else { + memcpy(destptr,ptr,size); + GlobalUnlock(handle); + } + } + return handle; +} + +HRESULT IDataObjectUtils::ExtractDataObjectContent(pfc::com_ptr_t obj, UINT format, DWORD aspect, LONG index, pfc::array_t & out) { + FORMATETC fmt = {}; + fmt.cfFormat = format; fmt.dwAspect = aspect; fmt.lindex = index; + fmt.tymed = TYMED_HGLOBAL /* | TYMED_ISTREAM*/; + + STGMEDIUM med = {}; + HRESULT state; + if (FAILED( state = obj->GetData(&fmt,&med) ) ) return state; + ReleaseStgMediumScope relScope(&med); + return STGMEDIUMToDataBlock(med, out); +} + +HRESULT IDataObjectUtils::STGMEDIUMToDataBlock(const STGMEDIUM & med, pfc::array_t & out) { + switch(med.tymed) { + case TYMED_HGLOBAL: + { + CGlobalLockScope lock(med.hGlobal); + out.set_data_fromptr( (const t_uint8*) lock.GetPtr(), lock.GetSize() ); + } + return S_OK; + case TYMED_ISTREAM: + { + HRESULT state; + IStream * stream = med.pstm; + LARGE_INTEGER offset = {}; + STATSTG stats = {}; + if (FAILED( state = stream->Stat(&stats,STATFLAG_NONAME ) ) ) return state; + t_size toRead = pfc::downcast_guarded(stats.cbSize.QuadPart); + out.set_size(toRead); + if (FAILED( state = stream->Seek(offset,STREAM_SEEK_SET,NULL) ) ) return state; + ULONG cbRead = 0; + if (FAILED( state = stream->Read(out.get_ptr(), pfc::downcast_guarded(toRead), &cbRead) ) ) return state; + if (cbRead != toRead) return E_UNEXPECTED; + } + return S_OK; + default: + return DV_E_TYMED; + } +} + +HRESULT IDataObjectUtils::ExtractDataObjectContent(pfc::com_ptr_t obj, UINT format, pfc::array_t & out) { + return ExtractDataObjectContent(obj, format, DVASPECT_CONTENT, -1, out); +} + +HRESULT IDataObjectUtils::ExtractDataObjectContentTest(pfc::com_ptr_t obj, UINT format, DWORD aspect, LONG index) { + FORMATETC fmt = {}; + fmt.cfFormat = format; fmt.dwAspect = aspect; fmt.lindex = index; + for(t_uint32 walk = 0; walk < 32; ++walk) { + const DWORD tymed = 1 << walk; + if ((ExtractDataObjectContent_SupportedTymeds & tymed) != 0) { + fmt.tymed = tymed; + HRESULT state = obj->QueryGetData(&fmt); + if (SUCCEEDED(state)) { + if (state == S_OK) return S_OK; + } else { + if (state != DV_E_TYMED) return state; + } + } + } + return E_FAIL; +} + +HRESULT IDataObjectUtils::ExtractDataObjectContentTest(pfc::com_ptr_t obj, UINT format) { + return ExtractDataObjectContentTest(obj,format,DVASPECT_CONTENT,-1); +} + +HRESULT IDataObjectUtils::ExtractDataObjectString(pfc::com_ptr_t obj, pfc::string_base & out) { + pfc::array_t data; + HRESULT state; + state = ExtractDataObjectContent(obj,CF_UNICODETEXT,data); + if (SUCCEEDED(state)) { + out = pfc::stringcvt::string_utf8_from_os_ex( (const wchar_t*) data.get_ptr(), data.get_size() / sizeof(wchar_t) ); + return S_OK; + } + state = ExtractDataObjectContent(obj,CF_TEXT,data); + if (SUCCEEDED(state)) { + out = pfc::stringcvt::string_utf8_from_os_ex( (const char*) data.get_ptr(), data.get_size() / sizeof(char) ); + return S_OK; + } + return E_FAIL; +} + +HRESULT IDataObjectUtils::SetDataObjectContent(pfc::com_ptr_t obj, UINT format, DWORD aspect, LONG index, const void * data, t_size dataSize) { + STGMEDIUM med = {}; + FORMATETC fmt = {}; + fmt.cfFormat = format; fmt.dwAspect = aspect; fmt.lindex = index; fmt.tymed = TYMED_HGLOBAL; + HRESULT state; + if (FAILED(state = DataBlockToSTGMEDIUM(data,dataSize,&med,TYMED_HGLOBAL,false))) return state; + return obj->SetData(&fmt,&med,TRUE); +} + +HRESULT IDataObjectUtils::SetDataObjectString(pfc::com_ptr_t obj, const char * str) { + pfc::stringcvt::string_wide_from_utf8 s(str); + return SetDataObjectContent(obj,CF_UNICODETEXT,DVASPECT_CONTENT,-1,s.get_ptr(), (s.length()+1) * sizeof(s[0])); +} + +HRESULT IDataObjectUtils::ExtractDataObjectDWORD(pfc::com_ptr_t obj, UINT format, DWORD & val) { + HRESULT state; + pfc::array_t buffer; + if (FAILED( state = ExtractDataObjectContent(obj, format, DVASPECT_CONTENT, -1, buffer) ) ) return state; + if (buffer.get_size() < sizeof(val)) return E_UNEXPECTED; + val = *(DWORD*) buffer.get_ptr(); + return S_OK; +} +HRESULT IDataObjectUtils::SetDataObjectDWORD(pfc::com_ptr_t obj, UINT format, DWORD val) { + return SetDataObjectContent(obj,format,DVASPECT_CONTENT,-1,&val,sizeof(val)); +} +HRESULT IDataObjectUtils::PasteSucceeded(pfc::com_ptr_t obj, DWORD effect) { + return SetDataObjectDWORD(obj, RegisterClipboardFormat(CFSTR_PASTESUCCEEDED), effect); +} diff --git a/libPPUI/IDataObjectUtils.h b/libPPUI/IDataObjectUtils.h new file mode 100644 index 0000000..4a7bf6a --- /dev/null +++ b/libPPUI/IDataObjectUtils.h @@ -0,0 +1,228 @@ +#pragma once + +#include +#include // IAsyncOperation +#include "pp-COM-macros.h" + +namespace IDataObjectUtils { + + class ReleaseStgMediumScope { + public: + ReleaseStgMediumScope(STGMEDIUM * medium) : m_medium(medium) {} + ~ReleaseStgMediumScope() {if (m_medium != NULL) ReleaseStgMedium(m_medium);} + private: + STGMEDIUM * m_medium; + + PFC_CLASS_NOT_COPYABLE_EX(ReleaseStgMediumScope) + }; + + static const DWORD DataBlockToSTGMEDIUM_SupportedTymeds = TYMED_ISTREAM | TYMED_HGLOBAL; + static const DWORD ExtractDataObjectContent_SupportedTymeds = TYMED_ISTREAM | TYMED_HGLOBAL; + + HRESULT DataBlockToSTGMEDIUM(const void * blockPtr, t_size blockSize, STGMEDIUM * medium, DWORD tymed, bool bHere) throw(); + + HGLOBAL HGlobalFromMemblock(const void * ptr,t_size size); + + HRESULT ExtractDataObjectContent(pfc::com_ptr_t obj, UINT format, DWORD aspect, LONG index, pfc::array_t & out); + HRESULT ExtractDataObjectContent(pfc::com_ptr_t obj, UINT format, pfc::array_t & out); + + HRESULT ExtractDataObjectContentTest(pfc::com_ptr_t obj, UINT format, DWORD aspect, LONG index); + HRESULT ExtractDataObjectContentTest(pfc::com_ptr_t obj, UINT format); + + HRESULT ExtractDataObjectString(pfc::com_ptr_t obj, pfc::string_base & out); + HRESULT SetDataObjectString(pfc::com_ptr_t obj, const char * str); + + HRESULT SetDataObjectContent(pfc::com_ptr_t obj, UINT format, DWORD aspect, LONG index, const void * data, t_size dataSize); + + HRESULT STGMEDIUMToDataBlock(const STGMEDIUM & med, pfc::array_t & out); + + HRESULT ExtractDataObjectDWORD(pfc::com_ptr_t obj, UINT format, DWORD & val); + HRESULT SetDataObjectDWORD(pfc::com_ptr_t obj, UINT format, DWORD val); + + HRESULT PasteSucceeded(pfc::com_ptr_t obj, DWORD effect); + + class comparator_FORMATETC { + public: + static int compare(const FORMATETC & v1, const FORMATETC & v2) { + int val; + val = pfc::compare_t(v1.cfFormat,v2.cfFormat); if (val != 0) return val; + val = pfc::compare_t(v1.dwAspect,v2.dwAspect); if (val != 0) return val; + val = pfc::compare_t(v1.lindex, v2.lindex ); if (val != 0) return val; + return 0; + } + }; + + class CDataObjectBase : public IDataObject { + public: + COM_QI_SIMPLE(IDataObject) + + HRESULT STDMETHODCALLTYPE GetData(FORMATETC * formatetc, STGMEDIUM * medium) override { + return GetData_internal(formatetc,medium,false); + } + + HRESULT STDMETHODCALLTYPE GetDataHere(FORMATETC * formatetc, STGMEDIUM * medium) override { + return GetData_internal(formatetc,medium,true); + } + + HRESULT STDMETHODCALLTYPE QueryGetData(FORMATETC * formatetc) override { + if (formatetc == NULL) return E_INVALIDARG; + + if ((DataBlockToSTGMEDIUM_SupportedTymeds & formatetc->tymed) == 0) return DV_E_TYMED; + + try { + return RenderDataTest(formatetc->cfFormat,formatetc->dwAspect,formatetc->lindex); + } PP_COM_CATCH; + } + + + HRESULT STDMETHODCALLTYPE GetCanonicalFormatEtc(FORMATETC * in, FORMATETC * out) override { + //check this again + if (in == NULL || out == NULL) + return E_INVALIDARG; + *out = *in; + return DATA_S_SAMEFORMATETC; + } + + HRESULT STDMETHODCALLTYPE EnumFormatEtc(DWORD dwDirection,IEnumFORMATETC ** ppenumFormatetc) override { + if (dwDirection == DATADIR_GET) { + if (ppenumFormatetc == NULL) return E_INVALIDARG; + return CreateIEnumFORMATETC(ppenumFormatetc); + } else if (dwDirection == DATADIR_SET) { + return E_NOTIMPL; + } else { + return E_INVALIDARG; + } + } + + HRESULT STDMETHODCALLTYPE SetData(FORMATETC * pFormatetc, STGMEDIUM * pmedium, BOOL fRelease) override { + try { + ReleaseStgMediumScope relScope(fRelease ? pmedium : NULL); + if (pFormatetc == NULL || pmedium == NULL) return E_INVALIDARG; + + /*TCHAR buf[256]; + if (GetClipboardFormatName(pFormatetc->cfFormat,buf,PFC_TABSIZE(buf)) > 0) { + buf[PFC_TABSIZE(buf)-1] = 0; + OutputDebugString(TEXT("SetData: ")); OutputDebugString(buf); OutputDebugString(TEXT("\n")); + } else { + OutputDebugString(TEXT("SetData: unknown clipboard format.\n")); + }*/ + + pfc::array_t temp; + HRESULT state = STGMEDIUMToDataBlock(*pmedium,temp); + if (FAILED(state)) return state; + m_entries.set(*pFormatetc,temp); + return S_OK; + } PP_COM_CATCH; + } + HRESULT STDMETHODCALLTYPE DAdvise(FORMATETC * pFormatetc, DWORD advf, IAdviseSink * pAdvSink, DWORD * pdwConnection) override {return OLE_E_ADVISENOTSUPPORTED;} + HRESULT STDMETHODCALLTYPE DUnadvise(DWORD dwConnection) override {return OLE_E_ADVISENOTSUPPORTED;} + HRESULT STDMETHODCALLTYPE EnumDAdvise(IEnumSTATDATA ** ppenumAdvise) override {return OLE_E_ADVISENOTSUPPORTED;} + protected: + typedef pfc::array_t data_t; + virtual HRESULT RenderData(UINT format,DWORD aspect,LONG dataIndex, data_t & out) const { + FORMATETC fmt = {}; + fmt.cfFormat = format; fmt.dwAspect = aspect; fmt.lindex = dataIndex; + const pfc::array_t * entry = m_entries.query_ptr(fmt); + if (entry != NULL) { + out = * entry; + return S_OK; + } + return DV_E_FORMATETC; + } + virtual HRESULT RenderDataTest(UINT format,DWORD aspect,LONG dataIndex) const { + FORMATETC fmt = {}; + fmt.cfFormat = format; fmt.dwAspect = aspect; fmt.lindex = dataIndex; + if (m_entries.have_item(fmt)) return S_OK; + return DV_E_FORMATETC; + } + typedef pfc::list_base_t TFormatList; + + static void AddFormat(TFormatList & out,UINT code) { + FORMATETC fmt = {}; + fmt.dwAspect = DVASPECT_CONTENT; + fmt.lindex = -1; + fmt.cfFormat = code; + for(t_size medWalk = 0; medWalk < 32; ++medWalk) { + const DWORD med = 1 << medWalk; + if ((DataBlockToSTGMEDIUM_SupportedTymeds & med) != 0) { + fmt.tymed = med; + out.add_item(fmt); + } + } + } + + virtual void EnumFormats(TFormatList & out) const { + pfc::avltree_t formats; + for(auto walk = m_entries.cfirst(); walk.is_valid(); ++walk) { + formats.add_item( walk->m_key.cfFormat ); + } + for(auto walk = formats.cfirst(); walk.is_valid(); ++walk) { + AddFormat(out, *walk); + } + } + HRESULT CreateIEnumFORMATETC(IEnumFORMATETC ** outptr) const throw() { + try { + pfc::list_t out; + EnumFormats(out); + return SHCreateStdEnumFmtEtc((UINT)out.get_count(), out.get_ptr(), outptr); + } PP_COM_CATCH; + } + private: + HRESULT GetData_internal(FORMATETC * formatetc, STGMEDIUM * medium,bool bHere) { + if (formatetc == NULL || medium == NULL) return E_INVALIDARG; + + try { + data_t out; + HRESULT hr = RenderData(formatetc->cfFormat,formatetc->dwAspect,formatetc->lindex,out); + if (FAILED(hr)) return hr; + return DataBlockToSTGMEDIUM(out.get_ptr(),out.get_size(),medium,formatetc->tymed,bHere); + } PP_COM_CATCH; + } + + typedef pfc::map_t, comparator_FORMATETC> t_entries; + t_entries m_entries; + }; + +#ifdef __IDataObjectAsyncCapability_INTERFACE_DEFINED__ + typedef IDataObjectAsyncCapability IDataObjectAsyncCapability_t; +#else + typedef IAsyncOperation IDataObjectAsyncCapability_t; +#endif + + class CAsyncDataObjectBase : public CDataObjectBase, public IDataObjectAsyncCapability_t { + BOOL m_inOperation = FALSE; + BOOL m_asyncMode = TRUE; + protected: + COM_QI_BEGIN() + COM_QI_CHAIN(CDataObjectBase) + COM_QI_ENTRY(IDataObjectAsyncCapability_t) + COM_QI_END() + public: + HRESULT STDMETHODCALLTYPE SetAsyncMode(BOOL fDoOpAsync) override { + m_asyncMode = fDoOpAsync; + return S_OK; + } + + HRESULT STDMETHODCALLTYPE GetAsyncMode(BOOL *pfIsOpAsync) override { + if ( pfIsOpAsync == nullptr ) return E_INVALIDARG; + *pfIsOpAsync = m_asyncMode; + return S_OK; + } + + HRESULT STDMETHODCALLTYPE StartOperation(IBindCtx *pbcReserved) override { + m_inOperation = TRUE; + return S_OK; + } + + HRESULT STDMETHODCALLTYPE InOperation(BOOL *pfInAsyncOp) override { + if ( pfInAsyncOp == nullptr ) return E_INVALIDARG; + *pfInAsyncOp = m_inOperation; + return S_OK; + } + + HRESULT STDMETHODCALLTYPE EndOperation(HRESULT hResult,IBindCtx *pbcReserved,DWORD dwEffects) override { + m_inOperation = FALSE; + return S_OK; + } + }; +} diff --git a/libPPUI/InPlaceCombo.cpp b/libPPUI/InPlaceCombo.cpp new file mode 100644 index 0000000..41b8057 --- /dev/null +++ b/libPPUI/InPlaceCombo.cpp @@ -0,0 +1,349 @@ +#include "stdafx.h" + +#include "InPlaceEdit.h" +#include "wtl-pp.h" +#include "win32_op.h" + +#include "AutoComplete.h" +#include "CWindowCreateAndDelete.h" +#include "win32_utility.h" +#include "listview_helper.h" // ListView_GetColumnCount +#include "clipboard.h" + +#include + +#ifndef WM_MOUSEHWHEEL +#define WM_MOUSEHWHEEL 0x20E +#endif + +using namespace InPlaceEdit; + + +namespace { + + enum { + MSG_COMPLETION = WM_USER, + MSG_DISABLE_EDITING + }; + + + // Rationale: more than one HWND on the list is extremely uncommon, hence forward_list + static std::forward_list g_editboxes; + static HHOOK g_hook = NULL /*, g_keyHook = NULL*/; + + static void GAbortEditing(HWND edit, t_uint32 code) { + CWindow parent = ::GetParent(edit); + parent.SendMessage(MSG_DISABLE_EDITING); + parent.PostMessage(MSG_COMPLETION, code, 0); + } + + static void GAbortEditing(t_uint32 code) { + for (auto walk = g_editboxes.begin(); walk != g_editboxes.end(); ++walk) { + GAbortEditing(*walk, code); + } + } + + static bool IsSamePopup(CWindow wnd1, CWindow wnd2) { + return pfc::findOwningPopup(wnd1) == pfc::findOwningPopup(wnd2); + } + + static void MouseEventTest(HWND target, CPoint pt, bool isWheel) { + for (auto walk = g_editboxes.begin(); walk != g_editboxes.end(); ++walk) { + CWindow edit(*walk); + bool cancel = false; + if (target != edit && IsSamePopup(target, edit)) { + cancel = true; + } else if (isWheel) { + CWindow target2 = WindowFromPoint(pt); + if (target2 != edit && IsSamePopup(target2, edit)) { + cancel = true; + } + } + + if (cancel) GAbortEditing(edit, KEditLostFocus); + } + } + + static LRESULT CALLBACK GMouseProc(int nCode, WPARAM wParam, LPARAM lParam) { + if (nCode == HC_ACTION) { + const MOUSEHOOKSTRUCT * mhs = reinterpret_cast(lParam); + switch (wParam) { + case WM_NCLBUTTONDOWN: + case WM_NCRBUTTONDOWN: + case WM_NCMBUTTONDOWN: + case WM_NCXBUTTONDOWN: + case WM_LBUTTONDOWN: + case WM_RBUTTONDOWN: + case WM_MBUTTONDOWN: + case WM_XBUTTONDOWN: + MouseEventTest(mhs->hwnd, mhs->pt, false); + break; + case WM_MOUSEWHEEL: + case WM_MOUSEHWHEEL: + MouseEventTest(mhs->hwnd, mhs->pt, true); + break; + } + } + return CallNextHookEx(g_hook, nCode, wParam, lParam); + } + + static void on_editbox_creation(HWND p_editbox) { + // PFC_ASSERT(core_api::is_main_thread()); + g_editboxes.push_front(p_editbox); + if (g_hook == NULL) { + g_hook = SetWindowsHookEx(WH_MOUSE, GMouseProc, NULL, GetCurrentThreadId()); + } + /*if (g_keyHook == NULL) { + g_keyHook = SetWindowsHookEx(WH_KEYBOARD, GKeyboardProc, NULL, GetCurrentThreadId()); + }*/ + } + static void UnhookHelper(HHOOK & hook) { + HHOOK v = pfc::replace_null_t(hook); + if (v != NULL) UnhookWindowsHookEx(v); + } + static void on_editbox_destruction(HWND p_editbox) { + // PFC_ASSERT(core_api::is_main_thread()); + g_editboxes.remove(p_editbox); + if (g_editboxes.empty()) { + UnhookHelper(g_hook); /*UnhookHelper(g_keyHook);*/ + } + } + + class CInPlaceComboBox : public CContainedWindowSimpleT { + public: + BEGIN_MSG_MAP_EX(CInPlaceComboBox) + //MSG_WM_CREATE(OnCreate) + MSG_WM_DESTROY(OnDestroy) + MSG_WM_GETDLGCODE(OnGetDlgCode) + // MSG_WM_KILLFOCUS(OnKillFocus) + MSG_WM_KEYDOWN(OnKeyDown) + END_MSG_MAP() + + void OnCreation() { + on_editbox_creation(m_hWnd); + } + private: + void OnDestroy() { + m_selfDestruct = true; + on_editbox_destruction(m_hWnd); + SetMsgHandled(FALSE); + } + int OnCreate(LPCREATESTRUCT lpCreateStruct) { + OnCreation(); + SetMsgHandled(FALSE); + return 0; + } + UINT OnGetDlgCode(LPMSG lpMsg) { + if (lpMsg == NULL) { + SetMsgHandled(FALSE); return 0; + } else { + switch (lpMsg->message) { + case WM_KEYDOWN: + case WM_SYSKEYDOWN: + switch (lpMsg->wParam) { + case VK_TAB: + case VK_ESCAPE: + case VK_RETURN: + return DLGC_WANTMESSAGE; + default: + SetMsgHandled(FALSE); return 0; + } + default: + SetMsgHandled(FALSE); return 0; + + } + } + } + void OnKillFocus(CWindow wndFocus) { + if ( wndFocus != NULL ) ForwardCompletion(KEditLostFocus); + SetMsgHandled(FALSE); + } + + void OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags) { + m_suppressChar = nFlags & 0xFF; + switch (nChar) { + case VK_TAB: + ForwardCompletion(IsKeyPressed(VK_SHIFT) ? KEditShiftTab : KEditTab); + return; + case VK_ESCAPE: + ForwardCompletion(KEditAborted); + return; + } + m_suppressChar = 0; + SetMsgHandled(FALSE); + } + + void ForwardCompletion(t_uint32 code) { + if (IsWindowEnabled()) { + CWindow owner = GetParent(); + owner.SendMessage(MSG_DISABLE_EDITING); + owner.PostMessage(MSG_COMPLETION, code, 0); + EnableWindow(FALSE); + } + } + + bool m_selfDestruct = false; + UINT m_suppressChar = 0; + }; + + class InPlaceComboContainer : public CWindowImpl { + public: + DECLARE_WND_CLASS_EX(_T("{18D85006-0CDB-49AB-A563-6A42014309A3}"), 0, -1); + + const pfc::string_list_const * m_initData; + const unsigned m_iDefault; + + HWND Create(CWindow parent) { + + RECT rect_cropped; + { + RECT client; + WIN32_OP_D(parent.GetClientRect(&client)); + IntersectRect(&rect_cropped, &client, &m_initRect); + } + const DWORD containerStyle = WS_BORDER | WS_CHILD; + AdjustWindowRect(&rect_cropped, containerStyle, FALSE); + + + + WIN32_OP(__super::Create(parent, rect_cropped, NULL, containerStyle) != NULL); + + try { + CRect rcClient; + WIN32_OP_D(GetClientRect(rcClient)); + + + DWORD style = WS_CHILD | WS_VISIBLE;//parent is invisible now + + style |= CBS_DROPDOWNLIST; + + + CComboBox edit; + + WIN32_OP(edit.Create(*this, rcClient, NULL, style, 0, ID_MYEDIT) != NULL); + edit.SetFont(parent.GetFont()); + + m_edit.SubclassWindow(edit); + m_edit.OnCreation(); + + +#if 0 // doesn't quite work + if (m_flags & (KFlagAlignCenter | KFlagAlignRight)) { + COMBOBOXINFO info = {sizeof(info)}; + if (m_edit.GetComboBoxInfo(&info)) { + CEdit edit2 = info.hwndList; + if (edit2) { + if (m_flags & KFlagAlignCenter) edit2.ModifyStyle(0, ES_CENTER); + else if (m_flags & KFlagAlignRight) edit2.ModifyStyle(0, ES_RIGHT); + } + } + } +#endif + + + if (m_initData != nullptr) { + const size_t count = m_initData->get_count(); + for (size_t walk = 0; walk < count; ++walk) { + m_edit.AddString(pfc::stringcvt::string_os_from_utf8(m_initData->get_item(walk))); + } + if (m_iDefault < count) m_edit.SetCurSel(m_iDefault); + } + } catch (...) { + PostMessage(MSG_COMPLETION, InPlaceEdit::KEditAborted, 0); + return m_hWnd; + } + + ShowWindow(SW_SHOW); + m_edit.SetFocus(); + + m_initialized = true; + + m_edit.ShowDropDown(); + + PFC_ASSERT(m_hWnd != NULL); + + return m_hWnd; + } + + InPlaceComboContainer(const RECT & p_rect, unsigned p_flags, pfc::string_list_const * initData, unsigned iDefault, comboReply_t p_notify) : m_notify(p_notify), m_initData(initData), m_iDefault(iDefault), m_initRect(p_rect), m_flags(p_flags) { } + + enum { ID_MYEDIT = 666 }; + + BEGIN_MSG_MAP_EX(InPlaceEditContainer) + MESSAGE_HANDLER_EX(WM_CTLCOLOREDIT, MsgForwardToParent) + MESSAGE_HANDLER_EX(WM_CTLCOLORSTATIC, MsgForwardToParent) + MESSAGE_HANDLER_EX(WM_MOUSEWHEEL, MsgLostFocus) + MESSAGE_HANDLER_EX(WM_MOUSEHWHEEL, MsgLostFocus) + MESSAGE_HANDLER_SIMPLE(MSG_DISABLE_EDITING, OnMsgDisableEditing) + MESSAGE_HANDLER_EX(MSG_COMPLETION, OnMsgCompletion) + COMMAND_ID_HANDLER_EX(ID_MYEDIT, OnComboMsg) + MSG_WM_DESTROY(OnDestroy) + END_MSG_MAP() + + HWND GetEditBox() const { return m_edit; } + + private: + void OnDestroy() { m_selfDestruct = true; } + + LRESULT MsgForwardToParent(UINT msg, WPARAM wParam, LPARAM lParam) { + return GetParent().SendMessage(msg, wParam, lParam); + } + LRESULT MsgLostFocus(UINT, WPARAM, LPARAM) { + PostMessage(MSG_COMPLETION, InPlaceEdit::KEditLostFocus, 0); + return 0; + } + void OnMsgDisableEditing() { + ShowWindow(SW_HIDE); + GetParent().UpdateWindow(); + } + LRESULT OnMsgCompletion(UINT, WPARAM wParam, LPARAM lParam) { + PFC_ASSERT(m_initialized); + if ((wParam & KEditMaskReason) != KEditLostFocus) { + GetParent().SetFocus(); + } + OnCompletion((unsigned) wParam ); + if (!m_selfDestruct) { + m_selfDestruct = true; + DestroyWindow(); + } + return 0; + } + void OnComboMsg(UINT code, int, CWindow source) { + if (m_initialized && (code == CBN_SELENDOK || code == CBN_SELENDCANCEL) ) { + PostMessage(MSG_COMPLETION, InPlaceEdit::KEditLostFocus, 0); + } + } + + private: + + void OnCompletion(unsigned p_status) { + if (!m_completed) { + m_completed = true; + if (m_notify) m_notify( p_status, m_edit.GetCurSel() ); + } + } + + const comboReply_t m_notify; + bool m_completed = false; + bool m_initialized = false; + bool m_selfDestruct = false; + const CRect m_initRect; + CInPlaceComboBox m_edit; + const unsigned m_flags; + }; + +} + +static void fail(comboReply_t p_notify) { + p_notify(KEditAborted, UINT_MAX); +} + +HWND InPlaceEdit::StartCombo(HWND p_parentwnd, const RECT & p_rect, unsigned p_flags, pfc::string_list_const & data, unsigned iDefault, comboReply_t p_notify) { + try { + PFC_ASSERT((CWindow(p_parentwnd).GetWindowLong(GWL_STYLE) & WS_CLIPCHILDREN) != 0); + return (new CWindowCreateAndDelete(p_parentwnd, p_rect, p_flags, &data, iDefault, p_notify))->GetEditBox(); + } catch (...) { + fail(p_notify); + return NULL; + } +} diff --git a/libPPUI/InPlaceEdit.cpp b/libPPUI/InPlaceEdit.cpp new file mode 100644 index 0000000..b402769 --- /dev/null +++ b/libPPUI/InPlaceEdit.cpp @@ -0,0 +1,520 @@ +#include "stdafx.h" + +#include "InPlaceEdit.h" +#include "wtl-pp.h" +#include "win32_op.h" + +#include "AutoComplete.h" +#include "CWindowCreateAndDelete.h" +#include "win32_utility.h" +#include "listview_helper.h" // ListView_GetColumnCount +#include "clipboard.h" + +#include + +#ifndef WM_MOUSEHWHEEL +#define WM_MOUSEHWHEEL 0x20E +#endif + +using namespace InPlaceEdit; + + +namespace { + + enum { + MSG_COMPLETION = WM_USER, + MSG_DISABLE_EDITING + }; + + + // Rationale: more than one HWND on the list is extremely uncommon, hence forward_list + static std::forward_list g_editboxes; + static HHOOK g_hook = NULL /*, g_keyHook = NULL*/; + + static void GAbortEditing(HWND edit, t_uint32 code) { + CWindow parent = ::GetParent(edit); + parent.SendMessage(MSG_DISABLE_EDITING); + parent.PostMessage(MSG_COMPLETION, code, 0); + } + + static void GAbortEditing(t_uint32 code) { + for (auto walk = g_editboxes.begin(); walk != g_editboxes.end(); ++walk) { + GAbortEditing(*walk, code); + } + } + + static bool IsSamePopup(CWindow wnd1, CWindow wnd2) { + return pfc::findOwningPopup(wnd1) == pfc::findOwningPopup(wnd2); + } + + static void MouseEventTest(HWND target, CPoint pt, bool isWheel) { + for (auto walk = g_editboxes.begin(); walk != g_editboxes.end(); ++walk) { + CWindow edit(*walk); + bool cancel = false; + if (target != edit && IsSamePopup(target, edit)) { + cancel = true; + } else if (isWheel) { + CWindow target2 = WindowFromPoint(pt); + if (target2 != edit && IsSamePopup(target2, edit)) { + cancel = true; + } + } + + if (cancel) GAbortEditing(edit, KEditLostFocus); + } + } + + static LRESULT CALLBACK GMouseProc(int nCode, WPARAM wParam, LPARAM lParam) { + if (nCode == HC_ACTION) { + const MOUSEHOOKSTRUCT * mhs = reinterpret_cast(lParam); + switch (wParam) { + case WM_NCLBUTTONDOWN: + case WM_NCRBUTTONDOWN: + case WM_NCMBUTTONDOWN: + case WM_NCXBUTTONDOWN: + case WM_LBUTTONDOWN: + case WM_RBUTTONDOWN: + case WM_MBUTTONDOWN: + case WM_XBUTTONDOWN: + MouseEventTest(mhs->hwnd, mhs->pt, false); + break; + case WM_MOUSEWHEEL: + case WM_MOUSEHWHEEL: + MouseEventTest(mhs->hwnd, mhs->pt, true); + break; + } + } + return CallNextHookEx(g_hook, nCode, wParam, lParam); + } + + static void on_editbox_creation(HWND p_editbox) { + // PFC_ASSERT(core_api::is_main_thread()); + g_editboxes.push_front(p_editbox); + if (g_hook == NULL) { + g_hook = SetWindowsHookEx(WH_MOUSE, GMouseProc, NULL, GetCurrentThreadId()); + } + /*if (g_keyHook == NULL) { + g_keyHook = SetWindowsHookEx(WH_KEYBOARD, GKeyboardProc, NULL, GetCurrentThreadId()); + }*/ + } + static void UnhookHelper(HHOOK & hook) { + HHOOK v = pfc::replace_null_t(hook); + if (v != NULL) UnhookWindowsHookEx(v); + } + static void on_editbox_destruction(HWND p_editbox) { + // PFC_ASSERT(core_api::is_main_thread()); + g_editboxes.remove(p_editbox); + if (g_editboxes.empty()) { + UnhookHelper(g_hook); /*UnhookHelper(g_keyHook);*/ + } + } + + class CInPlaceEditBox : public CContainedWindowSimpleT { + public: + CInPlaceEditBox(uint32_t flags) : m_flags(flags) {} + BEGIN_MSG_MAP_EX(CInPlaceEditBox) + //MSG_WM_CREATE(OnCreate) + MSG_WM_DESTROY(OnDestroy) + MSG_WM_GETDLGCODE(OnGetDlgCode) + MSG_WM_KILLFOCUS(OnKillFocus) + MSG_WM_CHAR(OnChar) + MSG_WM_KEYDOWN(OnKeyDown) + MSG_WM_PASTE(OnPaste) + END_MSG_MAP() + + void OnCreation() { + on_editbox_creation(m_hWnd); + } + private: + void OnDestroy() { + m_selfDestruct = true; + on_editbox_destruction(m_hWnd); + SetMsgHandled(FALSE); + } + int OnCreate(LPCREATESTRUCT lpCreateStruct) { + OnCreation(); + SetMsgHandled(FALSE); + return 0; + } + UINT OnGetDlgCode(LPMSG lpMsg) { + if (lpMsg == NULL) { + SetMsgHandled(FALSE); return 0; + } else { + switch (lpMsg->message) { + case WM_KEYDOWN: + case WM_SYSKEYDOWN: + switch (lpMsg->wParam) { + case VK_TAB: + case VK_ESCAPE: + case VK_RETURN: + return DLGC_WANTMESSAGE; + default: + SetMsgHandled(FALSE); return 0; + } + default: + SetMsgHandled(FALSE); return 0; + + } + } + } + void OnKillFocus(CWindow wndFocus) { + if ( wndFocus != NULL ) ForwardCompletion(KEditLostFocus); + SetMsgHandled(FALSE); + } + + bool testPaste(const char* str) { + if (m_flags & InPlaceEdit::KFlagNumberSigned) { + if (pfc::string_is_numeric(str)) return true; + if (str[0] == '-' && pfc::string_is_numeric(str + 1) && GetWindowTextLength() == 0) return true; + return false; + } + if (m_flags & InPlaceEdit::KFlagNumber) { + return pfc::string_is_numeric(str); + } + return true; + } + + void OnPaste() { + if (m_flags & (InPlaceEdit::KFlagNumber | InPlaceEdit::KFlagNumberSigned)) { + pfc::string8 temp; + ClipboardHelper::OpenScope scope; scope.Open(m_hWnd); + if (ClipboardHelper::GetString(temp)) { + if (!testPaste(temp)) return; + } + } + // Let edit box handle it + SetMsgHandled(FALSE); + } + bool testChar(UINT nChar) { + // Allow various non text characters + if (nChar < ' ') return true; + + if (m_flags & InPlaceEdit::KFlagNumberSigned) { + if (pfc::char_is_numeric(nChar)) return true; + if (nChar == '-') { + return GetWindowTextLength() == 0; + } + return false; + } + if (m_flags & InPlaceEdit::KFlagNumber) { + return pfc::char_is_numeric(nChar); + } + return true; + } + void OnChar(UINT nChar, UINT nRepCnt, UINT nFlags) { + if (m_suppressChar != 0) { + UINT code = nFlags & 0xFF; + if (code == m_suppressChar) return; + } + + if (!testChar(nChar)) { + MessageBeep(0); + return; + } + + SetMsgHandled(FALSE); + } + void OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags) { + m_suppressChar = nFlags & 0xFF; + switch (nChar) { + case VK_BACK: + if (GetHotkeyModifierFlags() == MOD_CONTROL) { + CEditPPHooks::DeleteLastWord(*this); + return; + } + break; + case 'A': + if (GetHotkeyModifierFlags() == MOD_CONTROL) { + this->SetSelAll(); return; + } + break; + case VK_RETURN: + if (!IsKeyPressed(VK_LCONTROL) && !IsKeyPressed(VK_RCONTROL)) { + ForwardCompletion(KEditEnter); + return; + } + break; + case VK_TAB: + ForwardCompletion(IsKeyPressed(VK_SHIFT) ? KEditShiftTab : KEditTab); + return; + case VK_ESCAPE: + ForwardCompletion(KEditAborted); + return; + } + m_suppressChar = 0; + SetMsgHandled(FALSE); + } + + void ForwardCompletion(t_uint32 code) { + if (IsWindowEnabled()) { + CWindow owner = GetParent(); + owner.SendMessage(MSG_DISABLE_EDITING); + owner.PostMessage(MSG_COMPLETION, code, 0); + EnableWindow(FALSE); + } + } + + const uint32_t m_flags; + bool m_selfDestruct = false; + UINT m_suppressChar = 0; + }; + + class InPlaceEditContainer : public CWindowImpl { + public: + DECLARE_WND_CLASS_EX(_T("{54340C80-248C-4b8e-8CD4-D624A8E9377B}"), 0, -1); + + + HWND Create(CWindow parent) { + + RECT rect_cropped; + { + RECT client; + WIN32_OP_D(parent.GetClientRect(&client)); + IntersectRect(&rect_cropped, &client, &m_initRect); + } + const DWORD containerStyle = WS_BORDER | WS_CHILD; + AdjustWindowRect(&rect_cropped, containerStyle, FALSE); + + + + WIN32_OP(__super::Create(parent, rect_cropped, NULL, containerStyle) != NULL); + + try { + CRect rcClient; + WIN32_OP_D(GetClientRect(rcClient)); + + + DWORD style = WS_CHILD | WS_VISIBLE;//parent is invisible now + if (m_flags & KFlagMultiLine) style |= WS_VSCROLL | ES_MULTILINE; + else style |= ES_AUTOHSCROLL; + if (m_flags & KFlagReadOnly) style |= ES_READONLY; + if (m_flags & KFlagAlignCenter) style |= ES_CENTER; + else if (m_flags & KFlagAlignRight) style |= ES_RIGHT; + else style |= ES_LEFT; + + // ES_NUMBER is buggy in many ways (repaint glitches after balloon popup) and does not allow signed numbers. + // We implement number handling by filtering WM_CHAR instead. + // if (m_flags & KFlagNumber) style |= ES_NUMBER; + + + CEdit edit; + + WIN32_OP(edit.Create(*this, rcClient, NULL, style, 0, ID_MYEDIT) != NULL); + edit.SetFont(parent.GetFont()); + + if (m_ACData.is_valid()) InitializeSimpleAC(edit, m_ACData.get_ptr(), m_ACOpts); + m_edit.SubclassWindow(edit); + m_edit.OnCreation(); + + pfc::setWindowText(m_edit, *m_content); + m_edit.SetSelAll(); + } catch (...) { + PostMessage(MSG_COMPLETION, InPlaceEdit::KEditAborted, 0); + return m_hWnd; + } + + ShowWindow(SW_SHOW); + m_edit.SetFocus(); + + m_initialized = true; + + PFC_ASSERT(m_hWnd != NULL); + + return m_hWnd; + } + + InPlaceEditContainer(const RECT & p_rect, t_uint32 p_flags, pfc::rcptr_t p_content, reply_t p_notify, IUnknown * ACData, DWORD ACOpts) + : m_content(p_content), m_notify(p_notify), m_completed(false), m_initialized(false), m_changed(false), m_disable_editing(false), m_initRect(p_rect), + m_flags(p_flags), m_selfDestruct(), m_ACData(ACData), m_ACOpts(ACOpts), + m_edit(p_flags) + { + } + + enum { ID_MYEDIT = 666 }; + + BEGIN_MSG_MAP_EX(InPlaceEditContainer) + MESSAGE_HANDLER_EX(WM_CTLCOLOREDIT, MsgForwardToParent) + MESSAGE_HANDLER_EX(WM_CTLCOLORSTATIC, MsgForwardToParent) + MESSAGE_HANDLER_EX(WM_MOUSEWHEEL, MsgLostFocus) + MESSAGE_HANDLER_EX(WM_MOUSEHWHEEL, MsgLostFocus) + MESSAGE_HANDLER_SIMPLE(MSG_DISABLE_EDITING, OnMsgDisableEditing) + MESSAGE_HANDLER_EX(MSG_COMPLETION, OnMsgCompletion) + COMMAND_HANDLER_EX(ID_MYEDIT, EN_CHANGE, OnEditChange) + MSG_WM_DESTROY(OnDestroy) + END_MSG_MAP() + + HWND GetEditBox() const { return m_edit; } + + private: + void OnDestroy() { m_selfDestruct = true; } + + LRESULT MsgForwardToParent(UINT msg, WPARAM wParam, LPARAM lParam) { + return GetParent().SendMessage(msg, wParam, lParam); + } + LRESULT MsgLostFocus(UINT, WPARAM, LPARAM) { + PostMessage(MSG_COMPLETION, InPlaceEdit::KEditLostFocus, 0); + return 0; + } + void OnMsgDisableEditing() { + ShowWindow(SW_HIDE); + GetParent().UpdateWindow(); + m_disable_editing = true; + } + LRESULT OnMsgCompletion(UINT, WPARAM wParam, LPARAM lParam) { + PFC_ASSERT(m_initialized); + if ((wParam & KEditMaskReason) != KEditLostFocus) { + GetParent().SetFocus(); + } + if ( m_changed && m_edit != NULL ) { + *m_content = pfc::getWindowText(m_edit); + } + OnCompletion((unsigned)wParam); + if (!m_selfDestruct) { + m_selfDestruct = true; + DestroyWindow(); + } + return 0; + } + void OnEditChange(UINT, int, CWindow source) { + if (m_initialized && !m_disable_editing) { + *m_content = pfc::getWindowText(source); + m_changed = true; + } + } + + private: + + void OnCompletion(unsigned p_status) { + if (!m_completed) { + m_completed = true; + p_status &= KEditMaskReason; + unsigned code = p_status; + if (m_changed && p_status != KEditAborted) code |= KEditFlagContentChanged; + if (m_notify) m_notify(code); + } + } + + const pfc::rcptr_t m_content; + const reply_t m_notify; + bool m_completed; + bool m_initialized, m_changed; + bool m_disable_editing; + bool m_selfDestruct; + const CRect m_initRect; + const t_uint32 m_flags; + CInPlaceEditBox m_edit; + + const pfc::com_ptr_t m_ACData; + const DWORD m_ACOpts; + }; + +} + +static void fail(reply_t p_notify) { + p_notify(KEditAborted); + // completion_notify::g_signal_completion_async(p_notify, KEditAborted); +} + +HWND InPlaceEdit::Start(HWND p_parentwnd, const RECT & p_rect, bool p_multiline, pfc::rcptr_t p_content, reply_t p_notify) { + return StartEx(p_parentwnd, p_rect, p_multiline ? KFlagMultiLine : 0, p_content, p_notify); +} + +void InPlaceEdit::Start_FromListView(HWND p_listview, unsigned p_item, unsigned p_subitem, unsigned p_linecount, pfc::rcptr_t p_content, reply_t p_notify) { + Start_FromListViewEx(p_listview, p_item, p_subitem, p_linecount, 0, p_content, p_notify); +} + +bool InPlaceEdit::TableEditAdvance_ListView(HWND p_listview, unsigned p_column_base, unsigned & p_item, unsigned & p_column, unsigned p_item_count, unsigned p_column_count, unsigned p_whathappened) { + if (p_column >= p_column_count) return false; + + + pfc::array_t orderRev; + { + pfc::array_t order; + const unsigned orderExCount = /*p_column_base + p_column_count*/ ListView_GetColumnCount(p_listview); + PFC_ASSERT(orderExCount >= p_column_base + p_column_count); + pfc::array_t orderEx; orderEx.set_size(orderExCount); + if (!ListView_GetColumnOrderArray(p_listview, orderExCount, orderEx.get_ptr())) { + PFC_ASSERT(!"Should not get here - probably mis-calculated column count"); + return false; + } + order.set_size(p_column_count); + for (unsigned walk = 0; walk < p_column_count; ++walk) order[walk] = orderEx[p_column_base + walk]; + + orderRev.set_size(p_column_count); order_helper::g_fill(orderRev); + pfc::sort_get_permutation_t(order, pfc::compare_t, p_column_count, orderRev.get_ptr()); + } + + unsigned columnVisible = (unsigned)orderRev[p_column]; + + + if (!TableEditAdvance(p_item, columnVisible, p_item_count, p_column_count, p_whathappened)) return false; + + p_column = (unsigned)order_helper::g_find_reverse(orderRev.get_ptr(), columnVisible); + + return true; +} + +bool InPlaceEdit::TableEditAdvance(unsigned & p_item, unsigned & p_column, unsigned p_item_count, unsigned p_column_count, unsigned p_whathappened) { + if (p_item >= p_item_count || p_column >= p_column_count) return false; + int delta = 0; + + switch (p_whathappened & KEditMaskReason) { + case KEditEnter: + delta = (int)p_column_count; + break; + case KEditTab: + delta = 1; + break; + case KEditShiftTab: + delta = -1; + break; + default: + return false; + } + while (delta > 0) { + p_column++; + if (p_column >= p_column_count) { + p_column = 0; + p_item++; + if (p_item >= p_item_count) return false; + } + delta--; + } + while (delta < 0) { + if (p_column == 0) { + if (p_item == 0) return false; + p_item--; + p_column = p_column_count; + } + p_column--; + delta++; + } + return true; +} + +HWND InPlaceEdit::StartEx(HWND p_parentwnd, const RECT & p_rect, unsigned p_flags, pfc::rcptr_t p_content, reply_t p_notify, IUnknown * ACData, DWORD ACOpts) { + try { + PFC_ASSERT((CWindow(p_parentwnd).GetWindowLong(GWL_STYLE) & WS_CLIPCHILDREN) != 0); + return (new CWindowCreateAndDelete(p_parentwnd, p_rect, p_flags, p_content, p_notify, ACData, ACOpts))->GetEditBox(); + } catch (...) { + fail(p_notify); + return NULL; + } +} + +void InPlaceEdit::Start_FromListViewEx(HWND p_listview, unsigned p_item, unsigned p_subitem, unsigned p_linecount, unsigned p_flags, pfc::rcptr_t p_content, reply_t p_notify) { + try { + ListView_EnsureVisible(p_listview, p_item, FALSE); + RECT itemrect; + WIN32_OP_D(ListView_GetSubItemRect(p_listview, p_item, p_subitem, LVIR_LABEL, &itemrect)); + + const bool multiline = p_linecount > 1; + if (multiline) { + itemrect.bottom = itemrect.top + (itemrect.bottom - itemrect.top) * p_linecount; + } + + StartEx(p_listview, itemrect, p_flags | (multiline ? KFlagMultiLine : 0), p_content, p_notify); + } catch (...) { + fail(p_notify); + } +} diff --git a/libPPUI/InPlaceEdit.h b/libPPUI/InPlaceEdit.h new file mode 100644 index 0000000..63a50e1 --- /dev/null +++ b/libPPUI/InPlaceEdit.h @@ -0,0 +1,44 @@ +#pragma once + +#include + +namespace InPlaceEdit { + + enum { + KEditAborted = 0, + KEditTab, + KEditShiftTab, + KEditEnter, + KEditLostFocus, + + KEditMaskReason = 0xFF, + KEditFlagContentChanged = 0x100, + + KFlagReadOnly = 1 << 0, + KFlagMultiLine = 1 << 1, + KFlagAlignCenter = 1 << 2, + KFlagAlignRight = 1 << 3, + KFlagNumber = 1 << 4, + KFlagNumberSigned = 1 << 5, + + KFlagCombo = 1 << 8, // FOR INTERNAL USE + }; + + typedef std::function< void (unsigned) > reply_t; + + typedef std::function< void(unsigned, unsigned) > comboReply_t; // status, index (UINT_MAX if n/a) + + HWND Start(HWND p_parentwnd, const RECT & p_rect, bool p_multiline, pfc::rcptr_t p_content, reply_t p_notify); + + HWND StartEx(HWND p_parentwnd, const RECT & p_rect, unsigned p_flags, pfc::rcptr_t p_content, reply_t p_notify, IUnknown * ACData = NULL, DWORD ACOpts = 0); + + + HWND StartCombo(HWND p_parentwnd, const RECT & p_rect, unsigned p_flags, pfc::string_list_const & data, unsigned iDefault, comboReply_t p_notify); + + void Start_FromListView(HWND p_listview, unsigned p_item, unsigned p_subitem, unsigned p_linecount, pfc::rcptr_t p_content, reply_t p_notify); + void Start_FromListViewEx(HWND p_listview, unsigned p_item, unsigned p_subitem, unsigned p_linecount, unsigned p_flags, pfc::rcptr_t p_content, reply_t p_notify); + + bool TableEditAdvance(unsigned & p_item, unsigned & p_column, unsigned p_item_count, unsigned p_column_count, unsigned p_whathappened); + bool TableEditAdvance_ListView(HWND p_listview, unsigned p_column_base, unsigned & p_item, unsigned & p_column, unsigned p_item_count, unsigned p_column_count, unsigned p_whathappened); + +} diff --git a/libPPUI/InPlaceEditTable.cpp b/libPPUI/InPlaceEditTable.cpp new file mode 100644 index 0000000..2363ea7 --- /dev/null +++ b/libPPUI/InPlaceEditTable.cpp @@ -0,0 +1,221 @@ +#include "stdafx.h" +#include "InPlaceEditTable.h" + +#include "win32_op.h" +#include "win32_utility.h" +#include "AutoComplete.h" + +#include "listview_helper.h" + +namespace InPlaceEdit { + + bool CTableEditHelperV2::tableEdit_cancel_task() { + bool rv = false; + if (m_taskKill) { + *m_taskKill = true; m_taskKill = nullptr; + rv = true; + } + return rv; + } + reply_t CTableEditHelperV2::tableEdit_create_task() { + tableEdit_cancel_task(); + auto ks = std::make_shared(false); + m_taskKill = ks; + return [ks,this](unsigned code) { + if ( ! * ks ) { + this->tableEdit_on_task_completion( code ); + } + }; + } + t_size CTableEditHelperV2::ColumnToPosition(t_size col) const { + PFC_ASSERT(TableEdit_IsColumnEditable(col)); + pfc::array_t colOrder; GrabColumnOrder(colOrder); + t_size skipped = 0; + for (t_size walk = 0; walk < colOrder.get_size(); ++walk) { + const t_size curCol = colOrder[walk]; + if (TableEdit_IsColumnEditable(curCol)) { + if (curCol == col) return skipped; + ++skipped; + } + } + PFC_ASSERT(!"Should not get here."); + return ~0; + } + t_size CTableEditHelperV2::PositionToColumn(t_size pos) const { + pfc::array_t colOrder; GrabColumnOrder(colOrder); + t_size skipped = 0; + for (t_size walk = 0; walk < colOrder.get_size(); ++walk) { + const t_size curCol = colOrder[walk]; + if (TableEdit_IsColumnEditable(curCol)) { + if (skipped == pos) return curCol; + ++skipped; + } + } + PFC_ASSERT(!"Should not get here."); + return ~0; + } + t_size CTableEditHelperV2::EditableColumnCount() const { + const t_size total = TableEdit_GetColumnCount(); + t_size found = 0; + for (t_size walk = 0; walk < total; ++walk) { + if (TableEdit_IsColumnEditable(walk)) found++; + } + return found; + } + + bool CTableEditHelperV2::TableEdit_Advance(t_size & p_item, t_size & p_subItem, t_uint32 whathappened) { + size_t guardItem = SIZE_MAX, guardSubItem = SIZE_MAX; // infinite loop guard + size_t item = p_item, subItem = p_subItem; + for ( ;; ) { + unsigned _item((unsigned)item), _subItem((unsigned)ColumnToPosition(subItem)); + if (!InPlaceEdit::TableEditAdvance(_item, _subItem, (unsigned)TableEdit_GetItemCount(), (unsigned)EditableColumnCount(), whathappened)) return false; + item = _item; subItem = PositionToColumn(_subItem); + + if ( guardItem == SIZE_MAX ) { + guardItem = item; guardSubItem = subItem; + } else { + // infinite loop guard + if ( item == guardItem && subItem == guardSubItem ) return false; + } + + if (TableEdit_CanAdvanceHere(item, subItem, whathappened)) break; + } + p_item = item; + p_subItem = subItem; + return true; + } + + void CTableEditHelperV2::TableEdit_Abort(bool forwardContent) { + if (tableEdit_cancel_task()) { + if (forwardContent && (m_editFlags & KFlagReadOnly) == 0) { + if (m_editData.is_valid()) { + pfc::string8 temp(*m_editData); + TableEdit_SetField(m_editItem, m_editSubItem, temp); + } + } + m_editData.release(); + m_editDataCombo.reset(); + ::SetFocus(TableEdit_GetParentWnd()); + TableEdit_Finished(); + } + + } + + void CTableEditHelperV2::TableEdit_Start(t_size item, t_size subItem) { + PFC_ASSERT(TableEdit_IsColumnEditable(subItem)); + m_editItem = item; m_editSubItem = subItem; + _ReStart(); + } + + void CTableEditHelperV2::_ReStart() { + PFC_ASSERT(m_editItem < TableEdit_GetItemCount()); + PFC_ASSERT(m_editSubItem < TableEdit_GetColumnCount()); + + TableEdit_SetItemFocus(m_editItem, m_editSubItem); + + m_editFlags = TableEdit_GetEditFlags(m_editItem, m_editSubItem); + + m_editData.release(); + m_editDataCombo.reset(); + + if (m_editFlags & InPlaceEdit::KFlagCombo) { + auto combo = TableEdit_GetCombo(m_editItem, m_editSubItem); + RECT rc = TableEdit_GetItemRect(m_editItem, m_editSubItem); + + auto data = std::make_shared< combo_t >(combo); + m_editDataCombo = data; + + auto task = tableEdit_create_task(); + auto comboTask = [data, task](unsigned status, unsigned sel) { + data->iDefault = sel; + task(status); + }; + + InPlaceEdit::StartCombo(TableEdit_GetParentWnd(), rc, m_editFlags, combo.strings, combo.iDefault, comboTask ); + return; + } + + m_editData.new_t(); + t_size lineCount = 1; + TableEdit_GetField(m_editItem, m_editSubItem, *m_editData, lineCount); + + RECT rc = TableEdit_GetItemRect(m_editItem, m_editSubItem); + if (lineCount > 1) { + rc.bottom = (LONG)( rc.top + (rc.bottom - rc.top) * lineCount ); + m_editFlags |= KFlagMultiLine; + } + auto ac = this->TableEdit_GetAutoCompleteEx(m_editItem, m_editSubItem ); + InPlaceEdit::StartEx(TableEdit_GetParentWnd(), rc, m_editFlags, m_editData, tableEdit_create_task(), ac.data.get_ptr(), ac.options); + } + + CTableEditHelperV2::combo_t CTableEditHelperV2::TableEdit_GetCombo(size_t item, size_t sub) { + return combo_t(); + } + CTableEditHelperV2::autoComplete_t CTableEditHelperV2::TableEdit_GetAutoCompleteEx( size_t item, size_t sub ) { + autoComplete_t ret; + if ( this->TableEdit_GetAutoComplete( item, sub, ret.data ) ) ret.options = ret.optsDefault; + return ret; + } + + void CTableEditHelperV2::tableEdit_on_task_completion(unsigned status) { + tableEdit_cancel_task(); + if (m_editData.is_valid()) { + if (status & InPlaceEdit::KEditFlagContentChanged) { + TableEdit_SetField(m_editItem, m_editSubItem, *m_editData); + } + m_editData.release(); + } + if (m_editDataCombo != nullptr) { + unsigned idx = m_editDataCombo->iDefault; + if ( idx < m_editDataCombo->strings.get_count()) { + const char * text = m_editDataCombo->strings.get_item(idx); + TableEdit_SetField(m_editItem, m_editSubItem, text); + } + + m_editDataCombo.reset(); + } + + if (TableEdit_Advance(m_editItem, m_editSubItem, status)) { + _ReStart(); + } else { + TableEdit_Finished(); + } + } + + + + + + void CTableEditHelperV2_ListView::TableEdit_GetColumnOrder(t_size * out, t_size count) const { + pfc::array_t temp; temp.set_size(count); + WIN32_OP_D(ListView_GetColumnOrderArray(TableEdit_GetParentWnd(), count, temp.get_ptr())); + for (t_size walk = 0; walk < count; ++walk) out[walk] = temp[walk]; + } + + RECT CTableEditHelperV2_ListView::TableEdit_GetItemRect(t_size item, t_size subItem) const { + RECT rc; + WIN32_OP_D(ListView_GetSubItemRect(TableEdit_GetParentWnd(), (int)item, (int)subItem, LVIR_LABEL, &rc)); + return rc; + } + + void CTableEditHelperV2_ListView::TableEdit_GetField(t_size item, t_size subItem, pfc::string_base & out, t_size & lineCount) { + listview_helper::get_item_text(TableEdit_GetParentWnd(), (int)item, (int)subItem, out); + lineCount = pfc::is_multiline(out) ? 5 : 1; + } + void CTableEditHelperV2_ListView::TableEdit_SetField(t_size item, t_size subItem, const char * value) { + WIN32_OP_D(listview_helper::set_item_text(TableEdit_GetParentWnd(), (int)item, (int)subItem, value)); + } + t_size CTableEditHelperV2_ListView::TableEdit_GetItemCount() const { + LRESULT temp; + WIN32_OP_D((temp = ListView_GetItemCount(TableEdit_GetParentWnd())) >= 0); + return (t_size)temp; + } + void CTableEditHelperV2_ListView::TableEdit_SetItemFocus(t_size item, t_size subItem) { + WIN32_OP_D(listview_helper::select_single_item(TableEdit_GetParentWnd(), (int) item)); + } + + t_size CTableEditHelperV2_ListView::TableEdit_GetColumnCount() const { + return (t_size)ListView_GetColumnCount(TableEdit_GetParentWnd()); + } + +} diff --git a/libPPUI/InPlaceEditTable.h b/libPPUI/InPlaceEditTable.h new file mode 100644 index 0000000..9697478 --- /dev/null +++ b/libPPUI/InPlaceEditTable.h @@ -0,0 +1,87 @@ +#pragma once + +#include +#include "InPlaceEdit.h" + +namespace InPlaceEdit { + class NOVTABLE CTableEditHelperV2 { + public: + virtual RECT TableEdit_GetItemRect(t_size item, t_size subItem) const = 0; + virtual void TableEdit_GetField(t_size item, t_size subItem, pfc::string_base & out, t_size & lineCount) = 0; + virtual void TableEdit_SetField(t_size item, t_size subItem, const char * value) = 0; + virtual HWND TableEdit_GetParentWnd() const = 0; + virtual bool TableEdit_Advance(t_size & item, t_size & subItem, t_uint32 whathappened); + virtual bool TableEdit_CanAdvanceHere( size_t item, size_t subItem, uint32_t whatHappened ) const { return true; } + virtual void TableEdit_Finished() {} + virtual t_size TableEdit_GetItemCount() const = 0; + virtual t_size TableEdit_GetColumnCount() const = 0; + virtual void TableEdit_SetItemFocus(t_size item, t_size subItem) = 0; + virtual bool TableEdit_IsColumnEditable(t_size subItem) const { return true; } + virtual void TableEdit_GetColumnOrder(t_size * out, t_size count) const { order_helper::g_fill(out, count); } + virtual t_uint32 TableEdit_GetEditFlags(t_size item, t_size subItem) const { return 0; } + virtual bool TableEdit_GetAutoComplete(t_size item, t_size subItem, pfc::com_ptr_t & out) { return false; } + + struct autoComplete_t { + pfc::com_ptr_t data; + enum { // ACO_* equivalents + optsNone = 0, + optsDefault = 1, + optsAutoSuggest = 1, + optsAutoAppend = 3, + }; + DWORD options = optsNone; + }; + virtual autoComplete_t TableEdit_GetAutoCompleteEx( size_t item, size_t sub ); + + struct combo_t { + unsigned iDefault = 0; + pfc::string_list_impl strings; + }; + + virtual combo_t TableEdit_GetCombo(size_t item, size_t sub); + + void TableEdit_Start(t_size item, t_size subItem); + void TableEdit_Abort(bool forwardContent); + bool TableEdit_IsActive() const { return !!m_taskKill; } + protected: + ~CTableEditHelperV2() { tableEdit_cancel_task(); } + CTableEditHelperV2() {} + private: + void tableEdit_on_task_completion(unsigned p_status); + reply_t tableEdit_create_task(); + bool tableEdit_cancel_task(); + + t_size ColumnToPosition(t_size col) const; + t_size PositionToColumn(t_size pos) const; + t_size EditableColumnCount() const; + void GrabColumnOrder(pfc::array_t & buffer) const { buffer.set_size(TableEdit_GetColumnCount()); TableEdit_GetColumnOrder(buffer.get_ptr(), buffer.get_size()); } + void _ReStart(); + + t_size m_editItem, m_editSubItem; + t_uint32 m_editFlags; + pfc::rcptr_t m_editData; + std::shared_ptr< combo_t > m_editDataCombo; + + std::shared_ptr m_taskKill; + + CTableEditHelperV2( const CTableEditHelperV2 & ) = delete; + void operator=( const CTableEditHelperV2 & ) = delete; + }; + + + + + class NOVTABLE CTableEditHelperV2_ListView : public CTableEditHelperV2 { + public: + RECT TableEdit_GetItemRect(t_size item, t_size subItem) const override; + void TableEdit_GetField(t_size item, t_size subItem, pfc::string_base & out, t_size & lineCount) override; + void TableEdit_SetField(t_size item, t_size subItem, const char * value) override; + + t_size TableEdit_GetColumnCount() const override; + + t_size TableEdit_GetItemCount() const override; + void TableEdit_SetItemFocus(t_size item, t_size subItem) override; + + void TableEdit_GetColumnOrder(t_size * out, t_size count) const override; + }; +} diff --git a/libPPUI/PaintUtils.cpp b/libPPUI/PaintUtils.cpp new file mode 100644 index 0000000..a8ae2cb --- /dev/null +++ b/libPPUI/PaintUtils.cpp @@ -0,0 +1,502 @@ +#include "stdafx.h" +#include + +#include "PaintUtils.h" +#include "gdiplus_helpers.h" +// #include +// #include + +#include "GDIUtils.h" +#include "win32_op.h" +#include "wtl-pp.h" + +namespace PaintUtils { + static t_uint16 extractChannel16(t_uint32 p_color,int p_which) throw() { + return (t_uint16)( ((p_color >> (p_which * 8)) & 0xFF) << 8 ); + } + + static t_uint8 extractbyte(t_uint32 p_val,t_size p_which) throw() { + return (t_uint8) ( (p_val >> (p_which * 8)) & 0xFF ); + } + + t_uint32 BlendColorEx(t_uint32 p_color1, t_uint32 p_color2, double mix) throw() { + PFC_ASSERT(mix >= 0 && mix <= 1); + t_uint32 ret = 0; + for(t_size walk = 0; walk < 3; ++walk) { + int val1 = extractbyte(p_color1,walk), val2 = extractbyte(p_color2,walk); + int val = val1 + pfc::rint32((val2 - val1) * mix); + ret |= (t_uint32)val << (walk * 8); + } + return ret; + } + t_uint32 BlendColor(t_uint32 p_color1, t_uint32 p_color2, int p_percentage) throw() { + PFC_ASSERT(p_percentage <= 100); + t_uint32 ret = 0; + for(t_size walk = 0; walk < 3; ++walk) { + int val1 = extractbyte(p_color1,walk), val2 = extractbyte(p_color2,walk); + int val = val1 + MulDiv(val2 - val1,p_percentage,100); + ret |= (t_uint32)val << (walk * 8); + } + return ret; + } + t_uint32 DriftColor(t_uint32 p_color,unsigned p_delta,bool p_direction) throw() { + t_uint32 ret = 0; + for(t_size walk = 0; walk < 3; ++walk) { + unsigned val = extractbyte(p_color,walk); + if (p_direction) val = 0xFF - val; + if (val < p_delta) val = p_delta; + val += p_delta; + if (val > 0xFF) val = 0xFF; + if (p_direction) val = 0xFF - val; + ret |= (t_uint32)val << (walk * 8); + } + return ret; + } + + void FillVertexColor(TRIVERTEX & p_vertex,t_uint32 p_color,t_uint16 p_alpha) throw() { + p_vertex.Red = extractChannel16(p_color,0); + p_vertex.Green = extractChannel16(p_color,1); + p_vertex.Blue = extractChannel16(p_color,2); + p_vertex.Alpha = p_alpha; + } + + void FillRectSimple(CDCHandle p_dc,const CRect & p_rect,t_uint32 p_color) throw() { + p_dc.FillSolidRect(p_rect, p_color); + } + + void GradientFillRect(CDCHandle p_dc,const CRect & p_rect,t_uint32 p_color1, t_uint32 p_color2, bool p_horizontal) throw() { + TRIVERTEX verticies[2]; + GRADIENT_RECT element = {0,1}; + FillVertexColor(verticies[0],p_color1); + FillVertexColor(verticies[1],p_color2); + verticies[0].x = p_rect.left; verticies[0].y = p_rect.top; + verticies[1].x = p_rect.right; verticies[1].y = p_rect.bottom; + WIN32_OP_D( p_dc.GradientFill(verticies,tabsize(verticies),&element,1,p_horizontal ? GRADIENT_FILL_RECT_H : GRADIENT_FILL_RECT_V) ); + } + + void GradientSplitRect(CDCHandle p_dc,const CRect & p_rect,t_uint32 p_bkColor, t_uint32 p_gradientColor,int p_splitPercent) throw() { + const long split = p_rect.top + MulDiv(p_rect.Height(),p_splitPercent,100); + CRect rcTemp; + rcTemp = p_rect; + rcTemp.bottom = split; + GradientFillRect(p_dc,rcTemp,p_bkColor,p_gradientColor,false); + rcTemp = p_rect; + rcTemp.top = split; + GradientFillRect(p_dc,rcTemp,p_gradientColor,p_bkColor,false); + } + + void GradientBar(CDCHandle p_dc,const CRect & p_rect,t_uint32 p_exterior, t_uint32 p_interior, int p_percentage) throw() { + const int gradientPix = MulDiv(p_rect.Height(),p_percentage,100); + CRect rcTemp; + + rcTemp = p_rect; + rcTemp.bottom = rcTemp.top + gradientPix; + GradientFillRect(p_dc,rcTemp,p_exterior,p_interior,false); + + rcTemp = p_rect; + rcTemp.top += gradientPix; rcTemp.bottom -= gradientPix; + FillRectSimple(p_dc,rcTemp,p_interior); + + rcTemp = p_rect; + rcTemp.top = rcTemp.bottom - gradientPix; + GradientFillRect(p_dc,rcTemp,p_interior,p_exterior,false); + } + + void RenderItemBackground(CDCHandle p_dc,const CRect & p_itemRect,t_size p_item,t_uint32 p_color) throw() { + const DWORD bkColor_base = p_color; + const DWORD bkColor = DriftColor(bkColor_base,3, (p_item&1) != 0); + + //GradientSplitRect(p_dc,p_itemRect,bkColor,BlendColor(bkColor,textColor,7),80); + GradientBar(p_dc,p_itemRect,bkColor_base,bkColor,10); + } + + double Luminance(t_uint32 color) throw() { + double r = extractbyte(color,0), g = extractbyte(color,1), b = extractbyte(color,2); + return (0.2126 * r + 0.7152 * g + 0.0722 * b) / 255.0; + //return (r * 0.3 + g * 0.59 + b * 0.11) / 255.0; + } + t_uint32 DetermineTextColor(t_uint32 bk) throw() { + double l = Luminance(bk); + if ( l > 0.6 ) { + return 0; // black + } else { + return 0xFFFFFF; // white + } + } + + void AddRectToRgn(HRGN p_rgn,CRect const & p_rect) throw() { + CRgn temp; + WIN32_OP_D( temp.CreateRectRgnIndirect(p_rect) != NULL ); + CRgnHandle(p_rgn).CombineRgn(temp,RGN_OR); + } + + void FocusRect2(CDCHandle dc, CRect const & rect, COLORREF bkColor) throw() { + COLORREF txColor = DetermineTextColor( bkColor ); + COLORREF useColor = BlendColor(bkColor, txColor, 50); + CDCBrush brush(dc, useColor); + WIN32_OP_D( dc.FrameRect(rect,brush) ); + } + void FocusRect(CDCHandle dc, CRect const & rect) throw() { + CDCBrush brush(dc, 0x7F7F7F); + WIN32_OP_D( dc.FrameRect(rect,brush) ); + //dc.DrawFocusRect(rect); + } + + namespace TrackBar { + void DrawThumb(HTHEME theme,HDC dc,int state,const RECT * rcThumb, const RECT * rcUpdate) { + if (theme == NULL) { + RECT blah = *rcThumb; + int flags = DFCS_BUTTONPUSH; + switch(state) { + case TUS_NORMAL: + break; + case TUS_DISABLED: + flags |= DFCS_INACTIVE; + break; + case TUS_PRESSED: + flags |= DFCS_PUSHED; + break; + } + DrawFrameControl(dc,&blah,DFC_BUTTON,flags); + } else { + DrawThemeBackground(theme,dc,TKP_THUMB,state,rcThumb,rcUpdate); + } + } + void DrawTrack(HTHEME theme,HDC dc,const RECT * rcTrack, const RECT * rcUpdate) { + if (theme == NULL) { + RECT blah = *rcTrack; + DrawFrameControl(dc,&blah,DFC_BUTTON,DFCS_BUTTONPUSH|DFCS_PUSHED); + } else { + DrawThemeBackground(theme,dc,TKP_TRACK,TKS_NORMAL,rcTrack,rcUpdate); + } + } + + void DrawTrackVolume(HTHEME theme,HDC p_dc,const RECT * rcTrack, const RECT * rcUpdate) { + CMemoryDC dc(p_dc,CRect(rcUpdate)); + CRect rc(*rcTrack); + CRect update(*rcUpdate); + + WIN32_OP_D( dc.BitBlt(update.left,update.top,update.Width(),update.Height(),p_dc,update.left,update.top,SRCCOPY) ); + + + + /*CDCHandle dc(p_dc); + CPen pen; pen.CreatePen(PS_SOLID,1, GetSysColor(COLOR_GRAYTEXT)); + SelectObjectScope scope(dc, pen); + dc.MoveTo(rc.left,rc.bottom); + dc.LineTo(rc.right,rc.bottom); + dc.LineTo(rc.right,rc.top); + dc.LineTo(rc.left,rc.bottom);*/ + + //DrawTrack(theme,dc,rcTrack,rcUpdate); + + try { + Gdiplus::Point points[] = { Gdiplus::Point(rc.left, rc.bottom), Gdiplus::Point(rc.right, rc.bottom), Gdiplus::Point(rc.right, rc.top) } ; + GdiplusErrorHandler eh; + Gdiplus::Graphics graphics(dc); + eh << graphics.GetLastStatus(); + Gdiplus::Color c; + c.SetFromCOLORREF( GetSysColor(COLOR_BTNHIGHLIGHT) ); + Gdiplus::Pen penHL(c); + c.SetFromCOLORREF( GetSysColor(COLOR_BTNSHADOW) ); + Gdiplus::Pen penSH(c); + eh << graphics.SetSmoothingMode(Gdiplus::SmoothingModeHighQuality); + //graphics.DrawPolygon(&pen,points,tabsize(points)); + eh << graphics.DrawLine(&penSH, points[0], points[0] + Gdiplus::Point(0,-1)); + eh << graphics.DrawLine(&penHL, points[0], points[1]); + eh << graphics.DrawLine(&penHL, points[1], points[2]); + eh << graphics.DrawLine(&penSH, points[2], points[0] + Gdiplus::Point(0,-1)); + } catch(std::exception const & e) { + (void) e; + // console::print(e.what()); + } + } + } + + void DrawSmoothedLine(HDC dc, CPoint pt1, CPoint pt2, COLORREF col, double width) { + try { + Gdiplus::Point points[] = { Gdiplus::Point(pt1.x,pt1.y), Gdiplus::Point(pt2.x,pt2.y) }; + GdiplusErrorHandler eh; + Gdiplus::Graphics graphics(dc); + eh << graphics.GetLastStatus(); + Gdiplus::Color c; + c.SetFromCOLORREF( col ); + Gdiplus::Pen pen(c, (Gdiplus::REAL)( width )); + eh << graphics.SetSmoothingMode(Gdiplus::SmoothingModeHighQuality); + //graphics.DrawPolygon(&pen,points,tabsize(points)); + eh << graphics.DrawLine(&pen, points[0], points[1]); + } catch(std::exception const & e) { + (void) e; + // console::print(e.what()); + } + } + + + + static int get_text_width(HDC dc,const TCHAR * src,int len) { + if (len<=0) return 0; + else { + SIZE goatse; + GetTextExtentPoint32(dc,src,len,&goatse); + return goatse.cx; + } + } + + static t_uint32 TextOutColors_TranslateColor(const t_uint32 colors[3], int offset) { + const double v = (double)offset / 3.0; + if (v <= -1) return colors[0]; + else if (v < 0) return BlendColorEx(colors[0], colors[1], v + 1); + else if (v == 0) return colors[1]; + else if (v < 1) return BlendColorEx(colors[1], colors[2], v); + else return colors[2]; + } + + void TextOutColors_StripCodesAppend(pfc::string_formatter & out, const char * in) { + t_size done = 0, walk = 0; + for(;;) { + if (in[walk] == 0) { + if (walk > done) out.add_string_nc(in + done, walk - done); + return; + } + if ((unsigned)in[walk] < 32) { + if (walk > done) {out.add_string_nc(in + done, walk - done);} + done = walk + 1; + } + ++walk; + } + } + void TextOutColors_StripCodes(pfc::string_formatter & out, const char * in) { + out.reset(); TextOutColors_StripCodesAppend(out, in); + } + + static bool IsControlChar(TCHAR c) { + return (unsigned)c < 32; + } + static int MatchTruncat(HDC dc, int & pixels, const TCHAR * text, int textLen) { + int min = 0, max = textLen; + int minWidth = 0; + while(min + 1 < max) { + const int probe = (min + max) / 2; + CSize size; + WIN32_OP( GetTextExtentPoint32(dc, text, probe, &size) ); + if (size.cx <= pixels) {min = probe; minWidth = size.cx;} + else max = probe; + } + pixels = minWidth; + return min; + } + static int TruncatHeadroom(HDC dc) { + CSize size; + WIN32_OP( GetTextExtentPoint32(dc, _T("\x2026"), 1, &size) ); + return size.cx; + } + static void ExtTextOut_Truncat(HDC dc, int x, int y, CRect const & clip, const TCHAR * text, int textLen) { + int width = pfc::max_t(0, clip.right - x - TruncatHeadroom(dc)); + int truncat = MatchTruncat(dc, width, text, textLen); + WIN32_OP( ExtTextOut(dc, x, y, ETO_CLIPPED, &clip, text, truncat, NULL) ); + WIN32_OP( ExtTextOut(dc, x + width, y, ETO_CLIPPED, &clip, _T("\x2026"), 1, NULL) ); + + + } + bool TextContainsCodes(const TCHAR * src) { + for(;;) { + if (*src == 0) return false; + if ((unsigned)*src < 32) return true; + ++src; + } + } + void TextOutColorsEx(HDC dc,const TCHAR * src,const CRect & target,DWORD flags,const t_uint32 colors[3]) { + if (!TextContainsCodes(src)) { + SetTextColorScope cs(dc, colors[1]); + CRect rc(target); + CDCHandle(dc).DrawText(src,(int)_tcslen(src),rc,DT_NOPREFIX | DT_END_ELLIPSIS | DT_SINGLELINE | DT_VCENTER | flags); + } else { + const CSize textSize = PaintUtils::TextOutColors_CalcSize(dc, src); + CPoint origin = target.TopLeft(); + origin.y = (target.top + target.bottom - textSize.cy) / 2; + switch(flags & (DT_LEFT | DT_RIGHT | DT_CENTER)) { + case DT_LEFT: + break; + case DT_RIGHT: + if (textSize.cx < target.Width()) origin.x = target.right - textSize.cx; + break; + case DT_CENTER: + if (textSize.cx < target.Width()) origin.x = (target.right + target.left - textSize.cx) / 2; + break; + } + TextOutColors(dc, src, (int)_tcslen(src), origin, target, colors); + } + } + void TextOutColors(HDC dc,const TCHAR * src,int len,CPoint offset,const CRect & clip,const t_uint32 colors[3], int tabWidthTotal, int tabWidthDiv) { + SetTextAlign(dc,TA_LEFT); + SetBkMode(dc,TRANSPARENT); + + + int walk = 0; + int position = offset.x; + int colorOffset = 0; + int tabs = 0; + int positionTabDelta = 0; + + for(;;) { + int base = walk; + while(walk < len && !IsControlChar(src[walk])) ++walk; + if (walk>base) { + SetTextColor(dc,TextOutColors_TranslateColor(colors, colorOffset)); + int width = get_text_width(dc,src+base,walk-base); + if (position + width > clip.right) { + ExtTextOut_Truncat(dc, position, offset.y, clip, src + base, walk - base); + return; + } + WIN32_OP( ExtTextOut(dc,position,offset.y,ETO_CLIPPED,&clip,src+base,walk-base,0) ); + position += width; + } + if (walk>=len) break; + + while(walk < len && IsControlChar(src[walk])) { + if (src[walk] == TextOutColors_Dim) --colorOffset; + else if (src[walk] == TextOutColors_Highlight) ++colorOffset; + else if (src[walk] == '\t') { + int newDelta = MulDiv(++tabs, tabWidthTotal, tabWidthDiv); + position += newDelta - positionTabDelta; + positionTabDelta = newDelta; + } + walk++; + } + } + } + + CSize TextOutColors_CalcSize(HDC dc, const TCHAR * src) { + CSize acc(0,0); + for(int walk = 0;;) { + const int done = walk; + while(!IsControlChar(src[walk])) ++walk; + if (walk > done) { + CSize temp; + WIN32_OP( GetTextExtentPoint32(dc,src + done, walk - done, &temp) ); + acc.cx += temp.cx; pfc::max_acc(acc.cy, temp.cy); + } + if (src[walk] == 0) return acc; + while(src[walk] != 0 && IsControlChar(src[walk])) ++walk; + } + } + t_uint32 TextOutColors_CalcWidth(HDC dc, const TCHAR * src) { + t_uint32 acc = 0; + for(int walk = 0;;) { + const int done = walk; + while(!IsControlChar(src[walk])) ++walk; + acc += get_text_width(dc, src + done, walk - done); + if (src[walk] == 0) return acc; + while(src[walk] != 0 && IsControlChar(src[walk])) ++walk; + } + } + static const unsigned Marker_Dim = '<', Marker_Highlight = '>'; + + pfc::string TextOutColors_ImportScript(pfc::string script) { + pfc::string_formatter temp; TextOutColors_ImportScript(temp, script.ptr()); return temp.get_ptr(); + } + void TextOutColors_ImportScript(pfc::string_base & out, const char * in) { + out.reset(); + for(;;) { + t_size delta; t_uint32 c; + delta = pfc::utf8_decode_char(in, c); + if (delta == 0) break; + switch(c) { + case '>': + c = PaintUtils::TextOutColors_Highlight; + break; + case '<': + c = PaintUtils::TextOutColors_Dim; + break; + } + out.add_char(c); + in += delta; + } + } + t_uint32 DrawText_TranslateHeaderAlignment(t_uint32 val) { + switch(val & HDF_JUSTIFYMASK) { + case HDF_LEFT: + default: + return DT_LEFT; + case HDF_RIGHT: + return DT_RIGHT; + case HDF_CENTER: + return DT_CENTER; + } + } + + void RenderButton(HWND wnd_, HDC dc_, CRect rcUpdate, bool bPressed) { + CDCHandle dc(dc_); CWindow wnd(wnd_); + CTheme theme; theme.OpenThemeData(wnd, L"BUTTON"); + + RelayEraseBkgnd(wnd, wnd.GetParent(), dc); + + const int part = BP_PUSHBUTTON; + + enum { + stNormal = PBS_NORMAL, + stHot = PBS_HOT, + stDisabled = PBS_DISABLED, + stPressed = PBS_PRESSED, + }; + + int state = 0; + if (!wnd.IsWindowEnabled()) state = stDisabled; + else if (bPressed) state = stPressed; + else state = stNormal; + + CRect rcClient; WIN32_OP_D( wnd.GetClientRect(rcClient) ); + + if (theme != NULL && IsThemePartDefined(theme, part, 0)) { + DrawThemeBackground(theme, dc, part, state, rcClient, &rcUpdate); + } else { + int stateEx = DFCS_BUTTONPUSH; + switch(state) { + case stPressed: stateEx |= DFCS_PUSHED; break; + case stDisabled: stateEx |= DFCS_INACTIVE; break; + } + DrawFrameControl(dc, rcClient, DFC_BUTTON, stateEx); + } + } + + + void PaintSeparatorControl(HWND wnd_) { + CWindow wnd(wnd_); + CPaintDC dc(wnd); + TCHAR buffer[512] = {}; + wnd.GetWindowText(buffer, _countof(buffer)); + const int txLen = (int) pfc::strlen_max_t(buffer, _countof(buffer)); + CRect contentRect; + WIN32_OP_D(wnd.GetClientRect(contentRect)); + + dc.SetTextColor(GetSysColor(COLOR_WINDOWTEXT)); + dc.SetBkMode(TRANSPARENT); + + { + CBrushHandle brush = (HBRUSH)wnd.GetParent().SendMessage(WM_CTLCOLORSTATIC, (WPARAM)(HDC)dc, (LPARAM)wnd.m_hWnd); + if (brush != NULL) dc.FillRect(contentRect, brush); + } + SelectObjectScope scopeFont(dc, wnd.GetFont()); + + if (txLen > 0) { + CRect rcText(contentRect); + if (!wnd.IsWindowEnabled()) { + dc.SetTextColor(GetSysColor(COLOR_GRAYTEXT)); + } + WIN32_OP_D(dc.DrawText(buffer, txLen, rcText, DT_NOPREFIX | DT_END_ELLIPSIS | DT_SINGLELINE | DT_VCENTER | DT_LEFT) > 0); + // WIN32_OP_D( dc.GrayString(NULL, NULL, (LPARAM) buffer, txLen, rcText.left, rcText.top, rcText.Width(), rcText.Height() ) ); + } + + SIZE txSize, probeSize; + const TCHAR probe[] = _T("#"); + if (dc.GetTextExtent(buffer, txLen, &txSize) && dc.GetTextExtent(probe, _countof(probe), &probeSize)) { + int spacing = txSize.cx > 0 ? (probeSize.cx / 4) : 0; + if (txSize.cx + spacing < contentRect.Width()) { + const CPoint center = contentRect.CenterPoint(); + CRect rcEdge(contentRect.left + txSize.cx + spacing, center.y, contentRect.right, contentRect.bottom); + WIN32_OP_D(dc.DrawEdge(rcEdge, EDGE_ETCHED, BF_TOP)); + } + } + } +} + diff --git a/libPPUI/PaintUtils.h b/libPPUI/PaintUtils.h new file mode 100644 index 0000000..995826b --- /dev/null +++ b/libPPUI/PaintUtils.h @@ -0,0 +1,53 @@ +#pragma once + +#include + +namespace PaintUtils { + t_uint32 BlendColor(t_uint32 p_color1, t_uint32 p_color2, int p_percentage = 50) throw(); + t_uint32 BlendColorEx(t_uint32 p_color1, t_uint32 p_color2, double mix = 0.5) throw(); + t_uint32 DriftColor(t_uint32 p_color,unsigned p_delta,bool p_direction) throw(); + void FillVertexColor(TRIVERTEX & p_vertex,t_uint32 p_color,t_uint16 p_alpha = 0) throw(); + void FillRectSimple(CDCHandle p_dc,const CRect & p_rect,t_uint32 p_color) throw(); + void GradientFillRect(CDCHandle p_dc,const CRect & p_rect,t_uint32 p_color1, t_uint32 p_color2, bool p_horizontal) throw(); + void GradientSplitRect(CDCHandle p_dc,const CRect & p_rect,t_uint32 p_bkColor, t_uint32 p_gradientColor,int p_splitPercent) throw(); + void GradientBar(CDCHandle p_dc,const CRect & p_rect,t_uint32 p_exterior, t_uint32 p_interior, int p_percentage) throw(); + void RenderItemBackground(CDCHandle p_dc,const CRect & p_itemRect,t_size p_item,t_uint32 p_color) throw(); + + t_uint32 DetermineTextColor(t_uint32 background) throw(); + double Luminance(t_uint32 color) throw(); + + void AddRectToRgn(HRGN rgn, CRect const & rect) throw(); + + void FocusRect(CDCHandle dc, CRect const & rect) throw(); + void FocusRect2(CDCHandle dc, CRect const & rect, COLORREF bkColor) throw(); + + namespace TrackBar { + void DrawThumb(HTHEME theme,HDC dc,int state,const RECT * rcThumb, const RECT * rcUpdate); + void DrawTrack(HTHEME theme,HDC dc,const RECT * rcTrack, const RECT * rcUpdate); + void DrawTrackVolume(HTHEME theme,HDC dc,const RECT * rcTrack, const RECT * rcUpdate); + }; + + void DrawSmoothedLine(HDC dc, CPoint p1, CPoint p2, COLORREF col, double width); + + enum { + TextOutColors_Dim = 21, + TextOutColors_Highlight = 22, + }; + void TextOutColors(HDC dc,const TCHAR * src,int len,CPoint offset,const CRect & clip,const t_uint32 colors[3], int tabWidthTotal = 0, int tabWidthDiv = 1); + void TextOutColorsEx(HDC dc,const TCHAR * src,const CRect & target,DWORD flags,const t_uint32 colors[3]); + void TextOutColors_StripCodesAppend(pfc::string_formatter & out, const char * in); + void TextOutColors_StripCodes(pfc::string_formatter & out, const char * in); + + t_uint32 TextOutColors_CalcWidth(HDC dc, const TCHAR * src); + CSize TextOutColors_CalcSize(HDC dc, const TCHAR * src); + + pfc::string TextOutColors_ImportScript(pfc::string script); + void TextOutColors_ImportScript(pfc::string_base & out, const char * in); + + bool TextContainsCodes(const TCHAR * src); + + t_uint32 DrawText_TranslateHeaderAlignment(t_uint32 val); + + void RenderButton(HWND wnd_, HDC dc_, CRect rcUpdate, bool bPressed); + void PaintSeparatorControl(HWND wnd_); +} diff --git a/libPPUI/SmartStrStr.cpp b/libPPUI/SmartStrStr.cpp new file mode 100644 index 0000000..850c5ef --- /dev/null +++ b/libPPUI/SmartStrStr.cpp @@ -0,0 +1,195 @@ +#include "StdAfx.h" +#include "SmartStrStr.h" + +SmartStrStr::SmartStrStr() { + for (uint32_t walk = 128; walk < 0x10000; ++walk) { + uint32_t c = Transform(walk); + if (c != walk) { + substituions[walk].insert(c); + } + } + for (uint32_t walk = 32; walk < 0x10000; ++walk) { + auto lo = ToLower(walk); + if (lo != walk) { + auto & s = substituions[walk]; s.insert(lo); + + auto iter = substituions.find(lo); + if (iter != substituions.end()) { + s.insert(iter->second.begin(), iter->second.end()); + } + } + } + + InitTwoCharMappings(); +} + + +const wchar_t * SmartStrStr::matchHereW(const wchar_t * pString, const wchar_t * pUserString) const { + auto walkData = pString; + auto walkUser = pUserString; + for (;; ) { + if (*walkUser == 0) return walkData; + + uint32_t cData, cUser; + size_t dData = pfc::utf16_decode_char(walkData, &cData); + size_t dUser = pfc::utf16_decode_char(walkUser, &cUser); + if (dData == 0 || dUser == 0) return nullptr; + + if (cData != cUser) { + bool gotMulti = false; + { + auto multi = twoCharMappings.find(cData); + if (multi != twoCharMappings.end()) { + const char * cDataSubst = multi->second; + PFC_ASSERT(strlen(cDataSubst) == 2); + if (matchOneChar(cUser, (uint32_t)cDataSubst[0])) { + auto walkUser2 = walkUser + dUser; + uint32_t cUser2; + auto dUser2 = pfc::utf16_decode_char(walkUser2, &cUser2); + if (matchOneChar(cUser2, (uint32_t)cDataSubst[1])) { + gotMulti = true; + dUser += dUser2; + } + } + } + } + if (!gotMulti) { + if (!matchOneChar(cUser, cData)) return nullptr; + } + } + + walkData += dData; + walkUser += dUser; + } +} + +bool SmartStrStr::equals(const char * pString, const char * pUserString) const { + auto p = matchHere(pString, pUserString); + if ( p == nullptr ) return false; + return *p == 0; +} + +const char * SmartStrStr::matchHere(const char * pString, const char * pUserString) const { + const char * walkData = pString; + const char * walkUser = pUserString; + for (;; ) { + if (*walkUser == 0) return walkData; + + uint32_t cData, cUser; + size_t dData = pfc::utf8_decode_char(walkData, cData); + size_t dUser = pfc::utf8_decode_char(walkUser, cUser); + if (dData == 0 || dUser == 0) return nullptr; + + if (cData != cUser) { + bool gotMulti = false; + { + auto multi = twoCharMappings.find(cData); + if (multi != twoCharMappings.end()) { + const char * cDataSubst = multi->second; + PFC_ASSERT(strlen(cDataSubst) == 2); + if (matchOneChar(cUser, (uint32_t)cDataSubst[0])) { + auto walkUser2 = walkUser + dUser; + uint32_t cUser2; + auto dUser2 = pfc::utf8_decode_char(walkUser2, cUser2); + if (matchOneChar(cUser2, (uint32_t)cDataSubst[1])) { + gotMulti = true; + dUser += dUser2; + } + } + } + } + if (!gotMulti) { + if (!matchOneChar(cUser, cData)) return nullptr; + } + } + + walkData += dData; + walkUser += dUser; + } +} + +const char * SmartStrStr::strStrEnd(const char * pString, const char * pSubString, size_t * outFoundAt) const { + size_t walk = 0; + for (;; ) { + if (pString[walk] == 0) return nullptr; + auto end = matchHere(pString+walk, pSubString); + if (end != nullptr) { + if ( outFoundAt != nullptr ) * outFoundAt = walk; + return end; + } + + size_t delta = pfc::utf8_char_len( pString + walk ); + if ( delta == 0 ) return nullptr; + walk += delta; + } +} + +const wchar_t * SmartStrStr::strStrEndW(const wchar_t * pString, const wchar_t * pSubString, size_t * outFoundAt) const { + size_t walk = 0; + for (;; ) { + if (pString[walk] == 0) return nullptr; + auto end = matchHereW(pString + walk, pSubString); + if (end != nullptr) { + if (outFoundAt != nullptr) * outFoundAt = walk; + return end; + } + + uint32_t dontcare; + size_t delta = pfc::utf16_decode_char(pString + walk, & dontcare); + if (delta == 0) return nullptr; + walk += delta; + } +} + +bool SmartStrStr::matchOneChar(uint32_t cInput, uint32_t cData) const { + if (cInput == cData) return true; + auto iter = substituions.find(cData); + if (iter == substituions.end()) return false; + auto & s = iter->second; + return s.find(cInput) != s.end(); +} + +uint32_t SmartStrStr::Transform(uint32_t c) { + wchar_t wide[2] = {}; char out[4] = {}; + pfc::utf16_encode_char(c, wide); + BOOL fail = FALSE; + if (WideCharToMultiByte(pfc::stringcvt::codepage_ascii, 0, wide, 2, out, 4, "?", &fail) > 0) { + if (!fail) { + if (out[0] > 0 && out[1] == 0) { + c = out[0]; + } + } + } + return c; +} + +uint32_t SmartStrStr::ToLower(uint32_t c) { + return pfc::charLower(c); +} + +void SmartStrStr::ImportTwoCharMappings(const wchar_t * list, const char * replacement) { + PFC_ASSERT(strlen(replacement) == 2); + for (const wchar_t* ptr = list; ; ) { + wchar_t c = *ptr++; + if (c == 0) break; + twoCharMappings[(uint32_t)c] = replacement; + } +} + +void SmartStrStr::InitTwoCharMappings() { + ImportTwoCharMappings(L"ÆǢǼ", "AE"); + ImportTwoCharMappings(L"æǣǽ", "ae"); + ImportTwoCharMappings(L"Œ", "OE"); + ImportTwoCharMappings(L"œɶ", "oe"); + ImportTwoCharMappings(L"DŽDZ", "DZ"); + ImportTwoCharMappings(L"dždzʣʥ", "dz"); + ImportTwoCharMappings(L"ß", "ss"); + ImportTwoCharMappings(L"LJ", "LJ"); + ImportTwoCharMappings(L"Lj", "Lj"); + ImportTwoCharMappings(L"lj", "lj"); + ImportTwoCharMappings(L"NJ", "NJ"); + ImportTwoCharMappings(L"Nj", "Nj"); + ImportTwoCharMappings(L"nj", "nj"); + ImportTwoCharMappings(L"IJ", "IJ"); + ImportTwoCharMappings(L"ij", "ij"); +} diff --git a/libPPUI/SmartStrStr.h b/libPPUI/SmartStrStr.h new file mode 100644 index 0000000..6c41325 --- /dev/null +++ b/libPPUI/SmartStrStr.h @@ -0,0 +1,42 @@ +#pragma once + + +#include +#include +#include + +//! Implementation of string matching for search purposes, such as media library search or typefind in list views. \n +//! Inspired by Unicode asymetic search, but not strictly implementing the Unicode asymetric search specifications. \n +//! Bootstraps its character mapping data from various Win32 API methods, requires no externally provided character mapping data. \n +//! Windows-only code. \n +//! \n +//! Keeping a global instance of it is recommended, due to one time init overhead. \n +//! Thread safety: safe to call concurrently once constructed. + +class SmartStrStr { +public: + SmartStrStr(); + + //! Returns ptr to the end of the string if positive (for continuing search), nullptr if negative. + const char * strStrEnd(const char * pString, const char * pSubString, size_t * outFoundAt = nullptr) const; + const wchar_t * strStrEndW(const wchar_t * pString, const wchar_t * pSubString, size_t * outFoundAt = nullptr) const; + //! Returns ptr to the end of the string if positive (for continuing search), nullptr if negative. + const char * matchHere(const char * pString, const char * pUserString) const; + const wchar_t * matchHereW( const wchar_t * pString, const wchar_t * pUserString) const; + + //! String-equals tool, compares strings rather than searching for occurance + bool equals( const char * pString, const char * pUserString) const; + + //! One-char match. Doesn't use twoCharMappings, use only if you have to operate on char by char basis rather than call the other methods. + bool matchOneChar(uint32_t cInput, uint32_t cData) const; +private: + + static uint32_t Transform(uint32_t c); + static uint32_t ToLower(uint32_t c); + + void ImportTwoCharMappings(const wchar_t * list, const char * replacement); + void InitTwoCharMappings(); + + std::map > substituions; + std::map twoCharMappings; +}; diff --git a/libPPUI/TreeMultiSel.h b/libPPUI/TreeMultiSel.h new file mode 100644 index 0000000..aa031e0 --- /dev/null +++ b/libPPUI/TreeMultiSel.h @@ -0,0 +1,397 @@ +#pragma once + +// ================================================================================ +// CTreeMultiSel +// Implementation of multi-selection in a tree view ctrl +// Instantiate with dialog ID of your treeview, +// plug into your dialog's message map. +// Doesn't work correctly with explorer-themed tree controls (glitches happen). +// ================================================================================ + +#include +#include + +class CTreeMultiSel : public CMessageMap { +public: + typedef std::set selection_t; + typedef std::vector selectionOrdered_t; + + CTreeMultiSel(unsigned ID) : m_ID(ID) {} + + BEGIN_MSG_MAP_EX(CTreeMultiSel) + NOTIFY_HANDLER_EX(m_ID, TVN_ITEMEXPANDED, OnItemExpanded) + NOTIFY_HANDLER_EX(m_ID, NM_CLICK, OnClick) + NOTIFY_HANDLER_EX(m_ID, TVN_DELETEITEM, OnItemDeleted) + NOTIFY_HANDLER_EX(m_ID, TVN_SELCHANGING, OnSelChanging) + NOTIFY_HANDLER_EX(m_ID, TVN_SELCHANGED, OnSelChangedFilter) + NOTIFY_HANDLER_EX(m_ID, NM_SETFOCUS, OnFocus) + NOTIFY_HANDLER_EX(m_ID, NM_KILLFOCUS, OnFocus) + END_MSG_MAP() + + const unsigned m_ID; + + // Retrieves selected items - on order of appearance in the view + selectionOrdered_t GetSelectionOrdered(CTreeViewCtrl tree) const { + HTREEITEM first = tree.GetRootItem(); + selectionOrdered_t ret; ret.reserve( m_selection.size() ); + for(HTREEITEM walk = first; walk != NULL; walk = tree.GetNextVisibleItem(walk)) { + if (m_selection.count(walk) > 0) ret.push_back( walk ); + } + return ret; + } + + //! Undefined order! Use only when order of selected items is not relevant. + selection_t GetSelection() const { return m_selection; } + selection_t const & GetSelectionRef() const { return m_selection; } + bool IsItemSelected(HTREEITEM item) const {return m_selection.count(item) > 0;} + size_t GetSelCount() const {return m_selection.size();} + //! Retrieves a single-selection item. Null if nothing or more than one item is selected. + HTREEITEM GetSingleSel() const { + if (m_selection.size() != 1) return NULL; + return *m_selection.begin(); + } + + void OnContextMenu_FixSelection(CTreeViewCtrl tree, CPoint pt) { + if (pt != CPoint(-1, -1)) { + WIN32_OP_D(tree.ScreenToClient(&pt)); + UINT flags = 0; + const HTREEITEM item = tree.HitTest(pt, &flags); + if (item != NULL && (flags & TVHT_ONITEM) != 0) { + if (!IsItemSelected(item)) { + SelectSingleItem(tree, item); + } + CallSelectItem(tree, item); + } + } + } + + void OnLButtonDown(CTreeViewCtrl tree, WPARAM wp, LPARAM lp) { + if (!IsKeyPressed(VK_CONTROL)) { + UINT flags = 0; + HTREEITEM item = tree.HitTest(CPoint(lp), &flags); + if (item != NULL && (flags & TVHT_ONITEM) != 0) { + if (!IsItemSelected(item)) tree.SelectItem(item); + } + } + } + static bool IsNavKey(UINT vk) { + switch(vk) { + case VK_UP: + case VK_DOWN: + case VK_RIGHT: + case VK_LEFT: + case VK_PRIOR: + case VK_NEXT: + case VK_HOME: + case VK_END: + return true; + default: + return false; + } + } + BOOL OnChar(CTreeViewCtrl tree, WPARAM code) { + switch(code) { + case ' ': + if (IsKeyPressed(VK_CONTROL) || !IsTypingInProgress()) { + HTREEITEM item = tree.GetSelectedItem(); + if (item != NULL) SelectToggleItem(tree, item); + return TRUE; + } + break; + } + m_lastTypingTime = GetTickCount(); m_lastTypingTimeValid = true; + return FALSE; + } + BOOL OnKeyDown(CTreeViewCtrl tree, UINT vKey) { + if (IsNavKey(vKey)) m_lastTypingTimeValid = false; + switch(vKey) { + case VK_UP: + if (IsKeyPressed(VK_CONTROL)) { + HTREEITEM item = tree.GetSelectedItem(); + if (item != NULL) { + HTREEITEM prev = tree.GetPrevVisibleItem(item); + if (prev != NULL) { + CallSelectItem(tree, prev); + if (IsKeyPressed(VK_SHIFT)) { + if (m_selStart == NULL) m_selStart = item; + SelectItemRange(tree, prev); + } + } + } + return TRUE; + } + break; + case VK_DOWN: + if (IsKeyPressed(VK_CONTROL)) { + HTREEITEM item = tree.GetSelectedItem(); + if (item != NULL) { + HTREEITEM next = tree.GetNextVisibleItem(item); + if (next != NULL) { + CallSelectItem(tree, next); + if (IsKeyPressed(VK_SHIFT)) { + if (m_selStart == NULL) m_selStart = item; + SelectItemRange(tree, next); + } + } + } + return TRUE; + } + break; + /*case VK_LEFT: + if (IsKeyPressed(VK_CONTROL)) { + tree.SendMessage(WM_HSCROLL, SB_LINEUP, 0); + } + break; + case VK_RIGHT: + if (IsKeyPressed(VK_CONTROL)) { + tree.SendMessage(WM_HSCROLL, SB_LINEDOWN, 0); + } + break;*/ + } + return FALSE; + } +private: + LRESULT OnFocus(LPNMHDR hdr) { + if ( m_selection.size() > 100 ) { + CTreeViewCtrl tree(hdr->hwndFrom); + tree.RedrawWindow(NULL, NULL, RDW_INVALIDATE | RDW_ERASE); + } else if (m_selection.size() > 0) { + CTreeViewCtrl tree(hdr->hwndFrom); + CRgn rgn; rgn.CreateRectRgn(0,0,0,0); + for(auto walk = m_selection.begin(); walk != m_selection.end(); ++walk) { + CRect rc; + if (tree.GetItemRect(*walk, rc, TRUE)) { + CRgn temp; temp.CreateRectRgnIndirect(rc); + rgn.CombineRgn(temp, RGN_OR); + } + } + tree.RedrawWindow(NULL, rgn, RDW_INVALIDATE | RDW_ERASE); + } + SetMsgHandled(FALSE); + return 0; + } + void CallSelectItem(CTreeViewCtrl tree, HTREEITEM item) { + const bool was = m_ownSelChange; m_ownSelChange = true; + tree.SelectItem(item); + m_ownSelChange = was; + } + LRESULT OnSelChangedFilter(LPNMHDR) { + if (m_ownSelChangeNotify) SetMsgHandled(FALSE); + return 0; + } + LRESULT OnItemDeleted(LPNMHDR pnmh) { + const HTREEITEM item = reinterpret_cast(pnmh)->itemOld.hItem; + m_selection.erase( item ); + if (m_selStart == item) m_selStart = NULL; + SetMsgHandled(FALSE); + return 0; + } + LRESULT OnItemExpanded(LPNMHDR pnmh) { + NMTREEVIEW * info = reinterpret_cast(pnmh); + CTreeViewCtrl tree ( pnmh->hwndFrom ); + if ((info->itemNew.state & TVIS_EXPANDED) == 0) { + if (DeselectChildren( tree, info->itemNew.hItem )) { + SendOnSelChanged(tree); + } + } + SetMsgHandled(FALSE); + return 0; + } + + BOOL HandleClick(CTreeViewCtrl tree, CPoint pt) { + UINT htFlags = 0; + HTREEITEM item = tree.HitTest(pt, &htFlags); + if (item != NULL && (htFlags & TVHT_ONITEM) != 0) { + if (IsKeyPressed(VK_CONTROL)) { + SelectToggleItem(tree, item); + return TRUE; + } else if (item == tree.GetSelectedItem() && !IsItemSelected(item)) { + SelectToggleItem(tree, item); + return TRUE; + } else { + //tree.SelectItem(item); + return FALSE; + } + } else { + return FALSE; + } + } + + LRESULT OnClick(LPNMHDR pnmh) { + CPoint pt(GetMessagePos()); + CTreeViewCtrl tree ( pnmh->hwndFrom ); + WIN32_OP_D ( tree.ScreenToClient( &pt ) ); + return HandleClick(tree, pt) ? 1 : 0; + } + + LRESULT OnSelChanging(LPNMHDR pnmh) { + if (!m_ownSelChange) { + //console::formatter() << "OnSelChanging"; + NMTREEVIEW * info = reinterpret_cast(pnmh); + CTreeViewCtrl tree ( pnmh->hwndFrom ); + const HTREEITEM item = info->itemNew.hItem; + + if (IsTypingInProgress()) { + SelectSingleItem(tree, item); + } else if (IsKeyPressed(VK_SHIFT)) { + SelectItemRange(tree, item); + } else if (IsKeyPressed(VK_CONTROL)) { + SelectToggleItem(tree, item); + } else { + SelectSingleItem(tree, item); + } + } + return 0; + } + + void SelectItemRange(CTreeViewCtrl tree, HTREEITEM item) { + if (m_selStart == NULL || m_selStart == item) { + SelectSingleItem(tree, item); + return; + } + + selection_t newSel = GrabRange(tree, m_selStart, item ); + ApplySelection(tree, newSel); + } + static selection_t GrabRange(CTreeViewCtrl tree, HTREEITEM item1, HTREEITEM item2) { + selection_t range1, range2; + HTREEITEM walk1 = item1, walk2 = item2; + for(;;) { + if (walk1 != NULL) { + range1.insert( walk1 ); + if (walk1 == item2) { + return range1; + } + walk1 = tree.GetNextVisibleItem(walk1); + } + if (walk2 != NULL) { + range2.insert( walk2 ); + if (walk2 == item1) { + return range2; + } + walk2 = tree.GetNextVisibleItem(walk2); + } + if (walk1 == NULL && walk2 == NULL) { + // should not get here + return selection_t(); + } + } + } + void SelectToggleItem(CTreeViewCtrl tree, HTREEITEM item) { + m_selStart = item; + if ( IsItemSelected( item ) ) { + m_selection.erase( item ); + } else { + m_selection.insert( item ); + } + UpdateItem(tree, item); + } +public: + void SelectSingleItem(CTreeViewCtrl tree, HTREEITEM item) { + m_selStart = item; + if (m_selection.size() == 1 && *m_selection.begin() == item) return; + DeselectAll(tree); SelectItem(tree, item); + } + + void ApplySelection(CTreeViewCtrl tree, selection_t const & newSel) { + CRgn updateRgn; + bool changed = false; + if (newSel.size() != m_selection.size() && newSel.size() + m_selection.size() > 100) { + // don't bother with regions + changed = true; + } else { + WIN32_OP_D(updateRgn.CreateRectRgn(0, 0, 0, 0) != NULL); + for (auto walk = m_selection.begin(); walk != m_selection.end(); ++walk) { + if (newSel.count(*walk) == 0) { + changed = true; + CRect rc; + if (tree.GetItemRect(*walk, rc, TRUE)) { + CRgn temp; WIN32_OP_D(temp.CreateRectRgnIndirect(rc)); + WIN32_OP_D(updateRgn.CombineRgn(temp, RGN_OR) != ERROR); + } + } + } + for (auto walk = newSel.begin(); walk != newSel.end(); ++walk) { + if (m_selection.count(*walk) == 0) { + changed = true; + CRect rc; + if (tree.GetItemRect(*walk, rc, TRUE)) { + CRgn temp; WIN32_OP_D(temp.CreateRectRgnIndirect(rc)); + WIN32_OP_D(updateRgn.CombineRgn(temp, RGN_OR) != ERROR); + } + } + } + } + if (changed) { + m_selection = newSel; + tree.RedrawWindow(NULL, updateRgn); + SendOnSelChanged(tree); + } + } + + void DeselectItem(CTreeViewCtrl tree, HTREEITEM item) { + if (IsItemSelected(item)) { + m_selection.erase(item); UpdateItem(tree, item); + } + } + void SelectItem(CTreeViewCtrl tree, HTREEITEM item) { + if (!IsItemSelected(item)) { + m_selection.insert(item); UpdateItem(tree, item); + } + } + + void DeselectAll(CTreeViewCtrl tree) { + if (m_selection.size() == 0) return; + CRgn updateRgn; + if (m_selection.size() <= 100) { + WIN32_OP_D(updateRgn.CreateRectRgn(0, 0, 0, 0) != NULL); + for (auto walk = m_selection.begin(); walk != m_selection.end(); ++walk) { + CRect rc; + if (tree.GetItemRect(*walk, rc, TRUE)) { + CRgn temp; WIN32_OP_D(temp.CreateRectRgnIndirect(rc)); + WIN32_OP_D(updateRgn.CombineRgn(temp, RGN_OR) != ERROR); + } + } + } + m_selection.clear(); + tree.RedrawWindow(NULL, updateRgn); + } +private: + void UpdateItem(CTreeViewCtrl tree, HTREEITEM item) { + CRect rc; + if (tree.GetItemRect(item, rc, TRUE) ) { + tree.RedrawWindow(rc); + } + SendOnSelChanged(tree); + } + void SendOnSelChanged(CTreeViewCtrl tree) { + NMHDR hdr = {}; + hdr.code = TVN_SELCHANGED; + hdr.hwndFrom = tree; + hdr.idFrom = m_ID; + const bool was = m_ownSelChangeNotify; m_ownSelChangeNotify = true; + tree.GetParent().SendMessage(WM_NOTIFY, m_ID, (LPARAM) &hdr ); + m_ownSelChangeNotify = was; + } + + bool DeselectChildren( CTreeViewCtrl tree, HTREEITEM item ) { + bool state = false; + for(HTREEITEM walk = tree.GetChildItem( item ); walk != NULL; walk = tree.GetNextSiblingItem( walk ) ) { + if (m_selection.erase(walk) > 0) state = true; + if (m_selStart == walk) m_selStart = NULL; + if (tree.GetItemState( walk, TVIS_EXPANDED ) ) { + if (DeselectChildren( tree, walk )) state = true; + } + } + return state; + } + + bool IsTypingInProgress() const { + return m_lastTypingTimeValid && (GetTickCount() - m_lastTypingTime < 500); + } + + selection_t m_selection; + HTREEITEM m_selStart = NULL; + bool m_ownSelChangeNotify = false, m_ownSelChange = false; + DWORD m_lastTypingTime = 0; bool m_lastTypingTimeValid = false; +}; diff --git a/libPPUI/TypeFind.cpp b/libPPUI/TypeFind.cpp new file mode 100644 index 0000000..05205f1 --- /dev/null +++ b/libPPUI/TypeFind.cpp @@ -0,0 +1,53 @@ +#include "stdafx.h" +#include "TypeFind.h" +#include "SmartStrStr.h" + +static size_t _ItemCount(HWND wnd) { + return ListView_GetItemCount(wnd); +} + +static const wchar_t * _ItemText(wchar_t * buffer, int bufSize, HWND wnd, size_t index, int subItem) { + NMLVDISPINFO info = {}; + info.hdr.code = LVN_GETDISPINFO; + info.hdr.idFrom = GetDlgCtrlID(wnd); + info.hdr.hwndFrom = wnd; + info.item.iItem = (int) index; + info.item.iSubItem = subItem; + info.item.mask = LVIF_TEXT; + info.item.pszText = buffer; + info.item.cchTextMax = bufSize; + ::SendMessage(::GetParent(wnd), WM_NOTIFY, info.hdr.idFrom, reinterpret_cast(&info)); + if (info.item.pszText == NULL) return L""; + if ( bufSize > 0 && info.item.pszText == buffer ) buffer[ bufSize - 1 ] = 0; + return info.item.pszText; +} + +LRESULT TypeFind::Handler(NMHDR* hdr, int subItemFrom, int subItemCnt) { + NMLVFINDITEM * info = reinterpret_cast(hdr); + const HWND wnd = hdr->hwndFrom; + if (info->lvfi.flags & LVFI_NEARESTXY) return -1; + const size_t count = _ItemCount(wnd); + if (count == 0) return -1; + const size_t base = (size_t)info->iStart % count; + + static SmartStrStr tool; + + enum { + bufSize = 1024, + }; + wchar_t textBuf[bufSize]; + + for (size_t walk = 0; walk < count; ++walk) { + const size_t index = (walk + base) % count; + for (int subItem = subItemFrom; subItem < subItemFrom + subItemCnt; ++subItem) { + if (tool.matchHereW(_ItemText(textBuf, bufSize, wnd, index, subItem), info->lvfi.psz)) return (LRESULT)index; + } + } + for (size_t walk = 0; walk < count; ++walk) { + const size_t index = (walk + base) % count; + for (int subItem = subItemFrom; subItem < subItemFrom + subItemCnt; ++subItem) { + if (tool.strStrEndW(_ItemText(textBuf, bufSize, wnd, index, subItem), info->lvfi.psz)) return (LRESULT)index; + } + } + return -1; +} diff --git a/libPPUI/TypeFind.h b/libPPUI/TypeFind.h new file mode 100644 index 0000000..a0c890b --- /dev/null +++ b/libPPUI/TypeFind.h @@ -0,0 +1,6 @@ +#pragma once + +class TypeFind { +public: + static LRESULT Handler(NMHDR* hdr, int subItemFrom = 0, int subItemCnt = 1); +}; diff --git a/libPPUI/clipboard.cpp b/libPPUI/clipboard.cpp new file mode 100644 index 0000000..52cda9a --- /dev/null +++ b/libPPUI/clipboard.cpp @@ -0,0 +1,51 @@ +#include "stdafx.h" + +#include "clipboard.h" +#include "win32_op.h" + +#ifdef UNICODE +#define CF_TCHAR CF_UNICODETEXT +#else +#define CF_TCHAR CF_TEXT +#endif + +namespace ClipboardHelper { + void OpenScope::Open(HWND p_owner) { + Close(); + WIN32_OP(OpenClipboard(p_owner)); + m_open = true; + } + void OpenScope::Close() { + if (m_open) { + m_open = false; + CloseClipboard(); + } + } + void SetRaw(UINT format,const void * data, t_size size) { + HANDLE buffer = GlobalAlloc(GMEM_DDESHARE,size); + if (buffer == NULL) throw std::bad_alloc(); + try { + CGlobalLockScope lock(buffer); + PFC_ASSERT(lock.GetSize() == size); + memcpy(lock.GetPtr(),data,size); + } catch(...) { + GlobalFree(buffer); throw; + } + + WIN32_OP(SetClipboardData(format,buffer) != NULL); + } + void SetString(const char * in) { + pfc::stringcvt::string_os_from_utf8 temp(in); + SetRaw(CF_TCHAR,temp.get_ptr(),(temp.length() + 1) * sizeof(TCHAR)); + } + + bool GetString(pfc::string_base & out) { + pfc::array_t temp; + if (!GetRaw(CF_TCHAR,temp)) return false; + out = pfc::stringcvt::string_utf8_from_os(reinterpret_cast(temp.get_ptr()),temp.get_size() / sizeof(TCHAR)); + return true; + } + bool IsTextAvailable() { + return IsClipboardFormatAvailable(CF_TCHAR) == TRUE; + } +} diff --git a/libPPUI/clipboard.h b/libPPUI/clipboard.h new file mode 100644 index 0000000..05b6479 --- /dev/null +++ b/libPPUI/clipboard.h @@ -0,0 +1,33 @@ +#pragma once + +namespace ClipboardHelper { + + class OpenScope { + public: + OpenScope() : m_open(false) {} + ~OpenScope() {Close();} + void Open(HWND p_owner); + void Close(); + private: + bool m_open; + + PFC_CLASS_NOT_COPYABLE_EX(OpenScope) + }; + + void SetRaw(UINT format,const void * buffer, t_size size); + void SetString(const char * in); + + bool GetString(pfc::string_base & out); + + template + bool GetRaw(UINT format,TArray & out) { + pfc::assert_byte_type(); + HANDLE data = GetClipboardData(format); + if (data == NULL) return false; + CGlobalLockScope lock(data); + out.set_size( lock.GetSize() ); + memcpy(out.get_ptr(), lock.GetPtr(), lock.GetSize() ); + return true; + } + bool IsTextAvailable(); +}; diff --git a/libPPUI/commandline_parser.cpp b/libPPUI/commandline_parser.cpp new file mode 100644 index 0000000..74db7a7 --- /dev/null +++ b/libPPUI/commandline_parser.cpp @@ -0,0 +1,65 @@ +#include "stdafx.h" +#include "commandline_parser.h" + +commandline_parser::commandline_parser() { + init(pfc::stringcvt::string_utf8_from_os(GetCommandLine())); +} + +void commandline_parser::init(const char * cmd) +{ + pfc::string8_fastalloc temp; + pfc::chain_list_v2_t out; + while(*cmd) + { + temp.reset(); + while(*cmd && *cmd!=' ') + { + if (*cmd=='\"') + { + cmd++; + while(*cmd && *cmd!='\"') temp.add_byte(*(cmd++)); + if (*cmd == '\"') cmd++; + } + else temp.add_byte(*(cmd++)); + } + out.insert_last(temp); + while(*cmd==' ') cmd++; + } + pfc::list_to_array(m_data,out); +} + +size_t commandline_parser::find_param(const char * ptr) const { + for(size_t n=1;n m_data; +}; diff --git a/libPPUI/gdiplus-helpers-webp.h b/libPPUI/gdiplus-helpers-webp.h new file mode 100644 index 0000000..d78f894 --- /dev/null +++ b/libPPUI/gdiplus-helpers-webp.h @@ -0,0 +1,46 @@ +#pragma once + +#include "gdiplus_helpers.h" + +// Presumes prior #include of webp/decode.h + +static bool IsImageWebP(const void * ptr, size_t bytes) { + if (bytes < 12) return false; + return memcmp(ptr, "RIFF", 4) == 0 && memcmp((const char*)ptr + 8, "WEBP", 4) == 0; +} + + +// WebP-aware GdiplusImageFromMem +static Gdiplus::Image * GdiplusImageFromMem2(const void * ptr, size_t bytes) { + GdiplusErrorHandler EH; + using namespace Gdiplus; + if (IsImageWebP(ptr, bytes)) { + WebPBitstreamFeatures bs; + if (WebPGetFeatures((const uint8_t*)ptr, bytes, &bs) != VP8_STATUS_OK) { + throw std::runtime_error("WebP decoding failure"); + } + const Gdiplus::PixelFormat pf = bs.has_alpha ? PixelFormat32bppARGB : PixelFormat32bppRGB; + const int pfBytes = 4; // Gdiplus RGB is 4 bytes + int w = 0, h = 0; + // ALWAYS decode BGRA, Gdiplus will disregard alpha if was not originally present + uint8_t * decodedData = WebPDecodeBGRA((const uint8_t*)ptr, bytes, &w, &h); + pfc::onLeaving scope([decodedData] {WebPFree(decodedData); }); + if (decodedData == nullptr || w <= 0 || h <= 0) throw std::runtime_error("WebP decoding failure"); + + pfc::ptrholder_t ret = new Gdiplus::Bitmap(w, h, pf); + EH << ret->GetLastStatus(); + Rect rc(0, 0, w, h); + Gdiplus::BitmapData bitmapData; + EH << ret->LockBits(&rc, 0, pf, &bitmapData); + uint8_t * target = (uint8_t*)bitmapData.Scan0; + const uint8_t * source = decodedData; + for (int y = 0; y < h; ++y) { + memcpy(target, source, w * pfBytes); + target += bitmapData.Stride; + source += pfBytes * w; + } + EH << ret->UnlockBits(&bitmapData); + return ret.detach(); + } + return GdiplusImageFromMem(ptr, bytes); +} diff --git a/libPPUI/gdiplus_helpers.h b/libPPUI/gdiplus_helpers.h new file mode 100644 index 0000000..e2d30a4 --- /dev/null +++ b/libPPUI/gdiplus_helpers.h @@ -0,0 +1,233 @@ +#pragma once +#include + +#include "win32_op.h" +#include "win32_utility.h" + +class GdiplusErrorHandler { +public: + void operator<<(Gdiplus::Status p_code) { + if (p_code != Gdiplus::Ok) { + throw pfc::exception(PFC_string_formatter() << "Gdiplus error (" << (unsigned) p_code << ")"); + } + } +}; + +class GdiplusScope { +public: + GdiplusScope() { + Gdiplus::GdiplusStartupInput input; + Gdiplus::GdiplusStartupOutput output; + GdiplusErrorHandler() << Gdiplus::GdiplusStartup(&m_token,&input,&output); + } + ~GdiplusScope() { + Gdiplus::GdiplusShutdown(m_token); + } + static void Once() { + static bool done = _Once(); + } +private: + static bool _Once() { + ULONG_PTR token = 0; + Gdiplus::GdiplusStartupInput input; + Gdiplus::GdiplusStartupOutput output; + GdiplusErrorHandler() << Gdiplus::GdiplusStartup(&token, &input, &output); + return true; + } + GdiplusScope( const GdiplusScope & ) = delete; + void operator=( const GdiplusScope & ) = delete; + + ULONG_PTR m_token = 0; +}; + +static HBITMAP GdiplusLoadBitmap(UINT id, const TCHAR * resType, CSize size) { + using namespace Gdiplus; + try { + auto stream = WinLoadResourceAsStream(GetThisModuleHandle(), MAKEINTRESOURCE(id), resType ); + + GdiplusErrorHandler EH; + pfc::ptrholder_t source = new Image(stream); + + EH << source->GetLastStatus(); + pfc::ptrholder_t resized = new Bitmap(size.cx, size.cy, PixelFormat32bppARGB); + EH << resized->GetLastStatus(); + + { + pfc::ptrholder_t target = new Graphics(resized.get_ptr()); + EH << target->GetLastStatus(); + EH << target->SetInterpolationMode(InterpolationModeHighQuality); + EH << target->Clear(Color(0,0,0,0)); + EH << target->DrawImage(source.get_ptr(), Rect(0,0,size.cx,size.cy)); + } + + HBITMAP bmp = NULL; + EH << resized->GetHBITMAP(Gdiplus::Color::White, & bmp ); + return bmp; + } catch(...) { + PFC_ASSERT( !"Should not get here"); + return NULL; + } +} + +static Gdiplus::Image * GdiplusImageFromMem(const void * ptr, size_t bytes) { + using namespace Gdiplus; + GdiplusErrorHandler EH; + pfc::ptrholder_t source; + + { + pfc::com_ptr_t stream; + stream.attach( SHCreateMemStream((const BYTE*)ptr, pfc::downcast_guarded(bytes)) ); + if (stream.is_empty()) throw std::bad_alloc(); + source = new Image(stream.get_ptr()); + } + + EH << source->GetLastStatus(); + return source.detach(); +} + +static Gdiplus::Bitmap * GdiplusResizeImage( Gdiplus::Image * source, CSize size ) { + using namespace Gdiplus; + GdiplusErrorHandler EH; + pfc::ptrholder_t resized = new Bitmap(size.cx, size.cy, PixelFormat32bppARGB); + EH << resized->GetLastStatus(); + pfc::ptrholder_t target = new Graphics(resized.get_ptr()); + EH << target->GetLastStatus(); + EH << target->SetInterpolationMode(InterpolationModeHighQuality); + EH << target->Clear(Color(0, 0, 0, 0)); + EH << target->DrawImage(source, Rect(0, 0, size.cx, size.cy)); + return resized.detach(); +} + +static HICON GdiplusLoadIconFromMem( const void * ptr, size_t bytes, CSize size ) { + using namespace Gdiplus; + try { + + GdiplusErrorHandler EH; + pfc::ptrholder_t source = GdiplusImageFromMem(ptr, bytes); + pfc::ptrholder_t resized = GdiplusResizeImage( source.get_ptr(), size ); + HICON icon = NULL; + EH << resized->GetHICON(&icon); + return icon; + } catch (...) { + PFC_ASSERT(!"Should not get here"); + return NULL; + } +} + +static HICON GdiplusLoadIcon(UINT id, const TCHAR * resType, CSize size) { + using namespace Gdiplus; + try { + auto stream = WinLoadResourceAsStream(GetThisModuleHandle(), MAKEINTRESOURCE(id), resType); + + GdiplusErrorHandler EH; + pfc::ptrholder_t source = new Image(stream); + + EH << source->GetLastStatus(); + pfc::ptrholder_t resized = new Bitmap(size.cx, size.cy, PixelFormat32bppARGB); + EH << resized->GetLastStatus(); + + { + pfc::ptrholder_t target = new Graphics(resized.get_ptr()); + EH << target->GetLastStatus(); + EH << target->SetInterpolationMode(InterpolationModeHighQuality); + EH << target->Clear(Color(0,0,0,0)); + EH << target->DrawImage(source.get_ptr(), Rect(0,0,size.cx,size.cy)); + } + + HICON icon = NULL; + EH << resized->GetHICON(&icon); + return icon; + } catch(...) { + PFC_ASSERT( !"Should not get here"); + return NULL; + } +} +static HICON GdiplusLoadPNGIcon(UINT id, CSize size) {return GdiplusLoadIcon(id, _T("PNG"), size);} + +static HICON LoadPNGIcon(UINT id, CSize size) { + GdiplusScope scope; + return GdiplusLoadPNGIcon(id, size); +} + +static void GdiplusLoadButtonPNG(CIcon & icon, HWND btn_, UINT image) { + CButton btn(btn_); + if (icon == NULL) { + CRect client; WIN32_OP_D( btn.GetClientRect(client) ); + CSize size = client.Size(); + int v = MulDiv(pfc::min_t(size.cx, size.cy), 3, 4); + if (v < 8) v = 8; + icon = GdiplusLoadPNGIcon(image, CSize(v,v)); + } + btn.SetIcon(icon); +} + +static Gdiplus::Bitmap * GdiplusLoadResource(UINT id, const TCHAR * resType) { + using namespace Gdiplus; + GdiplusErrorHandler EH; + + auto stream = WinLoadResourceAsStream(GetThisModuleHandle(), MAKEINTRESOURCE(id), resType); + pfc::ptrholder_t img = new Bitmap(stream); + EH << img->GetLastStatus(); + return img.detach(); +} + +static Gdiplus::Bitmap * GdiplusLoadResourceAsSize(UINT id, const TCHAR * resType, CSize size) { + using namespace Gdiplus; + GdiplusErrorHandler EH; + pfc::ptrholder_t source = GdiplusLoadResource(id, resType); + + pfc::ptrholder_t resized = new Bitmap(size.cx, size.cy, PixelFormat32bppARGB); + EH << resized->GetLastStatus(); + { + pfc::ptrholder_t target = new Graphics(resized.get_ptr()); + EH << target->GetLastStatus(); + EH << target->SetInterpolationMode(InterpolationModeHighQuality); + EH << target->DrawImage(source.get_ptr(), Rect(0, 0, size.cx, size.cy)); + } + return resized.detach(); +} + +static void GdiplusDimImage(Gdiplus::Bitmap * bmp) { + using namespace Gdiplus; + GdiplusErrorHandler EH; + Gdiplus::Rect r(0, 0, bmp->GetWidth(), bmp->GetHeight()); + BitmapData data = {}; + EH << bmp->LockBits(&r, ImageLockModeRead | ImageLockModeWrite, PixelFormat32bppARGB, &data); + + BYTE * scan = (BYTE*)data.Scan0; + for (UINT y = 0; y < data.Height; ++y) { + BYTE * px = scan; + for (UINT x = 0; x < data.Width; ++x) { + //px[0] = _dimPixel(px[0]); + //px[1] = _dimPixel(px[1]); + //px[2] = _dimPixel(px[2]); + px[3] /= 3; + px += 4; + } + scan += data.Stride; + } + + EH << bmp->UnlockBits(&data); +} +static void GdiplusInvertImage(Gdiplus::Bitmap * bmp) { + using namespace Gdiplus; + GdiplusErrorHandler EH; + Gdiplus::Rect r(0, 0, bmp->GetWidth(), bmp->GetHeight()); + BitmapData data = {}; + EH << bmp->LockBits(&r, ImageLockModeRead | ImageLockModeWrite, PixelFormat32bppARGB, &data); + + BYTE * scan = (BYTE*)data.Scan0; + for (UINT y = 0; y < data.Height; ++y) { + BYTE * px = scan; + for (UINT x = 0; x < data.Width; ++x) { + unsigned v = (unsigned)px[0] + (unsigned)px[1] + (unsigned)px[2]; + v /= 3; + px[0] = px[1] = px[2] = 255 - v; + px += 4; + } + scan += data.Stride; + } + + EH << bmp->UnlockBits(&data); +} +#pragma comment(lib, "gdiplus.lib") diff --git a/libPPUI/gesture.h b/libPPUI/gesture.h new file mode 100644 index 0000000..a0b7e1b --- /dev/null +++ b/libPPUI/gesture.h @@ -0,0 +1,264 @@ +#pragma once + +#ifndef WM_GESTURE + +#define WM_GESTURE 0x0119 +#define WM_GESTURENOTIFY 0x011A + + +DECLARE_HANDLE(HGESTUREINFO); + + +/* + * Gesture flags - GESTUREINFO.dwFlags + */ +#define GF_BEGIN 0x00000001 +#define GF_INERTIA 0x00000002 +#define GF_END 0x00000004 + +/* + * Gesture IDs + */ +#define GID_BEGIN 1 +#define GID_END 2 +#define GID_ZOOM 3 +#define GID_PAN 4 +#define GID_ROTATE 5 +#define GID_TWOFINGERTAP 6 +#define GID_PRESSANDTAP 7 +#define GID_ROLLOVER GID_PRESSANDTAP + +/* + * Gesture information structure + * - Pass the HGESTUREINFO received in the WM_GESTURE message lParam into the + * GetGestureInfo function to retrieve this information. + * - If cbExtraArgs is non-zero, pass the HGESTUREINFO received in the WM_GESTURE + * message lParam into the GetGestureExtraArgs function to retrieve extended + * argument information. + */ +typedef struct tagGESTUREINFO { + UINT cbSize; // size, in bytes, of this structure (including variable length Args field) + DWORD dwFlags; // see GF_* flags + DWORD dwID; // gesture ID, see GID_* defines + HWND hwndTarget; // handle to window targeted by this gesture + POINTS ptsLocation; // current location of this gesture + DWORD dwInstanceID; // internally used + DWORD dwSequenceID; // internally used + ULONGLONG ullArguments; // arguments for gestures whose arguments fit in 8 BYTES + UINT cbExtraArgs; // size, in bytes, of extra arguments, if any, that accompany this gesture +} GESTUREINFO, *PGESTUREINFO; +typedef GESTUREINFO const * PCGESTUREINFO; + + +/* + * Gesture notification structure + * - The WM_GESTURENOTIFY message lParam contains a pointer to this structure. + * - The WM_GESTURENOTIFY message notifies a window that gesture recognition is + * in progress and a gesture will be generated if one is recognized under the + * current gesture settings. + */ +typedef struct tagGESTURENOTIFYSTRUCT { + UINT cbSize; // size, in bytes, of this structure + DWORD dwFlags; // unused + HWND hwndTarget; // handle to window targeted by the gesture + POINTS ptsLocation; // starting location + DWORD dwInstanceID; // internally used +} GESTURENOTIFYSTRUCT, *PGESTURENOTIFYSTRUCT; + +/* + * Gesture argument helpers + * - Angle should be a double in the range of -2pi to +2pi + * - Argument should be an unsigned 16-bit value + */ +#define GID_ROTATE_ANGLE_TO_ARGUMENT(_arg_) ((USHORT)((((_arg_) + 2.0 * 3.14159265) / (4.0 * 3.14159265)) * 65535.0)) +#define GID_ROTATE_ANGLE_FROM_ARGUMENT(_arg_) ((((double)(_arg_) / 65535.0) * 4.0 * 3.14159265) - 2.0 * 3.14159265) + +/* + * Gesture information retrieval + * - HGESTUREINFO is received by a window in the lParam of a WM_GESTURE message. + */ +WINUSERAPI +BOOL +WINAPI +GetGestureInfo( + __in HGESTUREINFO hGestureInfo, + __out PGESTUREINFO pGestureInfo); + + + +/* + * Gesture extra arguments retrieval + * - HGESTUREINFO is received by a window in the lParam of a WM_GESTURE message. + * - Size, in bytes, of the extra argument data is available in the cbExtraArgs + * field of the GESTUREINFO structure retrieved using the GetGestureInfo function. + */ +WINUSERAPI +BOOL +WINAPI +GetGestureExtraArgs( + __in HGESTUREINFO hGestureInfo, + __in UINT cbExtraArgs, + __out_bcount(cbExtraArgs) PBYTE pExtraArgs); + +/* + * Gesture information handle management + * - If an application processes the WM_GESTURE message, then once it is done + * with the associated HGESTUREINFO, the application is responsible for + * closing the handle using this function. Failure to do so may result in + * process memory leaks. + * - If the message is instead passed to DefWindowProc, or is forwarded using + * one of the PostMessage or SendMessage class of API functions, the handle + * is transfered with the message and need not be closed by the application. + */ +WINUSERAPI +BOOL +WINAPI +CloseGestureInfoHandle( + __in HGESTUREINFO hGestureInfo); + + +/* + * Gesture configuration structure + * - Used in SetGestureConfig and GetGestureConfig + * - Note that any setting not included in either GESTURECONFIG.dwWant or + * GESTURECONFIG.dwBlock will use the parent window's preferences or + * system defaults. + */ +typedef struct tagGESTURECONFIG { + DWORD dwID; // gesture ID + DWORD dwWant; // settings related to gesture ID that are to be turned on + DWORD dwBlock; // settings related to gesture ID that are to be turned off +} GESTURECONFIG, *PGESTURECONFIG; + +/* + * Gesture configuration flags - GESTURECONFIG.dwWant or GESTURECONFIG.dwBlock + */ + +/* + * Common gesture configuration flags - set GESTURECONFIG.dwID to zero + */ +#define GC_ALLGESTURES 0x00000001 + +/* + * Zoom gesture configuration flags - set GESTURECONFIG.dwID to GID_ZOOM + */ +#define GC_ZOOM 0x00000001 + +/* + * Pan gesture configuration flags - set GESTURECONFIG.dwID to GID_PAN + */ +#define GC_PAN 0x00000001 +#define GC_PAN_WITH_SINGLE_FINGER_VERTICALLY 0x00000002 +#define GC_PAN_WITH_SINGLE_FINGER_HORIZONTALLY 0x00000004 +#define GC_PAN_WITH_GUTTER 0x00000008 +#define GC_PAN_WITH_INERTIA 0x00000010 + +/* + * Rotate gesture configuration flags - set GESTURECONFIG.dwID to GID_ROTATE + */ +#define GC_ROTATE 0x00000001 + +/* + * Two finger tap gesture configuration flags - set GESTURECONFIG.dwID to GID_TWOFINGERTAP + */ +#define GC_TWOFINGERTAP 0x00000001 + +/* + * PressAndTap gesture configuration flags - set GESTURECONFIG.dwID to GID_PRESSANDTAP + */ +#define GC_PRESSANDTAP 0x00000001 +#define GC_ROLLOVER GC_PRESSANDTAP + +#define GESTURECONFIGMAXCOUNT 256 // Maximum number of gestures that can be included + // in a single call to SetGestureConfig / GetGestureConfig + +WINUSERAPI +BOOL +WINAPI +SetGestureConfig( + __in HWND hwnd, // window for which configuration is specified + __in DWORD dwReserved, // reserved, must be 0 + __in UINT cIDs, // count of GESTURECONFIG structures + __in_ecount(cIDs) PGESTURECONFIG pGestureConfig, // array of GESTURECONFIG structures, dwIDs will be processed in the + // order specified and repeated occurances will overwrite previous ones + __in UINT cbSize); // sizeof(GESTURECONFIG) + + + + +#define GCF_INCLUDE_ANCESTORS 0x00000001 // If specified, GetGestureConfig returns consolidated configuration + // for the specified window and it's parent window chain + +WINUSERAPI +BOOL +WINAPI +GetGestureConfig( + __in HWND hwnd, // window for which configuration is required + __in DWORD dwReserved, // reserved, must be 0 + __in DWORD dwFlags, // see GCF_* flags + __in PUINT pcIDs, // *pcIDs contains the size, in number of GESTURECONFIG structures, + // of the buffer pointed to by pGestureConfig + __inout_ecount(*pcIDs) PGESTURECONFIG pGestureConfig, + // pointer to buffer to receive the returned array of GESTURECONFIG structures + __in UINT cbSize); // sizeof(GESTURECONFIG) + + + +typedef BOOL +(WINAPI *pGetGestureInfo_t)( + __in HGESTUREINFO hGestureInfo, + __out PGESTUREINFO pGestureInfo); + +typedef BOOL +(WINAPI * pCloseGestureInfoHandle_t)( + __in HGESTUREINFO hGestureInfo); + +typedef BOOL +(WINAPI * pSetGestureConfig_t) ( + __in HWND hwnd, // window for which configuration is specified + __in DWORD dwReserved, // reserved, must be 0 + __in UINT cIDs, // count of GESTURECONFIG structures + __in_ecount(cIDs) PGESTURECONFIG pGestureConfig, // array of GESTURECONFIG structures, dwIDs will be processed in the + // order specified and repeated occurances will overwrite previous ones + __in UINT cbSize); // sizeof(GESTURECONFIG) + +class CGestureAPI { +public: + CGestureAPI() { + HMODULE dll = GetModuleHandle(_T("user32.dll")); + + Bind(GetGestureInfo, dll, "GetGestureInfo"); + Bind(CloseGestureInfoHandle, dll, "CloseGestureInfoHandle"); + Bind(SetGestureConfig, dll, "SetGestureConfig"); + } + + bool IsAvailable() { + return this->GetGestureInfo != NULL; + } + + pGetGestureInfo_t GetGestureInfo; + pCloseGestureInfoHandle_t CloseGestureInfoHandle; + pSetGestureConfig_t SetGestureConfig; +private: + template static void Bind(func_t & f, HMODULE dll, const char * name) { + f = reinterpret_cast(GetProcAddress(dll, name)); + } +}; +#else + +class CGestureAPI { +public: + inline static bool IsAvailable() { return true; } + inline static BOOL GetGestureInfo(HGESTUREINFO hGestureInfo, PGESTUREINFO pGestureInfo) { + return ::GetGestureInfo(hGestureInfo, pGestureInfo); + } + inline static BOOL CloseGestureInfoHandle(HGESTUREINFO hGestureInfo) { + return ::CloseGestureInfoHandle(hGestureInfo); + } + + inline static BOOL SetGestureConfig(HWND hwnd, DWORD dwReserved, UINT cIDs, PGESTURECONFIG pGestureConfig, UINT cbSize) { + return ::SetGestureConfig(hwnd, dwReserved, cIDs, pGestureConfig, cbSize); + } +}; +#endif + diff --git a/libPPUI/libPPUI-license.txt b/libPPUI/libPPUI-license.txt new file mode 100644 index 0000000..f7a9287 --- /dev/null +++ b/libPPUI/libPPUI-license.txt @@ -0,0 +1,17 @@ +Copyright (C) 2002-2021 Peter Pawlowski + +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. \ No newline at end of file diff --git a/libPPUI/libPPUI-readme.txt b/libPPUI/libPPUI-readme.txt new file mode 100644 index 0000000..72244ef --- /dev/null +++ b/libPPUI/libPPUI-readme.txt @@ -0,0 +1,3 @@ +libPPUI + +A library of user interface classes used by foobar2000 codebase; freely available for anyone to use in their programming projects. diff --git a/libPPUI/libPPUI.vcxproj b/libPPUI/libPPUI.vcxproj new file mode 100644 index 0000000..ef3f31f --- /dev/null +++ b/libPPUI/libPPUI.vcxproj @@ -0,0 +1,260 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 15.0 + {7729EB82-4069-4414-964B-AD399091A03F} + Win32Proj + libPPUI + 8.1 + + + + StaticLibrary + true + v141 + Unicode + + + StaticLibrary + false + v141 + true + Unicode + + + StaticLibrary + true + v141 + Unicode + + + StaticLibrary + false + v141 + true + Unicode + + + + + + + + + + + + + + + + + + + + + true + + + true + + + false + + + false + + + + Use + Level3 + Disabled + false + .. + ProgramDatabase + true + 4715 + false + Fast + + + Windows + true + + + + + Use + Level3 + Disabled + false + true + false + .. + + + Windows + true + + + + + Use + Level3 + MinSpace + true + true + false + .. + true + 4715 + false + Fast + true + /d2notypeopt %(AdditionalOptions) + NDEBUG;_UNICODE;UNICODE;%(PreprocessorDefinitions) + + + Windows + true + true + true + + + + + Use + Level3 + MaxSpeed + true + true + false + Fast + true + false + .. + /d2notypeopt %(AdditionalOptions) + NDEBUG;_UNICODE;UNICODE;%(PreprocessorDefinitions) + + + Windows + true + true + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Create + Create + Create + Create + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/libPPUI/libPPUI.vcxproj.filters b/libPPUI/libPPUI.vcxproj.filters new file mode 100644 index 0000000..40cd268 --- /dev/null +++ b/libPPUI/libPPUI.vcxproj.filters @@ -0,0 +1,260 @@ + + + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;ipp;xsd + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {b5bb0d58-0b1e-442d-a84d-09288c375185} + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + + + Resources + + + + + Resources + + + \ No newline at end of file diff --git a/libPPUI/link-CommonControls6.h b/libPPUI/link-CommonControls6.h new file mode 100644 index 0000000..1934f7d --- /dev/null +++ b/libPPUI/link-CommonControls6.h @@ -0,0 +1,3 @@ +#pragma once + +#pragma comment(linker, "\"/manifestdependency:type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' publicKeyToken='6595b64144ccf1df' processorArchitecture='*'\"") diff --git a/libPPUI/listview_helper.cpp b/libPPUI/listview_helper.cpp new file mode 100644 index 0000000..b00882d --- /dev/null +++ b/libPPUI/listview_helper.cpp @@ -0,0 +1,284 @@ +#include "stdafx.h" + +#include "win32_utility.h" +#include "win32_op.h" +#include "listview_helper.h" +#include "CListViewCtrlEx.h" +#include "CHeaderCtrlEx.h" + +namespace listview_helper { + + unsigned insert_item(HWND p_listview,unsigned p_index,const char * p_name,LPARAM p_param) + { + if (p_index == ~0) p_index = ListView_GetItemCount(p_listview); + LVITEM item = {}; + + pfc::stringcvt::string_os_from_utf8 os_string_temp(p_name); + + item.mask = LVIF_TEXT | LVIF_PARAM; + item.iItem = p_index; + item.lParam = p_param; + item.pszText = const_cast(os_string_temp.get_ptr()); + + LRESULT ret = SendMessage(p_listview,LVM_INSERTITEM,0,(LPARAM)&item); + if (ret < 0) return ~0; + else return (unsigned) ret; + } + + unsigned insert_item2(HWND p_listview, unsigned p_index, const char * col0, const char * col1, LPARAM p_param) { + unsigned i = insert_item( p_listview, p_index, col0, p_param ); + if (i != ~0) { + set_item_text( p_listview, i, 1, col1 ); + } + return i; + } + + unsigned insert_item3(HWND p_listview, unsigned p_index, const char * col0, const char * col1, const char * col2, LPARAM p_param) { + unsigned i = insert_item( p_listview, p_index, col0, p_param ); + if (i != ~0) { + set_item_text( p_listview, i, 1, col1 ); + set_item_text( p_listview, i, 2, col2 ); + } + return i; + } + + unsigned insert_column(HWND p_listview,unsigned p_index,const char * p_name,unsigned p_width_dlu) + { + pfc::stringcvt::string_os_from_utf8 os_string_temp(p_name); + + RECT rect = {0,0,(LONG)p_width_dlu,0}; + MapDialogRect(GetParent(p_listview),&rect); + + LVCOLUMN data = {}; + data.mask = LVCF_TEXT | LVCF_WIDTH | LVCF_FMT; + data.fmt = LVCFMT_LEFT; + data.cx = rect.right; + data.pszText = const_cast(os_string_temp.get_ptr()); + + LRESULT ret = SendMessage(p_listview,LVM_INSERTCOLUMN,p_index,(LPARAM)&data); + if (ret < 0) return UINT_MAX; + else return (unsigned) ret; + } + + void get_item_text(HWND p_listview,unsigned p_index,unsigned p_column,pfc::string_base & p_out) { + enum {buffer_length = 1024*64}; + pfc::array_t buffer; buffer.set_size(buffer_length); + ListView_GetItemText(p_listview,p_index,p_column,buffer.get_ptr(),buffer_length); + p_out = pfc::stringcvt::string_utf8_from_os(buffer.get_ptr(),buffer_length); + } + + bool set_item_text(HWND p_listview,unsigned p_index,unsigned p_column,const char * p_name) + { + LVITEM item = {}; + + pfc::stringcvt::string_os_from_utf8 os_string_temp(p_name); + + item.mask = LVIF_TEXT; + item.iItem = p_index; + item.iSubItem = p_column; + item.pszText = const_cast(os_string_temp.get_ptr()); + return SendMessage(p_listview,LVM_SETITEM,0,(LPARAM)&item) ? true : false; + } + + bool is_item_selected(HWND p_listview,unsigned p_index) + { + LVITEM item = {}; + item.mask = LVIF_STATE; + item.iItem = p_index; + item.stateMask = LVIS_SELECTED; + if (!SendMessage(p_listview,LVM_GETITEM,0,(LPARAM)&item)) return false; + return (item.state & LVIS_SELECTED) ? true : false; + } + + void set_item_selection(HWND p_listview,unsigned p_index,bool p_state) + { + PFC_ASSERT( ::IsWindow(p_listview) ); + LVITEM item = {}; + item.stateMask = LVIS_SELECTED; + item.state = p_state ? LVIS_SELECTED : 0; + WIN32_OP_D( SendMessage(p_listview,LVM_SETITEMSTATE,(WPARAM)p_index,(LPARAM)&item) ); + } + + bool select_single_item(HWND p_listview,unsigned p_index) + { + LRESULT temp = SendMessage(p_listview,LVM_GETITEMCOUNT,0,0); + if (temp < 0) return false; + ListView_SetSelectionMark(p_listview,p_index); + unsigned n; const unsigned m = pfc::downcast_guarded(temp); + for(n=0;n= 0) { + ListView_EnsureVisible(p_list, firstsel, FALSE); + RECT rect; + WIN32_OP_D( ListView_GetItemRect(p_list,firstsel,&rect,LVIR_BOUNDS) ); + p_point.x = (rect.left + rect.right) / 2; + p_point.y = (rect.top + rect.bottom) / 2; + WIN32_OP_D( ClientToScreen(p_list,&p_point) ); + } else { + RECT rect; + WIN32_OP_D(GetClientRect(p_list,&rect)); + p_point.x = (rect.left + rect.right) / 2; + p_point.y = (rect.top + rect.bottom) / 2; + WIN32_OP_D(ClientToScreen(p_list,&p_point)); + } + p_selection = firstsel; + } else { + POINT pt = p_coords; // {(short)LOWORD(p_coords),(short)HIWORD(p_coords)}; + p_point = pt; + POINT client = pt; + WIN32_OP_D( ScreenToClient(p_list,&client) ); + LVHITTESTINFO info = {}; + info.pt = client; + p_selection = ListView_HitTest(p_list,&info); + } +} + +int ListView_GetColumnCount(HWND listView) { + HWND header = ListView_GetHeader(listView); + PFC_ASSERT(header != NULL); + return Header_GetItemCount(header); +} + +void ListView_FixContextMenuPoint(CListViewCtrl list, CPoint & coords) { + if (coords == CPoint(-1, -1)) { + int selWalk = -1; + CRect rcClient; WIN32_OP_D(list.GetClientRect(rcClient)); + for (;;) { + selWalk = list.GetNextItem(selWalk, LVNI_SELECTED); + if (selWalk < 0) { + CRect rc; + WIN32_OP_D(list.GetWindowRect(&rc)); + coords = rc.CenterPoint(); + return; + } + CRect rcItem, rcVisible; + WIN32_OP_D(list.GetItemRect(selWalk, &rcItem, LVIR_BOUNDS)); + if (rcVisible.IntersectRect(rcItem, rcClient)) { + coords = rcVisible.CenterPoint(); + WIN32_OP_D(list.ClientToScreen(&coords)); + return; + } + } + } +} + +unsigned CListViewCtrlEx::GetColunnCount() { + return (unsigned) ListView_GetColumnCount( *this ); +} +unsigned CListViewCtrlEx::AddColumnEx(const wchar_t * name, unsigned widthDLU) { + return InsertColumnEx( GetColunnCount(), name, widthDLU ); +} +unsigned CListViewCtrlEx::InsertColumnEx(unsigned index, const wchar_t * name, unsigned widthDLU) { + + RECT rect = { 0,0,(LONG)widthDLU,0 }; + MapDialogRect(GetParent(), &rect); + + LVCOLUMN data = {}; + data.mask = LVCF_TEXT | LVCF_WIDTH | LVCF_FMT; + data.fmt = LVCFMT_LEFT; + data.cx = rect.right; + data.pszText = const_cast(name); + + auto ret = this->InsertColumn(index, & data ); + if (ret < 0) return UINT_MAX; + else return (unsigned)ret; +} + +void CListViewCtrlEx::FixContextMenuPoint(CPoint & pt) { + ListView_FixContextMenuPoint(*this, pt); +} + + + +unsigned CListViewCtrlEx::InsertString(unsigned index, const wchar_t * str) { + LVITEM item = {}; + + item.mask = LVIF_TEXT; + item.iItem = index; + item.pszText = const_cast(str); + + auto ret = InsertItem(&item); + if ( ret < 0 ) return UINT_MAX; + else return (unsigned) ret; +} +unsigned CListViewCtrlEx::InsertString8(unsigned index, const char * str) { + return InsertString(index, pfc::stringcvt::string_os_from_utf8( str ) ); +} +unsigned CListViewCtrlEx::AddString(const wchar_t * str) { + return InsertString(GetItemCount(), str); +} +unsigned CListViewCtrlEx::AddString8(const char * str) { + return AddString(pfc::stringcvt::string_os_from_utf8( str ) ); +} +void CListViewCtrlEx::SetItemText(unsigned iItem, unsigned iSubItem, const wchar_t * str) { + LVITEM item = {}; + item.mask = LVIF_TEXT; + item.iItem = iItem; + item.iSubItem = iSubItem; + item.pszText = const_cast(str); + SetItem(&item); +} +void CListViewCtrlEx::SetItemText8(unsigned item, unsigned subItem, const char * str) { + SetItemText( item, subItem, pfc::stringcvt::string_os_from_utf8( str ) ); +} + + +DWORD CHeaderCtrlEx::GetItemFormat(int iItem) { + HDITEM item = {}; + item.mask = HDI_FORMAT; + if (!this->GetItem(iItem, &item)) return 0; + return item.fmt; +} +void CHeaderCtrlEx::SetItemFormat(int iItem, DWORD flags) { + HDITEM item = {}; + item.mask = HDI_FORMAT; + item.fmt = flags; + SetItem(iItem, &item); +} + +void CHeaderCtrlEx::SetItemSort(int iItem, int direction) { + DWORD fmtWas = GetItemFormat(iItem); + DWORD fmt = fmtWas & ~(HDF_SORTDOWN | HDF_SORTUP); + if (direction > 0) fmt |= HDF_SORTDOWN; + else if (direction < 0) fmt |= HDF_SORTUP; + if (fmt != fmtWas) SetItemFormat(iItem, fmt); +} + +void CHeaderCtrlEx::SetSingleItemSort(int iItem, int direction) { + const int total = GetItemCount(); + for (int walk = 0; walk < total; ++walk) { + SetItemSort(walk, walk == iItem ? direction : 0); + } +} + +void CHeaderCtrlEx::ClearSort() { + SetSingleItemSort(-1,0); +} + +int CListViewCtrlEx::AddGroup(int iGroupID, const wchar_t * header) { + LVGROUP g = { sizeof(g) }; + g.mask = LVGF_HEADER | LVGF_GROUPID; + g.pszHeader = const_cast( header ); + g.iGroupId = iGroupID; + return __super::AddGroup(&g); +} diff --git a/libPPUI/listview_helper.h b/libPPUI/listview_helper.h new file mode 100644 index 0000000..1e115dd --- /dev/null +++ b/libPPUI/listview_helper.h @@ -0,0 +1,49 @@ +#pragma once + +namespace listview_helper +{ + unsigned insert_item(HWND p_listview,unsigned p_index,const char * p_name,LPARAM p_param);//returns index of new item on success, infinite on failure + + unsigned insert_column(HWND p_listview,unsigned p_index,const char * p_name,unsigned p_width_dlu);//returns index of new item on success, infinite on failure + + bool set_item_text(HWND p_listview,unsigned p_index,unsigned p_column,const char * p_name); + + bool is_item_selected(HWND p_listview,unsigned p_index); + + void set_item_selection(HWND p_listview,unsigned p_index,bool p_state); + + bool select_single_item(HWND p_listview,unsigned p_index); + + bool ensure_visible(HWND p_listview,unsigned p_index); + + void get_item_text(HWND p_listview,unsigned p_index,unsigned p_column,pfc::string_base & p_out); + + unsigned insert_item2(HWND p_listview, unsigned p_index, const char * col0, const char * col1, LPARAM p_param = 0); + unsigned insert_item3(HWND p_listview, unsigned p_index, const char * col0, const char * col1, const char * col2, LPARAM p_param = 0); + + +}; + +static int ListView_GetFirstSelection(HWND p_listview) { + return ListView_GetNextItem(p_listview,-1,LVNI_SELECTED); +} + +static int ListView_GetSingleSelection(HWND p_listview) { + if (ListView_GetSelectedCount(p_listview) != 1) return -1; + return ListView_GetFirstSelection(p_listview); +} + +static int ListView_GetFocusItem(HWND p_listview) { + return ListView_GetNextItem(p_listview,-1,LVNI_FOCUSED); +} + +static bool ListView_IsItemSelected(HWND p_listview,int p_index) { + return ListView_GetItemState(p_listview,p_index,LVIS_SELECTED) != 0; +} + +void ListView_GetContextMenuPoint(HWND p_list,LPARAM p_coords,POINT & p_point,int & p_selection); +void ListView_GetContextMenuPoint(HWND p_list,POINT p_coords,POINT & p_point,int & p_selection); + +int ListView_GetColumnCount(HWND listView); + +void ListView_FixContextMenuPoint(CListViewCtrl list, CPoint & coords); \ No newline at end of file diff --git a/libPPUI/pp-COM-macros.h b/libPPUI/pp-COM-macros.h new file mode 100644 index 0000000..782a88c --- /dev/null +++ b/libPPUI/pp-COM-macros.h @@ -0,0 +1,13 @@ +#pragma once + +#define COM_QI_BEGIN() HRESULT STDMETHODCALLTYPE QueryInterface(REFIID iid,void ** ppvObject) { if (ppvObject == NULL) return E_INVALIDARG; +#define COM_QI_ENTRY(IWhat) { if (iid == __uuidof(IWhat)) {IWhat * temp = this; temp->AddRef(); * ppvObject = temp; return S_OK;} } +#define COM_QI_ENTRY_(IWhat, IID) { if (iid == IID) {IWhat * temp = this; temp->AddRef(); * ppvObject = temp; return S_OK;} } +#define COM_QI_END() * ppvObject = NULL; return E_NOINTERFACE; } + +#define COM_QI_CHAIN(Parent) { HRESULT status = Parent::QueryInterface(iid, ppvObject); if (SUCCEEDED(status)) return status; } + +#define COM_QI_SIMPLE(IWhat) COM_QI_BEGIN() COM_QI_ENTRY(IUnknown) COM_QI_ENTRY(IWhat) COM_QI_END() + + +#define PP_COM_CATCH catch(exception_com const & e) {return e.get_code();} catch(std::bad_alloc) {return E_OUTOFMEMORY;} catch(pfc::exception_invalid_params) {return E_INVALIDARG;} catch(...) {return E_UNEXPECTED;} \ No newline at end of file diff --git a/libPPUI/ppresources.h b/libPPUI/ppresources.h new file mode 100644 index 0000000..2d52d1a --- /dev/null +++ b/libPPUI/ppresources.h @@ -0,0 +1,10 @@ +#pragma once + +class CPPUIResources { +public: + static UINT get_IDI_SCROLL() +#ifdef IDI_SCROLL + { return IDI_SCROLL; } +#endif + ; +}; \ No newline at end of file diff --git a/libPPUI/stdafx.cpp b/libPPUI/stdafx.cpp new file mode 100644 index 0000000..fd4f341 --- /dev/null +++ b/libPPUI/stdafx.cpp @@ -0,0 +1 @@ +#include "stdafx.h" diff --git a/libPPUI/stdafx.h b/libPPUI/stdafx.h new file mode 100644 index 0000000..230e535 --- /dev/null +++ b/libPPUI/stdafx.h @@ -0,0 +1,21 @@ +// stdafx.h : include file for standard system include files, +// or project specific include files that are used frequently, but +// are changed infrequently +// + +#pragma once + +#include "targetver.h" + +#define _SECURE_ATL 1 + +#include +#include +#include +#include +#include +#include +#include +#include + +#include diff --git a/libPPUI/targetver.h b/libPPUI/targetver.h new file mode 100644 index 0000000..809eaad --- /dev/null +++ b/libPPUI/targetver.h @@ -0,0 +1,6 @@ +#pragma once + +#ifndef _WIN32_WINNT +#define _WIN32_WINNT 0x0601 +#include +#endif \ No newline at end of file diff --git a/libPPUI/win32_op.cpp b/libPPUI/win32_op.cpp new file mode 100644 index 0000000..23bf8ba --- /dev/null +++ b/libPPUI/win32_op.cpp @@ -0,0 +1,36 @@ +#include "stdafx.h" +#include "win32_op.h" +#include + +PFC_NORETURN PFC_NOINLINE void WIN32_OP_FAIL() { + const DWORD code = GetLastError(); + PFC_ASSERT(code != NO_ERROR); + throw exception_win32(code); +} + +PFC_NORETURN PFC_NOINLINE void WIN32_OP_FAIL_CRITICAL(const char * what) { +#if PFC_DEBUG + const DWORD code = GetLastError(); + PFC_ASSERT(code != NO_ERROR); +#endif + pfc::crash(); +#if 0 + pfc::string_formatter msg; msg << what << " failure #" << (uint32_t)code; + TRACK_CODE(msg.get_ptr(), uBugCheck()); +#endif +} + +#if PFC_DEBUG +void WIN32_OP_D_FAIL(const wchar_t * _Message, const wchar_t *_File, unsigned _Line) { + const DWORD code = GetLastError(); + pfc::array_t msgFormatted; msgFormatted.set_size(pfc::strlen_t(_Message) + 64); + wsprintfW(msgFormatted.get_ptr(), L"%s (code: %u)", _Message, code); + if (IsDebuggerPresent()) { + OutputDebugString(TEXT("WIN32_OP_D() failure:\n")); + OutputDebugString(msgFormatted.get_ptr()); + OutputDebugString(TEXT("\n")); + pfc::crash(); + } + _wassert(msgFormatted.get_ptr(), _File, _Line); +} +#endif diff --git a/libPPUI/win32_op.h b/libPPUI/win32_op.h new file mode 100644 index 0000000..9dd145f --- /dev/null +++ b/libPPUI/win32_op.h @@ -0,0 +1,36 @@ +#pragma once + +PFC_NORETURN PFC_NOINLINE void WIN32_OP_FAIL(); +PFC_NORETURN PFC_NOINLINE void WIN32_OP_FAIL_CRITICAL(const char * what); + +#ifdef _DEBUG +void WIN32_OP_D_FAIL(const wchar_t * _Message, const wchar_t *_File, unsigned _Line); +#endif + +//Throws an exception when (OP) evaluates to false/zero. +#define WIN32_OP(OP) \ + { \ + SetLastError(NO_ERROR); \ + if (!(OP)) WIN32_OP_FAIL(); \ + } + +// Kills the application with appropriate debug info when (OP) evaluates to false/zero. +#define WIN32_OP_CRITICAL(WHAT, OP) \ + { \ + SetLastError(NO_ERROR); \ + if (!(OP)) WIN32_OP_FAIL_CRITICAL(WHAT); \ + } + +//WIN32_OP_D() acts like an assert specialized for win32 operations in debug build, ignores the return value / error codes in release build. +//Use WIN32_OP_D() instead of WIN32_OP() on operations that are extremely unlikely to fail, so failure condition checks are performed in the debug build only, to avoid bloating release code with pointless error checks. +#ifdef _DEBUG +#define WIN32_OP_D(OP) \ + { \ + SetLastError(NO_ERROR); \ + if (!(OP)) WIN32_OP_D_FAIL(PFC_WIDESTRING(#OP), PFC_WIDESTRING(__FILE__), __LINE__); \ + } + +#else +#define WIN32_OP_D(OP) (void)( (OP), 0); +#endif + diff --git a/libPPUI/win32_utility.cpp b/libPPUI/win32_utility.cpp new file mode 100644 index 0000000..dbfdcf4 --- /dev/null +++ b/libPPUI/win32_utility.cpp @@ -0,0 +1,218 @@ +#include "stdafx.h" +#include "win32_utility.h" +#include "win32_op.h" + +unsigned QueryScreenDPI(HWND wnd) { + HDC dc = GetDC(wnd); + unsigned ret = GetDeviceCaps(dc, LOGPIXELSY); + ReleaseDC(wnd, dc); + return ret; +} +unsigned QueryScreenDPI_X(HWND wnd) { + HDC dc = GetDC(wnd); + unsigned ret = GetDeviceCaps(dc, LOGPIXELSX); + ReleaseDC(wnd, dc); + return ret; +} +unsigned QueryScreenDPI_Y(HWND wnd) { + HDC dc = GetDC(wnd); + unsigned ret = GetDeviceCaps(dc, LOGPIXELSY); + ReleaseDC(wnd, dc); + return ret; +} + +SIZE QueryScreenDPIEx(HWND wnd) { + HDC dc = GetDC(wnd); + SIZE ret = { GetDeviceCaps(dc,LOGPIXELSX), GetDeviceCaps(dc,LOGPIXELSY) }; + ReleaseDC(wnd, dc); + return ret; +} + +void HeaderControl_SetSortIndicator(HWND header_, int column, bool isUp) { + CHeaderCtrl header(header_); + const int total = header.GetItemCount(); + for (int walk = 0; walk < total; ++walk) { + HDITEM item = {}; item.mask = HDI_FORMAT; + if (header.GetItem(walk, &item)) { + DWORD newFormat = item.fmt; + newFormat &= ~(HDF_SORTUP | HDF_SORTDOWN); + if (walk == column) { + newFormat |= isUp ? HDF_SORTUP : HDF_SORTDOWN; + } + if (newFormat != item.fmt) { + item.fmt = newFormat; + header.SetItem(walk, &item); + } + } + } +} + +HINSTANCE GetThisModuleHandle() { + return (HINSTANCE)_AtlBaseModule.m_hInst; +} + +WinResourceRef_t WinLoadResource(HMODULE hMod, const TCHAR * name, const TCHAR * type, WORD wLang) { + SetLastError(0); + HRSRC res = wLang ? FindResourceEx(hMod, type, name, wLang) : FindResource(hMod, name, type); + if ( res == NULL ) WIN32_OP_FAIL(); + SetLastError(0); + HGLOBAL hglob = LoadResource(hMod, res); + if ( hglob == NULL ) WIN32_OP_FAIL(); + SetLastError(0); + void * ptr = LockResource(hglob); + if ( ptr == nullptr ) WIN32_OP_FAIL(); + WinResourceRef_t ref; + ref.ptr = ptr; + ref.bytes = SizeofResource(hMod, res); + return ref; +} + +CComPtr WinLoadResourceAsStream(HMODULE hMod, const TCHAR * name, const TCHAR * type, WORD wLang) { + auto res = WinLoadResource(hMod, name, type, wLang ); + auto str = SHCreateMemStream( (const BYTE*) res.ptr, (UINT) res.bytes ); + if ( str == nullptr ) throw std::bad_alloc(); + CComPtr ret; + ret.Attach( str ); + return ret; +} + +UINT GetFontHeight(HFONT font) +{ + UINT ret; + HDC dc = CreateCompatibleDC(0); + SelectObject(dc, font); + ret = GetTextHeight(dc); + DeleteDC(dc); + return ret; +} + +UINT GetTextHeight(HDC dc) +{ + TEXTMETRIC tm; + POINT pt[2]; + GetTextMetrics(dc, &tm); + pt[0].x = 0; + pt[0].y = tm.tmHeight; + pt[1].x = 0; + pt[1].y = 0; + LPtoDP(dc, pt, 2); + + int ret = pt[0].y - pt[1].y; + return ret > 1 ? (unsigned)ret : 1; +} + + +LRESULT RelayEraseBkgnd(HWND p_from, HWND p_to, HDC p_dc) { + LRESULT status; + POINT pt = { 0, 0 }, pt_old = { 0,0 }; + MapWindowPoints(p_from, p_to, &pt, 1); + OffsetWindowOrgEx(p_dc, pt.x, pt.y, &pt_old); + status = SendMessage(p_to, WM_ERASEBKGND, (WPARAM)p_dc, 0); + SetWindowOrgEx(p_dc, pt_old.x, pt_old.y, 0); + return status; +} + +pfc::string8 EscapeTooltipText(const char * src) +{ + pfc::string8 out; + while (*src) + { + if (*src == '&') + { + out.add_string("&&&"); + src++; + while (*src == '&') + { + out.add_string("&&"); + src++; + } + } else out.add_byte(*(src++)); + } + return out; +} + +bool IsMenuNonEmpty(HMENU menu) { + unsigned n, m = GetMenuItemCount(menu); + for (n = 0; n < m; n++) { + if (GetSubMenu(menu, n)) return true; + if (!(GetMenuState(menu, n, MF_BYPOSITION)&MF_SEPARATOR)) return true; + } + return false; +} + +void SetDefaultMenuItem(HMENU p_menu, unsigned p_id) { + MENUITEMINFO info = { sizeof(info) }; + info.fMask = MIIM_STATE; + GetMenuItemInfo(p_menu, p_id, FALSE, &info); + info.fState |= MFS_DEFAULT; + SetMenuItemInfo(p_menu, p_id, FALSE, &info); +} + +static bool running_under_wine(void) { + HMODULE module = GetModuleHandle(_T("ntdll.dll")); + if (!module) return false; + return GetProcAddress(module, "wine_server_call") != NULL; +} +static bool FetchWineInfoAppend(pfc::string_base & out) { + typedef const char *(__cdecl *t_wine_get_build_id)(void); + typedef void(__cdecl *t_wine_get_host_version)(const char **sysname, const char **release); + const HMODULE ntdll = GetModuleHandle(_T("ntdll.dll")); + if (ntdll == NULL) return false; + t_wine_get_build_id wine_get_build_id; + t_wine_get_host_version wine_get_host_version; + wine_get_build_id = (t_wine_get_build_id)GetProcAddress(ntdll, "wine_get_build_id"); + wine_get_host_version = (t_wine_get_host_version)GetProcAddress(ntdll, "wine_get_host_version"); + if (wine_get_build_id == NULL || wine_get_host_version == NULL) { + if (GetProcAddress(ntdll, "wine_server_call") != NULL) { + out << "wine (unknown version)"; + return true; + } + return false; + } + const char * sysname = NULL; const char * release = NULL; + wine_get_host_version(&sysname, &release); + out << wine_get_build_id() << ", on: " << sysname << " / " << release; + return true; +} + +static void GetOSVersionStringAppend(pfc::string_base & out) { + + if (FetchWineInfoAppend(out)) return; + + OSVERSIONINFO ver = {}; ver.dwOSVersionInfoSize = sizeof(ver); + WIN32_OP(GetVersionEx(&ver)); + SYSTEM_INFO info = {}; + GetNativeSystemInfo(&info); + + out << "Windows " << (int)ver.dwMajorVersion << "." << (int)ver.dwMinorVersion << "." << (int)ver.dwBuildNumber; + if (ver.szCSDVersion[0] != 0) out << " " << pfc::stringcvt::string_utf8_from_os(ver.szCSDVersion, PFC_TABSIZE(ver.szCSDVersion)); + + switch (info.wProcessorArchitecture) { + case PROCESSOR_ARCHITECTURE_AMD64: + out << " x64"; break; + case PROCESSOR_ARCHITECTURE_IA64: + out << " IA64"; break; + case PROCESSOR_ARCHITECTURE_INTEL: + out << " x86"; break; + } +} + +void GetOSVersionString(pfc::string_base & out) { + out.reset(); GetOSVersionStringAppend(out); +} +WORD GetOSVersionCode() { + OSVERSIONINFO ver = {sizeof(ver)}; + WIN32_OP_D(GetVersionEx(&ver)); + + DWORD ret = ver.dwMinorVersion; + ret += ver.dwMajorVersion << 8; + + return (WORD)ret; +} + + +POINT GetCursorPos() { + POINT pt; + WIN32_OP_D( GetCursorPos(&pt) ); + return pt; +} diff --git a/libPPUI/win32_utility.h b/libPPUI/win32_utility.h new file mode 100644 index 0000000..6cda539 --- /dev/null +++ b/libPPUI/win32_utility.h @@ -0,0 +1,48 @@ +#pragma once + +#include // CComPtr + +unsigned QueryScreenDPI(HWND wnd = NULL); +unsigned QueryScreenDPI_X(HWND wnd = NULL); +unsigned QueryScreenDPI_Y(HWND wnd = NULL); + +SIZE QueryScreenDPIEx(HWND wnd = NULL); + +void HeaderControl_SetSortIndicator(HWND header, int column, bool isUp); + +POINT GetCursorPos(); + +HINSTANCE GetThisModuleHandle(); + +struct WinResourceRef_t { + const void * ptr; + size_t bytes; +} ; + +WinResourceRef_t WinLoadResource(HMODULE hMod, const TCHAR * name, const TCHAR * type, WORD wLang = 0); +CComPtr WinLoadResourceAsStream(HMODULE hMod, const TCHAR * name, const TCHAR * type, WORD wLang = 0); + +UINT GetFontHeight(HFONT font); +UINT GetTextHeight(HDC dc); + +LRESULT RelayEraseBkgnd(HWND p_from, HWND p_to, HDC p_dc); + +pfc::string8 EscapeTooltipText(const char * text); + +class CloseHandleScope { +public: + CloseHandleScope(HANDLE handle) throw() : m_handle(handle) {} + ~CloseHandleScope() throw() { CloseHandle(m_handle); } + HANDLE Detach() throw() { return pfc::replace_t(m_handle, INVALID_HANDLE_VALUE); } + HANDLE Get() const throw() { return m_handle; } + void Close() throw() { CloseHandle(Detach()); } + PFC_CLASS_NOT_COPYABLE_EX(CloseHandleScope) +private: + HANDLE m_handle; +}; + +bool IsMenuNonEmpty(HMENU menu); +void SetDefaultMenuItem(HMENU p_menu, unsigned p_id); + +void GetOSVersionString(pfc::string_base & out); +WORD GetOSVersionCode(); diff --git a/libPPUI/wtl-pp.h b/libPPUI/wtl-pp.h new file mode 100644 index 0000000..a2070ce --- /dev/null +++ b/libPPUI/wtl-pp.h @@ -0,0 +1,474 @@ +#pragma once +// Various WTL extensions that are not fb2k specific and can be reused in other WTL based software + +#include +#include + +#define ATLASSERT_SUCCESS(X) {auto RetVal = (X); ATLASSERT( RetVal ); } + +class NoRedrawScope { +public: + NoRedrawScope(HWND p_wnd) throw() : m_wnd(p_wnd) { + m_wnd.SetRedraw(FALSE); + } + ~NoRedrawScope() throw() { + m_wnd.SetRedraw(TRUE); + } +private: + CWindow m_wnd; +}; + +class NoRedrawScopeEx { +public: + NoRedrawScopeEx(HWND p_wnd) throw() : m_wnd(p_wnd), m_active() { + if (m_wnd.IsWindowVisible()) { + m_active = true; + m_wnd.SetRedraw(FALSE); + } + } + ~NoRedrawScopeEx() throw() { + if (m_active) { + m_wnd.SetRedraw(TRUE); + m_wnd.RedrawWindow(NULL,NULL,RDW_INVALIDATE|RDW_ERASE|RDW_ALLCHILDREN); + } + } +private: + bool m_active; + CWindow m_wnd; +}; + + +#define MSG_WM_TIMER_EX(timerId, func) \ + if (uMsg == WM_TIMER && (UINT_PTR)wParam == timerId) \ + { \ + SetMsgHandled(TRUE); \ + func(); \ + lResult = 0; \ + if(IsMsgHandled()) \ + return TRUE; \ + } + +#define MESSAGE_HANDLER_SIMPLE(msg, func) \ + if(uMsg == msg) \ + { \ + SetMsgHandled(TRUE); \ + func(); \ + lResult = 0; \ + if(IsMsgHandled()) \ + return TRUE; \ + } + +// void OnSysCommandHelp() +#define MSG_WM_SYSCOMMAND_HELP(func) \ + if (uMsg == WM_SYSCOMMAND && wParam == SC_CONTEXTHELP) \ + { \ + SetMsgHandled(TRUE); \ + func(); \ + lResult = 0; \ + if(IsMsgHandled()) \ + return TRUE; \ + } + +//BOOL ProcessWindowMessage(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT& lResult, DWORD dwMsgMapID) +#define END_MSG_MAP_HOOK() \ + break; \ + default: \ + return __super::ProcessWindowMessage(hWnd, uMsg, wParam, lParam, lResult, dwMsgMapID); \ + } \ + return FALSE; \ + } + + + +class CImageListContainer : public CImageList { +public: + CImageListContainer() {} + ~CImageListContainer() {Destroy();} +private: + const CImageListContainer & operator=(const CImageListContainer&); + CImageListContainer(const CImageListContainer&); +}; + + +template class CThemeT { +public: + CThemeT(HTHEME source = NULL) : m_theme(source) {} + + ~CThemeT() { + Release(); + } + + HTHEME OpenThemeData(HWND wnd,LPCWSTR classList) { + Release(); + return m_theme = ::OpenThemeData(wnd, classList); + } + + void Release() { + HTHEME releaseme = pfc::replace_null_t(m_theme); + if (managed && releaseme != NULL) CloseThemeData(releaseme); + } + + operator HTHEME() const {return m_theme;} + HTHEME m_theme; +}; +typedef CThemeT CThemeHandle; +typedef CThemeT CTheme; + + +class CCheckBox : public CButton { +public: + void ToggleCheck(bool state) {SetCheck(state ? BST_CHECKED : BST_UNCHECKED);} + bool IsChecked() const {return GetCheck() == BST_CHECKED;} + + CCheckBox(HWND hWnd = NULL) : CButton(hWnd) { } + CCheckBox & operator=(HWND wnd) {m_hWnd = wnd; return *this; } +}; + +class CEditPPHooks : public CContainedWindowT, private CMessageMap { +public: + bool HandleCtrlA = true, NoEscSteal = false, NoEnterSteal = false; + + std::function onEnterKey; + std::function onEscKey; + + CEditPPHooks(CMessageMap * hookMM = nullptr, int hookMMID = 0) : CContainedWindowT(this, 0), m_hookMM(hookMM), m_hookMMID(hookMMID) {} + + BEGIN_MSG_MAP_EX(CEditPPHooks) + MSG_WM_KEYDOWN(OnKeyDown) + MSG_WM_CHAR(OnChar) + MSG_WM_GETDLGCODE(OnEditGetDlgCode) + + if ( m_hookMM != nullptr ) { + + CHAIN_MSG_MAP_ALT_MEMBER( ( * m_hookMM ), m_hookMMID ); + + } + + END_MSG_MAP() + + static void DeleteLastWord( CEdit wnd ) { + if ( wnd.GetWindowLong(GWL_STYLE) & ES_READONLY ) return; + int len = wnd.GetWindowTextLength(); + if ( len <= 0 ) return; + TCHAR * buffer = new TCHAR [ len + 1 ]; + if ( wnd.GetWindowText( buffer, len + 1 ) <= 0 ) { + delete[] buffer; + return; + } + buffer[len] = 0; + int selStart = len, selEnd = len; + wnd.GetSel(selStart, selEnd); + if ( selStart < 0 || selStart > len ) selStart = len; // sanity + if ( selEnd < selStart ) selEnd = selStart; // sanity + int work = selStart; + if ( work == selEnd ) { + // Only do our stuff if there is nothing yet selected. Otherwise first delete selection. + while( work > 0 && isWordDelimiter(buffer[work-1]) ) --work; + while( work > 0 && !isWordDelimiter(buffer[work-1] ) ) --work; + } + delete[] buffer; + if ( selEnd > work ) { + wnd.SetSel(work, selEnd, TRUE ); + wnd.ReplaceSel( TEXT(""), TRUE ); + } + } +private: + static bool isWordDelimiter( TCHAR c ) { + return (unsigned) c <= ' ' || c == ',' || c == '.' || c == ';' || c == ':'; + } + void OnChar(UINT nChar, UINT, UINT nFlags) { + if (m_suppressChar != 0) { + if (nChar == m_suppressChar) return; + } + if (m_suppressScanCode != 0) { + UINT code = nFlags & 0xFF; + if (code == m_suppressScanCode) return; + } + SetMsgHandled(FALSE); + } + void OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags) { + m_suppressChar = 0; + m_suppressScanCode = 0; + if (HandleCtrlA) { + if (nChar == 'A') { + if (GetHotkeyModifierFlags() == MOD_CONTROL) { + m_suppressScanCode = nFlags & 0xFF; + this->SetSelAll(); return; + } + } + if ( nChar == VK_BACK ) { + if (GetHotkeyModifierFlags() == MOD_CONTROL) { + m_suppressScanCode = nFlags & 0xFF; + DeleteLastWord( *this ) ; return; + } + } + if ( nChar == VK_RETURN && onEnterKey ) { + m_suppressChar = nChar; + onEnterKey(); return; + } + if ( nChar == VK_ESCAPE && onEscKey ) { + m_suppressChar = nChar; + onEscKey(); return; + } + } + SetMsgHandled(FALSE); + } + UINT OnEditGetDlgCode(LPMSG lpMsg) { + if (lpMsg == NULL) { + SetMsgHandled(FALSE); return 0; + } else { + switch(lpMsg->message) { + case WM_KEYDOWN: + case WM_SYSKEYDOWN: + switch(lpMsg->wParam) { + case VK_ESCAPE: + if (onEscKey) { + return DLGC_WANTMESSAGE; + } + SetMsgHandled(!!NoEscSteal); + return 0; + case VK_RETURN: + if (onEnterKey) { + return DLGC_WANTMESSAGE; + } + SetMsgHandled(!!NoEnterSteal); + return 0; + default: + SetMsgHandled(FALSE); return 0; + } + default: + SetMsgHandled(FALSE); return 0; + + } + } + } + UINT m_suppressChar = 0, m_suppressScanCode = 0; + CMessageMap * const m_hookMM; + const int m_hookMMID; +}; + + +class CEditNoEscSteal : public CContainedWindowT, private CMessageMap { +public: + CEditNoEscSteal() : CContainedWindowT(this, 0) {} + BEGIN_MSG_MAP_EX(CEditNoEscSteal) + MSG_WM_GETDLGCODE(OnEditGetDlgCode) + END_MSG_MAP() +private: + UINT OnEditGetDlgCode(LPMSG lpMsg) { + if (lpMsg == NULL) { + SetMsgHandled(FALSE); return 0; + } else { + switch(lpMsg->message) { + case WM_KEYDOWN: + case WM_SYSKEYDOWN: + switch(lpMsg->wParam) { + case VK_ESCAPE: + return 0; + default: + SetMsgHandled(FALSE); return 0; + } + default: + SetMsgHandled(FALSE); return 0; + + } + } + } +}; + +class CEditNoEnterEscSteal : public CContainedWindowT, private CMessageMap { +public: + CEditNoEnterEscSteal() : CContainedWindowT(this, 0) {} + BEGIN_MSG_MAP_EX(CEditNoEscSteal) + MSG_WM_GETDLGCODE(OnEditGetDlgCode) + END_MSG_MAP() +private: + UINT OnEditGetDlgCode(LPMSG lpMsg) { + if (lpMsg == NULL) { + SetMsgHandled(FALSE); return 0; + } else { + switch(lpMsg->message) { + case WM_KEYDOWN: + case WM_SYSKEYDOWN: + switch(lpMsg->wParam) { + case VK_ESCAPE: + case VK_RETURN: + return 0; + default: + SetMsgHandled(FALSE); return 0; + } + default: + SetMsgHandled(FALSE); return 0; + + } + } + } +}; + + + +class CWindowClassUnregisterScope { +public: + CWindowClassUnregisterScope() : name() {} + const TCHAR * name; + void Set(const TCHAR * n) {ATLASSERT( name == NULL ); name = n; } + bool IsActive() const {return name != NULL;} + void CleanUp() { + const TCHAR * n = name; name = NULL; + if (n != NULL) ATLASSERT_SUCCESS( UnregisterClass(n, (HINSTANCE)&__ImageBase) ); + } + ~CWindowClassUnregisterScope() {CleanUp();} +}; + + +// CWindowRegisteredT +// Minimalistic wrapper for registering own window classes that can be created by class name, included in dialogs and such. +// Usage: +// class myClass : public CWindowRegisteredT {...}; +// Call myClass::Register() before first use +template +class CWindowRegisteredT : public TBaseClass, public CMessageMap { +public: + static UINT GetClassStyle() { + return CS_VREDRAW | CS_HREDRAW; + } + static HCURSOR GetCursor() { + return ::LoadCursor(NULL, IDC_ARROW); + } + + BEGIN_MSG_MAP_EX(CWindowRegisteredT) + END_MSG_MAP() + + + static void Register() { + static CWindowClassUnregisterScope scope; + if (!scope.IsActive()) { + WNDCLASS wc = {}; + wc.style = TClass::GetClassStyle(); + wc.cbWndExtra = sizeof(void*); + wc.lpszClassName = TClass::GetClassName(); + wc.lpfnWndProc = myWindowProc; + wc.hInstance = (HINSTANCE)&__ImageBase; + wc.hCursor = TClass::GetCursor(); + ATLASSERT_SUCCESS( RegisterClass(&wc) != 0 ); + scope.Set(wc.lpszClassName); + } + } +protected: + virtual ~CWindowRegisteredT() {} +private: + static LRESULT CALLBACK myWindowProc(HWND wnd, UINT msg, WPARAM wp, LPARAM lp) { + TClass * i = NULL; + if (msg == WM_NCCREATE) { + i = new TClass; + i->Attach(wnd); + ::SetWindowLongPtr(wnd, 0, reinterpret_cast(i)); + } else { + i = reinterpret_cast( ::GetWindowLongPtr(wnd, 0) ); + } + LRESULT r; + if (i == NULL || !i->ProcessWindowMessage(wnd, msg, wp, lp, r)) r = ::DefWindowProc(wnd, msg, wp, lp); + if (msg == WM_NCDESTROY) { + ::SetWindowLongPtr(wnd, 0, 0); + delete i; + } + return r; + } +}; + + + + +class CSRWlock { +public: + CSRWlock() : theLock() { +#if _WIN32_WINNT < 0x600 + auto dll = GetModuleHandle(_T("kernel32")); + Bind(AcquireSRWLockExclusive, dll, "AcquireSRWLockExclusive"); + Bind(AcquireSRWLockShared, dll, "AcquireSRWLockShared"); + Bind(ReleaseSRWLockExclusive, dll, "ReleaseSRWLockExclusive"); + Bind(ReleaseSRWLockShared, dll, "ReleaseSRWLockShared"); +#endif + } + + bool HaveAPI() { +#if _WIN32_WINNT < 0x600 + return AcquireSRWLockExclusive != NULL; +#else + return true; +#endif + } + + void EnterShared() { + AcquireSRWLockShared( & theLock ); + } + void EnterExclusive() { + AcquireSRWLockExclusive( & theLock ); + } + void LeaveShared() { + ReleaseSRWLockShared( & theLock ); + } + void LeaveExclusive() { + ReleaseSRWLockExclusive( &theLock ); + } + +private: + CSRWlock(const CSRWlock&) = delete; + void operator=(const CSRWlock&) = delete; + + SRWLOCK theLock; +#if _WIN32_WINNT < 0x600 + template static void Bind(func_t & func, HMODULE dll, const char * name) { + func = reinterpret_cast(GetProcAddress( dll, name ) ); + } + + VOID (WINAPI * AcquireSRWLockExclusive)(PSRWLOCK SRWLock); + VOID (WINAPI * AcquireSRWLockShared)(PSRWLOCK SRWLock); + VOID (WINAPI * ReleaseSRWLockExclusive)(PSRWLOCK SRWLock); + VOID (WINAPI * ReleaseSRWLockShared)(PSRWLOCK SRWLock); +#endif +}; + +#if _WIN32_WINNT < 0x600 +class CSRWorCS { +public: + CSRWorCS() : cs() { + if (!srw.HaveAPI()) InitializeCriticalSection(&cs); + } + ~CSRWorCS() { + if (!srw.HaveAPI()) DeleteCriticalSection(& cs ); + } + void EnterShared() { + if (srw.HaveAPI()) srw.EnterShared(); + else EnterCriticalSection(&cs); + } + void EnterExclusive() { + if (srw.HaveAPI()) srw.EnterExclusive(); + else EnterCriticalSection(&cs); + } + void LeaveShared() { + if (srw.HaveAPI()) srw.LeaveShared(); + else LeaveCriticalSection(&cs); + } + void LeaveExclusive() { + if (srw.HaveAPI()) srw.LeaveExclusive(); + else LeaveCriticalSection(&cs); + } +private: + CSRWorCS(const CSRWorCS&) = delete; + void operator=(const CSRWorCS&) = delete; + + CSRWlock srw; + CRITICAL_SECTION cs; +}; +#else +typedef CSRWlock CSRWorCS; +#endif + + +template class CContainedWindowSimpleT : public CContainedWindowT, public CMessageMap { +public: + CContainedWindowSimpleT() : CContainedWindowT(this) {} + BEGIN_MSG_MAP(CContainedWindowSimpleT) + END_MSG_MAP() +}; diff --git a/pfc/alloc.h b/pfc/alloc.h new file mode 100644 index 0000000..46cab8b --- /dev/null +++ b/pfc/alloc.h @@ -0,0 +1,539 @@ +#pragma once + +namespace pfc { + + static void * raw_malloc(t_size p_size) { + return p_size > 0 ? new_ptr_check_t(malloc(p_size)) : NULL; + } + + static void raw_free(void * p_block) throw() {free(p_block);} + + inline void* raw_realloc(void * p_ptr,t_size p_size) { + if (p_size == 0) {raw_free(p_ptr); return NULL;} + else if (p_ptr == NULL) return raw_malloc(p_size); + else return pfc::new_ptr_check_t(::realloc(p_ptr,p_size)); + } + + inline bool raw_realloc_inplace(void * p_block,t_size p_size) throw() { + if (p_block == NULL) return p_size == 0; +#ifdef _MSC_VER + if (p_size == 0) return false; + return _expand(p_block,p_size) != NULL; +#else + return false; +#endif + } + + template + t_size calc_array_width(t_size p_width) { + return pfc::mul_safe_t(p_width,sizeof(T)); + } + + template + T * __raw_malloc_t(t_size p_size) { + return reinterpret_cast(raw_malloc(calc_array_width(p_size))); + } + + template + void __raw_free_t(T * p_block) throw() { + raw_free(reinterpret_cast(p_block)); + } + + template + T * __raw_realloc_t(T * p_block,t_size p_size) { + return reinterpret_cast(raw_realloc(p_block,calc_array_width(p_size))); + } + + template + bool __raw_realloc_inplace_t(T * p_block,t_size p_size) { + return raw_realloc_inplace(p_block,calc_array_width(p_size)); + } + + + template + inline t_int safe_shift_left_t(t_int p_val,t_size p_shift = 1) { + t_int newval = p_val << p_shift; + if (newval >> p_shift != p_val) throw t_exception(); + return newval; + } + + template class alloc_dummy { + private: typedef alloc_dummy t_self; + public: + alloc_dummy() {} + void set_size(t_size p_size) {throw pfc::exception_not_implemented();} + t_size get_size() const {throw pfc::exception_not_implemented();} + const t_item & operator[](t_size p_index) const {throw pfc::exception_not_implemented();} + t_item & operator[](t_size p_index) {throw pfc::exception_not_implemented();} + + bool is_ptr_owned(const void * p_item) const {return false;} + + //set to true when we prioritize speed over memory usage + enum { alloc_prioritizes_speed = false }; + + //not mandatory + const t_item * get_ptr() const {throw pfc::exception_not_implemented();} + t_item * get_ptr() {throw pfc::exception_not_implemented();} + void prealloc(t_size) {throw pfc::exception_not_implemented();} + void force_reset() {throw pfc::exception_not_implemented();} + void move_from(t_self &) {throw pfc::exception_not_implemented();} + private: + const t_self & operator=(const t_self &) {throw pfc::exception_not_implemented();} + alloc_dummy(const t_self&) {throw pfc::exception_not_implemented();} + }; + + template + bool is_pointer_in_range(const t_item * p_buffer,t_size p_buffer_size,const void * p_pointer) { + return p_pointer >= reinterpret_cast(p_buffer) && p_pointer < reinterpret_cast(p_buffer + p_buffer_size); + } + + + //! Simple inefficient fully portable allocator. + template class alloc_simple { + private: typedef alloc_simple t_self; + public: + alloc_simple() : m_data(NULL), m_size(0) {} + void set_size(t_size p_size) { + if (p_size != m_size) { + t_item * l_data = NULL; + if (p_size > 0) l_data = new t_item[p_size]; + try { + pfc::memcpy_t(l_data,m_data,pfc::min_t(m_size,p_size)); + } catch(...) { + delete[] l_data; + throw; + } + delete[] m_data; + m_data = l_data; + m_size = p_size; + } + } + t_size get_size() const {return m_size;} + const t_item & operator[](t_size p_index) const {PFC_ASSERT(p_index < m_size); return m_data[p_index];} + t_item & operator[](t_size p_index) {PFC_ASSERT(p_index < m_size); return m_data[p_index];} + bool is_ptr_owned(const void * p_item) const {return is_pointer_in_range(get_ptr(),get_size(),p_item);} + + enum { alloc_prioritizes_speed = false }; + + t_item * get_ptr() {return m_data;} + const t_item * get_ptr() const {return m_data;} + + void prealloc(t_size) {} + void force_reset() {set_size(0);} + + ~alloc_simple() {delete[] m_data;} + + void move_from(t_self & other) { + delete[] m_data; + m_data = replace_null_t(other.m_data); + m_size = replace_null_t(other.m_size); + } + private: + const t_self & operator=(const t_self &) = delete; + alloc_simple(const t_self&) = delete; + + t_item * m_data; + t_size m_size; + }; + + template class __array_fast_helper_t { + private: + typedef __array_fast_helper_t t_self; + public: + __array_fast_helper_t() : m_buffer(NULL), m_size(0), m_size_total(0) {} + + + void set_size(t_size p_size,t_size p_size_total) { + PFC_ASSERT(p_size <= p_size_total); + PFC_ASSERT(m_size <= m_size_total); + if (p_size_total > m_size_total) { + resize_storage(p_size_total); + resize_content(p_size); + } else { + resize_content(p_size); + resize_storage(p_size_total); + } + } + + + + t_size get_size() const {return m_size;} + t_size get_size_total() const {return m_size_total;} + const t_item & operator[](t_size p_index) const {PFC_ASSERT(p_index < m_size); return m_buffer[p_index];} + t_item & operator[](t_size p_index) {PFC_ASSERT(p_index < m_size); return m_buffer[p_index];} + ~__array_fast_helper_t() { + set_size(0,0); + } + t_item * get_ptr() {return m_buffer;} + const t_item * get_ptr() const {return m_buffer;} + bool is_ptr_owned(const void * p_item) const {return is_pointer_in_range(m_buffer,m_size_total,p_item);} + + void move_from(t_self & other) { + set_size(0,0); + m_buffer = replace_null_t(other.m_buffer); + m_size = replace_null_t(other.m_size); + m_size_total = replace_null_t(other.m_size_total); + } + private: + const t_self & operator=(const t_self &) = delete; + __array_fast_helper_t(const t_self &) = delete; + + + void resize_content(t_size p_size) { + if (traits_t::needs_constructor || traits_t::needs_destructor) { + if (p_size > m_size) {//expand + do { + __unsafe__in_place_constructor_t(m_buffer[m_size]); + m_size++; + } while(m_size < p_size); + } else if (p_size < m_size) { + __unsafe__in_place_destructor_array_t(m_buffer + p_size, m_size - p_size); + m_size = p_size; + } + } else { + m_size = p_size; + } + } + + void resize_storage(t_size p_size) { + PFC_ASSERT( m_size <= m_size_total ); + PFC_ASSERT( m_size <= p_size ); + if (m_size_total != p_size) { + if (pfc::traits_t::realloc_safe) { + m_buffer = pfc::__raw_realloc_t(m_buffer,p_size); + m_size_total = p_size; + } else if (__raw_realloc_inplace_t(m_buffer,p_size)) { + //success + m_size_total = p_size; + } else { + t_item * newbuffer = pfc::__raw_malloc_t(p_size); + try { + pfc::__unsafe__in_place_constructor_array_copy_t(newbuffer,m_size,m_buffer); + } catch(...) { + pfc::__raw_free_t(newbuffer); + throw; + } + pfc::__unsafe__in_place_destructor_array_t(m_buffer,m_size); + pfc::__raw_free_t(m_buffer); + m_buffer = newbuffer; + m_size_total = p_size; + } + } + } + + t_item * m_buffer; + t_size m_size,m_size_total; + }; + + template class __array_lite_helper_t { + private: + typedef __array_lite_helper_t t_self; + public: + __array_lite_helper_t() : m_buffer(NULL), m_size(0) {} + + + void set_size(t_size p_size) { + if (p_size > m_size) { // expand + resize_storage(p_size); + resize_content(p_size); + } else if (p_size < m_size) { // shrink + resize_content(p_size); + resize_storage(p_size); + } + } + + + + t_size get_size() const {return m_size;} + const t_item & operator[](t_size p_index) const {PFC_ASSERT(p_index < m_size); return m_buffer[p_index];} + t_item & operator[](t_size p_index) {PFC_ASSERT(p_index < m_size); return m_buffer[p_index];} + ~__array_lite_helper_t() { + set_size(0); + } + t_item * get_ptr() {return m_buffer;} + const t_item * get_ptr() const {return m_buffer;} + bool is_ptr_owned(const void * p_item) const {return is_pointer_in_range(m_buffer,m_size,p_item);} + + void move_from(t_self & other) { + set_size(0); + m_buffer = replace_null_t(other.m_buffer); + m_size = replace_null_t(other.m_size); + } + private: + const t_self & operator=(const t_self &) = delete; + __array_lite_helper_t(const t_self &) = delete; + + + void resize_content(t_size p_size) { + if (traits_t::needs_constructor || traits_t::needs_destructor) { + if (p_size > m_size) {//expand + do { + __unsafe__in_place_constructor_t(m_buffer[m_size]); + m_size++; + } while(m_size < p_size); + } else if (p_size < m_size) { + __unsafe__in_place_destructor_array_t(m_buffer + p_size, m_size - p_size); + m_size = p_size; + } + } else { + m_size = p_size; + } + } + + void resize_storage(t_size p_size) { + PFC_ASSERT( m_size <= p_size ); + if (pfc::traits_t::realloc_safe) { + m_buffer = pfc::__raw_realloc_t(m_buffer,p_size); + //m_size_total = p_size; + } else if (__raw_realloc_inplace_t(m_buffer,p_size)) { + //success + //m_size_total = p_size; + } else { + t_item * newbuffer = pfc::__raw_malloc_t(p_size); + try { + pfc::__unsafe__in_place_constructor_array_copy_t(newbuffer,m_size,m_buffer); + } catch(...) { + pfc::__raw_free_t(newbuffer); + throw; + } + pfc::__unsafe__in_place_destructor_array_t(m_buffer,m_size); + pfc::__raw_free_t(m_buffer); + m_buffer = newbuffer; + //m_size_total = p_size; + } + } + + t_item * m_buffer; + t_size m_size; + }; + + template class alloc_standard { + private: typedef alloc_standard t_self; + public: + alloc_standard() {} + void set_size(t_size p_size) {m_content.set_size(p_size);} + + t_size get_size() const {return m_content.get_size();} + + const t_item & operator[](t_size p_index) const {return m_content[p_index];} + t_item & operator[](t_size p_index) {return m_content[p_index];} + + const t_item * get_ptr() const {return m_content.get_ptr();} + t_item * get_ptr() {return m_content.get_ptr();} + + bool is_ptr_owned(const void * p_item) const {return m_content.is_ptr_owned(p_item);} + void prealloc(t_size p_size) {} + void force_reset() {set_size(0);} + + enum { alloc_prioritizes_speed = false }; + + void move_from(t_self & other) { m_content.move_from(other.m_content); } + private: + alloc_standard(const t_self &) = delete; + const t_self & operator=(const t_self&) = delete; + + __array_lite_helper_t m_content; + }; + + template class alloc_fast { + private: typedef alloc_fast t_self; + public: + alloc_fast() {} + + void set_size(t_size p_size) { + t_size size_base = m_data.get_size_total(); + if (size_base == 0) size_base = 1; + while(size_base < p_size) { + size_base = safe_shift_left_t(size_base,1); + } + while(size_base >> 2 > p_size) { + size_base >>= 1; + } + m_data.set_size(p_size,size_base); + } + + t_size get_size() const {return m_data.get_size();} + const t_item & operator[](t_size p_index) const {return m_data[p_index];} + t_item & operator[](t_size p_index) {return m_data[p_index];} + + const t_item * get_ptr() const {return m_data.get_ptr();} + t_item * get_ptr() {return m_data.get_ptr();} + bool is_ptr_owned(const void * p_item) const {return m_data.is_ptr_owned(p_item);} + void prealloc(t_size) {} + void force_reset() {m_data.set_size(0,0);} + + enum { alloc_prioritizes_speed = true }; + + void move_from(t_self & other) { m_data.move_from(other.m_data); } + private: + alloc_fast(const t_self &) = delete; + const t_self & operator=(const t_self&) = delete; + __array_fast_helper_t m_data; + }; + + template class alloc_fast_aggressive { + private: typedef alloc_fast_aggressive t_self; + public: + alloc_fast_aggressive() {} + + void set_size(t_size p_size) { + t_size size_base = m_data.get_size_total(); + if (size_base == 0) size_base = 1; + while(size_base < p_size) { + size_base = safe_shift_left_t(size_base,1); + } + m_data.set_size(p_size,size_base); + } + + void prealloc(t_size p_size) { + if (p_size > 0) { + t_size size_base = m_data.get_size_total(); + if (size_base == 0) size_base = 1; + while(size_base < p_size) { + size_base = safe_shift_left_t(size_base,1); + } + m_data.set_size(m_data.get_size(),size_base); + } + } + + t_size get_size() const {return m_data.get_size();} + const t_item & operator[](t_size p_index) const {;return m_data[p_index];} + t_item & operator[](t_size p_index) {return m_data[p_index];} + + const t_item * get_ptr() const {return m_data.get_ptr();} + t_item * get_ptr() {return m_data.get_ptr();} + bool is_ptr_owned(const void * p_item) const {return m_data.is_ptr_owned(p_item);} + void force_reset() {m_data.set_size(0,0);} + + enum { alloc_prioritizes_speed = true }; + + void move_from(t_self & other) { m_data.move_from(other.m_data); } + private: + alloc_fast_aggressive(const t_self &) = delete; + const t_self & operator=(const t_self&) = delete; + __array_fast_helper_t m_data; + }; + + template class alloc_fixed { + public: + template class alloc { + private: typedef alloc t_self; + public: + alloc() : m_size(0) {} + + void set_size(t_size p_size) { + static_assert_t(); + + if (p_size > p_width) throw pfc::exception_overflow(); + else if (p_size > m_size) { + __unsafe__in_place_constructor_array_t(get_ptr()+m_size,p_size-m_size); + m_size = p_size; + } else if (p_size < m_size) { + __unsafe__in_place_destructor_array_t(get_ptr()+p_size,m_size-p_size); + m_size = p_size; + } + } + + ~alloc() { + if (pfc::traits_t::needs_destructor) set_size(0); + } + + t_size get_size() const {return m_size;} + + t_item * get_ptr() {return reinterpret_cast(&m_array);} + const t_item * get_ptr() const {return reinterpret_cast(&m_array);} + + const t_item & operator[](t_size n) const {return get_ptr()[n];} + t_item & operator[](t_size n) {return get_ptr()[n];} + bool is_ptr_owned(const void * p_item) const {return is_pointer_in_range(get_ptr(),p_width,p_item);} + void prealloc(t_size) {} + void force_reset() {set_size(0);} + + enum { alloc_prioritizes_speed = false }; + + void move_from(t_self & other) { + const size_t count = other.get_size(); + set_size( count ); + for(size_t w = 0; w < count; ++w) this->get_ptr()[w] = other.get_ptr()[w]; + } + private: + alloc(const t_self&) {throw pfc::exception_not_implemented();} + const t_self& operator=(const t_self&) {throw pfc::exception_not_implemented();} + + t_uint8 m_array[sizeof(t_item[p_width])]; + t_size m_size; + }; + }; + + template class t_alloc = alloc_standard > class alloc_hybrid { + public: + template class alloc { + private: typedef alloc t_self; + public: + alloc() {} + + void set_size(t_size p_size) { + if (p_size > p_width) { + m_fixed.set_size(p_width); + m_variable.set_size(p_size - p_width); + } else { + m_fixed.set_size(p_size); + m_variable.set_size(0); + } + } + + t_item & operator[](t_size p_index) { + PFC_ASSERT(p_index < get_size()); + if (p_index < p_width) return m_fixed[p_index]; + else return m_variable[p_index - p_width]; + } + + const t_item & operator[](t_size p_index) const { + PFC_ASSERT(p_index < get_size()); + if (p_index < p_width) return m_fixed[p_index]; + else return m_variable[p_index - p_width]; + } + + t_size get_size() const {return m_fixed.get_size() + m_variable.get_size();} + bool is_ptr_owned(const void * p_item) const {return m_fixed.is_ptr_owned(p_item) || m_variable.is_ptr_owned(p_item);} + void prealloc(t_size p_size) { + if (p_size > p_width) m_variable.prealloc(p_size - p_width); + } + void force_reset() { + m_fixed.force_reset(); m_variable.force_reset(); + } + enum { alloc_prioritizes_speed = t_alloc::alloc_prioritizes_speed }; + + void move_from(t_self & other) { + m_fixed.move_from(other.m_fixed); + m_variable.move_from(other.m_variable); + } + private: + alloc(const t_self&) {throw pfc::exception_not_implemented();} + const t_self& operator=(const t_self&) {throw pfc::exception_not_implemented();} + + typename alloc_fixed::template alloc m_fixed; + t_alloc m_variable; + }; + }; + + template class traits_t > : public traits_default_movable {}; + template class traits_t<__array_fast_helper_t > : public traits_default_movable {}; + template class traits_t > : public pfc::traits_t<__array_fast_helper_t > {}; + template class traits_t > : public pfc::traits_t<__array_fast_helper_t > {}; + template class traits_t > : public pfc::traits_t<__array_fast_helper_t > {}; + +#if 0//not working (compiler bug?) + template class traits_t::template alloc > : public pfc::traits_t { + public: + enum { + needs_constructor = true, + }; + }; + + template class t_alloc,typename t_item> + class traits_t::template alloc > : public traits_combined::template alloc > {}; +#endif + + +}; diff --git a/pfc/array.h b/pfc/array.h new file mode 100644 index 0000000..bff8b1a --- /dev/null +++ b/pfc/array.h @@ -0,0 +1,361 @@ +#pragma once + +namespace pfc { + + template class t_alloc = alloc_standard> class array_t; + + + //! Special simplififed version of array class that avoids stepping on landmines with classes without public copy operators/constructors. + template + class array_staticsize_t { + public: typedef _t_item t_item; + private: typedef array_staticsize_t t_self; + public: + array_staticsize_t() : m_array(NULL), m_size(0) {} + array_staticsize_t(t_size p_size) : m_array(new t_item[p_size]), m_size(p_size) {} + ~array_staticsize_t() {release_();} + + //! Copy constructor nonfunctional when data type is not copyable. + array_staticsize_t(const t_self & p_source) : m_size(0), m_array(NULL) { + *this = p_source; + } + array_staticsize_t(t_self && p_source) { + move_(p_source); + } + + //! Copy operator nonfunctional when data type is not copyable. + const t_self & operator=(const t_self & p_source) { + release_(); + + const t_size newsize = p_source.get_size(); + if (newsize > 0) { + m_array = new t_item[newsize]; + m_size = newsize; + for(t_size n = 0; n < newsize; n++) m_array[n] = p_source[n]; + } + return *this; + } + + //! Move operator. + const t_self & operator=(t_self && p_source) { + release_(); + move_(p_source); + return *this; + } + + void set_size_discard(t_size p_size) { + release_(); + if (p_size > 0) { + m_array = new t_item[p_size]; + m_size = p_size; + } + } + template + void set_data_fromptr(const t_source * p_buffer,t_size p_count) { + if (p_count == m_size) { + pfc::copy_array_loop_t(*this,p_buffer,p_count); + } else { + t_item * arr = new t_item[p_count]; + try { + pfc::copy_array_loop_t(arr, p_buffer, p_count); + } catch(...) { delete[] arr; throw; } + delete[] m_array; + m_array = arr; + m_size = p_count; + } + } + + template + void assign(t_source const * items, size_t count) { + set_data_fromptr( items, count ); + } + + + t_size get_size() const {return m_size;} + t_size size() const {return m_size;} // std compat + const t_item * get_ptr() const {return m_array;} + t_item * get_ptr() {return m_array;} + + const t_item & operator[](t_size p_index) const {PFC_ASSERT(p_index < get_size());return m_array[p_index];} + t_item & operator[](t_size p_index) {PFC_ASSERT(p_index < get_size());return m_array[p_index];} + + template bool is_owned(const t_source & p_item) {return pfc::is_pointer_in_range(get_ptr(),get_size(),&p_item);} + + template void enumerate(t_out & out) const { for(t_size walk = 0; walk < m_size; ++walk) out(m_array[walk]); } + private: + void release_() { + m_size = 0; + delete[] pfc::replace_null_t(m_array); + } + void move_(t_self & from) { + m_size = from.m_size; + m_array = from.m_array; + from.m_size = 0; + from.m_array = NULL; + } + t_item * m_array; + t_size m_size; + }; + + template + void copy_array_t(t_to & p_to,const t_from & p_from) { + const t_size size = array_size_t(p_from); + if (p_to.has_owned_items(p_from)) {//avoid landmines with actual array data overlapping, or p_from being same as p_to + array_staticsize_t temp; + temp.set_size_discard(size); + pfc::copy_array_loop_t(temp,p_from,size); + p_to.set_size(size); + pfc::copy_array_loop_t(p_to,temp,size); + } else { + p_to.set_size(size); + pfc::copy_array_loop_t(p_to,p_from,size); + } + } + + template + void fill_array_t(t_array & p_array,const t_value & p_value) { + const t_size size = array_size_t(p_array); + for(t_size n=0;n class t_alloc> class array_t { + public: typedef _t_item t_item; + private: typedef array_t t_self; + public: + array_t() {} + array_t(const t_self & p_source) {copy_array_t(*this,p_source);} + template array_t(const t_source & p_source) {copy_array_t(*this,p_source);} + const t_self & operator=(const t_self & p_source) {copy_array_t(*this,p_source); return *this;} + template const t_self & operator=(const t_source & p_source) {copy_array_t(*this,p_source); return *this;} + + array_t(t_self && p_source) {move_from(p_source);} + const t_self & operator=(t_self && p_source) {move_from(p_source); return *this;} + + void set_size(t_size p_size) {m_alloc.set_size(p_size);} + void resize( size_t s ) { set_size(s); } // std compat + + template + void set_size_fill(size_t p_size, fill_t const & filler) { + size_t before = get_size(); + set_size( p_size ); + for(size_t w = before; w < p_size; ++w) this->get_ptr()[w] = filler; + } + + void set_size_in_range(size_t minSize, size_t maxSize) { + if (minSize >= maxSize) { set_size( minSize); return; } + size_t walk = maxSize; + for(;;) { + try { + set_size(walk); + return; + } catch(std::bad_alloc) { + if (walk <= minSize) throw; + // go on + } + walk >>= 1; + if (walk < minSize) walk = minSize; + } + } + void set_size_discard(t_size p_size) {m_alloc.set_size(p_size);} + void set_count(t_size p_count) {m_alloc.set_size(p_count);} + t_size get_size() const {return m_alloc.get_size();} + size_t size() const {return m_alloc.get_size();} // std compat + t_size get_count() const {return m_alloc.get_size();} + void force_reset() {m_alloc.force_reset();} + + const t_item & operator[](t_size p_index) const {PFC_ASSERT(p_index < get_size());return m_alloc[p_index];} + t_item & operator[](t_size p_index) {PFC_ASSERT(p_index < get_size());return m_alloc[p_index];} + + //! Warning: buffer pointer must not point to buffer allocated by this array (fixme). + template + void set_data_fromptr(const t_source * p_buffer,t_size p_count) { + set_size(p_count); + pfc::copy_array_loop_t(*this,p_buffer,p_count); + } + + template + void append(const t_array & p_source) { + if (has_owned_items(p_source)) append(array_t(p_source)); + else { + const t_size source_size = array_size_t(p_source); + const t_size base = get_size(); + increase_size(source_size); + for(t_size n=0;n + void insert_multi(const t_insert & value, t_size base, t_size count) { + const t_size oldSize = get_size(); + if (base > oldSize) base = oldSize; + increase_size(count); + pfc::memmove_t(get_ptr() + base + count, get_ptr() + base, oldSize - base); + pfc::fill_ptr_t(get_ptr() + base, count, value); + } + template void append_multi(const t_append & value, t_size count) {insert_multi(value,~0,count);} + + //! Warning: buffer pointer must not point to buffer allocated by this array (fixme). + template + void append_fromptr(const t_append * p_buffer,t_size p_count) { + PFC_ASSERT( !is_owned(&p_buffer[0]) ); + t_size base = get_size(); + increase_size(p_count); + for(t_size n=0;n + void add_item( item_t && item ) { + const t_size base = get_size(); + increase_size(1); + m_alloc[base] = std::forward( item ); + } + template + void append_single_val( item_t && item ) { + const t_size base = get_size(); + increase_size(1); + m_alloc[base] = std::forward( item ); + } + + template + void append_single(const t_append & p_item) { + if (is_owned(p_item)) append_single(t_append(p_item)); + else { + const t_size base = get_size(); + increase_size(1); + m_alloc[base] = p_item; + } + } + + template + void fill(const t_filler & p_filler) { + const t_size max = get_size(); + for(t_size n=0;n get_size()) set_size(p_size); + } + + //not supported by some allocs + const t_item * get_ptr() const {return m_alloc.get_ptr();} + t_item * get_ptr() {return m_alloc.get_ptr();} + + void prealloc(t_size p_size) {m_alloc.prealloc(p_size);} + + template + bool has_owned_items(const t_array & p_source) { + if (array_size_t(p_source) == 0) return false; + + //how the hell would we properly check if any of source items is owned by us, in case source array implements some weird mixing of references of items from different sources? + //the most obvious way means evil bottleneck here (whether it matters or not from caller's point of view which does something O(n) already is another question) + //at least this will work fine with all standard classes which don't crossreference anyhow and always use own storage + //perhaps we'll traitify this someday later + return is_owned(p_source[0]); + } + + template + bool is_owned(const t_source & p_item) { + return m_alloc.is_ptr_owned(&p_item); + } + + template + void set_single(const t_item & p_item) { + set_size(1); + (*this)[0] = p_item; + } + + template void enumerate(t_callback & p_callback) const { for(t_size n = 0; n < get_size(); n++ ) { p_callback((*this)[n]); } } + + void move_from(t_self & other) { + m_alloc.move_from(other.m_alloc); + } + private: + t_alloc m_alloc; + }; + + template class t_alloc = alloc_standard > + class array_hybrid_t : public array_t::template alloc > + {}; + + + template class traits_t > : public traits_default_movable {}; + template class t_alloc> class traits_t > : public pfc::traits_t > {}; + + + template + class comparator_array { + public: + template + static int compare(const t_array1 & p_array1, const t_array2 & p_array2) { + t_size walk = 0; + for(;;) { + if (walk >= p_array1.get_size() && walk >= p_array2.get_size()) return 0; + else if (walk >= p_array1.get_size()) return -1; + else if (walk >= p_array2.get_size()) return 1; + else { + int state = t_comparator::compare(p_array1[walk],p_array2[walk]); + if (state != 0) return state; + } + ++walk; + } + } + }; + + template + static bool array_equals(const t_a1 & arr1, const t_a2 & arr2) { + const t_size s = array_size_t(arr1); + if (s != array_size_t(arr2)) return false; + for(t_size walk = 0; walk < s; ++walk) { + if (arr1[walk] != arr2[walk]) return false; + } + return true; + } + + + + template class t_alloc = alloc_standard> class array_2d_t { + public: + array_2d_t() : m_d1(), m_d2() {} + void set_size(t_size d1, t_size d2) { + m_content.set_size(pfc::mul_safe_t(d1, d2)); + m_d1 = d1; m_d2 = d2; + } + t_size get_dim1() const {return m_d1;} + t_size get_dim2() const {return m_d2;} + + t_item & at(t_size i1, t_size i2) { + return * _transformPtr(m_content.get_ptr(), i1, i2); + } + const t_item & at(t_size i1, t_size i2) const { + return * _transformPtr(m_content.get_ptr(), i1, i2); + } + template void fill(const t_filler & p_filler) {m_content.fill(p_filler);} + void fill_null() {m_content.fill_null();} + + t_item * rowPtr(t_size i1) {return _transformPtr(m_content.get_ptr(), i1, 0);} + const t_item * rowPtr(t_size i1) const {return _transformPtr(m_content.get_ptr(), i1, 0);} + + const t_item * operator[](t_size i1) const {return rowPtr(i1);} + t_item * operator[](t_size i1) {return rowPtr(i1);} + private: + template t_ptr _transformPtr(t_ptr ptr, t_size i1, t_size i2) const { + PFC_ASSERT( i1 < m_d1 ); PFC_ASSERT( i2 < m_d2 ); + return ptr + i1 * m_d2 + i2; + } + pfc::array_t m_content; + t_size m_d1, m_d2; + }; + +} + diff --git a/pfc/audio_math.cpp b/pfc/audio_math.cpp new file mode 100644 index 0000000..b62a753 --- /dev/null +++ b/pfc/audio_math.cpp @@ -0,0 +1,141 @@ +#include "pfc.h" + +static audio_sample noopt_calculate_peak(const audio_sample * p_src,t_size p_num) +{ + audio_sample peak = 0; + t_size num = p_num; + for(;num;num--) + { + audio_sample temp = (audio_sample)fabs(*(p_src++)); + if (temp>peak) peak = temp; + } + return peak; +} + + + + +static void noopt_convert_to_32bit(const audio_sample * p_source,t_size p_count,t_int32 * p_output,float p_scale) +{ + t_size num = p_count; + for(;num;--num) + { + t_int64 val = pfc::audio_math::rint64( *(p_source++) * p_scale ); + if (val < -2147483648ll) val = -2147483648ll; + else if (val > 0x7FFFFFFF) val = 0x7FFFFFFF; + *(p_output++) = (t_int32) val; + } +} + +inline static void noopt_convert_to_16bit(const audio_sample * p_source,t_size p_count,t_int16 * p_output,float p_scale) { + for(t_size n=0;n(p_buffer); + for(;p_count;p_count--) + { + t_uint32 t = *ptr; + if ((t & 0x007FFFFF) && !(t & 0x7F800000)) *ptr=0; + ptr++; + } +#elif audio_sample_size == 64 + t_uint64 * ptr = reinterpret_cast(p_buffer); + for(;p_count;p_count--) + { + t_uint64 t = *ptr; + if ((t & 0x000FFFFFFFFFFFFF) && !(t & 0x7FF0000000000000)) *ptr=0; + ptr++; + } +#else +#error unsupported +#endif + } + + void audio_math::add_offset(audio_sample * p_buffer,audio_sample p_delta,t_size p_count) { + for(t_size n=0;n(sourcePtr); + u.bytes[0] = 0; + u.bytes[1] = s[0]; + u.bytes[2] = s[1]; + u.bytes[3] = s[2]; + return u.v; + } + audio_sample audio_math::decodeFloat24ptrbs(const void * sourcePtr) { + PFC_STATIC_ASSERT(pfc::byte_order_is_little_endian); + union { + uint8_t bytes[4]; + float v; + } u; + const uint8_t * s = reinterpret_cast(sourcePtr); + u.bytes[0] = 0; + u.bytes[1] = s[2]; + u.bytes[2] = s[1]; + u.bytes[3] = s[0]; + return u.v; + } + + audio_sample audio_math::decodeFloat16(uint16_t source) { + const unsigned fractionBits = 10; + const unsigned widthBits = 16; + typedef uint16_t source_t; + + /* typedef uint64_t out_t; typedef double retval_t; + enum { + outExponent = 11, + outFraction = 52, + outExponentShift = (1 << (outExponent-1))-1 + };*/ + + typedef uint32_t out_t; typedef float retval_t; + enum { + outExponent = 8, + outFraction = 23, + outExponentShift = (1 << (outExponent-1))-1 + }; + + const unsigned exponentBits = widthBits - fractionBits - 1; + // 1 bit sign | exponent | fraction + source_t fraction = source & (((source_t)1 << fractionBits)-1); + source >>= fractionBits; + int exponent = (int)( source & (((source_t)1 << exponentBits)-1) ) - (int)((1 << (exponentBits-1))-1); + source >>= exponentBits; + + if (outExponent + outExponentShift <= 0) return 0; + + out_t output = (out_t)( source&1 ); + output <<= outExponent; + output |= (unsigned) (exponent + outExponentShift) & ( (1<> -shift); + else output |= (out_t) (fraction << shift); + return *(retval_t*)&output / pfc::audio_math::float16scale; + } + + unsigned audio_math::bitrate_kbps(uint64_t fileSize, double duration) { + if (fileSize > 0 && duration > 0) return (unsigned)floor((double)fileSize * 8 / (duration * 1000) + 0.5); + return 0; + } +} diff --git a/pfc/audio_sample.h b/pfc/audio_sample.h new file mode 100644 index 0000000..9bcc5b0 --- /dev/null +++ b/pfc/audio_sample.h @@ -0,0 +1,92 @@ +#pragma once +#include + +#define audio_sample_size 32 + +#if audio_sample_size == 32 +typedef float audio_sample; +#define audio_sample_asm dword +#elif audio_sample_size == 64 +typedef double audio_sample; +#define audio_sample_asm qword +#else +#error wrong audio_sample_size +#endif + +#define audio_sample_bytes (audio_sample_size/8) + +namespace pfc { + // made a class so it can be redirected to an alternate class more easily than with namespacing + // in win desktop fb2k these are implemented in a DLL + class audio_math { + public: + + //! p_source/p_output can point to same buffer + static void scale(const audio_sample * p_source, t_size p_count, audio_sample * p_output, audio_sample p_scale); + static void convert_to_int16(const audio_sample * p_source, t_size p_count, t_int16 * p_output, audio_sample p_scale); + static void convert_to_int32(const audio_sample * p_source, t_size p_count, t_int32 * p_output, audio_sample p_scale); + static audio_sample convert_to_int16_calculate_peak(const audio_sample * p_source, t_size p_count, t_int16 * p_output, audio_sample p_scale); + static void convert_from_int16(const t_int16 * p_source, t_size p_count, audio_sample * p_output, audio_sample p_scale); + static void convert_from_int32(const t_int32 * p_source, t_size p_count, audio_sample * p_output, audio_sample p_scale); + static audio_sample convert_to_int32_calculate_peak(const audio_sample * p_source, t_size p_count, t_int32 * p_output, audio_sample p_scale); + static audio_sample calculate_peak(const audio_sample * p_source, t_size p_count); + static void remove_denormals(audio_sample * p_buffer, t_size p_count); + static void add_offset(audio_sample * p_buffer, audio_sample p_delta, t_size p_count); + + static inline t_uint64 time_to_samples(double p_time, t_uint32 p_sample_rate) { + return (t_uint64)floor((double)p_sample_rate * p_time + 0.5); + } + + static inline double samples_to_time(t_uint64 p_samples, t_uint32 p_sample_rate) { + PFC_ASSERT(p_sample_rate > 0); + return (double)p_samples / (double)p_sample_rate; + } + +#if defined(_MSC_VER) && defined(_M_IX86) + inline static t_int64 rint64(audio_sample val) { + t_int64 rv; + _asm { + fld val; + fistp rv; + } + return rv; + } +#if defined(_M_IX86_FP) && _M_IX86_FP >= 1 + static inline t_int32 rint32(float p_val) { + return (t_int32)_mm_cvtss_si32(_mm_load_ss(&p_val)); + } +#else + inline static t_int32 rint32(audio_sample val) { + t_int32 rv; + _asm { + fld val; + fistp rv; + } + return rv; + } +#endif + +#elif defined(_MSC_VER) && defined(_M_X64) + inline static t_int64 rint64(audio_sample val) { return (t_int64)floor(val + 0.5); } + static inline t_int32 rint32(float p_val) { + return (t_int32)_mm_cvtss_si32(_mm_load_ss(&p_val)); + } +#else + inline static t_int64 rint64(audio_sample val) { return (t_int64)floor(val + 0.5); } + inline static t_int32 rint32(audio_sample val) { return (t_int32)floor(val + 0.5); } +#endif + + + static inline audio_sample gain_to_scale(double p_gain) { return (audio_sample)pow(10.0, p_gain / 20.0); } + static inline double scale_to_gain(double scale) { return 20.0*log10(scale); } + + static const audio_sample float16scale; + + static audio_sample decodeFloat24ptr(const void * sourcePtr); + static audio_sample decodeFloat24ptrbs(const void * sourcePtr); + static audio_sample decodeFloat16(uint16_t source); + + static unsigned bitrate_kbps( uint64_t fileSize, double duration ); + }; // class audio_math + +} // namespace pfc diff --git a/pfc/autoref.h b/pfc/autoref.h new file mode 100644 index 0000000..e0e6094 --- /dev/null +++ b/pfc/autoref.h @@ -0,0 +1,38 @@ +#pragma once +#include + +namespace pfc { + + // autoref<> : turn arbitrary ptr that needs to be delete'd into a shared_ptr<> alike + template class autoref { + public: + autoref() {} + autoref(std::nullptr_t) {} + autoref(obj_t * source) { + attach(source); + } + void attach(obj_t * source) { + PFC_ASSERT( source != nullptr ); + m_obj = std::make_shared(source); + } + void reset() { + m_obj.reset(); + } + + obj_t * operator->() const { + return m_obj->get_ptr(); + } + + obj_t * get() const { + if (! m_obj ) return nullptr; + return m_obj->get_ptr(); + } + + operator bool() const { + return !!m_obj; + } + private: + typedef pfc::ptrholder_t< obj_t > holder_t; + std::shared_ptr< holder_t > m_obj; + }; +} \ No newline at end of file diff --git a/pfc/avltree.h b/pfc/avltree.h new file mode 100644 index 0000000..ec75057 --- /dev/null +++ b/pfc/avltree.h @@ -0,0 +1,570 @@ +#pragma once + +namespace pfc { + + template + class _avltree_node : public _list_node { + public: + typedef _list_node t_node; + typedef _avltree_node t_self; + template _avltree_node(t_param const& param) : t_node(param), m_left(), m_right(), m_depth() {} + + typedef refcounted_object_ptr_t t_ptr; + typedef t_self* t_rawptr; + + t_ptr m_left, m_right; + t_rawptr m_parent; + + t_size m_depth; + + void link_left(t_self* ptr) throw() { + m_left = ptr; + if (ptr != NULL) ptr->m_parent = this; + } + void link_right(t_self* ptr) throw() { + m_right = ptr; + if (ptr != NULL) ptr->m_parent = this; + } + + void link_child(bool which,t_self* ptr) throw() { + (which ? m_right : m_left) = ptr; + if (ptr != NULL) ptr->m_parent = this; + } + + void unlink() throw() { + m_left.release(); m_right.release(); m_parent = NULL; m_depth = 0; + } + + inline void add_ref() throw() {this->refcount_add_ref();} + inline void release() throw() {this->refcount_release();} + + inline t_rawptr child(bool which) const throw() {return which ? m_right.get_ptr() : m_left.get_ptr();} + inline bool which_child(const t_self* ptr) const throw() {return ptr == m_right.get_ptr();} + + + + t_rawptr step(bool direction) throw() { + t_self* walk = this; + for(;;) { + t_self* t = walk->child(direction); + if (t != NULL) return t->peakchild(!direction); + for(;;) { + t = walk->m_parent; + if (t == NULL) return NULL; + if (t->which_child(walk) != direction) return t; + walk = t; + } + } + } + t_rawptr peakchild(bool direction) throw() { + t_self* walk = this; + for(;;) { + t_rawptr next = walk->child(direction); + if (next == NULL) return walk; + walk = next; + } + } + t_node * prev() throw() {return step(false);} + t_node * next() throw() {return step(true);} + private: + ~_avltree_node() throw() {} + }; + + + template + class avltree_t { + public: + typedef avltree_t t_self; + typedef pfc::const_iterator const_iterator; + typedef pfc::iterator iterator; + typedef t_storage t_item; + private: + typedef _avltree_node t_node; +#if 1//MSVC8 bug fix + typedef refcounted_object_ptr_t t_nodeptr; + typedef t_node * t_noderawptr; +#else + typedef typename t_node::t_ptr t_nodeptr; + typedef typename t_node::t_rawptr t_noderawptr; +#endif + + static bool is_ptr_valid(t_nodeptr const & p) { return p.is_valid(); } + static bool is_ptr_valid(t_node const * p) { return p != NULL; } + + template + inline static int compare(const t_item1 & p_item1, const t_item2 & p_item2) { + return t_comparator::compare(p_item1,p_item2); + } + + t_nodeptr m_root; + + static t_size calc_depth(const t_nodeptr & ptr) + { + return ptr.is_valid() ? 1+ptr->m_depth : 0; + } + + static void recalc_depth(t_nodeptr const& ptr) { + ptr->m_depth = pfc::max_t(calc_depth(ptr->m_left), calc_depth(ptr->m_right)); + } + + static void assert_children(t_nodeptr ptr) { + PFC_ASSERT(ptr->m_depth == pfc::max_t(calc_depth(ptr->m_left),calc_depth(ptr->m_right)) ); + } + + static t_ssize test_depth(t_nodeptr const& ptr) + { + if (ptr==0) return 0; + else return calc_depth(ptr->m_right) - calc_depth(ptr->m_left); + } + + static t_nodeptr extract_left_leaf(t_nodeptr & p_base) { + if (is_ptr_valid(p_base->m_left)) { + t_nodeptr ret = extract_left_leaf(p_base->m_left); + recalc_depth(p_base); + g_rebalance(p_base); + return ret; + } else { + t_nodeptr node = p_base; + p_base = node->m_right; + if (p_base.is_valid()) p_base->m_parent = node->m_parent; + node->m_right.release(); + node->m_depth = 0; + node->m_parent = NULL; + return node; + } + } + + static t_nodeptr extract_right_leaf(t_nodeptr & p_base) { + if (is_ptr_valid(p_base->m_right)) { + t_nodeptr ret = extract_right_leaf(p_base->m_right); + recalc_depth(p_base); + g_rebalance(p_base); + return ret; + } else { + t_nodeptr node = p_base; + p_base = node->m_left; + if (p_base.is_valid()) p_base->m_parent = node->m_parent; + node->m_left.release(); + node->m_depth = 0; + node->m_parent = NULL; + return node; + } + } + + static void remove_internal(t_nodeptr & p_node) { + t_nodeptr oldval = p_node; + if (p_node->m_left.is_empty()) { + p_node = p_node->m_right; + if (p_node.is_valid()) p_node->m_parent = oldval->m_parent; + } else if (p_node->m_right.is_empty()) { + p_node = p_node->m_left; + if (p_node.is_valid()) p_node->m_parent = oldval->m_parent; + } else { + t_nodeptr swap = extract_left_leaf(p_node->m_right); + + swap->link_left(oldval->m_left.get_ptr()); + swap->link_right(oldval->m_right.get_ptr()); + swap->m_parent = oldval->m_parent; + recalc_depth(swap); + p_node = swap; + } + oldval->unlink(); + } + + template + static void __enum_items_recur(t_nodewalk * p_node,t_callback & p_callback) { + if (is_ptr_valid(p_node)) { + __enum_items_recur(p_node->m_left.get_ptr(),p_callback); + p_callback (p_node->m_content); + __enum_items_recur(p_node->m_right.get_ptr(),p_callback); + } + } + template + static t_node * g_find_or_add_node(t_nodeptr & p_base,t_node * parent,t_search const & p_search,bool & p_new) + { + if (p_base.is_empty()) { + p_base = new t_node(p_search); + p_base->m_parent = parent; + p_new = true; + return p_base.get_ptr(); + } + + PFC_ASSERT( p_base->m_parent == parent ); + + int result = compare(p_base->m_content,p_search); + if (result > 0) { + t_node * ret = g_find_or_add_node(p_base->m_left,p_base.get_ptr(),p_search,p_new); + PFC_ASSERT(compare(ret->m_content, p_search) == 0); + if (p_new) { + recalc_depth(p_base); + g_rebalance(p_base); + } + return ret; + } else if (result < 0) { + t_node * ret = g_find_or_add_node(p_base->m_right,p_base.get_ptr(),p_search,p_new); + PFC_ASSERT(compare(ret->m_content, p_search) == 0); + if (p_new) { + recalc_depth(p_base); + g_rebalance(p_base); + } + return ret; + } else { + p_new = false; + return p_base.get_ptr(); + } + } + + + + template + static t_storage * g_find_or_add(t_nodeptr & p_base,t_node * parent,t_search const & p_search,bool & p_new) { + return &g_find_or_add_node(p_base,parent,p_search,p_new)->m_content; + } + + + static void g_rotate_right(t_nodeptr & oldroot) { + t_nodeptr newroot ( oldroot->m_right ); + oldroot->link_child(true, newroot->m_left.get_ptr()); + newroot->m_left = oldroot; + newroot->m_parent = oldroot->m_parent; + oldroot->m_parent = newroot.get_ptr(); + recalc_depth(oldroot); + recalc_depth(newroot); + oldroot = newroot; + } + + static void g_rotate_left(t_nodeptr & oldroot) { + t_nodeptr newroot ( oldroot->m_left ); + oldroot->link_child(false, newroot->m_right.get_ptr()); + newroot->m_right = oldroot; + newroot->m_parent = oldroot->m_parent; + oldroot->m_parent = newroot.get_ptr(); + recalc_depth(oldroot); + recalc_depth(newroot); + oldroot = newroot; + } + + static void g_rebalance(t_nodeptr & p_node) { + t_ssize balance = test_depth(p_node); + if (balance > 1) { + //right becomes root + if (test_depth(p_node->m_right) < 0) { + g_rotate_left(p_node->m_right); + } + g_rotate_right(p_node); + } else if (balance < -1) { + //left becomes root + if (test_depth(p_node->m_left) > 0) { + g_rotate_right(p_node->m_left); + } + g_rotate_left(p_node); + } + selftest(p_node); + } + + template + static bool g_remove(t_nodeptr & p_node,t_search const & p_search) { + if (p_node.is_empty()) return false; + + int result = compare(p_node->m_content,p_search); + if (result == 0) { + remove_internal(p_node); + if (is_ptr_valid(p_node)) { + recalc_depth(p_node); + g_rebalance(p_node); + } + return true; + } else { + if (g_remove(result > 0 ? p_node->m_left : p_node->m_right,p_search)) { + recalc_depth(p_node); + g_rebalance(p_node); + return true; + } else { + return false; + } + } + } + + static void selftest(t_nodeptr const& p_node) { + #if 0 //def _DEBUG//SLOW! + if (is_ptr_valid(p_node)) { + selftest(p_node->m_left); + selftest(p_node->m_right); + assert_children(p_node); + t_ssize delta = test_depth(p_node); + PFC_ASSERT(delta >= -1 && delta <= 1); + + if (p_node->m_left.is_valid()) { + PFC_ASSERT( p_node.get_ptr() == p_node->m_left->m_parent ); + } + if (p_node->m_right.is_valid()) { + PFC_ASSERT( p_node.get_ptr() == p_node->m_right->m_parent ); + } + + if (is_ptr_valid(p_node->m_parent)) { + PFC_ASSERT(p_node == p_node->m_parent->m_left || p_node == p_node->m_parent->m_right); + } + } + #endif + } + + + static t_size calc_count(const t_node * p_node) throw() { + if (is_ptr_valid(p_node)) { + return 1 + calc_count(p_node->m_left.get_ptr()) + calc_count(p_node->m_right.get_ptr()); + } else { + return 0; + } + } + + template + t_storage * _find_item_ptr(t_param const & p_item) const { + t_node* ptr = m_root.get_ptr(); + while(is_ptr_valid(ptr)) { + int result = compare(ptr->m_content,p_item); + if (result > 0) ptr=ptr->m_left.get_ptr(); + else if (result < 0) ptr=ptr->m_right.get_ptr(); + else return &ptr->m_content; + } + return NULL; + } + + template + t_node * _find_node_ptr(t_param const & p_item) const { + t_node* ptr = m_root.get_ptr(); + while(is_ptr_valid(ptr)) { + int result = compare(ptr->m_content,p_item); + if (result > 0) ptr=ptr->m_left.get_ptr(); + else if (result < 0) ptr=ptr->m_right.get_ptr(); + else return ptr; + } + return NULL; + } + + template t_storage * __find_nearest(const t_search & p_search) const { + t_node * ptr = m_root.get_ptr(); + t_storage * found = NULL; + while(is_ptr_valid(ptr)) { + int result = compare(ptr->m_content,p_search); + if (above) result = -result; + if (inclusive && result == 0) { + //direct hit + found = &ptr->m_content; + break; + } else if (result < 0) { + //match + found = &ptr->m_content; + ptr = ptr->child(!above); + } else { + //mismatch + ptr = ptr->child(above); + } + } + return found; + } + public: + avltree_t() : m_root(NULL) {} + ~avltree_t() {reset();} + const t_self & operator=(const t_self & p_other) {__copy(p_other);return *this;} + avltree_t(const t_self & p_other) : m_root(NULL) {try{__copy(p_other);} catch(...) {remove_all(); throw;}} + + template const t_self & operator=(const t_other & p_other) {copy_list_enumerated(*this,p_other);return *this;} + template avltree_t(const t_other & p_other) : m_root(NULL) {try{copy_list_enumerated(*this,p_other);}catch(...){remove_all(); throw;}} + + + template const t_storage * find_nearest_item(const t_search & p_search) const { + return __find_nearest(p_search); + } + + template t_storage * find_nearest_item(const t_search & p_search) { + return __find_nearest(p_search); + } + + avltree_t( t_self && other ) { + m_root = std::move( other.m_root ); other.m_root.release(); + } + + const t_self & operator=( t_self && other ) { + move_from ( other ); return *this; + } + + void move_from( t_self & other ) { + reset(); m_root = std::move( other.m_root ); other.m_root.release(); + } + + template + t_storage & add_item(t_param const & p_item) { + bool dummy; + return add_item_ex(p_item,dummy); + } + + template + t_self & operator+=(const t_param & p_item) {add_item(p_item);return *this;} + + template + t_self & operator-=(const t_param & p_item) {remove_item(p_item);return *this;} + + //! Returns true when the list has been altered, false when the item was already present before. + template + bool add_item_check(t_param const & item) { + bool isNew = false; + g_find_or_add(m_root,NULL,item,isNew); + selftest(m_root); + return isNew; + } + template + t_storage & add_item_ex(t_param const & p_item,bool & p_isnew) { + t_storage * ret = g_find_or_add(m_root,NULL,p_item,p_isnew); + selftest(m_root); + return *ret; + } + + template + void set_item(const t_param & p_item) { + bool isnew; + t_storage & found = add_item_ex(p_item,isnew); + if (isnew) found = p_item; + } + + template + const t_storage * find_item_ptr(t_param const & p_item) const {return _find_item_ptr(p_item);} + + //! Unsafe! Caller must not modify items in a way that changes sort order! + template + t_storage * find_item_ptr(t_param const & p_item) { return _find_item_ptr(p_item); } + + template const_iterator find(t_param const & item) const { return _find_node_ptr(item);} + + //! Unsafe! Caller must not modify items in a way that changes sort order! + template iterator find(t_param const & item) { return _find_node_ptr(item);} + + + template + bool contains(const t_param & p_item) const { + return find_item_ptr(p_item) != NULL; + } + + //! Same as contains(). + template + bool have_item(const t_param & p_item) const {return contains(p_item);} + + void remove_all() throw() { + _unlink_recur(m_root); + m_root.release(); + } + + bool remove(const_iterator const& iter) { + PFC_ASSERT(iter.is_valid()); + return remove_item(*iter);//OPTIMIZEME + //should never return false unless there's a bug in calling code + } + + template + bool remove_item(t_param const & p_item) { + bool ret = g_remove(m_root,p_item); + selftest(m_root); + return ret; + } + + t_size get_count() const throw() { + return calc_count(m_root.get_ptr()); + } + + template + void enumerate(t_callback & p_callback) const { + __enum_items_recur(m_root.get_ptr(),p_callback); + } + + //! Allows callback to modify the tree content. + //! Unsafe! Caller must not modify items in a way that changes sort order! + template + void _enumerate_var(t_callback & p_callback) { __enum_items_recur(m_root.get_ptr(),p_callback); } + + template iterator insert(const t_param & p_item) { + bool isNew; + t_node * ret = g_find_or_add_node(m_root,NULL,p_item,isNew); + selftest(m_root); + return ret; + } + + //deprecated backwards compatibility method wrappers + template t_storage & add(const t_param & p_item) {return add_item(p_item);} + template t_storage & add_ex(const t_param & p_item,bool & p_isnew) {return add_item_ex(p_item,p_isnew);} + template const t_storage * find_ptr(t_param const & p_item) const {return find_item_ptr(p_item);} + template t_storage * find_ptr(t_param const & p_item) {return find_item_ptr(p_item);} + template bool exists(t_param const & p_item) const {return have_item(p_item);} + void reset() {remove_all();} + + + + + const_iterator first() const throw() {return _firstlast(false);} + const_iterator last() const throw() {return _firstlast(true);} + //! Unsafe! Caller must not modify items in a way that changes sort order! + iterator _first_var() { return _firstlast(false); } + //! Unsafe! Caller must not modify items in a way that changes sort order! + iterator _last_var() { return _firstlast(true); } + + const_iterator cfirst() const throw() {return _firstlast(false);} + const_iterator clast() const throw() {return _firstlast(true);} + + template bool get_first(t_param & p_item) const throw() { + const_iterator iter = first(); + if (!iter.is_valid()) return false; + p_item = *iter; + return true; + } + template bool get_last(t_param & p_item) const throw() { + const_iterator iter = last(); + if (!iter.is_valid()) return false; + p_item = *iter; + return true; + } + + static bool equals(const t_self & v1, const t_self & v2) { + return listEquals(v1,v2); + } + bool operator==(const t_self & other) const {return equals(*this,other);} + bool operator!=(const t_self & other) const {return !equals(*this,other);} + + private: + static void _unlink_recur(t_nodeptr & node) { + if (node.is_valid()) { + _unlink_recur(node->m_left); + _unlink_recur(node->m_right); + node->unlink(); + } + } + t_node* _firstlast(bool which) const throw() { + if (m_root.is_empty()) return NULL; + for(t_node * walk = m_root.get_ptr(); ; ) { + t_node * next = walk->child(which); + if (next == NULL) return walk; + PFC_ASSERT( next->m_parent == walk ); + walk = next; + } + } + static t_nodeptr __copy_recur(t_node * p_source,t_node * parent) { + if (p_source == NULL) { + return NULL; + } else { + t_nodeptr newnode = new t_node(p_source->m_content); + newnode->m_depth = p_source->m_depth; + newnode->m_left = __copy_recur(p_source->m_left.get_ptr(),newnode.get_ptr()); + newnode->m_right = __copy_recur(p_source->m_right.get_ptr(),newnode.get_ptr()); + newnode->m_parent = parent; + return newnode; + } + } + + void __copy(const t_self & p_other) { + reset(); + m_root = __copy_recur(p_other.m_root.get_ptr(),NULL); + selftest(m_root); + } + }; + + + template + class traits_t > : public traits_default_movable {}; +} diff --git a/pfc/base64.cpp b/pfc/base64.cpp new file mode 100644 index 0000000..bb2f0e5 --- /dev/null +++ b/pfc/base64.cpp @@ -0,0 +1,118 @@ +#include "pfc.h" + +namespace bitWriter { + static void set_bit(t_uint8 * p_stream,size_t p_offset, bool state) { + t_uint8 mask = 1 << (7-(p_offset&7)); + t_uint8 & byte = p_stream[p_offset>>3]; + byte = (byte & ~mask) | (state ? mask : 0); + } + static void set_bits(t_uint8 * stream, t_size offset, t_size word, t_size bits) { + for(t_size walk = 0; walk < bits; ++walk) { + t_uint8 bit = (t_uint8)((word >> (bits - walk - 1))&1); + set_bit(stream, offset+walk, bit != 0); + } + } +}; + +namespace pfc { + static const char alphabet[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + + static const uint8_t alphabetRev[256] = + {0x40,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF + ,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF + ,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x3E,0xFF,0xFF,0xFF,0x3F + ,0x34,0x35,0x36,0x37,0x38,0x39,0x3A,0x3B,0x3C,0x3D,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF + ,0xFF,0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0A,0x0B,0x0C,0x0D,0x0E + ,0x0F,0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0xFF,0xFF,0xFF,0xFF,0xFF + ,0xFF,0x1A,0x1B,0x1C,0x1D,0x1E,0x1F,0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27,0x28 + ,0x29,0x2A,0x2B,0x2C,0x2D,0x2E,0x2F,0x30,0x31,0x32,0x33,0xFF,0xFF,0xFF,0xFF,0xFF + ,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF + ,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF + ,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF + ,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF + ,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF + ,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF + ,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF + ,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF + }; + + size_t base64_strlen( const char * text ) { + size_t ret = 0; + for ( size_t w = 0; ;++w ) { + uint8_t c = (uint8_t) text[w]; + if (c == 0) break; + if ( alphabetRev[c] != 0xFF ) ++ret; + } + return ret; + } + t_size base64_decode_estimate(const char * text) { + t_size textLen = base64_strlen(text); + return textLen * 3 / 4; + } + + + + void base64_decode(const char * text, void * out) { + + size_t outWritePtr = 0; + size_t inTemp = 0; + uint8_t temp[3]; + for ( size_t textWalk = 0 ; ; ++ textWalk ) { + const uint8_t c = (uint8_t) text[textWalk]; + if (c == 0) break; + const uint8_t v = alphabetRev[ c ]; + if ( v != 0xFF ) { + PFC_ASSERT( inTemp + 6 <= 24 ); + bitWriter::set_bits(temp,inTemp,v,6); + inTemp += 6; + if ( inTemp == 24 ) { + memcpy( (uint8_t*) out + outWritePtr, temp, 3 ); + outWritePtr += 3; + inTemp = 0; + } + } + } + if ( inTemp > 0 ) { + size_t delta = inTemp / 8; + memcpy( (uint8_t*) out + outWritePtr, temp, delta ); + outWritePtr += delta; + inTemp = 0; + } +#if PFC_DEBUG + size_t estimated = base64_decode_estimate( text ) ; + PFC_ASSERT( outWritePtr == estimated ); +#endif + } + void base64_encode(pfc::string_base & out, const void * in, t_size inSize) { + out.reset(); base64_encode_append(out, in, inSize); + } + void base64_encode_append(pfc::string_base & out, const void * in, t_size inSize) { + int shift = 0; + int accum = 0; + const t_uint8 * inPtr = reinterpret_cast(in); + + for(t_size walk = 0; walk < inSize; ++walk) { + accum <<= 8; + shift += 8; + accum |= inPtr[walk]; + while ( shift >= 6 ) { + shift -= 6; + out << format_char( alphabet[(accum >> shift) & 0x3F] ); + } + } + if (shift == 4) { + out << format_char( alphabet[(accum & 0xF)<<2] ) << "="; + } else if (shift == 2) { + out << format_char( alphabet[(accum & 0x3)<<4] ) << "=="; + } + } + + void base64_decode_to_string( pfc::string_base & out, const char * text ) { + char * ptr = out.lock_buffer( base64_decode_estimate( text ) ); + base64_decode(text, ptr); + out.unlock_buffer(); + } + void base64_encode_from_string( pfc::string_base & out, const char * in ) { + base64_encode( out, in, strlen(in) ); + } +} diff --git a/pfc/base64.h b/pfc/base64.h new file mode 100644 index 0000000..0f8f346 --- /dev/null +++ b/pfc/base64.h @@ -0,0 +1,18 @@ +#pragma once + +namespace pfc { + class string_base; + void base64_encode(pfc::string_base & out, const void * in, t_size inSize); + void base64_encode_append(pfc::string_base & out, const void * in, t_size inSize); + void base64_encode_from_string( pfc::string_base & out, const char * in ); + t_size base64_decode_estimate(const char * text); + void base64_decode(const char * text, void * out); + + template void base64_decode_array(t_buffer & out, const char * text) { + PFC_STATIC_ASSERT( sizeof(out[0]) == 1 ); + out.set_size_discard( base64_decode_estimate(text) ); + base64_decode(text, out.get_ptr()); + } + + void base64_decode_to_string( pfc::string_base & out, const char * text ); +} diff --git a/pfc/binary_search.h b/pfc/binary_search.h new file mode 100644 index 0000000..94c72cf --- /dev/null +++ b/pfc/binary_search.h @@ -0,0 +1,83 @@ +#pragma once + +namespace pfc { + class comparator_default; + + template + class binarySearch { + public: + + template + static bool run(const t_container & p_container,t_size p_base,t_size p_count,const t_param & p_param,t_size & p_result) { + t_size max = p_base + p_count; + t_size min = p_base; + while(min> 1); + int state = t_comparator::compare(p_param,p_container[ptr]); + if (state > 0) min = ptr + 1; + else if (state < 0) max = ptr; + else { + p_result = ptr; + return true; + } + } + p_result = min; + return false; + } + + + template + static bool runGroupBegin(const t_container & p_container,t_size p_base,t_size p_count,const t_param & p_param,t_size & p_result) { + t_size max = p_base + p_count; + t_size min = p_base; + bool found = false; + while(min> 1); + int state = t_comparator::compare(p_param,p_container[ptr]); + if (state > 0) min = ptr + 1; + else if (state < 0) max = ptr; + else { + found = true; max = ptr; + } + } + p_result = min; + return found; + } + + template + static bool runGroupEnd(const t_container & p_container,t_size p_base,t_size p_count,const t_param & p_param,t_size & p_result) { + t_size max = p_base + p_count; + t_size min = p_base; + bool found = false; + while(min> 1); + int state = t_comparator::compare(p_param,p_container[ptr]); + if (state > 0) min = ptr + 1; + else if (state < 0) max = ptr; + else { + found = true; min = ptr + 1; + } + } + p_result = min; + return found; + } + + template + static bool runGroup(const t_container & p_container,t_size p_base,t_size p_count,const t_param & p_param,t_size & p_result,t_size & p_resultCount) { + if (!runGroupBegin(p_container,p_base,p_count,p_param,p_result)) { + p_resultCount = 0; + return false; + } + t_size groupEnd; + if (!runGroupEnd(p_container,p_result,p_count - p_result,p_param,groupEnd)) { + //should not happen.. + PFC_ASSERT(0); + p_resultCount = 0; + return false; + } + PFC_ASSERT(groupEnd > p_result); + p_resultCount = groupEnd - p_result; + return true; + } + }; +}; diff --git a/pfc/bit_array.cpp b/pfc/bit_array.cpp new file mode 100644 index 0000000..96de09a --- /dev/null +++ b/pfc/bit_array.cpp @@ -0,0 +1,182 @@ +#include "pfc.h" + +namespace pfc { + void bit_array::for_each(bool value, size_t base, size_t max, std::function f) const { + for( size_t idx = find_first(value, base, max); idx < max; idx = find_next(value, idx, max) ) { + f(idx); + } + } + t_size bit_array::find(bool val, t_size start, t_ssize count) const + { + t_ssize d, todo, ptr = start; + if (count == 0) return start; + else if (count<0) { d = -1; todo = -count; } + else { d = 1; todo = count; } + while (todo>0 && get(ptr) != val) { ptr += d;todo--; } + return ptr; + } + + t_size bit_array::calc_count(bool val, t_size start, t_size count, t_size count_max) const + { + t_size found = 0; + t_size max = start + count; + t_size ptr; + for (ptr = find(val, start, count);found f, bool val ) const { + for ( size_t w = find_first(val, 0, to ); w < to; w = find_next(val, w, to) ) { + f(w); + } + } + void bit_array::walkBack(size_t from, std::function f, bool val) const { + t_ssize walk = from; + if ( walk == 0 ) return; + for( ;; ) { + walk = find(val, walk-1, - walk ); + if ( walk < 0 ) return; + f ( walk ); + } + } + + bit_array_var_impl::bit_array_var_impl( const bit_array & source, size_t sourceCount) { + for(size_t w = source.find_first( true, 0, sourceCount); w < sourceCount; w = source.find_next( true, w, sourceCount ) ) { + set(w, true); + } + } + + bool bit_array_var_impl::get(t_size n) const { + return m_data.have_item(n); + } + t_size bit_array_var_impl::find(bool val,t_size start,t_ssize count) const { + if (!val) { + return bit_array::find(false, start, count); //optimizeme. + } else if (count > 0) { + const t_size * v = m_data.find_nearest_item(start); + if (v == NULL || *v > start+count) return start + count; + return *v; + } else if (count < 0) { + const t_size * v = m_data.find_nearest_item(start); + if (v == NULL || *v < start+count) return start + count; + return *v; + } else return start; + } + + void bit_array_var_impl::set(t_size n,bool val) { + if (val) m_data += n; + else m_data -= n; + } + + + bit_array_flatIndexList::bit_array_flatIndexList() { + m_content.prealloc( 1024 ); + } + + void bit_array_flatIndexList::add( size_t n ) { + m_content.append_single_val( n ); + } + + bool bit_array_flatIndexList::get(t_size n) const { + size_t dummy; + return _find( n, dummy ); + } + t_size bit_array_flatIndexList::find(bool val,t_size start,t_ssize count) const { + if (val == false) { + // unoptimized but not really used + return bit_array::find(val, start, count); + } + + if (count==0) return start; + else if (count<0) { + size_t idx; + if (!_findNearestDown( start, idx ) || (t_ssize)m_content[idx] < (t_ssize)start+count) return start + count; + return m_content[idx]; + } else { // count > 0 + size_t idx; + if (!_findNearestUp( start, idx ) || m_content[idx] > start+count) return start + count; + return m_content[idx]; + } + } + + bool bit_array_flatIndexList::_findNearestUp( size_t val, size_t & outIdx ) const { + size_t idx; + if (_find( val, idx )) { outIdx = idx; return true; } + // we have a valid outIdx at where the bsearch gave up + PFC_ASSERT ( idx == 0 || m_content [ idx - 1 ] < val ); + PFC_ASSERT ( idx == m_content.get_size() || m_content[ idx ] > val ); + if (idx == m_content.get_size()) return false; + outIdx = idx; + return true; + } + bool bit_array_flatIndexList::_findNearestDown( size_t val, size_t & outIdx ) const { + size_t idx; + if (_find( val, idx )) { outIdx = idx; return true; } + // we have a valid outIdx at where the bsearch gave up + PFC_ASSERT ( idx == 0 || m_content [ idx - 1 ] < val ); + PFC_ASSERT ( idx == m_content.get_size() || m_content[ idx ] > val ); + if (idx == 0) return false; + outIdx = idx - 1; + return true; + } + + void bit_array_flatIndexList::presort() { + pfc::sort_t( m_content, pfc::compare_t< size_t, size_t >, m_content.get_size( ) ); + } + + + bit_array_bittable::bit_array_bittable(const pfc::bit_array & in, size_t inSize) : m_count() { + resize(inSize); + for (size_t w = in.find_first(true, 0, inSize); w < inSize; w = in.find_next(true, w, inSize)) { + set(w, true); + } + } + + void bit_array_bittable::resize(t_size p_count) + { + t_size old_bytes = g_estimate_size(m_count); + m_count = p_count; + t_size bytes = g_estimate_size(m_count); + m_data.set_size(bytes); + if (bytes > old_bytes) pfc::memset_null_t(m_data.get_ptr() + old_bytes, bytes - old_bytes); + } + + void bit_array_bittable::set(t_size n, bool val) + { + if (n0) + return (val >= start && (t_ssize)val < (t_ssize)start + count) ? val : start + count; + else + return (val <= start && (t_ssize)val > (t_ssize)start + count) ? val : start + count; + } + else + { + if (start == val) return count>0 ? start + 1 : start - 1; + else return start; + } + } +} // namespace pfc diff --git a/pfc/bit_array.h b/pfc/bit_array.h new file mode 100644 index 0000000..758953c --- /dev/null +++ b/pfc/bit_array.h @@ -0,0 +1,69 @@ +#pragma once + +#include + +namespace pfc { + //! Bit array interface class, constant version (you can only retrieve values). \n + //! Range of valid indexes depends on the context. When passing a bit_array as a parameter to some code, valid index range must be signaled independently. + class NOVTABLE bit_array { + public: + virtual bool get(t_size n) const = 0; + //! Returns the first occurance of val between start and start+count (excluding start+count), or start+count if not found; count may be negative to search back rather than forward. \n + //! Can be overridden by bit_array implementations for improved speed in specific cases. + virtual t_size find(bool val, t_size start, t_ssize count) const; + bool operator[](t_size n) const { return get(n); } + + t_size calc_count(bool val, t_size start, t_size count, t_size count_max = ~0) const;//counts number of vals for start<=n f) const; + + void walk(size_t to, std::function< void ( size_t ) > f, bool val = true ) const; + void walkBack(size_t from, std::function< void ( size_t ) > f, bool val = true ) const; + protected: + bit_array() {} + ~bit_array() {} + }; + + //! Minimal lambda-based bit_array implementation, no find(), only get(). + class bit_array_lambda : public bit_array { + public: + typedef std::function f_t; + f_t f; + + bit_array_lambda( f_t f_ ) : f(f_) { } + + bool get(t_size n) const {return f(n);} + }; + + //! Bit array interface class, variable version (you can both set and retrieve values). \n + //! As with the constant version, valid index range depends on the context. + class NOVTABLE bit_array_var : public bit_array { + public: + virtual void set(t_size n,bool val)=0; + protected: + bit_array_var() {} + ~bit_array_var() {} + }; + + class bit_array_wrapper_permutation : public bit_array { + public: + bit_array_wrapper_permutation(const t_size * p_permutation, t_size p_size) : m_permutation(p_permutation), m_size(p_size) {} + bool get(t_size n) const { + if (n < m_size) { + return m_permutation[n] != n; + } else { + return false; + } + } + private: + const t_size * const m_permutation; + const t_size m_size; + }; + + +} +#define PFC_FOR_EACH_INDEX( bitArray, var, count ) \ +for( size_t var = (bitArray).find_first( true, 0, (count) ); var < (count); var = (bitArray).find_next( true, var, (count) ) ) diff --git a/pfc/bit_array_impl.h b/pfc/bit_array_impl.h new file mode 100644 index 0000000..7c7a545 --- /dev/null +++ b/pfc/bit_array_impl.h @@ -0,0 +1,200 @@ +#pragma once + +namespace pfc { + template + class bit_array_table_t : public bit_array + { + const T * data; + t_size count; + bool after; + public: + inline bit_array_table_t(const T * p_data,t_size p_count,bool p_after = false) + : data(p_data), count(p_count), after(p_after) + { + } + + bool get(t_size n) const + { + if (n + class bit_array_var_table_t : public bit_array_var + { + T * data; + t_size count; + bool after; + public: + inline bit_array_var_table_t(T * p_data,t_size p_count,bool p_after = false) + : data(p_data), count(p_count), after(p_after) + { + } + + bool get(t_size n) const { + if (n bit_array_table; + typedef bit_array_var_table_t bit_array_var_table; + + class bit_array_range : public bit_array + { + t_size begin,end; + bool state; + public: + bit_array_range(t_size first,t_size count,bool p_state = true) : begin(first), end(first+count), state(p_state) {} + + bool get(t_size n) const + { + bool rv = n>=begin && n m_data; + t_size m_count; + public: + //helpers + template + inline static bool g_get(const t_array & p_array,t_size idx) + { + return !! (p_array[idx>>3] & (1<<(idx&7))); + } + + template + inline static void g_set(t_array & p_array,t_size idx,bool val) + { + unsigned char & dst = p_array[idx>>3]; + unsigned char mask = 1<<(idx&7); + dst = val ? dst|mask : dst&~mask; + } + + inline static t_size g_estimate_size(t_size p_count) {return (p_count+7)>>3;} + + void resize(t_size p_count); + + bit_array_bittable(t_size p_count) : m_count(0) {resize(p_count);} + bit_array_bittable(const pfc::bit_array & in, size_t inSize); + bit_array_bittable() : m_count() {} + + + void set(t_size n, bool val); + + bool get(t_size n) const; + + size_t size() const {return m_count;} + }; + + + //! Bit array that takes a permutation and signals indexes reordered by the permutation. \n + //! Valid index range same as length of the permutation. + class bit_array_order_changed : public bit_array { + public: + bit_array_order_changed(const t_size * p_order) : m_order(p_order) {} + bool get(t_size n) const + { + return m_order[n] != n; + } + + private: + const t_size * m_order; + }; +} +// #define for_each_bit_array(var,mask,val,start,count) for(var = mask.find(val,start,count);var m_data; + }; + + + //! Specialized implementation of bit_array. \n + //! Indended for scenarios where fast searching for true values in a large list is needed, combined with low footprint regardless of the amount of items. + //! Call add() repeatedly with the true val indexes. If the indexes were not added in increasing order, call presort() when done with adding. + class bit_array_flatIndexList : public bit_array { + public: + bit_array_flatIndexList(); + + void add( size_t n ); + + bool get(t_size n) const; + t_size find(bool val,t_size start,t_ssize count) const; + + void presort(); + + size_t get_count() const { return m_content.get_size(); } + + private: + bool _findNearestUp( size_t val, size_t & outIdx ) const; + bool _findNearestDown( size_t val, size_t & outIdx ) const; + bool _find( size_t val, size_t & outIdx ) const { + return pfc::bsearch_simple_inline_t( m_content, m_content.get_size(), val, outIdx); + } + + pfc::array_t< size_t, pfc::alloc_fast > m_content; + }; +} diff --git a/pfc/bsearch.cpp b/pfc/bsearch.cpp new file mode 100644 index 0000000..699740f --- /dev/null +++ b/pfc/bsearch.cpp @@ -0,0 +1,19 @@ +#include "pfc.h" + +//deprecated + +/* +class NOVTABLE bsearch_callback +{ +public: + virtual int test(t_size p_index) const = 0; +}; +*/ + +namespace pfc { + + bool bsearch(t_size p_count, bsearch_callback const & p_callback,t_size & p_result) { + return bsearch_inline_t(p_count,p_callback,p_result); + } + +} \ No newline at end of file diff --git a/pfc/bsearch.h b/pfc/bsearch.h new file mode 100644 index 0000000..d5b2a17 --- /dev/null +++ b/pfc/bsearch.h @@ -0,0 +1,89 @@ +#pragma once + +namespace pfc { + + //deprecated + + class NOVTABLE bsearch_callback + { + public: + virtual int test(t_size n) const = 0; + }; + + bool bsearch(t_size p_count, bsearch_callback const & p_callback,t_size & p_result); + + template + class bsearch_callback_impl_simple_t : public bsearch_callback { + public: + int test(t_size p_index) const { + return m_compare(m_container[p_index],m_param); + } + bsearch_callback_impl_simple_t(const t_container & p_container,t_compare p_compare,const t_param & p_param) + : m_container(p_container), m_compare(p_compare), m_param(p_param) + { + } + private: + const t_container & m_container; + t_compare m_compare; + const t_param & m_param; + }; + + template + class bsearch_callback_impl_permutation_t : public bsearch_callback { + public: + int test(t_size p_index) const { + return m_compare(m_container[m_permutation[p_index]],m_param); + } + bsearch_callback_impl_permutation_t(const t_container & p_container,t_compare p_compare,const t_param & p_param,const t_permutation & p_permutation) + : m_container(p_container), m_compare(p_compare), m_param(p_param), m_permutation(p_permutation) + { + } + private: + const t_container & m_container; + t_compare m_compare; + const t_param & m_param; + const t_permutation & m_permutation; + }; + + + template + bool bsearch_t(t_size p_count,const t_container & p_container,t_compare p_compare,const t_param & p_param,t_size & p_index) { + return bsearch( + p_count, + bsearch_callback_impl_simple_t(p_container,p_compare,p_param), + p_index); + } + + template + bool bsearch_permutation_t(t_size p_count,const t_container & p_container,t_compare p_compare,const t_param & p_param,const t_permutation & p_permutation,t_size & p_index) { + t_size index; + if (bsearch( + p_count, + bsearch_callback_impl_permutation_t(p_container,p_compare,p_param,p_permutation), + index)) + { + p_index = p_permutation[index]; + return true; + } else { + return false; + } + } + + template + bool bsearch_range_t(const t_size p_count,const t_container & p_container,t_compare p_compare,const t_param & p_param,t_size & p_range_base,t_size & p_range_count) + { + t_size probe; + if (!bsearch( + p_count, + bsearch_callback_impl_simple_t(p_container,p_compare,p_param), + probe)) return false; + + t_size base = probe, count = 1; + while(base > 0 && p_compare(p_container[base-1],p_param) == 0) {base--; count++;} + while(base + count < p_count && p_compare(p_container[base+count],p_param) == 0) {count++;} + p_range_base = base; + p_range_count = count; + return true; + } + +} diff --git a/pfc/bsearch_inline.h b/pfc/bsearch_inline.h new file mode 100644 index 0000000..22c64f6 --- /dev/null +++ b/pfc/bsearch_inline.h @@ -0,0 +1,50 @@ +#pragma once + +namespace pfc { + + //deprecated + +template +inline bool bsearch_inline_t(t_size p_count, const t_callback & p_callback,t_size & p_result) +{ + t_size max = p_count; + t_size min = 0; + t_size ptr; + while(min> 1); + int result = p_callback.test(ptr); + if (result<0) min = ptr + 1; + else if (result>0) max = ptr; + else + { + p_result = ptr; + return true; + } + } + p_result = min; + return false; +} + +template +inline bool bsearch_simple_inline_t(const t_buffer & p_buffer,t_size p_count,t_value const & p_value,t_size & p_result) +{ + t_size max = p_count; + t_size min = 0; + t_size ptr; + while(min> 1); + if (p_value > p_buffer[ptr]) min = ptr + 1; + else if (p_value < p_buffer[ptr]) max = ptr; + else + { + p_result = ptr; + return true; + } + } + p_result = min; + return false; +} + +} diff --git a/pfc/byte_order.h b/pfc/byte_order.h new file mode 100644 index 0000000..920a56f --- /dev/null +++ b/pfc/byte_order.h @@ -0,0 +1,256 @@ +#pragma once + +namespace pfc { + void byteswap_raw(void * p_buffer,t_size p_bytes); + + template T byteswap_t(T p_source); + + template<> inline char byteswap_t(char p_source) {return p_source;} + template<> inline unsigned char byteswap_t(unsigned char p_source) {return p_source;} + template<> inline signed char byteswap_t(signed char p_source) {return p_source;} + + template T byteswap_int_t(T p_source) { + enum { width = sizeof(T) }; + typedef typename sized_int_t::t_unsigned tU; + tU in = p_source, out = 0; + for(unsigned walk = 0; walk < width; ++walk) { + out |= ((in >> (walk * 8)) & 0xFF) << ((width - 1 - walk) * 8); + } + return out; + } + +#ifdef _MSC_VER//does this even help with performance/size? + template<> inline wchar_t byteswap_t(wchar_t p_source) {return _byteswap_ushort(p_source);} + + template<> inline short byteswap_t(short p_source) {return _byteswap_ushort(p_source);} + template<> inline unsigned short byteswap_t(unsigned short p_source) {return _byteswap_ushort(p_source);} + + template<> inline int byteswap_t(int p_source) {return _byteswap_ulong(p_source);} + template<> inline unsigned int byteswap_t(unsigned int p_source) {return _byteswap_ulong(p_source);} + + template<> inline long byteswap_t(long p_source) {return _byteswap_ulong(p_source);} + template<> inline unsigned long byteswap_t(unsigned long p_source) {return _byteswap_ulong(p_source);} + + template<> inline long long byteswap_t(long long p_source) {return _byteswap_uint64(p_source);} + template<> inline unsigned long long byteswap_t(unsigned long long p_source) {return _byteswap_uint64(p_source);} +#else + template<> inline wchar_t byteswap_t(wchar_t p_source) {return byteswap_int_t(p_source);} + + template<> inline short byteswap_t(short p_source) {return byteswap_int_t(p_source);} + template<> inline unsigned short byteswap_t(unsigned short p_source) {return byteswap_int_t(p_source);} + + template<> inline int byteswap_t(int p_source) {return byteswap_int_t(p_source);} + template<> inline unsigned int byteswap_t(unsigned int p_source) {return byteswap_int_t(p_source);} + + template<> inline long byteswap_t(long p_source) {return byteswap_int_t(p_source);} + template<> inline unsigned long byteswap_t(unsigned long p_source) {return byteswap_int_t(p_source);} + + template<> inline long long byteswap_t(long long p_source) {return byteswap_int_t(p_source);} + template<> inline unsigned long long byteswap_t(unsigned long long p_source) {return byteswap_int_t(p_source);} +#endif + + template<> inline float byteswap_t(float p_source) { + float ret; + *(t_uint32*) &ret = byteswap_t(*(const t_uint32*)&p_source ); + return ret; + } + + template<> inline double byteswap_t(double p_source) { + double ret; + *(t_uint64*) &ret = byteswap_t(*(const t_uint64*)&p_source ); + return ret; + } + + //blargh at GUID byteswap issue + template<> inline GUID byteswap_t(GUID p_guid) { + GUID ret; + ret.Data1 = pfc::byteswap_t(p_guid.Data1); + ret.Data2 = pfc::byteswap_t(p_guid.Data2); + ret.Data3 = pfc::byteswap_t(p_guid.Data3); + ret.Data4[0] = p_guid.Data4[0]; + ret.Data4[1] = p_guid.Data4[1]; + ret.Data4[2] = p_guid.Data4[2]; + ret.Data4[3] = p_guid.Data4[3]; + ret.Data4[4] = p_guid.Data4[4]; + ret.Data4[5] = p_guid.Data4[5]; + ret.Data4[6] = p_guid.Data4[6]; + ret.Data4[7] = p_guid.Data4[7]; + return ret; + } + +#if ! defined(_MSC_VER) || _MSC_VER >= 1900 + template<> inline char16_t byteswap_t(char16_t v) { return (char16_t)byteswap_t((uint16_t)v); } + template<> inline char32_t byteswap_t(char32_t v) { return (char32_t)byteswap_t((uint32_t)v); } +#endif +}; + +#ifdef _MSC_VER + +#if defined(_M_IX86) || defined(_M_X64) || defined(_M_ARM) +#define PFC_BYTE_ORDER_IS_BIG_ENDIAN 0 +#endif + +#else//_MSC_VER + +#if defined(__APPLE__) +#include +#else +#include +#endif + +#if __BYTE_ORDER == __LITTLE_ENDIAN +#define PFC_BYTE_ORDER_IS_BIG_ENDIAN 0 +#else +#define PFC_BYTE_ORDER_IS_BIG_ENDIAN 1 +#endif + +#endif//_MSC_VER + +#ifdef PFC_BYTE_ORDER_IS_BIG_ENDIAN +#define PFC_BYTE_ORDER_IS_LITTLE_ENDIAN (!(PFC_BYTE_ORDER_IS_BIG_ENDIAN)) +#else +#error please update byte order #defines +#endif + + +namespace pfc { + static const bool byte_order_is_big_endian = !!PFC_BYTE_ORDER_IS_BIG_ENDIAN; + static const bool byte_order_is_little_endian = !!PFC_BYTE_ORDER_IS_LITTLE_ENDIAN; + + template T byteswap_if_be_t(T p_param) {return byte_order_is_big_endian ? byteswap_t(p_param) : p_param;} + template T byteswap_if_le_t(T p_param) {return byte_order_is_little_endian ? byteswap_t(p_param) : p_param;} +} + +namespace byte_order { + +#if PFC_BYTE_ORDER_IS_BIG_ENDIAN//big endian + template inline void order_native_to_le_t(T& param) {param = pfc::byteswap_t(param);} + template inline void order_native_to_be_t(T& param) {} + template inline void order_le_to_native_t(T& param) {param = pfc::byteswap_t(param);} + template inline void order_be_to_native_t(T& param) {} +#else//little endian + template inline void order_native_to_le_t(T& param) {} + template inline void order_native_to_be_t(T& param) {param = pfc::byteswap_t(param);} + template inline void order_le_to_native_t(T& param) {} + template inline void order_be_to_native_t(T& param) {param = pfc::byteswap_t(param);} +#endif +}; + + + +namespace pfc { + template + class __EncodeIntHelper { + public: + inline static void Run(TInt p_value,t_uint8 * p_out) { + *p_out = (t_uint8)(p_value); + __EncodeIntHelper::Run(p_value >> 8,p_out + (IsBigEndian ? -1 : 1)); + } + }; + + template + class __EncodeIntHelper { + public: + inline static void Run(TInt p_value,t_uint8* p_out) { + *p_out = (t_uint8)(p_value); + } + }; + template + class __EncodeIntHelper { + public: + inline static void Run(TInt,t_uint8*) {} + }; + + template + inline void encode_little_endian(t_uint8 * p_buffer,TInt p_value) { + __EncodeIntHelper::Run(p_value,p_buffer); + } + template + inline void encode_big_endian(t_uint8 * p_buffer,TInt p_value) { + __EncodeIntHelper::Run(p_value,p_buffer + (sizeof(TInt) - 1)); + } + + + template + class __DecodeIntHelper { + public: + inline static TInt Run(const t_uint8 * p_in) { + return (__DecodeIntHelper::Run(p_in + (IsBigEndian ? -1 : 1)) << 8) + *p_in; + } + }; + + template + class __DecodeIntHelper { + public: + inline static TInt Run(const t_uint8* p_in) {return *p_in;} + }; + + template + class __DecodeIntHelper { + public: + inline static TInt Run(const t_uint8*) {return 0;} + }; + + template + inline void decode_little_endian(TInt & p_out,const t_uint8 * p_buffer) { + p_out = __DecodeIntHelper::Run(p_buffer); + } + + template + inline void decode_big_endian(TInt & p_out,const t_uint8 * p_buffer) { + p_out = __DecodeIntHelper::Run(p_buffer + (sizeof(TInt) - 1)); + } + + template + inline TInt decode_little_endian(const t_uint8 * p_buffer) { + TInt temp; + decode_little_endian(temp,p_buffer); + return temp; + } + + template + inline TInt decode_big_endian(const t_uint8 * p_buffer) { + TInt temp; + decode_big_endian(temp,p_buffer); + return temp; + } + + template + inline void decode_endian(TInt & p_out,const t_uint8 * p_buffer) { + if (IsBigEndian) decode_big_endian(p_out,p_buffer); + else decode_little_endian(p_out,p_buffer); + } + template + inline void encode_endian(t_uint8 * p_buffer,TInt p_in) { + if (IsBigEndian) encode_big_endian(p_in,p_buffer); + else encode_little_endian(p_in,p_buffer); + } + + + + template + inline void reverse_bytes(t_uint8 * p_buffer) { + pfc::swap_t(p_buffer[0],p_buffer[width-1]); + reverse_bytes(p_buffer+1); + } + + template<> inline void reverse_bytes<1>(t_uint8 * ) { } + template<> inline void reverse_bytes<0>(t_uint8 * ) { } + + inline int32_t readInt24(const void * mem) { + const uint8_t * p = (const uint8_t*) mem; + int32_t ret; + if (byte_order_is_little_endian) { + ret = p[0]; + ret |= (uint32_t)p[1] << 8; + ret |= (int32_t)(int8_t)p[2] << 16; + return ret; + } else { + ret = p[2]; + ret |= (uint32_t)p[1] << 8; + ret |= (int32_t)(int8_t)p[0] << 16; + return ret; + } + } +} + diff --git a/pfc/chain_list_v2.h b/pfc/chain_list_v2.h new file mode 100644 index 0000000..cba2009 --- /dev/null +++ b/pfc/chain_list_v2.h @@ -0,0 +1,296 @@ +#pragma once + +namespace pfc { + + template + class __chain_list_elem : public _list_node { + public: + typedef _list_node t_node; + template __chain_list_elem( arg_t && ... args ) : t_node( std::forward(args) ... ) {m_prev = m_next = NULL;} + + typedef __chain_list_elem t_self; + + t_self * m_prev, * m_next; + + t_node * prev() throw() {return m_prev;} + t_node * next() throw() {return m_next;} + + //helper wrappers + void add_ref() throw() {this->refcount_add_ref();} + void release() throw() {this->refcount_release();} + //workaround for cross-list-relinking case - never actually deletes p_elem + void __release_temporary() throw() {this->_refcount_release_temporary();} + }; + + //! Differences between chain_list_v2_t<> and old chain_list_t<>: \n + //! Iterators pointing to removed items as well as to items belonging to no longer existing list objects remain valid but they're no longer walkable - as if the referenced item was the only item in the list. The old class invalidated iterators on deletion instead. + template + class chain_list_v2_t { + public: + typedef _t_item t_item; + typedef chain_list_v2_t t_self; + typedef ::pfc::iterator iterator; + typedef ::pfc::const_iterator const_iterator; + typedef __chain_list_elem t_elem; + + chain_list_v2_t() {} + chain_list_v2_t(const t_self & p_source) { + try { + *this = p_source; + } catch(...) { + remove_all(); + throw; + } + } + + chain_list_v2_t(t_self && p_source) { + append_move_from( p_source ); + } + + template void _set(const t_in & in) { + remove_all(); _add(in); + } + template void _add(const t_in & in) { + for(typename t_in::const_iterator iter = in.first(); iter.is_valid(); ++in) add_item(*iter); + } + template t_self & operator=(const t_in & in) {_set(in); return *this;} + + t_self & operator=(const t_self & p_other) { + remove_all(); + for(t_elem * walk = p_other.m_first; walk != NULL; walk = walk->m_next) { + add_item(walk->m_content); + } + return *this; + } + t_self & operator=(t_self && p_other) { + move_from(p_other); return *this; + } + + // templated constructors = spawn of satan + // template chain_list_v2_t(const t_other & in) { try {_add(in);} catch(...) {remove_all(); throw;} } + + t_size get_count() const {return m_count;} + + iterator first() {return iterator(m_first);} + iterator last() {return iterator(m_last);} + const_iterator first() const {return const_iterator(m_first);} + const_iterator last() const {return const_iterator(m_last);} + const_iterator cfirst() const {return const_iterator(m_first);} + const_iterator clast() const {return const_iterator(m_last);} + + void remove_single(const_iterator const & p_iter) { + PFC_ASSERT(p_iter.is_valid()); + __unlink(_elem(p_iter)); + } + + void remove(const_iterator const & p_iter) { + PFC_ASSERT(p_iter.is_valid()); + __unlink(_elem(p_iter)); + } + + void remove_all() throw() { + while(m_first != NULL) __unlink(m_first); + PFC_ASSERT(m_count == 0); + } + void remove_range(const_iterator const & p_from,const_iterator const & p_to) { + for(t_elem * walk = _elem(p_from);;) { + if (walk == NULL) {PFC_ASSERT(!"Should not get here"); break;}//should not happen unless there is a bug in calling code + t_elem * next = walk->m_next; + __unlink(walk); + if (walk == _elem(p_to)) break; + walk = next; + } + } + + template void enumerate(t_callback & p_callback) const {__enumerate_chain(m_first,p_callback);} + template void enumerate(t_callback & p_callback) {__enumerate_chain(m_first,p_callback);} + + template bool remove_item(const t_source & p_item) { + t_elem * elem; + if (__find(elem,p_item)) { + __unlink(elem); + return true; + } else { + return false; + } + } + + ~chain_list_v2_t() {remove_all();} + + template + inline void add_item(arg_t && ... arg) { + __link_last(new t_elem(std::forward(arg) ... )); + } + template + inline t_self & operator+=(t_source && p_source) { + add_item(std::forward(p_source)); return *this; + } + iterator insert_last() {return __link_last(new t_elem);} + iterator insert_first() {return __link_first(new t_elem);} + iterator insert_after(const_iterator const & p_iter) {return __link_next(_elem(p_iter),new t_elem);} + iterator insert_before(const_iterator const & p_iter) {return __link_prev(_elem(p_iter),new t_elem);} + template iterator insert_last(arg_t && ... arg) {return __link_last(new t_elem(std::forward(arg) ...));} + template iterator insert_first(arg_t && ... arg) {return __link_first(new t_elem(std::forward(arg) ...));} + template iterator insert_after(const_iterator const & p_iter,arg_t && ... arg) {return __link_next(_elem(p_iter),new t_elem(std::forward(arg) ... ));} + template iterator insert_before(const_iterator const & p_iter,arg_t && ... arg) {return __link_prev(_elem(p_iter),new t_elem(std::forward(arg) ... ));} + + template const_iterator find_item(const t_source & p_item) const { + t_elem * elem; + if (!__find(elem,p_item)) return const_iterator(); + return const_iterator(elem); + } + + template iterator find_item(const t_source & p_item) { + t_elem * elem; + if (!__find(elem,p_item)) return iterator(); + return iterator(elem); + } + + template bool have_item(const t_source & p_item) const { + t_elem * dummy; + return __find(dummy,p_item); + } + template void set_single(const t_source & p_item) { + remove_all(); add_item(p_item); + } + + //! Slow! + const_iterator by_index(t_size p_index) const {return __by_index(p_index);} + //! Slow! + iterator by_index(t_size p_index) {return __by_index(p_index);} + + t_self & operator<<(t_self & p_other) { + while(p_other.m_first != NULL) { + __link_last( p_other.__unlink_temporary(p_other.m_first) ); + } + return *this; + } + t_self & operator>>(t_self & p_other) { + while(m_last != NULL) { + p_other.__link_first(__unlink_temporary(m_last)); + } + return p_other; + } + //! Links an object that has been unlinked from another list. Unsafe. + void _link_last(const_iterator const& iter) { + PFC_ASSERT(iter.is_valid()); + PFC_ASSERT( _elem(iter)->m_prev == NULL && _elem(iter)->m_next == NULL ); + __link_last(_elem(iter)); + } + //! Links an object that has been unlinked from another list. Unsafe. + void _link_first(const_iterator const& iter) { + PFC_ASSERT(iter.is_valid()); + PFC_ASSERT( _elem(iter)->m_prev == NULL && _elem(iter)->m_next == NULL ); + __link_first(_elem(iter)); + } + void append_move_from( t_self & p_other ) { + while (p_other.m_first != NULL) { + __link_last(p_other.__unlink_temporary(p_other.m_first)); + } + } + void move_from( t_self & p_other ) { + remove_all(); + append_move_from( p_other ); + } + private: + static t_elem * _elem(const_iterator const & iter) { + return static_cast(iter._node()); + } + t_elem * __by_index(t_size p_index) const { + t_elem * walk = m_first; + while(p_index > 0 && walk != NULL) { + p_index--; + walk = walk->m_next; + } + return walk; + } + template + static void __enumerate_chain(t_elemwalk * p_elem,t_callback & p_callback) { + t_elemwalk * walk = p_elem; + while(walk != NULL) { + p_callback(walk->m_content); + walk = walk->m_next; + } + } + + template bool __find(t_elem * & p_elem,const t_source & p_item) const { + for(t_elem * walk = m_first; walk != NULL; walk = walk->m_next) { + if (walk->m_content == p_item) { + p_elem = walk; return true; + } + } + return false; + } + + void __unlink_helper(t_elem * p_elem) throw() { + (p_elem->m_prev == NULL ? m_first : p_elem->m_prev->m_next) = p_elem->m_next; + (p_elem->m_next == NULL ? m_last : p_elem->m_next->m_prev) = p_elem->m_prev; + p_elem->m_next = p_elem->m_prev = NULL; + } + + //workaround for cross-list-relinking case - never actually deletes p_elem + t_elem * __unlink_temporary(t_elem * p_elem) throw() { + __unlink_helper(p_elem); + --m_count; p_elem->__release_temporary(); + return p_elem; + } + + t_elem * __unlink(t_elem * p_elem) throw() { + __unlink_helper(p_elem); + --m_count; p_elem->release(); + return p_elem; + } + void __on_link(t_elem * p_elem) throw() { + p_elem->add_ref(); ++m_count; + } + t_elem * __link_first(t_elem * p_elem) throw() { + __on_link(p_elem); + p_elem->m_next = m_first; + p_elem->m_prev = NULL; + (m_first == NULL ? m_last : m_first->m_prev) = p_elem; + m_first = p_elem; + return p_elem; + } + t_elem * __link_last(t_elem * p_elem) throw() { + __on_link(p_elem); + p_elem->m_prev = m_last; + p_elem->m_next = NULL; + (m_last == NULL ? m_first : m_last->m_next) = p_elem; + m_last = p_elem; + return p_elem; + } + t_elem * __link_next(t_elem * p_prev,t_elem * p_elem) throw() { + __on_link(p_elem); + p_elem->m_prev = p_prev; + p_elem->m_next = p_prev->m_next; + (p_prev->m_next != NULL ? p_prev->m_next->m_prev : m_last) = p_elem; + p_prev->m_next = p_elem; + return p_elem; + } + t_elem * __link_prev(t_elem * p_next,t_elem * p_elem) throw() { + __on_link(p_elem); + p_elem->m_next = p_next; + p_elem->m_prev = p_next->m_prev; + (p_next->m_prev != NULL ? p_next->m_prev->m_next : m_first) = p_elem; + p_next->m_prev = p_elem; + return p_elem; + } + t_elem * m_first = nullptr, * m_last = nullptr; + t_size m_count = 0; + }; + + + template class traits_t > : public traits_default_movable {}; + + class __chain_list_iterator_traits : public traits_default_movable { + public: + enum { + constructor_may_fail = false + }; + }; + + template class traits_t > : public traits_t > > {}; + + template class traits_t > : public traits_t > {}; + +}//namespace pfc diff --git a/pfc/cmd_thread.h b/pfc/cmd_thread.h new file mode 100644 index 0000000..7596014 --- /dev/null +++ b/pfc/cmd_thread.h @@ -0,0 +1,46 @@ +#pragma once + +#include "wait_queue.h" +#include +#include +#include + +namespace pfc { + + class cmdThread { + public: + typedef std::function cmd_t; + typedef pfc::waitQueue queue_t; + + void add(cmd_t c) { + std::call_once(m_once, [this] { + auto q = std::make_shared(); + pfc::splitThread([q] { + cmd_t cmd; + while (q->get(cmd)) { + cmd(); + } + }); + m_queue = q; + }); + m_queue->put(c); + } + + void shutdown() { + if (m_queue) { + m_queue->set_eof(); + m_queue.reset(); + } + } + ~cmdThread() { + shutdown(); + } + private: + + std::shared_ptr< queue_t > m_queue; + + + std::once_flag m_once; + }; + +} \ No newline at end of file diff --git a/pfc/com_ptr_t.h b/pfc/com_ptr_t.h new file mode 100644 index 0000000..1f850e8 --- /dev/null +++ b/pfc/com_ptr_t.h @@ -0,0 +1,87 @@ +#pragma once + +#ifdef _WIN32 +namespace pfc { + + template static void _COM_AddRef(what * ptr) { + if (ptr != NULL) ptr->AddRef(); + } + template static void _COM_Release(what * ptr) { + if (ptr != NULL) ptr->Release(); + } + + template + class com_ptr_t { + public: + typedef com_ptr_t t_self; + + com_ptr_t( nullptr_t ) throw() : m_ptr() {} + + com_ptr_t() throw() : m_ptr() {} + template inline com_ptr_t(source * p_ptr) throw() : m_ptr(p_ptr) {_COM_AddRef(m_ptr);} + com_ptr_t(const t_self & p_source) throw() : m_ptr(p_source.m_ptr) {_COM_AddRef(m_ptr);} + template inline com_ptr_t(const com_ptr_t & p_source) throw() : m_ptr(p_source.get_ptr()) {_COM_AddRef(m_ptr);} + + inline ~com_ptr_t() throw() {_COM_Release(m_ptr);} + + inline void copy(T * p_ptr) throw() { + _COM_Release(m_ptr); + m_ptr = p_ptr; + _COM_AddRef(m_ptr); + } + + template inline void copy(const com_ptr_t & p_source) throw() {copy(p_source.get_ptr());} + + inline void attach(T * p_ptr) throw() { + _COM_Release(m_ptr); + m_ptr = p_ptr; + } + + inline const t_self & operator=(const t_self & p_source) throw() {copy(p_source); return *this;} + inline const t_self & operator=(T* p_source) throw() {copy(p_source); return *this;} + template inline const t_self & operator=(const com_ptr_t & p_source) throw() {copy(p_source); return *this;} + template inline const t_self & operator=(source * p_ptr) throw() {copy(p_ptr); return *this;} + + inline void release() throw() { + _COM_Release(m_ptr); + m_ptr = NULL; + } + + + inline T* operator->() const throw() {PFC_ASSERT(m_ptr);return m_ptr;} + + inline T* get_ptr() const throw() {return m_ptr;} + + inline T* duplicate_ptr() const throw() //should not be used ! temporary ! + { + _COM_AddRef(m_ptr); + return m_ptr; + } + + inline T* detach() throw() { + return replace_null_t(m_ptr); + } + + inline bool is_valid() const throw() {return m_ptr != 0;} + inline bool is_empty() const throw() {return m_ptr == 0;} + + inline bool operator==(const com_ptr_t & p_item) const throw() {return m_ptr == p_item.m_ptr;} + inline bool operator!=(const com_ptr_t & p_item) const throw() {return m_ptr != p_item.m_ptr;} + inline bool operator>(const com_ptr_t & p_item) const throw() {return m_ptr > p_item.m_ptr;} + inline bool operator<(const com_ptr_t & p_item) const throw() {return m_ptr < p_item.m_ptr;} + + inline static void g_swap(com_ptr_t & item1, com_ptr_t & item2) throw() { + pfc::swap_t(item1.m_ptr,item2.m_ptr); + } + + inline T** receive_ptr() throw() {release();return &m_ptr;} + inline void** receive_void_ptr() throw() {return (void**) receive_ptr();} + + inline t_self & operator<<(t_self & p_source) throw() {attach(p_source.detach());return *this;} + inline t_self & operator>>(t_self & p_dest) throw() {p_dest.attach(detach());return *this;} + private: + T* m_ptr; + }; + +} +#endif diff --git a/pfc/cpuid.cpp b/pfc/cpuid.cpp new file mode 100644 index 0000000..a4d4cfd --- /dev/null +++ b/pfc/cpuid.cpp @@ -0,0 +1,64 @@ +#include "pfc.h" + + +#if PFC_HAVE_CPUID + +namespace pfc { + + bool query_cpu_feature_set(unsigned p_value) { +#if _M_IX86_FP >= 2 + // don't bother checking for SSE/SSE2 if compiled to use them + p_value &= ~(CPU_HAVE_SSE | CPU_HAVE_SSE2); + if (p_value == 0) return true; +#endif + +#ifdef _MSC_VER + __try { +#endif + if (p_value & (CPU_HAVE_SSE | CPU_HAVE_SSE2 | CPU_HAVE_SSE3 | CPU_HAVE_SSSE3 | CPU_HAVE_SSE41 | CPU_HAVE_SSE42)) { + int buffer[4]; + __cpuid(buffer,1); + if (p_value & CPU_HAVE_SSE) { + if ((buffer[3]&(1<<25)) == 0) return false; + } + if (p_value & CPU_HAVE_SSE2) { + if ((buffer[3]&(1<<26)) == 0) return false; + } + if (p_value & CPU_HAVE_SSE3) { + if ((buffer[2]&(1<<0)) == 0) return false; + } + if (p_value & CPU_HAVE_SSSE3) { + if ((buffer[2]&(1<<9)) == 0) return false; + } + if (p_value & CPU_HAVE_SSE41) { + if ((buffer[2]&(1<<19)) == 0) return false; + } + if (p_value & CPU_HAVE_SSE42) { + if ((buffer[2]&(1<<20)) == 0) return false; + } + } + #ifdef _M_IX86 + if (p_value & (CPU_HAVE_3DNOW_EX | CPU_HAVE_3DNOW)) { + int buffer_amd[4]; + __cpuid(buffer_amd,0x80000000); + if ((unsigned)buffer_amd[0] < 0x80000001) return false; + __cpuid(buffer_amd,0x80000001); + + if (p_value & CPU_HAVE_3DNOW) { + if ((buffer_amd[3]&(1<<31)) == 0) return false; + } + if (p_value & CPU_HAVE_3DNOW_EX) { + if ((buffer_amd[3]&(1<<30)) == 0) return false; + } + } + #endif + return true; +#ifdef _MSC_VER + } __except(1) { + return false; + } +#endif + } +} + +#endif diff --git a/pfc/cpuid.h b/pfc/cpuid.h new file mode 100644 index 0000000..6fb70a4 --- /dev/null +++ b/pfc/cpuid.h @@ -0,0 +1,24 @@ +#pragma once + +// CPUID stuff supported only on MSVC for now, irrelevant for non x86 +#if defined(_MSC_VER) && (defined(_M_IX86) || defined(_M_X64)) +#define PFC_HAVE_CPUID 1 +namespace pfc { + enum { + CPU_HAVE_3DNOW = 1 << 0, + CPU_HAVE_3DNOW_EX = 1 << 1, + CPU_HAVE_SSE = 1 << 2, + CPU_HAVE_SSE2 = 1 << 3, + CPU_HAVE_SSE3 = 1 << 4, + CPU_HAVE_SSSE3 = 1 << 5, + CPU_HAVE_SSE41 = 1 << 6, + CPU_HAVE_SSE42 = 1 << 7, + }; + + bool query_cpu_feature_set(unsigned p_value); +}; +#endif + +#ifndef PFC_HAVE_CPUID +#define PFC_HAVE_CPUID 0 +#endif \ No newline at end of file diff --git a/pfc/event.h b/pfc/event.h new file mode 100644 index 0000000..8ce583f --- /dev/null +++ b/pfc/event.h @@ -0,0 +1,23 @@ +namespace pfc { +#ifdef _WIN32 + + typedef HANDLE eventHandle_t; + + static const eventHandle_t eventInvalid = NULL; + + class event : public win32_event { + public: + event() { create(true, false); } + + HANDLE get_handle() const {return win32_event::get();} + }; +#else + + typedef int eventHandle_t; + + static const eventHandle_t eventInvalid = -1; + + typedef nix_event event; + +#endif +} diff --git a/pfc/filehandle.cpp b/pfc/filehandle.cpp new file mode 100644 index 0000000..0da7f3a --- /dev/null +++ b/pfc/filehandle.cpp @@ -0,0 +1,33 @@ +#include "pfc.h" + +#ifndef _WIN32 +#include +#endif + +namespace pfc { +void fileHandleClose( fileHandle_t h ) { + if (h == fileHandleInvalid) return; +#ifdef _WIN32 + CloseHandle( h ); +#else + close( h ); +#endif +} + +fileHandle_t fileHandleDup( fileHandle_t h ) { +#ifdef _WIN32 + auto proc = GetCurrentProcess(); + HANDLE out; + if (!DuplicateHandle ( proc, h, proc, &out, 0, FALSE, DUPLICATE_SAME_ACCESS )) return fileHandleInvalid; + return out; +#else + return dup( h ); +#endif +} + +void fileHandle::close() { + fileHandleClose( h ); + clear(); +} + +} \ No newline at end of file diff --git a/pfc/filehandle.h b/pfc/filehandle.h new file mode 100644 index 0000000..6389852 --- /dev/null +++ b/pfc/filehandle.h @@ -0,0 +1,31 @@ +#pragma once + +namespace pfc { +#ifdef _WIN32 + typedef HANDLE fileHandle_t; + const fileHandle_t fileHandleInvalid = INVALID_HANDLE_VALUE; +#else + typedef int fileHandle_t; + const fileHandle_t fileHandleInvalid = -1; +#endif + + void fileHandleClose( fileHandle_t h ); + fileHandle_t fileHandleDup( fileHandle_t h ); + + class fileHandle { + public: + fileHandle( fileHandle_t val ) : h(val) {} + fileHandle() : h ( fileHandleInvalid ) {} + ~fileHandle() { close(); } + fileHandle( fileHandle && other ) { h = other.h; other.clear(); } + void operator=( fileHandle && other ) { close(); h = other.h; other.clear(); } + void operator=( fileHandle_t other ) { close(); h = other; } + void close(); + void clear() { h = fileHandleInvalid; } + bool isValid() { return h != fileHandleInvalid; } + fileHandle_t h; + private: + fileHandle( const fileHandle & ); + void operator=( const fileHandle & ); + }; +} diff --git a/pfc/guid.cpp b/pfc/guid.cpp new file mode 100644 index 0000000..a82f998 --- /dev/null +++ b/pfc/guid.cpp @@ -0,0 +1,196 @@ +#include "pfc.h" + +#ifdef _WIN32 +#include +#endif + +/* +6B29FC40-CA47-1067-B31D-00DD010662DA +. +typedef struct _GUID { // size is 16 + DWORD Data1; + WORD Data2; + WORD Data3; + BYTE Data4[8]; +} GUID; + +// {B296CF59-4D51-466f-8E0B-E57D3F91D908} +static const GUID <> = +{ 0xb296cf59, 0x4d51, 0x466f, { 0x8e, 0xb, 0xe5, 0x7d, 0x3f, 0x91, 0xd9, 0x8 } }; + +*/ +namespace { + class _GUID_from_text : public GUID + { + unsigned read_hex(char c); + unsigned read_byte(const char * ptr); + unsigned read_word(const char * ptr); + unsigned read_dword(const char * ptr); + void read_bytes(unsigned char * out,unsigned num,const char * ptr); + + public: + _GUID_from_text(const char * text); + }; + + unsigned _GUID_from_text::read_hex(char c) + { + if (c>='0' && c<='9') return (unsigned)c - '0'; + else if (c>='a' && c<='f') return 0xa + (unsigned)c - 'a'; + else if (c>='A' && c<='F') return 0xa + (unsigned)c - 'A'; + else return 0; + } + + unsigned _GUID_from_text::read_byte(const char * ptr) + { + return (read_hex(ptr[0])<<4) | read_hex(ptr[1]); + } + unsigned _GUID_from_text::read_word(const char * ptr) + { + return (read_byte(ptr)<<8) | read_byte(ptr+2); + } + + unsigned _GUID_from_text::read_dword(const char * ptr) + { + return (read_word(ptr)<<16) | read_word(ptr+4); + } + + void _GUID_from_text::read_bytes(uint8_t * out,unsigned num,const char * ptr) + { + for(;num;num--) + { + *out = read_byte(ptr); + out++;ptr+=2; + } + } + + + _GUID_from_text::_GUID_from_text(const char * text) + { + if (*text=='{') text++; + const char * max; + + { + const char * t = strchr(text,'}'); + if (t) max = t; + else max = text + strlen(text); + } + + (GUID)*this = pfc::guid_null; + + + do { + if (text+8>max) break; + Data1 = read_dword(text); + text += 8; + while(*text=='-') text++; + if (text+4>max) break; + Data2 = read_word(text); + text += 4; + while(*text=='-') text++; + if (text+4>max) break; + Data3 = read_word(text); + text += 4; + while(*text=='-') text++; + if (text+4>max) break; + read_bytes(Data4,2,text); + text += 4; + while(*text=='-') text++; + if (text+12>max) break; + read_bytes(Data4+2,6,text); + } while(false); + } +} + +namespace pfc { + +GUID GUID_from_text(const char * text) { + return _GUID_from_text( text ); +} + +static inline char print_hex_digit(unsigned val) +{ + static const char table[16] = {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'}; + PFC_ASSERT((val & ~0xF) == 0); + return table[val]; +} + +static void print_hex(unsigned val,char * &out,unsigned bytes) +{ + unsigned n; + for(n=0;n> ((bytes - 1 - n) << 3)) & 0xFF); + *(out++) = print_hex_digit( c >> 4 ); + *(out++) = print_hex_digit( c & 0xF ); + } + *out = 0; +} + + +print_guid::print_guid(const GUID & p_guid) +{ + char * out = m_data; + print_hex(p_guid.Data1,out,4); + *(out++) = '-'; + print_hex(p_guid.Data2,out,2); + *(out++) = '-'; + print_hex(p_guid.Data3,out,2); + *(out++) = '-'; + print_hex(p_guid.Data4[0],out,1); + print_hex(p_guid.Data4[1],out,1); + *(out++) = '-'; + print_hex(p_guid.Data4[2],out,1); + print_hex(p_guid.Data4[3],out,1); + print_hex(p_guid.Data4[4],out,1); + print_hex(p_guid.Data4[5],out,1); + print_hex(p_guid.Data4[6],out,1); + print_hex(p_guid.Data4[7],out,1); + *out = 0; +} + + +void print_hex_raw(const void * buffer,unsigned bytes,char * p_out) +{ + char * out = p_out; + const unsigned char * in = (const unsigned char *) buffer; + unsigned n; + for(n=0;n inline int compare_t(const GUID & p_item1,const GUID & p_item2) {return guid_compare(p_item1,p_item2);} + + extern const GUID guid_null; + + void print_hex_raw(const void * buffer,unsigned bytes,char * p_out); + + inline GUID makeGUID(t_uint32 Data1, t_uint16 Data2, t_uint16 Data3, t_uint8 Data4_1, t_uint8 Data4_2, t_uint8 Data4_3, t_uint8 Data4_4, t_uint8 Data4_5, t_uint8 Data4_6, t_uint8 Data4_7, t_uint8 Data4_8) { + GUID guid = { Data1, Data2, Data3, {Data4_1, Data4_2, Data4_3, Data4_4, Data4_5, Data4_6, Data4_7, Data4_8 } }; + return guid; + } + inline GUID xorGUID(const GUID & v1, const GUID & v2) { + GUID temp; memxor(&temp, &v1, &v2, sizeof(GUID)); return temp; + } + + class format_guid_cpp : public string_formatter { + public: + format_guid_cpp(const GUID & guid); + }; + + GUID createGUID(); + uint64_t halveGUID( const GUID & id ); +} diff --git a/pfc/instance_tracker.h b/pfc/instance_tracker.h new file mode 100644 index 0000000..a399d01 --- /dev/null +++ b/pfc/instance_tracker.h @@ -0,0 +1,51 @@ +#pragma once + +namespace pfc { + template + class instance_tracker_server_t { + public: + void add(t_object * p_object) { + m_list.add_item(p_object); + } + void remove(t_object * p_object) { + m_list.remove_item(p_object); + } + + t_size get_count() const {return m_list.get_count();} + t_object * get_item(t_size p_index) {return m_list[p_index];} + t_object * operator[](t_size p_index) {return m_list[p_index];} + + private: + ptr_list_hybrid_t m_list; + }; + + + template & p_server> + class instance_tracker_client_t { + public: + instance_tracker_client_t(t_object* p_ptr) : m_ptr(NULL), m_added(false) {initialize(p_ptr);} + instance_tracker_client_t() : m_ptr(NULL), m_added(false) {} + + void initialize(t_object * p_ptr) { + uninitialize(); + p_server.add(p_ptr); + m_ptr = p_ptr; + m_added = true; + } + + void uninitialize() { + if (m_added) { + p_server.remove(m_ptr); + m_ptr = NULL; + m_added = false; + } + } + + ~instance_tracker_client_t() { + uninitialize(); + } + private: + bool m_added; + t_object * m_ptr; + }; +} \ No newline at end of file diff --git a/pfc/int_types.h b/pfc/int_types.h new file mode 100644 index 0000000..43187fc --- /dev/null +++ b/pfc/int_types.h @@ -0,0 +1,95 @@ +#pragma once + +#include +typedef int64_t t_int64; +typedef uint64_t t_uint64; +typedef int32_t t_int32; +typedef uint32_t t_uint32; +typedef int16_t t_int16; +typedef uint16_t t_uint16; +typedef int8_t t_int8; +typedef uint8_t t_uint8; + +typedef int t_int; +typedef unsigned int t_uint; + +typedef float t_float32; +typedef double t_float64; + + + +namespace pfc { + template + class sized_int_t; + + template<> class sized_int_t<1> { + public: + typedef t_uint8 t_unsigned; + typedef t_int8 t_signed; + }; + + template<> class sized_int_t<2> { + public: + typedef t_uint16 t_unsigned; + typedef t_int16 t_signed; + }; + + template<> class sized_int_t<4> { + public: + typedef t_uint32 t_unsigned; + typedef t_int32 t_signed; + }; + + template<> class sized_int_t<8> { + public: + typedef t_uint64 t_unsigned; + typedef t_int64 t_signed; + }; +} + +typedef size_t t_size; +typedef pfc::sized_int_t< sizeof(size_t) >::t_signed t_ssize; + + +inline t_size MulDiv_Size(t_size x,t_size y,t_size z) {return (t_size) ( ((t_uint64)x * (t_uint64)y) / (t_uint64)z );} + +#define pfc_infinite (~0) + +namespace pfc { + const t_uint16 infinite16 = (t_uint16)(~0); + const t_uint32 infinite32 = (t_uint32)(~0); + const t_uint64 infinite64 = (t_uint64)(~0); + const t_size infinite_size = (t_size)(~0); + + template class int_specs_t; + + template + class int_specs_signed_t { + public: + inline static T get_min() {return ((T)1<<(sizeof(T)*8-1));} + inline static T get_max() {return ~((T)1<<(sizeof(T)*8-1));} + enum {is_signed = true}; + }; + + template + class int_specs_unsigned_t { + public: + inline static T get_min() {return (T)0;} + inline static T get_max() {return (T)~0;} + enum {is_signed = false}; + }; + + template<> class int_specs_t : public int_specs_signed_t {}; + template<> class int_specs_t : public int_specs_unsigned_t {}; + template<> class int_specs_t : public int_specs_signed_t {}; + template<> class int_specs_t : public int_specs_unsigned_t {}; + template<> class int_specs_t : public int_specs_signed_t {}; + template<> class int_specs_t : public int_specs_unsigned_t {}; + template<> class int_specs_t : public int_specs_signed_t {}; + template<> class int_specs_t : public int_specs_unsigned_t {}; + template<> class int_specs_t : public int_specs_signed_t {}; + template<> class int_specs_t : public int_specs_unsigned_t {}; + + template<> class int_specs_t : public int_specs_unsigned_t {}; + +}; diff --git a/pfc/iterators.h b/pfc/iterators.h new file mode 100644 index 0000000..ce98b91 --- /dev/null +++ b/pfc/iterators.h @@ -0,0 +1,140 @@ +#pragma once +#include "ref_counter.h" + +namespace pfc { + //! Base class for list nodes. Implemented by list implementers. + template class _list_node : public refcounted_object_root { + public: + typedef _list_node t_self; + + template _list_node(arg_t && ... arg) : m_content( std::forward(arg) ...) {} + + t_item m_content; + + virtual t_self * prev() throw() {return NULL;} + virtual t_self * next() throw() {return NULL;} + + t_self * walk(bool forward) throw() {return forward ? next() : prev();} + }; + + template class const_iterator { + public: + typedef _list_node t_node; + typedef refcounted_object_ptr_t t_nodeptr; + typedef const_iterator t_self; + + bool is_empty() const throw() {return m_content.is_empty();} + bool is_valid() const throw() {return m_content.is_valid();} + void invalidate() throw() {m_content = NULL;} + + void walk(bool forward) throw() {m_content = m_content->walk(forward);} + void prev() throw() {m_content = m_content->prev();} + void next() throw() {m_content = m_content->next();} + + //! For internal use / list implementations only! Do not call! + t_node* _node() const throw() {return m_content.get_ptr();} + + const_iterator() {} + const_iterator(t_node* source) : m_content(source) {} + const_iterator(t_nodeptr const & source) : m_content(source) {} + const_iterator(t_self const & other) : m_content(other.m_content) {} + const_iterator(t_self && other) : m_content(std::move(other.m_content)) {} + + t_self const & operator=(t_self const & other) {m_content = other.m_content; return *this;} + t_self const & operator=(t_self && other) {m_content = std::move(other.m_content); return *this;} + + const t_item& operator*() const throw() {return m_content->m_content;} + const t_item* operator->() const throw() {return &m_content->m_content;} + + const t_self & operator++() throw() {this->next(); return *this;} + const t_self & operator--() throw() {this->prev(); return *this;} + t_self operator++(int) throw() {t_self old = *this; this->next(); return old;} + t_self operator--(int) throw() {t_self old = *this; this->prev(); return old;} + + bool operator==(const t_self & other) const throw() {return this->m_content == other.m_content;} + bool operator!=(const t_self & other) const throw() {return this->m_content != other.m_content;} + protected: + t_nodeptr m_content; + }; + template class iterator : public const_iterator { + public: + typedef const_iterator t_selfConst; + typedef iterator t_self; + typedef _list_node t_node; + typedef refcounted_object_ptr_t t_nodeptr; + + iterator() {} + iterator(t_node* source) : t_selfConst(source) {} + iterator(t_nodeptr const & source) : t_selfConst(source) {} + iterator(t_self const & other) : t_selfConst(other) {} + iterator(t_self && other) : t_selfConst(std::move(other)) {} + + t_self const & operator=(t_self const & other) {this->m_content = other.m_content; return *this;} + t_self const & operator=(t_self && other) {this->m_content = std::move(other.m_content); return *this;} + + t_item& operator*() const throw() {return this->m_content->m_content;} + t_item* operator->() const throw() {return &this->m_content->m_content;} + + const t_self & operator++() throw() {this->next(); return *this;} + const t_self & operator--() throw() {this->prev(); return *this;} + t_self operator++(int) throw() {t_self old = *this; this->next(); return old;} + t_self operator--(int) throw() {t_self old = *this; this->prev(); return old;} + + bool operator==(const t_self & other) const throw() {return this->m_content == other.m_content;} + bool operator!=(const t_self & other) const throw() {return this->m_content != other.m_content;} + }; + + template + class comparator_list { + public: + template + static int compare(const t_list1 & p_list1, const t_list2 & p_list2) { + typename t_list1::const_iterator iter1 = p_list1.first(); + typename t_list2::const_iterator iter2 = p_list2.first(); + for(;;) { + if (iter1.is_empty() && iter2.is_empty()) return 0; + else if (iter1.is_empty()) return -1; + else if (iter2.is_empty()) return 1; + else { + int state = t_comparator::compare(*iter1,*iter2); + if (state != 0) return state; + } + ++iter1; ++iter2; + } + } + }; + + template + static bool listEquals(const t_list1 & p_list1, const t_list2 & p_list2) { + typename t_list1::const_iterator iter1 = p_list1.first(); + typename t_list2::const_iterator iter2 = p_list2.first(); + for(;;) { + if (iter1.is_empty() && iter2.is_empty()) return true; + else if (iter1.is_empty() || iter2.is_empty()) return false; + else if (*iter1 != *iter2) return false; + ++iter1; ++iter2; + } + } + + template + class comparator_stdlist { + public: + template + static int compare(const t_list1 & p_list1, const t_list2 & p_list2) { + auto iter1 = p_list1.begin(); + auto iter2 = p_list2.begin(); + for(;;) { + const bool end1 = iter1 == p_list1.end(); + const bool end2 = iter2 == p_list2.end(); + if ( end1 && end2 ) return 0; + else if ( end1 ) return -1; + else if ( end2 ) return 1; + else { + int state = comparator_t::compare(*iter1,*iter2); + if (state != 0) return state; + } + ++iter1; ++iter2; + } + } + }; +} diff --git a/pfc/list.h b/pfc/list.h new file mode 100644 index 0000000..6e53a9f --- /dev/null +++ b/pfc/list.h @@ -0,0 +1,640 @@ +#pragma once + +namespace pfc { + +template +class NOVTABLE list_base_const_t { +private: typedef list_base_const_t t_self; +public: + typedef T t_item; + virtual t_size get_count() const = 0; + virtual void get_item_ex(T& p_out, t_size n) const = 0; + + inline t_size get_size() const {return get_count();} + + inline T get_item(t_size n) const {T temp; get_item_ex(temp,n); return temp;} + inline T operator[](t_size n) const {T temp; get_item_ex(temp,n); return temp;} + + template + t_size find_duplicates_sorted_t(t_compare p_compare,bit_array_var & p_out) const + { + return ::pfc::find_duplicates_sorted_t const &,t_compare>(*this,get_count(),p_compare,p_out); + } + + template + t_size find_duplicates_sorted_permutation_t(t_compare p_compare,t_permutation const & p_permutation,bit_array_var & p_out) + { + return ::pfc::find_duplicates_sorted_permutation_t const &,t_compare,t_permutation>(*this,get_count(),p_compare,p_permutation,p_out); + } + + template + t_size find_item(const t_search & p_item) const//returns index of first occurance, infinite if not found + { + t_size n,max = get_count(); + for(n=0;n + inline bool have_item(const t_search & p_item) const {return find_item(p_item)!=~0;} + + + template + bool bsearch_t(t_compare p_compare,t_param const & p_param,t_size &p_index) const { + return ::pfc::bsearch_t(get_count(),*this,p_compare,p_param,p_index); + } + + template + bool bsearch_permutation_t(t_compare p_compare,t_param const & p_param,const t_permutation & p_permutation,t_size & p_index) const { + return ::pfc::bsearch_permutation_t(get_count(),*this,p_compare,p_param,p_permutation,p_index); + } + + template + void sort_get_permutation_t(t_compare p_compare,t_permutation const & p_permutation) const { + ::pfc::sort_get_permutation_t,t_compare,t_permutation>(*this,p_compare,get_count(),p_permutation); + } + + template + void sort_stable_get_permutation_t(t_compare p_compare,t_permutation const & p_permutation) const { + ::pfc::sort_stable_get_permutation_t,t_compare,t_permutation>(*this,p_compare,get_count(),p_permutation); + } + + template + void enumerate(t_callback & p_callback) const { + for(t_size n = 0, m = get_count(); n < m; ++n ) { + p_callback( (*this)[n] ); + } + } + + static bool g_equals(const t_self & item1, const t_self & item2) { + const t_size count = item1.get_count(); + if (count != item2.get_count()) return false; + for(t_size walk = 0; walk < count; ++walk) if (item1[walk] != item2[walk]) return false; + return true; + } + bool operator==(const t_self & item2) const {return g_equals(*this,item2);} + bool operator!=(const t_self & item2) const {return !g_equals(*this,item2);} + +protected: + list_base_const_t() {} + ~list_base_const_t() {} + + list_base_const_t(const t_self &) {} + void operator=(const t_self &) {} + +}; + + +template +class list_single_ref_t : public list_base_const_t +{ +public: + list_single_ref_t(const T & p_item,t_size p_count = 1) : m_item(p_item), m_count(p_count) {} + t_size get_count() const {return m_count;} + void get_item_ex(T& p_out,t_size n) const {PFC_ASSERT(n +class list_partial_ref_t : public list_base_const_t +{ +public: + list_partial_ref_t(const list_base_const_t & p_list,t_size p_base,t_size p_count) + : m_list(p_list), m_base(p_base), m_count(p_count) + { + PFC_ASSERT(m_base + m_count <= m_list.get_count()); + } + +private: + const list_base_const_t & m_list; + t_size m_base,m_count; + + t_size get_count() const {return m_count;} + void get_item_ex(T & p_out,t_size n) const {m_list.get_item_ex(p_out,n+m_base);} +}; + +template +class list_const_array_t : public list_base_const_t +{ +public: + inline list_const_array_t(A p_data,t_size p_count) : m_data(p_data), m_count(p_count) {} + t_size get_count() const {return m_count;} + void get_item_ex(T & p_out,t_size n) const {p_out = m_data[n];} +private: + A m_data; + t_size m_count; +}; +template +class list_const_array_ref_t : public list_base_const_t { +public: + list_const_array_ref_t(const t_array & data) : m_data(data) {} + t_size get_count() const {return m_data.get_size();} + void get_item_ex(typename t_array::t_item & out, t_size n) const {out = m_data[n];} +private: + const t_array & m_data; +}; + +template +class list_const_cast_t : public list_base_const_t +{ +public: + list_const_cast_t(const list_base_const_t & p_from) : m_from(p_from) {} + t_size get_count() const {return m_from.get_count();} + void get_item_ex(to & p_out,t_size n) const + { + from temp; + m_from.get_item_ex(temp,n); + p_out = temp; + } +private: + const list_base_const_t & m_from; +}; + +template +class ptr_list_const_array_t : public list_base_const_t +{ +public: + inline ptr_list_const_array_t(A p_data,t_size p_count) : m_data(p_data), m_count(p_count) {} + t_size get_count() const {return m_count;} + void get_item_ex(T* & p_out,t_size n) const {p_out = &m_data[n];} +private: + A m_data; + t_size m_count; +}; +template +class list_const_ptr_t : public list_base_const_t +{ +public: + inline list_const_ptr_t(const T * p_data,t_size p_count) : m_data(p_data), m_count(p_count) {} + t_size get_count() const {return m_count;} + void get_item_ex(T & p_out,t_size n) const {p_out = m_data[n];} +private: + const T * m_data; + t_size m_count; +}; + +template +class NOVTABLE list_base_t : public list_base_const_t { +private: + typedef list_base_t t_self; + typedef const list_base_const_t t_self_const; +public: + class NOVTABLE sort_callback + { + public: + virtual int compare(const T& p_item1,const T& p_item2) = 0; + }; + + virtual void filter_mask(const bit_array & mask) = 0; + virtual t_size insert_items(const list_base_const_t & items,t_size base) = 0; + virtual void reorder_partial(t_size p_base,const t_size * p_data,t_size p_count) = 0; + virtual void sort(sort_callback & p_callback) = 0; + virtual void sort_stable(sort_callback & p_callback) = 0; + virtual void replace_item(t_size p_index,const T& p_item) = 0; + virtual void swap_item_with(t_size p_index,T & p_item) = 0; + virtual void swap_items(t_size p_index1,t_size p_index2) = 0; + + inline void reorder(const t_size * p_data) {reorder_partial(0,p_data,this->get_count());} + + inline t_size insert_item(const T & item,t_size base) {return insert_items(list_single_ref_t(item),base);} + t_size insert_items_repeat(const T & item,t_size num,t_size base) {return insert_items(list_single_ref_t(item,num),base);} + inline t_size add_items_repeat(T item,t_size num) {return insert_items_repeat(item,num,~0);} + t_size insert_items_fromptr(const T* source,t_size num,t_size base) {return insert_items(list_const_ptr_t(source,num),base);} + inline t_size add_items_fromptr(const T* source,t_size num) {return insert_items_fromptr(source,num,~0);} + + inline t_size add_items(const list_base_const_t & items) {return insert_items(items,~0);} + inline t_size add_item(const T& item) {return insert_item(item,~0);} + + inline void remove_mask(const bit_array & mask) {filter_mask(bit_array_not(mask));} + inline void remove_all() {filter_mask(bit_array_false());} + inline void truncate(t_size val) {if (val < this->get_count()) remove_mask(bit_array_range(val,this->get_count()-val,true));} + + inline T replace_item_ex(t_size p_index,const T & p_item) {T ret = p_item;swap_item_with(p_index,ret);return ret;} + + inline T operator[](t_size n) const {return this->get_item(n);} + + template + class sort_callback_impl_t : public sort_callback + { + public: + sort_callback_impl_t(t_compare p_compare) : m_compare(p_compare) {} + int compare(const T& p_item1,const T& p_item2) {return m_compare(p_item1,p_item2);} + private: + t_compare m_compare; + }; + + class sort_callback_auto : public sort_callback + { + public: + int compare(const T& p_item1,const T& p_item2) {return ::pfc::compare_t(p_item1,p_item2);} + }; + + void sort() {sort_callback_auto cb;sort(cb);} + template void sort_t(t_compare p_compare) {sort_callback_impl_t cb(p_compare);sort(cb);} + template void sort_stable_t(t_compare p_compare) {sort_callback_impl_t cb(p_compare); sort_stable(cb);} + + template void sort_remove_duplicates_t(t_compare p_compare) + { + sort_t(p_compare); + bit_array_bittable array(this->get_count()); + if (this->template find_duplicates_sorted_t(p_compare,array) > 0) + remove_mask(array); + } + + template void sort_stable_remove_duplicates_t(t_compare p_compare) + { + sort_stable_t(p_compare); + bit_array_bittable array(this->get_count()); + if (this->template find_duplicates_sorted_t(p_compare,array) > 0) + remove_mask(array); + } + + + template void remove_duplicates_t(t_compare p_compare) + { + order_helper order(this->get_count()); + sort_get_permutation_t(p_compare,order); + bit_array_bittable array(this->get_count()); + if (this->template find_duplicates_sorted_permutation_t(p_compare,order,array) > 0) + remove_mask(array); + } + + template + void for_each(t_func p_func) { + t_size n,max=this->get_count(); + for(n=0;nget_item(n)); + } + + template + void for_each(t_func p_func,const bit_array & p_mask) { + t_size n,max=this->get_count(); + for(n=p_mask.find(true,0,max);nget_item(n)); + } + } + + template + void remove_mask_ex(const bit_array & p_mask,t_releasefunc p_func) { + this->template for_each(p_func,p_mask); + remove_mask(p_mask); + } + + template + void remove_all_ex(t_releasefunc p_func) { + this->template for_each(p_func); + remove_all(); + } + + template t_self & operator=(t_in const & source) {remove_all(); add_items(source); return *this;} + template t_self & operator+=(t_in const & p_source) {add_item(p_source); return *this;} + template t_self & operator|=(t_in const & p_source) {add_items(p_source); return *this;} + +protected: + list_base_t() {} + ~list_base_t() {} + list_base_t(const t_self&) {} + void operator=(const t_self&) {} +}; + + +template +class list_impl_t : public list_base_t +{ +public: + typedef list_base_t t_base; + typedef list_impl_t t_self; + list_impl_t() {} + list_impl_t(const t_self & p_source) { add_items(p_source); } + + void prealloc(t_size count) {m_buffer.prealloc(count);} + + void set_count(t_size p_count) {m_buffer.set_size(p_count);} + void set_size(t_size p_count) {m_buffer.set_size(p_count);} + + template + t_size _insert_item_t(const t_in & item, t_size idx) { + return ::pfc::insert_t(m_buffer, item, idx); + } + template + t_size insert_item(const t_in & item, t_size idx) { + return _insert_item_t(item, idx); + } + + t_size insert_item(const T& item,t_size idx) { + return _insert_item_t(item, idx); + } + + T remove_by_idx(t_size idx) + { + T ret = m_buffer[idx]; + t_size n; + t_size max = m_buffer.get_size(); + for(n=idx+1;n=0); + PFC_ASSERT(n=0); + PFC_ASSERT(n= 0); + PFC_ASSERT(n < get_size() ); + return m_buffer[n]; + }; + + inline t_size get_count() const {return m_buffer.get_size();} + inline t_size get_size() const {return m_buffer.get_size();} + + inline const T & operator[](t_size n) const + { + PFC_ASSERT(n>=0); + PFC_ASSERT(n & source,t_size base) { //workaround for inefficient operator[] on virtual-interface-accessed lists + t_size count = get_size(); + if (base>count) base = count; + t_size num = source.get_count(); + m_buffer.set_size(count+num); + if (count > base) { + for(t_size n=count-1;(int)n>=(int)base;n--) { + ::pfc::move_t(m_buffer[n+num],m_buffer[n]); + } + } + + for(t_size n=0;n & source,t_size base) {return _insert_items_v(source, base);} + t_size insert_items(const list_base_t & source,t_size base) {return _insert_items_v(source, base);} + + template + t_size insert_items(const t_in & source,t_size base) { + t_size count = get_size(); + if (base>count) base = count; + t_size num = array_size_t(source); + m_buffer.set_size(count+num); + if (count > base) { + for(t_size n=count-1;(int)n>=(int)base;n--) { + ::pfc::move_t(m_buffer[n+num],m_buffer[n]); + } + } + + for(t_size n=0;n + void add_items(const t_in & in) {insert_items(in, ~0);} + + void get_items_mask(list_impl_t & out,const bit_array & mask) + { + mask.walk( get_size(), [&] (size_t n) { + out.add_item(m_buffer[n]); + } ); + } + + void filter_mask(const bit_array & mask) + { + t_size n,count = get_size(), total = 0; + + n = total = mask.find(false,0,count); + + if (n=0); + PFC_ASSERT(idx wrapper(m_buffer); + ::pfc::sort(wrapper,get_size()); + } + + template + void sort_t(t_compare p_compare) + { + ::pfc::sort_callback_impl_simple_wrap_t wrapper(m_buffer,p_compare); + ::pfc::sort(wrapper,get_size()); + } + + template + void sort_stable_t(t_compare p_compare) + { + ::pfc::sort_callback_impl_simple_wrap_t wrapper(m_buffer,p_compare); + ::pfc::sort_stable(wrapper,get_size()); + } + inline void reorder_partial(t_size p_base,const t_size * p_order,t_size p_count) + { + PFC_ASSERT(p_base+p_count<=get_size()); + ::pfc::reorder_partial_t(m_buffer,p_base,p_order,p_count); + } + + template + t_size find_duplicates_sorted_t(t_compare p_compare,bit_array_var & p_out) const + { + return ::pfc::find_duplicates_sorted_t const &,t_compare>(*this,get_size(),p_compare,p_out); + } + + template + t_size find_duplicates_sorted_permutation_t(t_compare p_compare,t_permutation p_permutation,bit_array_var & p_out) + { + return ::pfc::find_duplicates_sorted_permutation_t const &,t_compare,t_permutation>(*this,get_size(),p_compare,p_permutation,p_out); + } + + + void move_from(t_self & other) { + remove_all(); + m_buffer = std::move(other.m_buffer); + } + +private: + class sort_callback_wrapper + { + public: + explicit inline sort_callback_wrapper(typename t_base::sort_callback & p_callback) : m_callback(p_callback) {} + inline int operator()(const T& item1,const T& item2) const {return m_callback.compare(item1,item2);} + private: + typename t_base::sort_callback & m_callback; + }; +public: + void sort(typename t_base::sort_callback & p_callback) + { + sort_t(sort_callback_wrapper(p_callback)); + } + + void sort_stable(typename t_base::sort_callback & p_callback) + { + sort_stable_t(sort_callback_wrapper(p_callback)); + } + + void remove_mask(const bit_array & mask) {filter_mask(bit_array_not(mask));} + + void remove_mask(const bool * mask) {remove_mask(bit_array_table(mask,get_size()));} + void filter_mask(const bool * mask) {filter_mask(bit_array_table(mask,get_size()));} + + t_size add_item(const T& item) { + return insert_item(item, ~0); + } + + template t_size add_item(const t_in & item) { + return insert_item(item, ~0); + } + + void remove_all() {remove_mask(bit_array_true());} + + void remove_item(const T& item) + { + t_size n,max = get_size(); + bit_array_bittable mask(max); + for(n=0;n & p_item1,list_impl_t & p_item2) + { + swap_t(p_item1.m_buffer,p_item2.m_buffer); + } + + template + t_size find_item(const t_search & p_item) const//returns index of first occurance, infinite if not found + { + t_size n,max = get_size(); + for(n=0;n + inline bool have_item(const t_search & p_item) const {return this->template find_item(p_item)!=~0;} + + template t_self & operator=(t_in const & source) {remove_all(); add_items(source); return *this;} + template t_self & operator+=(t_in const & p_source) {add_item(p_source); return *this;} + template t_self & operator|=(t_in const & p_source) {add_items(p_source); return *this;} +protected: + t_storage m_buffer; +}; + +template class t_alloc = alloc_fast > +class list_t : public list_impl_t > { +public: + typedef list_t t_self; + template t_self & operator=(t_in const & source) {this->remove_all(); this->add_items(source); return *this;} + template t_self & operator+=(t_in const & p_source) {this->add_item(p_source); return *this;} + template t_self & operator|=(t_in const & p_source) {this->add_items(p_source); return *this;} +}; + +template class t_alloc = alloc_fast > +class list_hybrid_t : public list_impl_t > { +public: + typedef list_hybrid_t t_self; + template t_self & operator=(t_in const & source) {this->remove_all(); this->add_items(source); return *this;} + template t_self & operator+=(t_in const & p_source) {this->add_item(p_source); return *this;} + template t_self & operator|=(t_in const & p_source) {this->add_items(p_source); return *this;} +}; + +template +class ptr_list_const_cast_t : public list_base_const_t +{ +public: + inline ptr_list_const_cast_t(const list_base_const_t & p_param) : m_param(p_param) {} + t_size get_count() const {return m_param.get_count();} + void get_item_ex(const T * & p_out,t_size n) const {T* temp; m_param.get_item_ex(temp,n); p_out = temp;} +private: + const list_base_const_t & m_param; + +}; + + +template +class list_const_permutation_t : public list_base_const_t +{ +public: + inline list_const_permutation_t(const list_base_const_t & p_list,P p_permutation) : m_list(p_list), m_permutation(p_permutation) {} + t_size get_count() const {return m_list.get_count();} + void get_item_ex(T & p_out,t_size n) const {m_list.get_item_ex(p_out,m_permutation[n]);} +private: + P m_permutation; + const list_base_const_t & m_list; +}; + + +template +class list_permutation_t : public list_base_const_t +{ +public: + t_size get_count() const {return m_count;} + void get_item_ex(T & p_out,t_size n) const {m_base.get_item_ex(p_out,m_order[n]);} + list_permutation_t(const list_base_const_t & p_base,const t_size * p_order,t_size p_count) + : m_base(p_base), m_order(p_order), m_count(p_count) + { + PFC_ASSERT(m_base.get_count() >= m_count); + } +private: + const list_base_const_t & m_base; + const t_size * m_order; + t_size m_count; +}; + +template class alloc> class traits_t > : public combine_traits >, traits_vtable> {}; + +} diff --git a/pfc/lockless.h b/pfc/lockless.h new file mode 100644 index 0000000..83b297c --- /dev/null +++ b/pfc/lockless.h @@ -0,0 +1,72 @@ +#pragma once + +#include +#ifdef _MSC_VER +#include +#endif +namespace pfc { + + class threadSafeInt { + public: + typedef long t_val; + + threadSafeInt(t_val p_val = 0) : m_val(p_val) {} + long operator++() throw() { return inc(); } + long operator--() throw() { return dec(); } + long operator++(int) throw() { return inc() - 1; } + long operator--(int) throw() { return dec() + 1; } + operator t_val() const throw() { return m_val; } + + t_val exchange(t_val newVal) { +#ifdef _MSC_VER + return InterlockedExchange(&m_val, newVal); +#else + return __sync_lock_test_and_set(&m_val, newVal); +#endif + } + private: + t_val inc() { +#ifdef _MSC_VER + return _InterlockedIncrement(&m_val); +#else + return __sync_add_and_fetch(&m_val, 1); +#endif + } + t_val dec() { +#ifdef _MSC_VER + return _InterlockedDecrement(&m_val); +#else + return __sync_sub_and_fetch(&m_val, 1); +#endif + } + + volatile t_val m_val; + }; + + typedef threadSafeInt counter; + typedef threadSafeInt refcounter; + + void yield(); // forward declaration + + //! Minimalist class to call some function only once. \n + //! Presumes low probability of concurrent run() calls actually happening, \n + //! but frequent calls once already initialized, hence only using basic volatile bool check. \n + //! If using a modern compiler you might want to use std::call_once instead. \n + //! The called function is not expected to throw exceptions. + class runOnceLock { + public: + void run(std::function f) { + if (m_done) return; + if (m_once.exchange(1) == 0) { + f(); + m_done = true; + } else { + while (!m_done) yield(); + } + } + private: + threadSafeInt m_once; + volatile bool m_done = false; + + }; +} diff --git a/pfc/map.h b/pfc/map.h new file mode 100644 index 0000000..3105e62 --- /dev/null +++ b/pfc/map.h @@ -0,0 +1,268 @@ +#pragma once + +namespace pfc { + PFC_DECLARE_EXCEPTION(exception_map_entry_not_found,exception,"Map entry not found"); + + template class __map_overwrite_wrapper { + public: + __map_overwrite_wrapper(t_destination & p_destination) : m_destination(p_destination) {} + template void operator() (const t_key & p_key,const t_value & p_value) {m_destination.set(p_key,p_value);} + private: + t_destination & m_destination; + }; + + template + class map_t { + private: + typedef map_t t_self; + public: + typedef t_storage_key t_key; typedef t_storage_value t_value; + template + void set(const _t_key & p_key, const _t_value & p_value) { + bool isnew; + t_storage & storage = m_data.add_ex(t_search_set<_t_key,_t_value>(p_key,p_value), isnew); + if (!isnew) storage.m_value = p_value; + } + + template + t_storage_value & find_or_add(_t_key const & p_key) { + return m_data.add(t_search_query<_t_key>(p_key)).m_value; + } + + template + t_storage_value & find_or_add_ex(_t_key const & p_key,bool & p_isnew) { + return m_data.add_ex(t_search_query<_t_key>(p_key),p_isnew).m_value; + } + + template + bool have_item(const _t_key & p_key) const { + return m_data.have_item(t_search_query<_t_key>(p_key)); + } + + template + bool query(const _t_key & p_key,_t_value & p_value) const { + const t_storage * storage = m_data.find_ptr(t_search_query<_t_key>(p_key)); + if (storage == NULL) return false; + p_value = storage->m_value; + return true; + } + + template + const t_storage_value & operator[] (const _t_key & p_key) const { + const t_storage_value * ptr = query_ptr(p_key); + if (ptr == NULL) throw exception_map_entry_not_found(); + return *ptr; + } + + template + t_storage_value & operator[] (const _t_key & p_key) { + return find_or_add(p_key); + } + + template + const t_storage_value * query_ptr(const _t_key & p_key) const { + const t_storage * storage = m_data.find_ptr(t_search_query<_t_key>(p_key)); + if (storage == NULL) return NULL; + return &storage->m_value; + } + + template + t_storage_value * query_ptr(const _t_key & p_key) { + t_storage * storage = m_data.find_ptr(t_search_query<_t_key>(p_key)); + if (storage == NULL) return NULL; + return &storage->m_value; + } + + template + bool query_ptr(const _t_key & p_key, const t_storage_value * & out) const { + const t_storage * storage = m_data.find_ptr(t_search_query<_t_key>(p_key)); + if (storage == NULL) return false; + out = &storage->m_value; + return true; + } + + template + bool query_ptr(const _t_key & p_key, t_storage_value * & out) { + t_storage * storage = m_data.find_ptr(t_search_query<_t_key>(p_key)); + if (storage == NULL) return false; + out = &storage->m_value; + return true; + } + + template + const t_storage_value * query_nearest_ptr(_t_key & p_key) const { + const t_storage * storage = m_data.template find_nearest_item(t_search_query<_t_key>(p_key)); + if (storage == NULL) return NULL; + p_key = storage->m_key; + return &storage->m_value; + } + + template + t_storage_value * query_nearest_ptr(_t_key & p_key) { + t_storage * storage = m_data.template find_nearest_item(t_search_query<_t_key>(p_key)); + if (storage == NULL) return NULL; + p_key = storage->m_key; + return &storage->m_value; + } + + template + bool query_nearest(_t_key & p_key,_t_value & p_value) const { + const t_storage * storage = m_data.template find_nearest_item(t_search_query<_t_key>(p_key)); + if (storage == NULL) return false; + p_key = storage->m_key; + p_value = storage->m_value; + return true; + } + + + template + bool remove(const _t_key & p_key) { + return m_data.remove_item(t_search_query<_t_key>(p_key)); + } + + template + void enumerate(t_callback && p_callback) const { + enumeration_wrapper cb(p_callback); + m_data.enumerate(cb); + } + + template + void enumerate(t_callback && p_callback) { + enumeration_wrapper_var cb(p_callback); + m_data._enumerate_var(cb); + } + + + t_size get_count() const throw() {return m_data.get_count();} + + void remove_all() throw() {m_data.remove_all();} + + template + void overwrite(const t_source & p_source) { + __map_overwrite_wrapper wrapper(*this); + p_source.enumerate(wrapper); + } + + //backwards compatibility method wrappers + template bool exists(const _t_key & p_key) const {return have_item(p_key);} + + + template bool get_first(_t_key & p_out) const { + t_retrieve_key<_t_key> wrap(p_out); + return m_data.get_first(wrap); + } + + template bool get_last(_t_key & p_out) const { + t_retrieve_key<_t_key> wrap(p_out); + return m_data.get_last(wrap); + } + + + map_t() {} + map_t( const t_self & other ) : m_data( other.m_data ) {} + map_t( t_self && other ) : m_data( std::move(other.m_data) ) {} + const t_self & operator=( const t_self & other ) {m_data = other.m_data; return *this;} + const t_self & operator=( t_self && other ) { m_data = std::move(other.m_data); return *this; } + + void move_from(t_self & other) { + m_data.move_from( other.m_data ); + } + + private: + template + struct t_retrieve_key { + typedef t_retrieve_key<_t_key> t_self; + t_retrieve_key(_t_key & p_key) : m_key(p_key) {} + template const t_self & operator=(const t_what & p_what) {m_key = p_what.m_key; return *this;} + _t_key & m_key; + }; + template + struct t_search_query { + t_search_query(const _t_key & p_key) : m_key(p_key) {} + _t_key const & m_key; + }; + template + struct t_search_set { + t_search_set(const _t_key & p_key, const _t_value & p_value) : m_key(p_key), m_value(p_value) {} + + _t_key const & m_key; + _t_value const & m_value; + }; + + struct t_storage { + const t_storage_key m_key; + t_storage_value m_value; + + template + t_storage(t_search_query<_t_key> const & p_source) : m_key(p_source.m_key), m_value() {} + + template + t_storage(t_search_set<_t_key,_t_value> const & p_source) : m_key(p_source.m_key), m_value(p_source.m_value) {} + + static bool equals(const t_storage & v1, const t_storage & v2) {return v1.m_key == v2.m_key && v1.m_value == v2.m_value;} + bool operator==(const t_storage & other) const {return equals(*this,other);} + bool operator!=(const t_storage & other) const {return !equals(*this,other);} + }; + + class comparator_wrapper { + public: + template + inline static int compare(const t1 & p_item1,const t2 & p_item2) { + return t_comparator::compare(p_item1.m_key,p_item2.m_key); + } + }; + + template + class enumeration_wrapper { + public: + enumeration_wrapper(t_callback & p_callback) : m_callback(p_callback) {} + void operator()(const t_storage & p_item) {m_callback(p_item.m_key,p_item.m_value);} + private: + t_callback & m_callback; + }; + + template + class enumeration_wrapper_var { + public: + enumeration_wrapper_var(t_callback & p_callback) : m_callback(p_callback) {} + void operator()(t_storage & p_item) {m_callback(implicit_cast(p_item.m_key),p_item.m_value);} + private: + t_callback & m_callback; + }; + + typedef avltree_t t_content; + + t_content m_data; + public: + typedef traits_t traits; + typedef typename t_content::const_iterator const_iterator; + typedef typename t_content::iterator iterator; + + iterator first() throw() {return m_data._first_var();} + iterator last() throw() {return m_data._last_var();} + const_iterator first() const throw() {return m_data.first();} + const_iterator last() const throw() {return m_data.last();} + const_iterator cfirst() const throw() {return m_data.first();} + const_iterator clast() const throw() {return m_data.last();} + + template iterator find(const _t_key & key) {return m_data.find(t_search_query<_t_key>(key));} + template const_iterator find(const _t_key & key) const {return m_data.find(t_search_query<_t_key>(key));} + + static bool equals(const t_self & v1, const t_self & v2) { + return t_content::equals(v1.m_data,v2.m_data); + } + bool operator==(const t_self & other) const {return equals(*this,other);} + bool operator!=(const t_self & other) const {return !equals(*this,other);} + + bool remove(iterator const& iter) { + PFC_ASSERT(iter.is_valid()); + return m_data.remove(iter); + //should never return false unless there's a bug in calling code + } + bool remove(const_iterator const& iter) { + PFC_ASSERT(iter.is_valid()); + return m_data.remove(iter); + //should never return false unless there's a bug in calling code + } + }; +} diff --git a/pfc/memalign.h b/pfc/memalign.h new file mode 100644 index 0000000..bd2478f --- /dev/null +++ b/pfc/memalign.h @@ -0,0 +1,137 @@ +#pragma once + +#ifndef _MSC_VER +#include +#endif + +namespace pfc { + template + class mem_block_aligned { + public: + typedef mem_block_aligned self_t; + mem_block_aligned() : m_ptr(), m_size() {} + + void * ptr() {return m_ptr;} + const void * ptr() const {return m_ptr;} + void * get_ptr() {return m_ptr;} + const void * get_ptr() const {return m_ptr;} + size_t size() const {return m_size;} + size_t get_size() const {return m_size;} + + void resize(size_t s) { + if (s == m_size) { + // nothing to do + } else if (s == 0) { + _free(m_ptr); + m_ptr = NULL; + } else { + void * ptr; +#ifdef _MSC_VER + if (m_ptr == NULL) ptr = _aligned_malloc(s, alignBytes); + else ptr = _aligned_realloc(m_ptr, s, alignBytes); + if ( ptr == NULL ) throw std::bad_alloc(); +#else +#ifdef __ANDROID__ + if ((ptr = memalign( alignBytes, s )) == NULL) throw std::bad_alloc(); +#else + if (posix_memalign( &ptr, alignBytes, s ) < 0) throw std::bad_alloc(); +#endif + if (m_ptr != NULL) { + memcpy( ptr, m_ptr, min_t( m_size, s ) ); + _free( m_ptr ); + } +#endif + m_ptr = ptr; + } + m_size = s; + } + void set_size(size_t s) {resize(s);} + + ~mem_block_aligned() { + _free(m_ptr); + } + + self_t const & operator=(self_t const & other) { + assign(other); + return *this; + } + mem_block_aligned(self_t const & other) : m_ptr(), m_size() { + assign(other); + } + void assign(self_t const & other) { + resize(other.size()); + memcpy(ptr(), other.ptr(), size()); + } + mem_block_aligned(self_t && other) { + m_ptr = other.m_ptr; + m_size = other.m_size; + other.m_ptr = NULL; other.m_size = 0; + } + self_t const & operator=(self_t && other) { + _free(m_ptr); + m_ptr = other.m_ptr; + m_size = other.m_size; + other.m_ptr = NULL; other.m_size = 0; + return *this; + } + + private: + static void _free(void * ptr) { +#ifdef _MSC_VER + _aligned_free(ptr); +#else + free(ptr); +#endif + } + + void * m_ptr; + size_t m_size; + }; + + template + class mem_block_aligned_t { + public: + typedef mem_block_aligned_t self_t; + void resize(size_t s) { m.resize( multiply_guarded(s, sizeof(obj_t) ) ); } + void set_size(size_t s) {resize(s);} + size_t size() const { return m.size() / sizeof(obj_t); } + size_t get_size() const {return size();} + obj_t * ptr() { return reinterpret_cast(m.ptr()); } + const obj_t * ptr() const { return reinterpret_cast(m.ptr()); } + obj_t * get_ptr() { return reinterpret_cast(m.ptr()); } + const obj_t * get_ptr() const { return reinterpret_cast(m.ptr()); } + mem_block_aligned_t() {} + private: + mem_block_aligned m; + }; + + template + class mem_block_aligned_incremental_t { + public: + typedef mem_block_aligned_t self_t; + + void resize(size_t s) { + if (s > m.size()) { + m.resize( multiply_guarded(s, 3) / 2 ); + } + m_size = s; + } + void set_size(size_t s) {resize(s);} + + size_t size() const { return m_size; } + size_t get_size() const {return m_size; } + + obj_t * ptr() { return m.ptr(); } + const obj_t * ptr() const { return m.ptr(); } + obj_t * get_ptr() { return m.ptr(); } + const obj_t * get_ptr() const { return m.ptr(); } + mem_block_aligned_incremental_t() : m_size() {} + mem_block_aligned_incremental_t(self_t const & other) : m(other.m), m_size(other.m_size) {} + mem_block_aligned_incremental_t(self_t && other) : m(std::move(other.m)), m_size(other.m_size) { other.m_size = 0; } + self_t const & operator=(self_t const & other) {m = other.m; m_size = other.m_size; return *this;} + self_t const & operator=(self_t && other) {m = std::move(other.m); m_size = other.m_size; other.m_size = 0; return *this;} + private: + mem_block_aligned_t m; + size_t m_size; + }; +} diff --git a/pfc/nix-objects.cpp b/pfc/nix-objects.cpp new file mode 100644 index 0000000..df49393 --- /dev/null +++ b/pfc/nix-objects.cpp @@ -0,0 +1,274 @@ +#include "pfc.h" + +#ifndef _WIN32 +#include +#include +#include +#include +#include + +#ifdef __APPLE__ +#include +#endif + +namespace pfc { + void nixFormatError( string_base & str, int code ) { + char buffer[512] = {}; + strerror_r(code, buffer, sizeof(buffer)); + str = buffer; + } + + void setNonBlocking( int fd, bool bNonBlocking ) { + int flags = fcntl(fd, F_GETFL, 0); + int flags2 = flags; + if (bNonBlocking) flags2 |= O_NONBLOCK; + else flags2 &= ~O_NONBLOCK; + if (flags2 != flags) fcntl(fd, F_SETFL, flags2); + } + + void setCloseOnExec( int fd, bool bCloseOnExec ) { + int flags = fcntl(fd, F_GETFD); + int flags2 = flags; + if (bCloseOnExec) flags2 |= FD_CLOEXEC; + else flags2 &= ~FD_CLOEXEC; + if (flags != flags2) fcntl(fd, F_SETFD, flags2); + } + + void setInheritable( int fd, bool bInheritable ) { + setCloseOnExec( fd, !bInheritable ); + } + + void createPipe( int fd[2], bool bInheritable ) { +#if defined(__linux__) && defined(O_CLOEXEC) + if (pipe2(fd, bInheritable ? 0 : O_CLOEXEC) < 0) throw exception_nix(); +#else + if (pipe(fd) < 0) throw exception_nix(); + if (!bInheritable) { + setInheritable( fd[0], false ); + setInheritable( fd[1], false ); + } +#endif + } + + exception_nix::exception_nix() { + _init(errno); + } + exception_nix::exception_nix(int code) { + _init(code); + } + void exception_nix::_init(int code) { + m_code = code; + nixFormatError(m_msg, code); + } + + timeval makeTimeVal( double timeSeconds ) { + timeval tv = {}; + uint64_t temp = (uint64_t) floor( timeSeconds * 1000000.0 + 0.5); + tv.tv_usec = (uint32_t) (temp % 1000000); + tv.tv_sec = (uint32_t) (temp / 1000000); + return tv; + } + double importTimeval(const timeval & in) { + return (double)in.tv_sec + (double)in.tv_usec / 1000000.0; + } + + void fdSet::operator+=( int fd ) { + m_fds.insert( fd ); + } + void fdSet::operator-=( int fd ) { + m_fds.erase(fd); + } + bool fdSet::operator[] (int fd ) { + return m_fds.find( fd ) != m_fds.end(); + } + void fdSet::clear() { + m_fds.clear(); + } + + void fdSet::operator+=( fdSet const & other ) { + for(auto i = other.m_fds.begin(); i != other.m_fds.end(); ++ i ) { + (*this) += *i; + } + } + + int fdSelect::Select() { + return Select_( -1 ); + } + int fdSelect::Select( double timeOutSeconds ) { + int ms; + if (timeOutSeconds < 0) { + ms = -1; + } else if (timeOutSeconds == 0) { + ms = 0; + } else { + ms = pfc::rint32( timeOutSeconds * 1000 ); + if (ms < 1) ms = 1; + } + return Select_( ms ); + } + + int fdSelect::Select_( int timeOutMS ) { + fdSet total = Reads; + total += Writes; + total += Errors; + const size_t count = total.m_fds.size(); + pfc::array_t< pollfd > v; + v.set_size_discard( count ); + size_t walk = 0; + for( auto i = total.m_fds.begin(); i != total.m_fds.end(); ++ i ) { + const int fd = *i; + auto & f = v[walk++]; + f.fd = fd; + f.events = (Reads[fd] ? POLLIN : 0) | (Writes[fd] ? POLLOUT : 0); + // POLLERR ignored in events, only used in revents + f.revents = 0; + } + int status = poll(v.get_ptr(), (int)count, timeOutMS); + if (status < 0) throw exception_nix(); + + Reads.clear(); Writes.clear(); Errors.clear(); + + if (status > 0) { + for(walk = 0; walk < count; ++walk) { + auto & f = v[walk]; + if (f.revents & POLLIN) Reads += f.fd; + if (f.revents & POLLOUT) Writes += f.fd; + if (f.revents & POLLERR) Errors += f.fd; + } + } + + return status; + } + + bool fdCanRead( int fd ) { + return fdWaitRead( fd, 0 ); + } + bool fdCanWrite( int fd ) { + return fdWaitWrite( fd, 0 ); + } + + bool fdWaitRead( int fd, double timeOutSeconds ) { + fdSelect sel; sel.Reads += fd; + return sel.Select( timeOutSeconds ) > 0; + } + bool fdWaitWrite( int fd, double timeOutSeconds ) { + fdSelect sel; sel.Writes += fd; + return sel.Select( timeOutSeconds ) > 0; + } + + nix_event::nix_event() { + createPipe( m_fd ); + setNonBlocking( m_fd[0] ); + setNonBlocking( m_fd[1] ); + } + nix_event::~nix_event() { + close( m_fd[0] ); + close( m_fd[1] ); + } + + void nix_event::set_state( bool state ) { + if (state) { + // Ensure that there is a byte in the pipe + if (!fdCanRead(m_fd[0] ) ) { + uint8_t dummy = 0; + write( m_fd[1], &dummy, 1); + } + } else { + // Keep reading until clear + for(;;) { + uint8_t dummy; + if (read(m_fd[0], &dummy, 1 ) != 1) break; + } + } + } + + bool nix_event::wait_for( double p_timeout_seconds ) { + return fdWaitRead( m_fd[0], p_timeout_seconds ); + } + bool nix_event::g_wait_for( int p_event, double p_timeout_seconds ) { + return fdWaitRead( p_event, p_timeout_seconds ); + } + int nix_event::g_twoEventWait( int h1, int h2, double timeout ) { + fdSelect sel; + sel.Reads += h1; + sel.Reads += h2; + int state = sel.Select( timeout ); + if (state < 0) throw exception_nix(); + if (state == 0) return 0; + if (sel.Reads[ h1 ] ) return 1; + if (sel.Reads[ h2 ] ) return 2; + crash(); // should not get here + return 0; + } + int nix_event::g_twoEventWait( nix_event & ev1, nix_event & ev2, double timeout ) { + return g_twoEventWait( ev1.get_handle(), ev2.get_handle(), timeout ); + } + + void nixSleep(double seconds) { + fdSelect sel; sel.Select( seconds ); + } + void sleepSeconds(double seconds) { + return nixSleep(seconds); + } + + void yield() { + return nixSleep(0.001); + } + + double nixGetTime() { + timeval tv = {}; + gettimeofday(&tv, NULL); + return importTimeval(tv); + } + + bool nixReadSymLink( string_base & strOut, const char * path ) { + size_t l = 1024; + for(;;) { + array_t buffer; buffer.set_size( l + 1 ); + ssize_t rv = (size_t) readlink(path, buffer.get_ptr(), l); + if (rv < 0) return false; + if ((size_t)rv <= l) { + buffer.get_ptr()[rv] = 0; + strOut = buffer.get_ptr(); + return true; + } + l *= 2; + } + } + bool nixSelfProcessPath( string_base & strOut ) { +#ifdef __APPLE__ + uint32_t len = 0; + _NSGetExecutablePath(NULL, &len); + array_t temp; temp.set_size( len + 1 ); temp.fill_null(); + _NSGetExecutablePath(temp.get_ptr(), &len); + strOut = temp.get_ptr(); + return true; +#else + return nixReadSymLink( strOut, PFC_string_formatter() << "/proc/" << (unsigned) getpid() << "/exe"); +#endif + } + + void nixGetRandomData( void * outPtr, size_t outBytes ) { + try { + fileHandle randomData; + randomData = open("/dev/urandom", O_RDONLY); + if (randomData.h < 0) throw exception_nix(); + if (read(randomData.h, outPtr, outBytes) != outBytes) throw exception_nix(); + } + catch (std::exception const & e) { + throw std::runtime_error("getRandomData failure"); + } + } + +#ifndef __APPLE__ // for Apple they are implemented in Obj-C + bool isShiftKeyPressed() {return false;} + bool isCtrlKeyPressed() {return false;} + bool isAltKeyPressed() {return false;} +#endif +} + +void uSleepSeconds( double seconds, bool ) { + pfc::nixSleep( seconds ); +} +#endif // _WIN32 + diff --git a/pfc/nix-objects.h b/pfc/nix-objects.h new file mode 100644 index 0000000..768a3e9 --- /dev/null +++ b/pfc/nix-objects.h @@ -0,0 +1,110 @@ +#pragma once + +#include +#include +#include + +namespace pfc { + + void nixFormatError( string_base & str, int code ); + + class exception_nix : public std::exception { + public: + exception_nix(); + exception_nix(int code); + + ~exception_nix() throw() { } + + int code() const throw() {return m_code;} + const char * what() const throw() {return m_msg.get_ptr();} + private: + void _init(int code); + int m_code; + string8 m_msg; + }; + + // Toggles non-blocking mode on a file descriptor. + void setNonBlocking( int fd, bool bNonBlocking = true ); + + // Toggles close-on-exec mode on a file descriptor. + void setCloseOnExec( int fd, bool bCloseOnExec = true ); + + // Toggles inheritable mode on a file descriptor. Reverse of close-on-exec. + void setInheritable( int fd, bool bInheritable = true ); + + // Creates a pipe. The pipe is NOT inheritable by default (close-on-exec set). + void createPipe( int fd[2], bool bInheritable = false ); + + timeval makeTimeVal( double seconds ); + double importTimeval(const timeval & tv); + + class fdSet { + public: + + void operator+=( int fd ); + void operator-=( int fd ); + bool operator[] (int fd ); + void clear(); + + void operator+=( fdSet const & other ); + + std::set m_fds; + }; + + + bool fdCanRead( int fd ); + bool fdCanWrite( int fd ); + + bool fdWaitRead( int fd, double timeOutSeconds ); + bool fdWaitWrite( int fd, double timeOutSeconds ); + + class fdSelect { + public: + + int Select(); + int Select( double timeOutSeconds ); + int Select_( int timeOutMS ); + + fdSet Reads, Writes, Errors; + }; + + void nixSleep(double seconds); + + class nix_event { + public: + nix_event(); + ~nix_event(); + + void set_state( bool state ); + + bool is_set( ) {return wait_for(0); } + + bool wait_for( double p_timeout_seconds ); + + static bool g_wait_for( int p_event, double p_timeout_seconds ); + + int get_handle() const {return m_fd[0]; } + + // Two-wait event functions, return 0 on timeout, 1 on evt1 set, 2 on evt2 set + static int g_twoEventWait( nix_event & ev1, nix_event & ev2, double timeout ); + static int g_twoEventWait( int h1, int h2, double timeOut ); + + private: + nix_event(nix_event const&); + void operator=(nix_event const&); + int m_fd[2]; + }; + + double nixGetTime(); + + bool nixReadSymLink( string_base & strOut, const char * path ); + bool nixSelfProcessPath( string_base & strOut ); + + void nixGetRandomData( void * outPtr, size_t outBytes ); + + bool isShiftKeyPressed(); + bool isCtrlKeyPressed(); + bool isAltKeyPressed(); +} + +void uSleepSeconds( double seconds, bool ); diff --git a/pfc/notifyList.h b/pfc/notifyList.h new file mode 100644 index 0000000..4e8565f --- /dev/null +++ b/pfc/notifyList.h @@ -0,0 +1,85 @@ +#pragma once + +#include +#include +#include + +namespace pfc { + class notifyList { + public: + + typedef size_t token_t; + + typedef std::function notify_t; + token_t add( notify_t f ) { + auto token = ++ m_increment; + m_notify[token] = f; + return token; + } + + void remove( token_t t ) { + m_notify.erase(t); + } + + void dispatch() { + // Safeguard against someone altering our state in mid-dispatch + auto temp = m_notify; + for( auto walk = temp.begin(); walk != temp.end(); ++ walk ) { + if ( m_notify.count( walk->first ) > 0 ) { // still there? + walk->second(); + } + } + } + + + static std::shared_ptr make() { + return std::make_shared(); + } + private: + token_t m_increment = 0; + + std::map m_notify; + }; + + typedef std::shared_ptr< notifyList > notifyListRef_t; + + class notifyEntry { + public: + notifyEntry() {} + + notifyEntry & operator<<( notifyList & l ) { + PFC_ASSERT( m_list == nullptr ); + m_list = &l; + return *this; + } + notifyEntry & operator<<( notifyListRef_t l ) { + PFC_ASSERT( m_list == nullptr ); + m_listShared = l; + m_list = &*l; + return *this; + } + notifyEntry & operator<<( notifyList::notify_t f ) { + PFC_ASSERT( m_list != nullptr ); + PFC_ASSERT( m_token == 0 ); + m_token = m_list->add( f ); + return *this; + } + void clear() { + if ( m_list != nullptr && m_token != 0 ) { + m_list->remove(m_token); + m_token = 0; + } + } + ~notifyEntry() { + clear(); + } + + private: + notifyListRef_t m_listShared; + notifyList * m_list = nullptr; + notifyList::token_t m_token = 0; + + notifyEntry( const notifyEntry & ) = delete; + void operator=( const notifyEntry & ) = delete; + }; +} \ No newline at end of file diff --git a/pfc/obj-c.mm b/pfc/obj-c.mm new file mode 100644 index 0000000..7c234ba --- /dev/null +++ b/pfc/obj-c.mm @@ -0,0 +1,61 @@ +// +// PFC-ObjC.m +// pfc-test +// +// Created by PEPE on 28/07/14. +// Copyright (c) 2014 PEPE. All rights reserved. +// +#ifdef __APPLE__ +#import + + +#include + +#if TARGET_OS_MAC && !TARGET_OS_IPHONE +#import +#endif + +#include "pfc.h" + + +namespace pfc { + void * thread::g_entry(void * arg) { + @autoreleasepool { + reinterpret_cast(arg)->entry(); + } + return NULL; + } + void thread::appleStartThreadPrologue() { + if (![NSThread isMultiThreaded]) [[[NSThread alloc] init] start]; + } + + bool isShiftKeyPressed() { +#if TARGET_OS_MAC && !TARGET_OS_IPHONE + return ( [NSEvent modifierFlags] & NSShiftKeyMask ) != 0; +#else + return false; +#endif + } + bool isCtrlKeyPressed() { +#if TARGET_OS_MAC && !TARGET_OS_IPHONE + return ( [NSEvent modifierFlags] & NSControlKeyMask ) != 0; +#else + return false; +#endif + } + bool isAltKeyPressed() { +#if TARGET_OS_MAC && !TARGET_OS_IPHONE + return ( [NSEvent modifierFlags] & NSAlternateKeyMask ) != 0; +#else + return false; +#endif + } + + void inAutoReleasePool(std::function f) { + @autoreleasepool { + f(); + } + } +} + +#endif diff --git a/pfc/order_helper.h b/pfc/order_helper.h new file mode 100644 index 0000000..1670379 --- /dev/null +++ b/pfc/order_helper.h @@ -0,0 +1,73 @@ +#pragma once + +namespace pfc { + PFC_DECLARE_EXCEPTION( exception_invalid_permutation, exception_invalid_params, "Invalid permutation" ); + t_size permutation_find_reverse(t_size const * order, t_size count, t_size value); + + //! For critical sanity checks. Speed: O(n), allocates memory. + bool permutation_is_valid(t_size const * order, t_size count); + //! For critical sanity checks. Speed: O(n), allocates memory. + void permutation_validate(t_size const * order, t_size count); + + //! Creates a permutation that moves selected items in a list box by the specified delta-offset. + void create_move_items_permutation(t_size * p_output,t_size p_count,const class bit_array & p_selection,int p_delta); + + void create_move_item_permutation( size_t * p_output, size_t p_count, size_t from, size_t to ); + bool create_drop_permutation(size_t * out, size_t itemCount, pfc::bit_array const & maskSelected, size_t insertMark ); +} + +class order_helper +{ + pfc::array_t m_data; +public: + order_helper(t_size p_size) { + m_data.set_size(p_size); + for(t_size n=0;n static bool g_is_identity(const t_array & p_array) { + const t_size count = pfc::array_size_t(p_array); + for(t_size walk = 0; walk < count; ++walk) if (p_array[walk] != walk) return false; + return true; + } + + template + static void g_fill(t_int * p_order,const t_size p_count) { + t_size n; for(n=0;n + static void g_fill(t_array & p_array) { + t_size n; const t_size max = pfc::array_size_t(p_array); + for(n=0;n +#include +#endif +#ifndef _MSC_VER +#include +#endif + +#if defined(__ANDROID__) +#include +#endif + +#include + +#include "pfc-fb2k-hooks.h" + +namespace pfc { + bool permutation_is_valid(t_size const * order, t_size count) { + bit_array_bittable found(count); + for(t_size walk = 0; walk < count; ++walk) { + if (order[walk] >= count) return false; + if (found[walk]) return false; + found.set(walk,true); + } + return true; + } + void permutation_validate(t_size const * order, t_size count) { + if (!permutation_is_valid(order,count)) throw exception_invalid_permutation(); + } + + t_size permutation_find_reverse(t_size const * order, t_size count, t_size value) { + if (value >= count) return ~0; + for(t_size walk = 0; walk < count; ++walk) { + if (order[walk] == value) return walk; + } + return ~0; + } + + void create_move_item_permutation( size_t * order, size_t count, size_t from, size_t to ) { + PFC_ASSERT( from < count ); + PFC_ASSERT( to < count ); + for ( size_t w = 0; w < count; ++w ) { + size_t i = w; + if ( w == to ) i = from; + else if ( w < to && w >= from ) { + ++i; + } else if ( w > to && w <= from ) { + --i; + } + order[w] = i; + } + } + + void create_move_items_permutation(t_size * p_output,t_size p_count,const bit_array & p_selection,int p_delta) { + t_size * const order = p_output; + const t_size count = p_count; + + pfc::array_t selection; selection.set_size(p_count); + + for(t_size walk = 0; walk < count; ++walk) { + order[walk] = walk; + selection[walk] = p_selection[walk]; + } + + if (p_delta<0) + { + for(;p_delta<0;p_delta++) + { + t_size idx; + for(idx=1;idx0;p_delta--) + { + t_size idx; + for(idx=count-2;(int)idx>=0;idx--) + { + if (selection[idx] && !selection[idx+1]) + { + pfc::swap_t(order[idx],order[idx+1]); + pfc::swap_t(selection[idx],selection[idx+1]); + } + } + } + } + } + bool create_drop_permutation(size_t * out, size_t itemCount, pfc::bit_array const & maskSelected, size_t insertMark ) { + const t_size count = itemCount; + if (insertMark > count) insertMark = count; + { + t_size selBefore = 0; + for(t_size walk = 0; walk < insertMark; ++walk) { + if (maskSelected[walk]) selBefore++; + } + insertMark -= selBefore; + } + { + pfc::array_t permutation, selected, nonselected; + + const t_size selcount = maskSelected.calc_count( true, 0, count ); + selected.set_size(selcount); nonselected.set_size(count - selcount); + permutation.set_size(count); + if (insertMark > nonselected.get_size()) insertMark = nonselected.get_size(); + for(t_size walk = 0, swalk = 0, nwalk = 0; walk < count; ++walk) { + if (maskSelected[walk]) { + selected[swalk++] = walk; + } else { + nonselected[nwalk++] = walk; + } + } + for(t_size walk = 0; walk < insertMark; ++walk) { + permutation[walk] = nonselected[walk]; + } + for(t_size walk = 0; walk < selected.get_size(); ++walk) { + permutation[insertMark + walk] = selected[walk]; + } + for(t_size walk = insertMark; walk < nonselected.get_size(); ++walk) { + permutation[selected.get_size() + walk] = nonselected[walk]; + } + for(t_size walk = 0; walk < permutation.get_size(); ++walk) { + if (permutation[walk] != walk) { + memcpy(out, permutation.get_ptr(), count * sizeof(size_t)); + return true; + } + } + } + return false; + } + +} + +void order_helper::g_swap(t_size * data,t_size ptr1,t_size ptr2) +{ + t_size temp = data[ptr1]; + data[ptr1] = data[ptr2]; + data[ptr2] = temp; +} + + +t_size order_helper::g_find_reverse(const t_size * order,t_size val) +{ + t_size prev = val, next = order[val]; + while(next != val) + { + prev = next; + next = order[next]; + } + return prev; +} + + +void order_helper::g_reverse(t_size * order,t_size base,t_size count) +{ + t_size max = count>>1; + t_size n; + t_size base2 = base+count-1; + for(n=0;n>1;n++) swap_t(ptr[n],ptr[p_bytes-n-1]); +} + +void pfc::outputDebugLine(const char * msg) { +#ifdef _WIN32 + OutputDebugString(pfc::stringcvt::string_os_from_utf8(PFC_string_formatter() << msg << "\n") ); +#elif defined(__ANDROID__) + __android_log_write(ANDROID_LOG_INFO, "Debug", msg); +#else + printf("%s\n", msg); +#endif +} + +#if PFC_DEBUG + +#ifdef _WIN32 +void pfc::myassert_win32(const wchar_t * _Message, const wchar_t *_File, unsigned _Line) { + if (IsDebuggerPresent()) pfc::crash(); + PFC_DEBUGLOG << "PFC_ASSERT failure: " << _Message; + PFC_DEBUGLOG << "PFC_ASSERT location: " << _File << " : " << _Line; + _wassert(_Message,_File,_Line); +} +#else + +void pfc::myassert(const char * _Message, const char *_File, unsigned _Line) +{ + PFC_DEBUGLOG << "Assert failure: \"" << _Message << "\" in: " << _File << " line " << _Line; + crash(); +} +#endif + +#endif + + +t_uint64 pfc::pow_int(t_uint64 base, t_uint64 exp) { + t_uint64 mul = base; + t_uint64 val = 1; + t_uint64 mask = 1; + while(exp != 0) { + if (exp & mask) { + val *= mul; + exp ^= mask; + } + mul = mul * mul; + mask <<= 1; + } + return val; +} + +double pfc::exp_int( const double base, const int expS ) { + // return pow(base, (double)v); + + bool neg; + unsigned exp; + if (expS < 0) { + neg = true; + exp = (unsigned) -expS; + } else { + neg = false; + exp = (unsigned) expS; + } + double v = 1.0; + if (true) { + if (exp) { + double mul = base; + for(;;) { + if (exp & 1) v *= mul; + exp >>= 1; + if (exp == 0) break; + mul *= mul; + } + } + } else { + for(unsigned i = 0; i < exp; ++i) { + v *= base; + } + } + if (neg) v = 1.0 / v; + return v; +} + + +t_int32 pfc::rint32(double p_val) { return (t_int32)floor(p_val + 0.5); } +t_int64 pfc::rint64(double p_val) { return (t_int64)floor(p_val + 0.5); } + + +namespace pfc { + // bigmem impl + + void bigmem::resize(size_t newSize) { + clear(); + m_data.set_size((newSize + slice - 1) / slice); + m_data.fill_null(); + for (size_t walk = 0; walk < m_data.get_size(); ++walk) { + size_t thisSlice = slice; + if (walk + 1 == m_data.get_size()) { + size_t cut = newSize % slice; + if (cut) thisSlice = cut; + } + void* ptr = malloc(thisSlice); + if (ptr == NULL) { clear(); throw std::bad_alloc(); } + m_data[walk] = (uint8_t*)ptr; + } + m_size = newSize; + } + void bigmem::clear() { + for (size_t walk = 0; walk < m_data.get_size(); ++walk) free(m_data[walk]); + m_data.set_size(0); + m_size = 0; + } + void bigmem::read(void * ptrOut, size_t bytes, size_t offset) { + PFC_ASSERT(offset + bytes <= size()); + uint8_t * outWalk = (uint8_t*)ptrOut; + while (bytes > 0) { + size_t o1 = offset / slice, o2 = offset % slice; + size_t delta = slice - o2; if (delta > bytes) delta = bytes; + memcpy(outWalk, m_data[o1] + o2, delta); + offset += delta; + bytes -= delta; + outWalk += delta; + } + } + void bigmem::write(const void * ptrIn, size_t bytes, size_t offset) { + PFC_ASSERT(offset + bytes <= size()); + const uint8_t * inWalk = (const uint8_t*)ptrIn; + while (bytes > 0) { + size_t o1 = offset / slice, o2 = offset % slice; + size_t delta = slice - o2; if (delta > bytes) delta = bytes; + memcpy(m_data[o1] + o2, inWalk, delta); + offset += delta; + bytes -= delta; + inWalk += delta; + } + } + uint8_t * bigmem::_slicePtr(size_t which) { return m_data[which]; } + size_t bigmem::_sliceCount() { return m_data.get_size(); } + size_t bigmem::_sliceSize(size_t which) { + if (which + 1 == _sliceCount()) { + size_t s = m_size % slice; + if (s) return s; + } + return slice; + } + +} \ No newline at end of file diff --git a/pfc/other.h b/pfc/other.h new file mode 100644 index 0000000..0c76026 --- /dev/null +++ b/pfc/other.h @@ -0,0 +1,289 @@ +#pragma once + +namespace pfc { + template + class vartoggle_t { + T oldval; T & var; + public: + vartoggle_t(T & p_var,const T & val) : var(p_var) { + oldval = var; + var = val; + } + ~vartoggle_t() {var = oldval;} + }; + + template + class vartoggle_volatile_t { + T oldval; volatile T & var; + public: + vartoggle_volatile_t(volatile T & p_var,const T & val) : var(p_var) { + oldval = var; + var = val; + } + ~vartoggle_volatile_t() {var = oldval;} + }; + + typedef vartoggle_t booltoggle; +}; + +#ifdef _MSC_VER + +class fpu_control +{ + unsigned old_val; + unsigned mask; +public: + inline fpu_control(unsigned p_mask,unsigned p_val) + { + mask = p_mask; + _controlfp_s(&old_val,p_val,mask); + } + inline ~fpu_control() + { + unsigned dummy; + _controlfp_s(&dummy,old_val,mask); + } +}; + +class fpu_control_roundnearest : private fpu_control +{ +public: + fpu_control_roundnearest() : fpu_control(_MCW_RC,_RC_NEAR) {} +}; + +class fpu_control_flushdenormal : private fpu_control +{ +public: + fpu_control_flushdenormal() : fpu_control(_MCW_DN,_DN_FLUSH) {} +}; + +class fpu_control_default : private fpu_control +{ +public: + fpu_control_default() : fpu_control(_MCW_DN|_MCW_RC,_DN_FLUSH|_RC_NEAR) {} +}; + +#ifdef _M_IX86 +class sse_control { +public: + sse_control(unsigned p_mask,unsigned p_val) : m_mask(p_mask) { + __control87_2(p_val,p_mask,NULL,&m_oldval); + } + ~sse_control() { + __control87_2(m_oldval,m_mask,NULL,&m_oldval); + } +private: + unsigned m_mask,m_oldval; +}; +class sse_control_flushdenormal : private sse_control { +public: + sse_control_flushdenormal() : sse_control(_MCW_DN,_DN_FLUSH) {} +}; +#endif + +#endif + +namespace pfc { + + class releaser_delete { + public: + template static void release(T* p_ptr) {delete p_ptr;} + }; + class releaser_delete_array { + public: + template static void release(T* p_ptr) {delete[] p_ptr;} + }; + class releaser_free { + public: + static void release(void * p_ptr) {free(p_ptr);} + }; + + //! Assumes t_freefunc to never throw exceptions. + template + class ptrholder_t { + private: + typedef ptrholder_t t_self; + public: + inline ptrholder_t(T* p_ptr) : m_ptr(p_ptr) {} + inline ptrholder_t() : m_ptr(NULL) {} + inline ~ptrholder_t() {t_releaser::release(m_ptr);} + inline bool is_valid() const {return m_ptr != NULL;} + inline bool is_empty() const {return m_ptr == NULL;} + inline T* operator->() const {return m_ptr;} + inline T* get_ptr() const {return m_ptr;} + inline void release() {t_releaser::release(replace_null_t(m_ptr));;} + inline void attach(T * p_ptr) {release(); m_ptr = p_ptr;} + inline const t_self & operator=(T * p_ptr) {set(p_ptr);return *this;} + inline T* detach() {return pfc::replace_null_t(m_ptr);} + inline T& operator*() const {return *m_ptr;} + + inline t_self & operator<<(t_self & p_source) {attach(p_source.detach());return *this;} + inline t_self & operator>>(t_self & p_dest) {p_dest.attach(detach());return *this;} + + //deprecated + inline void set(T * p_ptr) {attach(p_ptr);} + + ptrholder_t(t_self&& other) { m_ptr = other.detach(); } + const t_self& operator=(t_self&& other) { attach(other.detach()); return this; } + private: + ptrholder_t(const t_self &) {throw pfc::exception_not_implemented();} + const t_self & operator=(const t_self & ) {throw pfc::exception_not_implemented();} + + T* m_ptr; + }; + + //avoid "void&" breakage + template + class ptrholder_t { + private: + typedef void T; + typedef ptrholder_t t_self; + public: + inline ptrholder_t(T* p_ptr) : m_ptr(p_ptr) {} + inline ptrholder_t() : m_ptr(NULL) {} + inline ~ptrholder_t() {t_releaser::release(m_ptr);} + inline bool is_valid() const {return m_ptr != NULL;} + inline bool is_empty() const {return m_ptr == NULL;} + inline T* operator->() const {return m_ptr;} + inline T* get_ptr() const {return m_ptr;} + inline void release() {t_releaser::release(replace_null_t(m_ptr));;} + inline void attach(T * p_ptr) {release(); m_ptr = p_ptr;} + inline const t_self & operator=(T * p_ptr) {set(p_ptr);return *this;} + inline T* detach() {return pfc::replace_null_t(m_ptr);} + + inline t_self & operator<<(t_self & p_source) {attach(p_source.detach());return *this;} + inline t_self & operator>>(t_self & p_dest) {p_dest.attach(detach());return *this;} + + //deprecated + inline void set(T * p_ptr) {attach(p_ptr);} + private: + ptrholder_t(const t_self &) {throw pfc::exception_not_implemented();} + const t_self & operator=(const t_self & ) {throw pfc::exception_not_implemented();} + + T* m_ptr; + }; + + PFC_NORETURN void crash(); + void outputDebugLine(const char * msg); + + class debugLog : public string_formatter { + public: + ~debugLog() { outputDebugLine(this->get_ptr()); } + }; +#define PFC_DEBUGLOG ::pfc::debugLog()._formatter() + + + template + class int_container_helper { + public: + int_container_helper() : m_val(p_initval) {} + t_type m_val; + }; + + + + //warning: not multi-thread-safe + template + class instanceTracker : public t_base { + private: + typedef instanceTracker t_self; + public: + TEMPLATE_CONSTRUCTOR_FORWARD_FLOOD_WITH_INITIALIZER(instanceTracker,t_base,{g_list += this;}); + + instanceTracker(const t_self & p_other) : t_base( (const t_base &)p_other) {g_list += this;} + ~instanceTracker() {g_list -= this;} + + typedef pfc::avltree_t t_list; + static const t_list & instanceList() {return g_list;} + template static void forEach(t_callback & p_callback) {instanceList().enumerate(p_callback);} + private: + static t_list g_list; + }; + + template + typename instanceTracker::t_list instanceTracker::g_list; + + + //warning: not multi-thread-safe + template + class instanceTrackerV2 { + private: + typedef instanceTrackerV2 t_self; + public: + instanceTrackerV2(const t_self & p_other) {g_list += static_cast(this);} + instanceTrackerV2() {g_list += static_cast(this);} + ~instanceTrackerV2() {g_list -= static_cast(this);} + + typedef pfc::avltree_t t_instanceList; + static const t_instanceList & instanceList() {return g_list;} + template static void forEach(t_callback & p_callback) {instanceList().enumerate(p_callback);} + private: + static t_instanceList g_list; + }; + + template + typename instanceTrackerV2::t_instanceList instanceTrackerV2::g_list; + + + struct objDestructNotifyData { + bool m_flag; + objDestructNotifyData * m_next; + + }; + class objDestructNotify { + public: + objDestructNotify() : m_data() {} + ~objDestructNotify() { + set(); + } + + void set() { + objDestructNotifyData * w = m_data; + while(w) { + w->m_flag = true; w = w->m_next; + } + } + objDestructNotifyData * m_data; + }; + + class objDestructNotifyScope : private objDestructNotifyData { + public: + objDestructNotifyScope(objDestructNotify &obj) : m_obj(&obj) { + m_next = m_obj->m_data; + m_obj->m_data = this; + } + ~objDestructNotifyScope() { + if (!m_flag) m_obj->m_data = m_next; + } + bool get() const {return m_flag;} + PFC_CLASS_NOT_COPYABLE_EX(objDestructNotifyScope) + private: + objDestructNotify * m_obj; + + }; + + + class bigmem { + public: + enum {slice = 1024*1024}; + bigmem() : m_size() {} + ~bigmem() {clear();} + + void resize(size_t newSize); + size_t size() const {return m_size;} + void clear(); + void read(void * ptrOut, size_t bytes, size_t offset); + void write(const void * ptrIn, size_t bytes, size_t offset); + uint8_t * _slicePtr(size_t which); + size_t _sliceCount(); + size_t _sliceSize(size_t which); + private: + array_t m_data; + size_t m_size; + + PFC_CLASS_NOT_COPYABLE_EX(bigmem) + }; + + + double exp_int( double base, int exp ); +} diff --git a/pfc/pathUtils.cpp b/pfc/pathUtils.cpp new file mode 100644 index 0000000..321820f --- /dev/null +++ b/pfc/pathUtils.cpp @@ -0,0 +1,297 @@ +#include "pfc.h" + +static_assert(L'Ö' == 0xD6, "Compile as Unicode!!!"); + +namespace pfc { namespace io { namespace path { + +#ifdef _WINDOWS +#define KPathSeparators "\\/|" +#else +#define KPathSeparators "/" +#endif + +string getFileName(string path) { + t_size split = path.lastIndexOfAnyChar(KPathSeparators); + if (split == ~0) return path; + else return path.subString(split+1); +} +string getFileNameWithoutExtension(string path) { + string fn = getFileName(path); + t_size split = fn.lastIndexOf('.'); + if (split == ~0) return fn; + else return fn.subString(0,split); +} +string getFileExtension(string path) { + string fn = getFileName(path); + t_size split = fn.lastIndexOf('.'); + if (split == ~0) return ""; + else return fn.subString(split); +} +string getDirectory(string filePath) {return getParent(filePath);} + +string getParent(string filePath) { + t_size split = filePath.lastIndexOfAnyChar(KPathSeparators); + if (split == ~0) return ""; +#ifdef _WINDOWS + if (split > 0 && getIllegalNameChars().contains(filePath[split-1])) { + if (split + 1 < filePath.length()) return filePath.subString(0,split+1); + else return ""; + } +#endif + return filePath.subString(0,split); +} +string combine(string basePath,string fileName) { + if (basePath.length() > 0) { + if (!isSeparator(basePath.lastChar())) { + basePath += getDefaultSeparator(); + } + return basePath + fileName; + } else { + //todo? + return fileName; + } +} + +bool isSeparator(char c) { + return strchr(KPathSeparators, c) != nullptr; +} +string getSeparators() { + return KPathSeparators; +} + +const char * charReplaceDefault(char c) { + switch (c) { + case '*': + return "x"; + case '\"': + return "\'\'"; + case ':': + case '/': + case '\\': + return "-"; + case '?': + return ""; + default: + return "_"; + } +} + +const char * charReplaceModern(char c) { + switch (c) { + case '*': + return u8"∗"; + case '\"': + return u8"''"; + case ':': + return u8"∶"; + case '/': + return u8"⁄"; + case '\\': + return u8"⧵"; + case '?': + return u8"?"; + case '<': + return u8"˂"; + case '>': + return u8"˃"; + case '|': + return u8"∣"; + default: + return "_"; + } +} + +string replaceIllegalPathChars(string fn, charReplace_t replaceIllegalChar) { + string illegal = getIllegalNameChars(); + string separators = getSeparators(); + string_formatter output; + for(t_size walk = 0; walk < fn.length(); ++walk) { + const char c = fn[walk]; + if (separators.contains(c)) { + output.add_byte(getDefaultSeparator()); + } else if (string::isNonTextChar(c) || illegal.contains(c)) { + string replacement = replaceIllegalChar(c); + if (replacement.containsAnyChar(illegal)) /*per-OS weirdness security*/ replacement = "_"; + output << replacement.ptr(); + } else { + output.add_byte(c); + } + } + return output.toString(); +} + +string replaceIllegalNameChars(string fn, bool allowWC, charReplace_t replaceIllegalChar) { + const string illegal = getIllegalNameChars(allowWC); + string_formatter output; + for(t_size walk = 0; walk < fn.length(); ++walk) { + const char c = fn[walk]; + if (string::isNonTextChar(c) || illegal.contains(c)) { + string replacement = replaceIllegalChar(c); + if (replacement.containsAnyChar(illegal)) /*per-OS weirdness security*/ replacement = "_"; + output << replacement.ptr(); + } else { + output.add_byte(c); + } + } + return output.toString(); +} + +bool isInsideDirectory(pfc::string directory, pfc::string inside) { + //not very efficient + string walk = inside; + for(;;) { + walk = getParent(walk); + if (walk == "") return false; + if (equals(directory,walk)) return true; + } +} +bool isDirectoryRoot(string path) { + return getParent(path).isEmpty(); +} +//OS-dependant part starts here + + +char getDefaultSeparator() { +#ifdef _WINDOWS + return '\\'; +#else + return '/'; +#endif +} + +#ifdef _WINDOWS +#define KIllegalNameCharsEx ":<>\"" +#else +// Mac OS allows : in filenames but does funny things presenting them in Finder, so don't use it +#define KIllegalNameCharsEx ":" +#endif + +#define KWildcardChars "*?" + +#define KIllegalNameChars KPathSeparators KIllegalNameCharsEx KWildcardChars +#define KIllegalNameChars_noWC KPathSeparators KIllegalNameCharsEx + +static string g_illegalNameChars ( KIllegalNameChars ); +static string g_illegalNameChars_noWC ( KIllegalNameChars_noWC ); + +string getIllegalNameChars(bool allowWC) { + return allowWC ? g_illegalNameChars_noWC : g_illegalNameChars; +} + +#ifdef _WINDOWS +static const char * const specialIllegalNames[] = { + "con", "aux", "lst", "prn", "nul", "eof", "inp", "out" +}; + +enum { maxPathComponent = 255 }; +static size_t safeTruncat( const char * str, size_t maxLen ) { + size_t i = 0; + size_t ret = 0; + for( ; i < maxLen; ++ i ) { + auto d = pfc::utf8_char_len( str + ret ); + if ( d == 0 ) break; + ret += d; + } + return ret; +} + +static size_t utf8_length( const char * str ) { + size_t ret = 0; + for (; ++ret;) { + size_t d = pfc::utf8_char_len( str ); + if ( d == 0 ) break; + str += d; + } + return ret; +} +static string truncatePathComponent( string name, bool preserveExt ) { + + if (name.length() <= maxPathComponent) return name; + if (preserveExt) { + auto dot = name.lastIndexOf('.'); + if (dot != pfc_infinite) { + const auto ext = name.subString(dot); + const auto extLen = utf8_length( ext.c_str() ); + if (extLen < maxPathComponent) { + auto lim = maxPathComponent - extLen; + lim = safeTruncat( name.c_str(), lim ); + if (lim < dot) { + return name.subString(0, lim) + ext; + } + } + } + } + + size_t truncat = safeTruncat( name.c_str(), maxPathComponent ); + return name.subString(0, truncat); +} +#endif // _WINDOWS + +static string trailingSanity(string name, bool preserveExt, const char * lstIllegal) { + + const auto isIllegalTrailingChar = [lstIllegal](char c) { + return strchr(lstIllegal, c) != nullptr; + }; + + t_size end = name.length(); + if (preserveExt) { + size_t offset = pfc::string_find_last(name.c_str(), '.'); + if (offset < end) end = offset; + } + const size_t endEx = end; + while (end > 0) { + if (!isIllegalTrailingChar(name[end - 1])) break; + --end; + } + t_size begin = 0; + while (begin < end) { + if (!isIllegalTrailingChar(name[begin])) break; + ++begin; + } + if (end < endEx || begin > 0) { + name = name.subString(begin, end - begin) + name.subString(endEx); + } + return name; +} +string validateFileName(string name, bool allowWC, bool preserveExt, charReplace_t replaceIllegalChar) { + if (!allowWC) { // special fix for filenames that consist only of question marks + size_t end = name.length(); + if (preserveExt) { + size_t offset = pfc::string_find_last(name.c_str(), '.'); + if (offset < end) end = offset; + } + bool unnamed = true; + for (size_t walk = 0; walk < end; ++walk) { + if (name[walk] != '?') unnamed = false; + } + if (unnamed) { + name = string("[unnamed]") + name.subString(end); + } + } + + // Trailing sanity AFTER replaceIllegalNameChars + // replaceIllegalNameChars may remove chars exposing illegal prefix/suffix chars + name = replaceIllegalNameChars(name, allowWC, replaceIllegalChar); + if (name.length() > 0 && !allowWC) { + pfc::string8 lstIllegal = " "; + if (!preserveExt) lstIllegal += "."; + + name = trailingSanity(name, preserveExt, lstIllegal); + } + +#ifdef _WINDOWS + name = truncatePathComponent(name, preserveExt); + + for( unsigned w = 0; w < _countof(specialIllegalNames); ++w ) { + if (pfc::stringEqualsI_ascii( name.c_str(), specialIllegalNames[w] ) ) { + name += "-"; + break; + } + } +#endif + + if (name.isEmpty()) name = "_"; + return name; +} + +}}} // namespaces diff --git a/pfc/pathUtils.h b/pfc/pathUtils.h new file mode 100644 index 0000000..e4b95fe --- /dev/null +++ b/pfc/pathUtils.h @@ -0,0 +1,42 @@ +#pragma once + +#include +#include "stringNew.h" + +namespace pfc { + namespace io { + namespace path { +#ifdef _WINDOWS + typedef string::comparatorCaseInsensitive comparator; +#else + typedef string::comparatorCaseSensitive comparator; // wild assumption +#endif + + + typedef std::function charReplace_t; + + const char * charReplaceDefault(char); + const char * charReplaceModern(char); + + string getFileName(string path); + string getFileNameWithoutExtension(string path); + string getFileExtension(string path); + string getParent(string filePath); + string getDirectory(string filePath);//same as getParent() + string combine(string basePath,string fileName); + char getDefaultSeparator(); + string getSeparators(); + bool isSeparator(char c); + string getIllegalNameChars(bool allowWC = false); + string replaceIllegalNameChars(string fn, bool allowWC = false, charReplace_t replace = charReplaceDefault); + string replaceIllegalPathChars(string fn, charReplace_t replace = charReplaceDefault); + bool isInsideDirectory(pfc::string directory, pfc::string inside); + bool isDirectoryRoot(string path); + string validateFileName(string name, bool allowWC = false, bool preserveExt = false, charReplace_t replace = charReplaceDefault);//removes various illegal things from the name, exact effect depends on the OS, includes removal of the invalid characters + + template inline bool equals(const t1 & v1, const t2 & v2) {return comparator::compare(v1,v2) == 0;} + + template inline int compare( t1 const & p1, t2 const & p2 ) {return comparator::compare(p1, p2); } + } + } +} diff --git a/pfc/pfc-fb2k-hooks.cpp b/pfc/pfc-fb2k-hooks.cpp new file mode 100644 index 0000000..7dc8ede --- /dev/null +++ b/pfc/pfc-fb2k-hooks.cpp @@ -0,0 +1,4 @@ +#include "pfc.h" +#include "pfc-fb2k-hooks.h" + +#include "suppress_fb2k_hooks.h" diff --git a/pfc/pfc-fb2k-hooks.h b/pfc/pfc-fb2k-hooks.h new file mode 100644 index 0000000..40a87ee --- /dev/null +++ b/pfc/pfc-fb2k-hooks.h @@ -0,0 +1,9 @@ +#pragma once + +namespace pfc { + void crashImpl(); + BOOL winFormatSystemErrorMessageImpl(pfc::string_base & p_out, DWORD p_code); + + void crashHook(); + BOOL winFormatSystemErrorMessageHook(pfc::string_base & p_out, DWORD p_code); +} diff --git a/pfc/pfc-license.txt b/pfc/pfc-license.txt new file mode 100644 index 0000000..b806fb0 --- /dev/null +++ b/pfc/pfc-license.txt @@ -0,0 +1,17 @@ +Copyright (C) 2002-2021 Peter Pawlowski + +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 remove5d or altered from any source distribution. \ No newline at end of file diff --git a/pfc/pfc-readme.txt b/pfc/pfc-readme.txt new file mode 100644 index 0000000..8589ed0 --- /dev/null +++ b/pfc/pfc-readme.txt @@ -0,0 +1,9 @@ +PFC : Peter's Foundation Classes + +A library of loosely connected classes used by foobar2000 codebase; freely available and reusable for other projects. + +PFC is not state-of-art code. Many parts of it exist only to keep old bits of foobar2000 codebase ( also third party foobar2000 components ) compiling without modification. For an example, certain classes predating 'pfc' namespace use exist outside the namespace. + +Regarding build configurations- +"Release FB2K" and "Debug FB2K" suppress the compilation of pfc-fb2k-hooks.cpp - which allows relevant calls to be redirected to shared.dll +These configurations should be used when compiling fb2k components. Regular configurations should be used for non fb2k apps instead. \ No newline at end of file diff --git a/pfc/pfc.h b/pfc/pfc.h new file mode 100644 index 0000000..49a4e71 --- /dev/null +++ b/pfc/pfc.h @@ -0,0 +1,231 @@ +#ifndef ___PFC_H___ +#define ___PFC_H___ + +// Global flag - whether it's OK to leak static objects as they'll be released anyway by process death +#ifndef PFC_LEAK_STATIC_OBJECTS +#define PFC_LEAK_STATIC_OBJECTS 1 +#endif + + +#ifdef __clang__ +// Suppress a warning for a common practice in pfc/fb2k code +#pragma clang diagnostic ignored "-Wdelete-non-virtual-dtor" +#endif + +#if !defined(_WINDOWS) && (defined(WIN32) || defined(_WIN32) || defined(WIN64) || defined(_WIN64) || defined(_WIN32_WCE)) +#define _WINDOWS +#endif + + +#ifdef _WINDOWS +#include "targetver.h" + +#ifndef STRICT +#define STRICT +#endif + +#ifndef _SYS_GUID_OPERATOR_EQ_ +#define _NO_SYS_GUID_OPERATOR_EQ_ //fix retarded warning with operator== on GUID returning int +#endif + +// WinSock2.h *before* Windows.h or else VS2017 15.3 breaks +#include +#include + +#if !defined(PFC_WINDOWS_STORE_APP) && !defined(PFC_WINDOWS_DESKTOP_APP) + +#ifdef WINAPI_FAMILY_PARTITION +#if ! WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) +#define PFC_WINDOWS_STORE_APP // Windows store or Windows phone app, not a desktop app +#endif // #if ! WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) +#endif // #ifdef WINAPI_FAMILY_PARTITION + +#ifndef PFC_WINDOWS_STORE_APP +#define PFC_WINDOWS_DESKTOP_APP +#endif + +#endif // #if !defined(PFC_WINDOWS_STORE_APP) && !defined(PFC_WINDOWS_DESKTOP_APP) + +#ifndef _SYS_GUID_OPERATOR_EQ_ +__inline bool __InlineIsEqualGUID(REFGUID rguid1, REFGUID rguid2) +{ + return ( + ((unsigned long *) &rguid1)[0] == ((unsigned long *) &rguid2)[0] && + ((unsigned long *) &rguid1)[1] == ((unsigned long *) &rguid2)[1] && + ((unsigned long *) &rguid1)[2] == ((unsigned long *) &rguid2)[2] && + ((unsigned long *) &rguid1)[3] == ((unsigned long *) &rguid2)[3]); +} + +inline bool operator==(REFGUID guidOne, REFGUID guidOther) {return __InlineIsEqualGUID(guidOne,guidOther);} +inline bool operator!=(REFGUID guidOne, REFGUID guidOther) {return !__InlineIsEqualGUID(guidOne,guidOther);} +#endif + +#include + +#else // not Windows + +#include +#include +#include +#include // memcmp + +typedef struct { + uint32_t Data1; + uint16_t Data2; + uint16_t Data3; + uint8_t Data4[ 8 ]; + } GUID; //same as win32 GUID + +inline bool operator==(const GUID & p_item1,const GUID & p_item2) { + return memcmp(&p_item1,&p_item2,sizeof(GUID)) == 0; +} + +inline bool operator!=(const GUID & p_item1,const GUID & p_item2) { + return memcmp(&p_item1,&p_item2,sizeof(GUID)) != 0; +} + +#endif // Windows vs not Windows + + + +#define PFC_MEMORY_SPACE_LIMIT ((t_uint64)1<<(sizeof(void*)*8-1)) + +#define PFC_ALLOCA_LIMIT (4096) + +#define INDEX_INVALID ((unsigned)(-1)) + + +#include +#include +#include + +#define _PFC_WIDESTRING(_String) L ## _String +#define PFC_WIDESTRING(_String) _PFC_WIDESTRING(_String) + +#if defined(_DEBUG) || defined(DEBUG) +#define PFC_DEBUG 1 +#else +#define PFC_DEBUG 0 +#endif + +#if ! PFC_DEBUG + +#ifndef NDEBUG +#pragma message("WARNING: release build without NDEBUG") +#endif + +#define PFC_ASSERT(_Expression) ((void)0) +#define PFC_ASSERT_SUCCESS(_Expression) (void)( (_Expression), 0) +#define PFC_ASSERT_NO_EXCEPTION(_Expression) { _Expression; } +#else + +#ifdef _WIN32 +namespace pfc { void myassert_win32(const wchar_t * _Message, const wchar_t *_File, unsigned _Line); } +#define PFC_ASSERT(_Expression) (void)( (!!(_Expression)) || (pfc::myassert_win32(PFC_WIDESTRING(#_Expression), PFC_WIDESTRING(__FILE__), __LINE__), 0) ) +#define PFC_ASSERT_SUCCESS(_Expression) PFC_ASSERT(_Expression) +#else +namespace pfc { void myassert (const char * _Message, const char *_File, unsigned _Line); } +#define PFC_ASSERT(_Expression) (void)( (!!(_Expression)) || (pfc::myassert(#_Expression, __FILE__, __LINE__), 0) ) +#define PFC_ASSERT_SUCCESS(_Expression) PFC_ASSERT( _Expression ) +#endif + +#define PFC_ASSERT_NO_EXCEPTION(_Expression) { try { _Expression; } catch(...) { PFC_ASSERT(!"Should not get here - unexpected exception"); } } +#endif + +#ifdef _MSC_VER + +#if PFC_DEBUG +#define NOVTABLE +#else +#define NOVTABLE _declspec(novtable) +#endif + +#if PFC_DEBUG +#define ASSUME(X) PFC_ASSERT(X) +#else +#define ASSUME(X) __assume(X) +#endif + +#define PFC_DEPRECATE(X) // __declspec(deprecated(X)) don't do this since VS2015 defaults to erroring these +#define PFC_NORETURN __declspec(noreturn) +#define PFC_NOINLINE __declspec(noinline) +#else + +#define NOVTABLE +#define ASSUME(X) PFC_ASSERT(X) +#define PFC_DEPRECATE(X) +#define PFC_NORETURN __attribute__ ((noreturn)) +#define PFC_NOINLINE + +#endif + +namespace pfc { + void selftest(); +} + +#include "int_types.h" +#include "traits.h" +#include "bit_array.h" +#include "primitives.h" +#include "alloc.h" +#include "array.h" +#include "bit_array_impl.h" +#include "binary_search.h" +#include "bsearch_inline.h" +#include "bsearch.h" +#include "sort.h" +#include "order_helper.h" +#include "list.h" +#include "ptr_list.h" +#include "string_base.h" +#include "string_list.h" +#include "lockless.h" +#include "ref_counter.h" +#include "iterators.h" +#include "avltree.h" +#include "map.h" +#include "bit_array_impl_part2.h" +#include "timers.h" +#include "guid.h" +#include "byte_order.h" +#include "other.h" +#include "chain_list_v2.h" +#include "rcptr.h" +#include "com_ptr_t.h" +#include "string_conv.h" +#include "stringNew.h" +#include "pathUtils.h" +#include "instance_tracker.h" +#include "threads.h" +#include "base64.h" +#include "primitives_part2.h" +#include "cpuid.h" +#include "memalign.h" + +#ifdef _WIN32 +#include "synchro_win.h" +#else +#include "synchro_nix.h" +#endif + +#include "syncd_storage.h" + +#ifdef _WIN32 +#include "win-objects.h" +#else +#include "nix-objects.h" +#endif + +#include "event.h" + +#include "audio_sample.h" +#include "wildcard.h" +#include "filehandle.h" + +#define PFC_INCLUDED 1 + +#ifndef PFC_SET_THREAD_DESCRIPTION +#define PFC_SET_THREAD_DESCRIPTION(X) +#endif + +#endif //___PFC_H___ diff --git a/pfc/pfc.vcxproj b/pfc/pfc.vcxproj new file mode 100644 index 0000000..ff07a40 --- /dev/null +++ b/pfc/pfc.vcxproj @@ -0,0 +1,521 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Debug FB2K + Win32 + + + Debug FB2K + x64 + + + Release FB2K + Win32 + + + Release FB2K + x64 + + + Release + Win32 + + + Release + x64 + + + + {EBFFFB4E-261D-44D3-B89C-957B31A0BF9C} + pfc + 8.1 + + + + StaticLibrary + true + v141 + Unicode + + + StaticLibrary + true + v141 + Unicode + + + StaticLibrary + true + v141 + Unicode + + + StaticLibrary + true + v141 + Unicode + + + StaticLibrary + v141 + Unicode + + + StaticLibrary + v141 + Unicode + + + StaticLibrary + v141 + Unicode + + + StaticLibrary + v141 + Unicode + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <_ProjectFileVersion>10.0.30319.1 + $(SolutionDir)$(Configuration)\ + $(SolutionDir)$(Configuration)\ + $(Configuration)\ + $(Configuration)\ + $(SolutionDir)$(Configuration)\ + $(SolutionDir)$(Configuration)\ + $(Configuration)\ + $(Configuration)\ + + + + Disabled + EnableFastChecks + Use + pfc.h + Level3 + true + ProgramDatabase + MultiThreadedDebugDLL + 4715 + true + false + Fast + + + _DEBUG;%(PreprocessorDefinitions) + 0x0409 + + + true + + + + + Disabled + EnableFastChecks + Use + pfc.h + Level3 + true + ProgramDatabase + MultiThreadedDebugDLL + 4715 + true + false + Fast + + + _DEBUG;%(PreprocessorDefinitions) + 0x0409 + + + true + + + + + Disabled + EnableFastChecks + Use + pfc.h + Level3 + true + ProgramDatabase + MultiThreadedDebugDLL + 4715 + true + false + + + _DEBUG;%(PreprocessorDefinitions) + 0x0409 + + + true + + + + + Disabled + EnableFastChecks + Use + pfc.h + Level3 + true + ProgramDatabase + MultiThreadedDebugDLL + 4715 + true + false + + + _DEBUG;%(PreprocessorDefinitions) + 0x0409 + + + true + + + + + MaxSpeed + true + false + Fast + false + Use + pfc.h + Level3 + true + ProgramDatabase + /d2notypeopt %(AdditionalOptions) + 4715 + true + true + NDEBUG;_UNICODE;UNICODE;%(PreprocessorDefinitions) + + + NDEBUG;%(PreprocessorDefinitions) + 0x0409 + + + true + + + + + MaxSpeed + true + false + Fast + false + Use + pfc.h + Level3 + true + ProgramDatabase + /d2notypeopt %(AdditionalOptions) + 4715 + true + true + NDEBUG;_UNICODE;UNICODE;%(PreprocessorDefinitions) + + + NDEBUG;%(PreprocessorDefinitions) + 0x0409 + + + true + + + + + MaxSpeed + true + false + Fast + false + Use + pfc.h + Level3 + true + ProgramDatabase + MultiThreadedDLL + /d2notypeopt %(AdditionalOptions) + 4715 + true + true + NDEBUG;_UNICODE;UNICODE;%(PreprocessorDefinitions) + + + NDEBUG;%(PreprocessorDefinitions) + 0x0409 + + + true + + + + + MaxSpeed + true + false + Fast + false + Use + pfc.h + Level3 + true + ProgramDatabase + MultiThreadedDLL + /d2notypeopt %(AdditionalOptions) + 4715 + true + true + NDEBUG;_UNICODE;UNICODE;%(PreprocessorDefinitions) + + + NDEBUG;%(PreprocessorDefinitions) + 0x0409 + + + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Disabled + Disabled + %(PreprocessorDefinitions) + %(PreprocessorDefinitions) + EnableFastChecks + EnableFastChecks + EnableFastChecks + EnableFastChecks + MaxSpeed + MaxSpeed + %(PreprocessorDefinitions) + %(PreprocessorDefinitions) + + + + + Disabled + Disabled + %(PreprocessorDefinitions) + %(PreprocessorDefinitions) + EnableFastChecks + EnableFastChecks + EnableFastChecks + EnableFastChecks + MaxSpeed + MaxSpeed + %(PreprocessorDefinitions) + %(PreprocessorDefinitions) + + + true + true + true + true + true + true + true + true + + + Disabled + Disabled + %(PreprocessorDefinitions) + %(PreprocessorDefinitions) + EnableFastChecks + EnableFastChecks + EnableFastChecks + EnableFastChecks + MaxSpeed + MaxSpeed + %(PreprocessorDefinitions) + %(PreprocessorDefinitions) + + + + true + true + true + true + + + + + Disabled + Disabled + %(PreprocessorDefinitions) + %(PreprocessorDefinitions) + EnableFastChecks + EnableFastChecks + EnableFastChecks + EnableFastChecks + MaxSpeed + MaxSpeed + %(PreprocessorDefinitions) + %(PreprocessorDefinitions) + + + Disabled + Disabled + %(PreprocessorDefinitions) + %(PreprocessorDefinitions) + EnableFastChecks + EnableFastChecks + EnableFastChecks + EnableFastChecks + Create + Create + Create + Create + MaxSpeed + MaxSpeed + %(PreprocessorDefinitions) + %(PreprocessorDefinitions) + Create + Create + Create + Create + + + + + + + + Disabled + Disabled + %(PreprocessorDefinitions) + %(PreprocessorDefinitions) + EnableFastChecks + EnableFastChecks + EnableFastChecks + EnableFastChecks + MaxSpeed + MaxSpeed + %(PreprocessorDefinitions) + %(PreprocessorDefinitions) + + + + + + + + + + + + \ No newline at end of file diff --git a/pfc/pfc.vcxproj.filters b/pfc/pfc.vcxproj.filters new file mode 100644 index 0000000..fb812c1 --- /dev/null +++ b/pfc/pfc.vcxproj.filters @@ -0,0 +1,290 @@ + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + + + Doc + + + Doc + + + + + {70b1137d-0c8f-4bb0-8adb-d406ad38bdd0} + + + {225fc8b6-5fca-4e3f-b56e-1ad97e992841} + + + {e1ea3e94-74b7-464e-ab17-69508b52a4e6} + + + \ No newline at end of file diff --git a/pfc/pocket_char_ops.h b/pfc/pocket_char_ops.h new file mode 100644 index 0000000..fe0e7f7 --- /dev/null +++ b/pfc/pocket_char_ops.h @@ -0,0 +1,245 @@ +#pragma once + +// Standalone header (no dependencies) with implementations of PFC UTF-8 & UTF-16 manipulation routines + +static const uint8_t mask_tab[6] = { 0x80,0xE0,0xF0,0xF8,0xFC,0xFE }; + +static const uint8_t val_tab[6] = { 0,0xC0,0xE0,0xF0,0xF8,0xFC }; + +size_t utf8_char_len_from_header(char p_c) throw() +{ + size_t cnt = 0; + for (;;) + { + if ((p_c & mask_tab[cnt]) == val_tab[cnt]) break; + if (++cnt >= 6) return 0; + } + + return cnt + 1; + +} +size_t utf8_decode_char(const char *p_utf8, unsigned & wide) throw() { + const uint8_t * utf8 = (const uint8_t*)p_utf8; + const size_t max = 6; + + if (utf8[0]<0x80) { + wide = utf8[0]; + return utf8[0]>0 ? 1 : 0; + } + wide = 0; + + unsigned res = 0; + unsigned n; + unsigned cnt = 0; + for (;;) + { + if ((*utf8&mask_tab[cnt]) == val_tab[cnt]) break; + if (++cnt >= max) return 0; + } + cnt++; + + if (cnt == 2 && !(*utf8 & 0x1E)) return 0; + + if (cnt == 1) + res = *utf8; + else + res = (0xFF >> (cnt + 1))&*utf8; + + for (n = 1; n> (7 - cnt))) + return 0; + + res = (res << 6) | (utf8[n] & 0x3F); + } + + wide = res; + + return cnt; +} + +size_t utf8_decode_char(const char *p_utf8, unsigned & wide, size_t max) throw() +{ + const uint8_t * utf8 = (const uint8_t*)p_utf8; + + if (max == 0) { + wide = 0; + return 0; + } + + if (utf8[0]<0x80) { + wide = utf8[0]; + return utf8[0]>0 ? 1 : 0; + } + if (max>6) max = 6; + wide = 0; + + unsigned res = 0; + unsigned n; + unsigned cnt = 0; + for (;;) + { + if ((*utf8&mask_tab[cnt]) == val_tab[cnt]) break; + if (++cnt >= max) return 0; + } + cnt++; + + if (cnt == 2 && !(*utf8 & 0x1E)) return 0; + + if (cnt == 1) + res = *utf8; + else + res = (0xFF >> (cnt + 1))&*utf8; + + for (n = 1; n> (7 - cnt))) + return 0; + + res = (res << 6) | (utf8[n] & 0x3F); + } + + wide = res; + + return cnt; +} + + +size_t utf8_encode_char(unsigned wide, char * target) throw() +{ + size_t count; + + if (wide < 0x80) + count = 1; + else if (wide < 0x800) + count = 2; + else if (wide < 0x10000) + count = 3; + else if (wide < 0x200000) + count = 4; + else if (wide < 0x4000000) + count = 5; + else if (wide <= 0x7FFFFFFF) + count = 6; + else + return 0; + //if (count>max) return 0; + + if (target == 0) + return count; + + switch (count) + { + case 6: + target[5] = 0x80 | (wide & 0x3F); + wide = wide >> 6; + wide |= 0x4000000; + case 5: + target[4] = 0x80 | (wide & 0x3F); + wide = wide >> 6; + wide |= 0x200000; + case 4: + target[3] = 0x80 | (wide & 0x3F); + wide = wide >> 6; + wide |= 0x10000; + case 3: + target[2] = 0x80 | (wide & 0x3F); + wide = wide >> 6; + wide |= 0x800; + case 2: + target[1] = 0x80 | (wide & 0x3F); + wide = wide >> 6; + wide |= 0xC0; + case 1: + target[0] = wide & 0xFF; + } + + return count; +} + +size_t utf16_encode_char(unsigned cur_wchar, char16_t * out) throw() +{ + if (cur_wchar < 0x10000) { + *out = (char16_t)cur_wchar; return 1; + } else if (cur_wchar < (1 << 20)) { + unsigned c = cur_wchar - 0x10000; + //MSDN: + //The first (high) surrogate is a 16-bit code value in the range U+D800 to U+DBFF. The second (low) surrogate is a 16-bit code value in the range U+DC00 to U+DFFF. Using surrogates, Unicode can support over one million characters. For more details about surrogates, refer to The Unicode Standard, version 2.0. + out[0] = (char16_t)(0xD800 | (0x3FF & (c >> 10))); + out[1] = (char16_t)(0xDC00 | (0x3FF & c)); + return 2; + } else { + *out = '?'; return 1; + } +} + +size_t utf16_decode_char(const char16_t * p_source, unsigned * p_out, size_t p_source_length) throw() { + if (p_source_length == 0) { *p_out = 0; return 0; } else if (p_source_length == 1) { + *p_out = p_source[0]; + return 1; + } else { + size_t retval = 0; + unsigned decoded = p_source[0]; + if (decoded != 0) + { + retval = 1; + if ((decoded & 0xFC00) == 0xD800) + { + unsigned low = p_source[1]; + if ((low & 0xFC00) == 0xDC00) + { + decoded = 0x10000 + (((decoded & 0x3FF) << 10) | (low & 0x3FF)); + retval = 2; + } + } + } + *p_out = decoded; + return retval; + } +} + +unsigned utf8_get_char(const char * src) +{ + unsigned rv = 0; + utf8_decode_char(src, rv); + return rv; +} + + +size_t utf8_char_len(const char * s, size_t max) throw() +{ + unsigned dummy; + return utf8_decode_char(s, dummy, max); +} + +size_t skip_utf8_chars(const char * ptr, size_t count) throw() +{ + size_t num = 0; + for (; count && ptr[num]; count--) + { + size_t d = utf8_char_len(ptr + num, (size_t)(-1)); + if (d <= 0) break; + num += d; + } + return num; +} + +bool is_valid_utf8(const char * param, size_t max) { + size_t walk = 0; + while (walk < max && param[walk] != 0) { + size_t d; + unsigned dummy; + d = utf8_decode_char(param + walk, dummy, max - walk); + if (d == 0) return false; + walk += d; + if (walk > max) { + // should not get here + return false; + } + } + return true; +} diff --git a/pfc/pool.h b/pfc/pool.h new file mode 100644 index 0000000..d81b665 --- /dev/null +++ b/pfc/pool.h @@ -0,0 +1,42 @@ +#pragma once +#include "synchro.h" +#include +#include + +namespace pfc { + template + class objPool { + public: + objPool() : m_maxCount(pfc_infinite) {} + typedef std::shared_ptr objRef_t; + + objRef_t get() { + insync(m_sync); + auto i = m_pool.begin(); + if ( i == m_pool.end() ) return nullptr; + auto ret = *i; + m_pool.erase(i); + return ret; + } + objRef_t make() { + auto obj = get(); + if ( ! obj ) obj = std::make_shared(); + return obj; + } + void setMaxCount(size_t c) { + insync(m_sync); + m_maxCount = c; + } + void put(objRef_t obj) { + insync(m_sync); + if ( m_pool.size() < m_maxCount ) { + m_pool.push_back(obj); + } + } + private: + size_t m_maxCount; + std::list m_pool; + critical_section m_sync; + }; + +} \ No newline at end of file diff --git a/pfc/pp-gettickcount.h b/pfc/pp-gettickcount.h new file mode 100644 index 0000000..453ba5f --- /dev/null +++ b/pfc/pp-gettickcount.h @@ -0,0 +1,16 @@ +#if !defined(PP_GETTICKCOUNT_H_INCLUDED) && defined(_WIN32) +#define PP_GETTICKCOUNT_H_INCLUDED + +namespace PP { +#if _WIN32_WINNT >= 0x600 + typedef uint64_t tickcount_t; + inline tickcount_t getTickCount() { return ::GetTickCount64(); } +#else +#define PFC_TICKCOUNT_32BIT + typedef uint32_t tickcount_t; + inline tickcount_t getTickCount() { return ::GetTickCount(); } +#endif + +} + +#endif // #if !defined(PP_GETTICKCOUNT_H_INCLUDED) && defined(_WIN32) \ No newline at end of file diff --git a/pfc/pp-winapi.h b/pfc/pp-winapi.h new file mode 100644 index 0000000..38e9e16 --- /dev/null +++ b/pfc/pp-winapi.h @@ -0,0 +1,105 @@ +#if !defined(PP_WINAPI_H_INCLUDED) && defined(_WIN32) +#define PP_WINAPI_H_INCLUDED + +#ifdef WINAPI_FAMILY_PARTITION + +#if ! WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) + +#ifndef CreateEvent // SPECIAL HACK: disable this stuff if somehow these functions are already defined + +inline HANDLE CreateEventW(LPSECURITY_ATTRIBUTES lpEventAttributes, BOOL bManualReset, BOOL bInitialState, LPCTSTR lpName) { + DWORD flags = 0; + if (bManualReset) flags |= CREATE_EVENT_MANUAL_RESET; + if (bInitialState) flags |= CREATE_EVENT_INITIAL_SET; + DWORD rights = SYNCHRONIZE | EVENT_MODIFY_STATE; + return CreateEventEx(lpEventAttributes, lpName, flags, rights); +} + +#define CreateEvent CreateEventW + +inline DWORD WaitForSingleObject(HANDLE hHandle, DWORD dwMilliseconds) { + return WaitForSingleObjectEx(hHandle, dwMilliseconds, FALSE); +} + +inline DWORD WaitForMultipleObjects(DWORD nCount, const HANDLE *lpHandles, BOOL bWaitAll, DWORD dwMilliseconds) { + return WaitForMultipleObjectsEx(nCount, lpHandles, bWaitAll, dwMilliseconds, FALSE); +} + +inline void InitializeCriticalSection(LPCRITICAL_SECTION lpCriticalSection) { + InitializeCriticalSectionEx(lpCriticalSection, 0, 0); +} + +#endif // #ifndef CreateEvent + + +#ifndef CreateMutex + +inline HANDLE CreateMutexW(LPSECURITY_ATTRIBUTES lpMutexAttributes, BOOL bInitialOwner, LPCTSTR lpName) { + DWORD rights = MUTEX_MODIFY_STATE | SYNCHRONIZE; + DWORD flags = 0; + if (bInitialOwner) flags |= CREATE_MUTEX_INITIAL_OWNER; + return CreateMutexExW(lpMutexAttributes, lpName, flags, rights); +} + +#define CreateMutex CreateMutexW + +#endif // CreateMutex + + +#ifndef FindFirstFile + +inline HANDLE FindFirstFileW(LPCTSTR lpFileName, LPWIN32_FIND_DATA lpFindFileData) { + return FindFirstFileEx(lpFileName, FindExInfoStandard, lpFindFileData, FindExSearchNameMatch, NULL, 0); +} + +#define FindFirstFile FindFirstFileW + +#endif // #ifndef FindFirstFile + +// No reliable way to detect if GetFileSizeEx is present?? Give ours another name +inline BOOL GetFileSizeEx_Fallback(HANDLE hFile, PLARGE_INTEGER lpFileSize) { + FILE_STANDARD_INFO info; + if (!GetFileInformationByHandleEx(hFile, FileStandardInfo, &info, sizeof(info))) return FALSE; + *lpFileSize = info.EndOfFile; + return TRUE; +} + +#define PP_GetFileSizeEx_Fallback_Present + +#ifndef CreateFile + +inline HANDLE CreateFileW(LPCTSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode, LPSECURITY_ATTRIBUTES lpSecurityAttributes, DWORD dwCreationDisposition, DWORD dwFlagsAndAttributes, HANDLE hTemplateFile) { + CREATEFILE2_EXTENDED_PARAMETERS arg = {}; + arg.dwSize = sizeof(arg); + arg.hTemplateFile = hTemplateFile; + arg.lpSecurityAttributes = lpSecurityAttributes; + arg.dwFileAttributes = dwFlagsAndAttributes & 0x0000FFFF; + arg.dwFileFlags = dwFlagsAndAttributes & 0xFFFF0000; + return CreateFile2(lpFileName, dwDesiredAccess, dwShareMode, dwCreationDisposition, &arg); +} + +#define CreateFile CreateFileW + +#endif // #ifndef CreateFile + +#ifndef GetFileAttributes + +inline DWORD GetFileAttributesW(const wchar_t * path) { + WIN32_FILE_ATTRIBUTE_DATA data = {}; + if (!GetFileAttributesEx(path, GetFileExInfoStandard, &data)) return 0xFFFFFFFF; + return data.dwFileAttributes; +} + +#define GetFileAttributes GetFileAttributesW + +#endif // #ifndef GetFileAttributes + +#endif // #if ! WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) + +#endif // #ifdef WINAPI_FAMILY_PARTITION + +#ifndef PP_GetFileSizeEx_Fallback_Present +#define GetFileSizeEx_Fallback GetFileSizeEx +#endif + +#endif // !defined(PP_WINAPI_H_INCLUDED) && defined(_WIN32) diff --git a/pfc/primitives.h b/pfc/primitives.h new file mode 100644 index 0000000..15f35cf --- /dev/null +++ b/pfc/primitives.h @@ -0,0 +1,933 @@ +#pragma once + +#include + +#define tabsize(x) ((size_t)(sizeof(x)/sizeof(*x))) +#define PFC_TABSIZE(x) ((size_t)(sizeof(x)/sizeof(*x))) + +#define TEMPLATE_CONSTRUCTOR_FORWARD_FLOOD_WITH_INITIALIZER(THISCLASS,MEMBER,INITIALIZER) \ + THISCLASS() : MEMBER() INITIALIZER \ + template THISCLASS(const t_param1 & p_param1) : MEMBER(p_param1) INITIALIZER \ + template THISCLASS(const t_param1 & p_param1,const t_param2 & p_param2) : MEMBER(p_param1,p_param2) INITIALIZER \ + template THISCLASS(const t_param1 & p_param1,const t_param2 & p_param2,const t_param3 & p_param3) : MEMBER(p_param1,p_param2,p_param3) INITIALIZER \ + template THISCLASS(const t_param1 & p_param1,const t_param2 & p_param2,const t_param3 & p_param3,const t_param4 & p_param4) : MEMBER(p_param1,p_param2,p_param3,p_param4) INITIALIZER \ + template THISCLASS(const t_param1 & p_param1,const t_param2 & p_param2,const t_param3 & p_param3,const t_param4 & p_param4,const t_param5 & p_param5) : MEMBER(p_param1,p_param2,p_param3,p_param4,p_param5) INITIALIZER \ + template THISCLASS(const t_param1 & p_param1,const t_param2 & p_param2,const t_param3 & p_param3,const t_param4 & p_param4,const t_param5 & p_param5,const t_param6 & p_param6) : MEMBER(p_param1,p_param2,p_param3,p_param4,p_param5,p_param6) INITIALIZER \ + template THISCLASS(const t_param1 & p_param1,const t_param2 & p_param2,const t_param3 & p_param3,const t_param4 & p_param4,const t_param5 & p_param5,const t_param6 & p_param6,const t_param7 & p_param7) : MEMBER(p_param1,p_param2,p_param3,p_param4,p_param5,p_param6,p_param7) INITIALIZER \ + template THISCLASS(const t_param1 & p_param1,const t_param2 & p_param2,const t_param3 & p_param3,const t_param4 & p_param4,const t_param5 & p_param5,const t_param6 & p_param6,const t_param7 & p_param7, const t_param8 & p_param8) : MEMBER(p_param1,p_param2,p_param3,p_param4,p_param5,p_param6,p_param7, p_param8) INITIALIZER + +#define TEMPLATE_CONSTRUCTOR_FORWARD_FLOOD(THISCLASS,MEMBER) TEMPLATE_CONSTRUCTOR_FORWARD_FLOOD_WITH_INITIALIZER(THISCLASS,MEMBER,{}) + + +#ifdef _WIN32 + +#ifndef _MSC_VER +#error MSVC expected +#endif + +// MSVC specific - part of fb2k ABI - cannot ever change on MSVC/Windows + +#define PFC_DECLARE_EXCEPTION(NAME,BASECLASS,DEFAULTMSG) \ +class NAME : public BASECLASS { \ +public: \ + static const char * g_what() {return DEFAULTMSG;} \ + NAME() : BASECLASS(DEFAULTMSG,0) {} \ + NAME(const char * p_msg) : BASECLASS(p_msg) {} \ + NAME(const char * p_msg,int) : BASECLASS(p_msg,0) {} \ + NAME(const NAME & p_source) : BASECLASS(p_source) {} \ +}; + +namespace pfc { + template PFC_NORETURN inline void throw_exception_with_message(const char * p_message) { + throw t_exception(p_message); + } +} + +#else + +#define PFC_DECLARE_EXCEPTION(NAME,BASECLASS,DEFAULTMSG) \ +class NAME : public BASECLASS { \ +public: \ + static const char * g_what() {return DEFAULTMSG;} \ + const char* what() const throw() {return DEFAULTMSG;} \ +}; + +namespace pfc { + template class __exception_with_message_t : public t_base { + private: typedef __exception_with_message_t t_self; + public: + __exception_with_message_t(const char * p_message) : m_message(NULL) { + set_message(p_message); + } + __exception_with_message_t() : m_message(NULL) {} + __exception_with_message_t(const t_self & p_source) : m_message(NULL) {set_message(p_source.m_message);} + + const char* what() const throw() {return m_message != NULL ? m_message : "unnamed exception";} + + const t_self & operator=(const t_self & p_source) {set_message(p_source.m_message);} + + ~__exception_with_message_t() throw() {cleanup();} + + private: + void set_message(const char * p_message) throw() { + cleanup(); + if (p_message != NULL) m_message = strdup(p_message); + } + void cleanup() throw() { + if (m_message != NULL) {free(m_message); m_message = NULL;} + } + char * m_message; + }; + template PFC_NORETURN void throw_exception_with_message(const char * p_message) { + throw __exception_with_message_t(p_message); + } +} +#endif + +namespace pfc { + + template class assert_same_type; + template class assert_same_type {}; + + template + class is_same_type { public: enum {value = false}; }; + template + class is_same_type { public: enum {value = true}; }; + + template class static_assert_t; + template<> class static_assert_t {}; + +#define PFC_STATIC_ASSERT(X) { ::pfc::static_assert_t<(X)>(); } + + template + void assert_raw_type() {static_assert_t< !traits_t::needs_constructor && !traits_t::needs_destructor >();} + + template class assert_byte_type; + template<> class assert_byte_type {}; + template<> class assert_byte_type {}; + template<> class assert_byte_type {}; + + + template void __unsafe__memcpy_t(t_type * p_dst,const t_type * p_src,t_size p_count) { + ::memcpy(reinterpret_cast(p_dst), reinterpret_cast(p_src), p_count * sizeof(t_type)); + } + + template void __unsafe__in_place_destructor_t(t_type & p_item) throw() { + if (traits_t::needs_destructor) try{ p_item.~t_type(); } catch(...) {} + } + + template void __unsafe__in_place_constructor_t(t_type & p_item) { + if (traits_t::needs_constructor) { + t_type * ret = new(&p_item) t_type; + PFC_ASSERT(ret == &p_item); + (void) ret; // suppress warning + } + } + + template void __unsafe__in_place_destructor_array_t(t_type * p_items, t_size p_count) throw() { + if (traits_t::needs_destructor) { + t_type * walk = p_items; + for(t_size n=p_count;n;--n) __unsafe__in_place_destructor_t(*(walk++)); + } + } + + template t_type * __unsafe__in_place_constructor_array_t(t_type * p_items,t_size p_count) { + if (traits_t::needs_constructor) { + t_size walkptr = 0; + try { + for(walkptr=0;walkptr t_type * __unsafe__in_place_resize_array_t(t_type * p_items,t_size p_from,t_size p_to) { + if (p_from < p_to) __unsafe__in_place_constructor_array_t(p_items + p_from, p_to - p_from); + else if (p_from > p_to) __unsafe__in_place_destructor_array_t(p_items + p_to, p_from - p_to); + return p_items; + } + + template void __unsafe__in_place_constructor_copy_t(t_type & p_item,const t_copy & p_copyfrom) { + if (traits_t::needs_constructor) { + t_type * ret = new(&p_item) t_type(p_copyfrom); + PFC_ASSERT(ret == &p_item); + (void) ret; // suppress warning + } else { + p_item = p_copyfrom; + } + } + + template t_type * __unsafe__in_place_constructor_array_copy_t(t_type * p_items,t_size p_count, const t_copy * p_copyfrom) { + t_size walkptr = 0; + try { + for(walkptr=0;walkptr t_type * __unsafe__in_place_constructor_array_copy_partial_t(t_type * p_items,t_size p_count, const t_copy * p_copyfrom,t_size p_copyfrom_count) { + if (p_copyfrom_count > p_count) p_copyfrom_count = p_count; + __unsafe__in_place_constructor_array_copy_t(p_items,p_copyfrom_count,p_copyfrom); + try { + __unsafe__in_place_constructor_array_t(p_items + p_copyfrom_count,p_count - p_copyfrom_count); + } catch(...) { + __unsafe__in_place_destructor_array_t(p_items,p_copyfrom_count); + throw; + } + return p_items; + } + + template t_ret implicit_cast(t_ret val) {return val;} + + template + t_ret * safe_ptr_cast(t_param * p_param) { + if (pfc::is_same_type::value) return p_param; + else { + if (p_param == NULL) return NULL; + else return p_param; + } + } + + typedef std::exception exception; + + PFC_DECLARE_EXCEPTION(exception_overflow,exception,"Overflow"); + PFC_DECLARE_EXCEPTION(exception_bug_check,exception,"Bug check"); + PFC_DECLARE_EXCEPTION(exception_invalid_params,exception_bug_check,"Invalid parameters"); + PFC_DECLARE_EXCEPTION(exception_unexpected_recursion,exception_bug_check,"Unexpected recursion"); + PFC_DECLARE_EXCEPTION(exception_not_implemented,exception_bug_check,"Feature not implemented"); + PFC_DECLARE_EXCEPTION(exception_dynamic_assert,exception_bug_check,"dynamic_assert failure"); + + template + t_ret downcast_guarded(const t_param & p_param) { + t_ret temp = (t_ret) p_param; + if ((t_param) temp != p_param) throw exception_overflow(); + return temp; + } + + template + t_ret downcast_guarded_ex(const t_param & p_param) { + t_ret temp = (t_ret) p_param; + if ((t_param) temp != p_param) throw t_exception(); + return temp; + } + + template + void accumulate_guarded(t_acc & p_acc, const t_add & p_add) { + t_acc delta = downcast_guarded(p_add); + delta += p_acc; + if (delta < p_acc) throw exception_overflow(); + p_acc = delta; + } + + //deprecated + inline void bug_check_assert(bool p_condition, const char * p_msg) { + if (!p_condition) { + PFC_ASSERT(0); + throw_exception_with_message(p_msg); + } + } + //deprecated + inline void bug_check_assert(bool p_condition) { + if (!p_condition) { + PFC_ASSERT(0); + throw exception_bug_check(); + } + } + + inline void dynamic_assert(bool p_condition, const char * p_msg) { + if (!p_condition) { + PFC_ASSERT(0); + throw_exception_with_message(p_msg); + } + } + inline void dynamic_assert(bool p_condition) { + if (!p_condition) { + PFC_ASSERT(0); + throw exception_dynamic_assert(); + } + } + + template + inline void swap_multi_t(T * p_buffer1,T * p_buffer2,t_size p_size) { + T * walk1 = p_buffer1, * walk2 = p_buffer2; + for(t_size n=p_size;n;--n) { + T temp (* walk1); + *walk1 = *walk2; + *walk2 = temp; + walk1++; walk2++; + } + } + + template + inline void swap_multi_t(T * p_buffer1,T * p_buffer2) { + T * walk1 = p_buffer1, * walk2 = p_buffer2; + for(t_size n=p_size;n;--n) { + T temp (* walk1); + *walk1 = *walk2; + *walk2 = temp; + walk1++; walk2++; + } + } + + + template + inline void __unsafe__swap_raw_t(void * p_object1, void * p_object2) { + if (p_size % sizeof(t_size) == 0) { + swap_multi_t(reinterpret_cast(p_object1),reinterpret_cast(p_object2)); + } else { + swap_multi_t(reinterpret_cast(p_object1),reinterpret_cast(p_object2)); + } + } + + template + inline void swap_t(T & p_item1, T & p_item2) { + if (traits_t::realloc_safe) { + __unsafe__swap_raw_t( reinterpret_cast( &p_item1 ), reinterpret_cast( &p_item2 ) ); + } else { + T temp( std::move(p_item2) ); + p_item2 = std::move(p_item1); + p_item1 = std::move(temp); + } + } + + //! This is similar to plain p_item1 = p_item2; assignment, but optimized for the case where p_item2 content is no longer needed later on. This can be overridden for specific classes for optimal performance. \n + //! p_item2 value is undefined after performing a move_t. For an example, in certain cases move_t will fall back to swap_t. + template + inline void move_t(T & p_item1, T & p_item2) { + typedef traits_t t; + if (t::needs_constructor || t::needs_destructor) { + if (t::realloc_safe) swap_t(p_item1, p_item2); + else p_item1 = std::move( p_item2 ); + } else { + p_item1 = std::move( p_item2 ); + } + } + + template + t_size array_size_t(const t_array & p_array) {return p_array.get_size();} + + template + t_size array_size_t(const t_item (&p_array)[p_width]) {return p_width;} + + template static bool array_isLast(const t_array & arr, const t_item & item) { + const t_size size = pfc::array_size_t(arr); + return size > 0 && arr[size-1] == item; + } + template static bool array_isFirst(const t_array & arr, const t_item & item) { + const t_size size = pfc::array_size_t(arr); + return size > 0 && arr[0] == item; + } + + template + inline void fill_t(t_array & p_buffer,const t_size p_count, const t_filler & p_filler) { + for(t_size n=0;n + inline void fill_ptr_t(t_array * p_buffer,const t_size p_count, const t_filler & p_filler) { + for(t_size n=0;n + inline int compare_t(const t_item1 & p_item1, const t_item2 & p_item2) { + if (p_item1 < p_item2) return -1; + else if (p_item1 > p_item2) return 1; + else return 0; + } + + //! For use with avltree/map etc. + class comparator_default { + public: + template + inline static int compare(const t_item1 & p_item1,const t_item2 & p_item2) {return pfc::compare_t(p_item1,p_item2);} + }; + + template class comparator_pointer { public: + template static int compare(const t_item1 & p_item1,const t_item2 & p_item2) {return t_comparator::compare(*p_item1,*p_item2);} + }; + + template class comparator_dual { public: + template static int compare(const t_item1 & p_item1,const t_item2 & p_item2) { + int state = t_primary::compare(p_item1,p_item2); + if (state != 0) return state; + return t_secondary::compare(p_item1,p_item2); + } + }; + + class comparator_memcmp { + public: + template + inline static int compare(const t_item1 & p_item1,const t_item2 & p_item2) { + static_assert_t(); + return memcmp(&p_item1,&p_item2,sizeof(t_item1)); + } + }; + + template + t_size subtract_sorted_lists_calculate_count(const t_source1 & p_source1, const t_source2 & p_source2) { + t_size walk1 = 0, walk2 = 0, walk_out = 0; + const t_size max1 = p_source1.get_size(), max2 = p_source2.get_size(); + for(;;) { + int state; + if (walk1 < max1 && walk2 < max2) { + state = pfc::compare_t(p_source1[walk1],p_source2[walk2]); + } else if (walk1 < max1) { + state = -1; + } else if (walk2 < max2) { + state = 1; + } else { + break; + } + if (state < 0) walk_out++; + if (state <= 0) walk1++; + if (state >= 0) walk2++; + } + return walk_out; + } + + //! Subtracts p_source2 contents from p_source1 and stores result in p_destination. Both source lists must be sorted. + //! Note: duplicates will be carried over (and ignored for p_source2). + template + void subtract_sorted_lists(t_destination & p_destination,const t_source1 & p_source1, const t_source2 & p_source2) { + p_destination.set_size(subtract_sorted_lists_calculate_count(p_source1,p_source2)); + t_size walk1 = 0, walk2 = 0, walk_out = 0; + const t_size max1 = p_source1.get_size(), max2 = p_source2.get_size(); + for(;;) { + int state; + if (walk1 < max1 && walk2 < max2) { + state = pfc::compare_t(p_source1[walk1],p_source2[walk2]); + } else if (walk1 < max1) { + state = -1; + } else if (walk2 < max2) { + state = 1; + } else { + break; + } + + + if (state < 0) p_destination[walk_out++] = p_source1[walk1]; + if (state <= 0) walk1++; + if (state >= 0) walk2++; + } + } + + template + t_size merge_sorted_lists_calculate_count(const t_source1 & p_source1, const t_source2 & p_source2) { + t_size walk1 = 0, walk2 = 0, walk_out = 0; + const t_size max1 = p_source1.get_size(), max2 = p_source2.get_size(); + for(;;) { + int state; + if (walk1 < max1 && walk2 < max2) { + state = pfc::compare_t(p_source1[walk1],p_source2[walk2]); + } else if (walk1 < max1) { + state = -1; + } else if (walk2 < max2) { + state = 1; + } else { + break; + } + if (state <= 0) walk1++; + if (state >= 0) walk2++; + walk_out++; + } + return walk_out; + } + + //! Merges p_source1 and p_source2, storing content in p_destination. Both source lists must be sorted. + //! Note: duplicates will be carried over. + template + void merge_sorted_lists(t_destination & p_destination,const t_source1 & p_source1, const t_source2 & p_source2) { + p_destination.set_size(merge_sorted_lists_calculate_count(p_source1,p_source2)); + t_size walk1 = 0, walk2 = 0, walk_out = 0; + const t_size max1 = p_source1.get_size(), max2 = p_source2.get_size(); + for(;;) { + int state; + if (walk1 < max1 && walk2 < max2) { + state = pfc::compare_t(p_source1[walk1],p_source2[walk2]); + } else if (walk1 < max1) { + state = -1; + } else if (walk2 < max2) { + state = 1; + } else { + break; + } + if (state < 0) { + p_destination[walk_out] = p_source1[walk1++]; + } else if (state > 0) { + p_destination[walk_out] = p_source2[walk2++]; + } else { + p_destination[walk_out] = p_source1[walk1]; + walk1++; walk2++; + } + walk_out++; + } + } + + + + template + inline t_size append_t(t_array & p_array,const T & p_item) + { + t_size old_count = p_array.get_size(); + p_array.set_size(old_count + 1); + p_array[old_count] = p_item; + return old_count; + } + + template + inline t_size append_swap_t(t_array & p_array,T & p_item) + { + t_size old_count = p_array.get_size(); + p_array.set_size(old_count + 1); + swap_t(p_array[old_count],p_item); + return old_count; + } + + template + inline t_size insert_uninitialized_t(t_array & p_array,t_size p_index) { + t_size old_count = p_array.get_size(); + if (p_index > old_count) p_index = old_count; + p_array.set_size(old_count + 1); + for(t_size n=old_count;n>p_index;n--) move_t(p_array[n], p_array[n-1]); + return p_index; + } + + template + inline t_size insert_t(t_array & p_array,const T & p_item,t_size p_index) { + t_size old_count = p_array.get_size(); + if (p_index > old_count) p_index = old_count; + p_array.set_size(old_count + 1); + for(t_size n=old_count;n>p_index;n--) + move_t(p_array[n], p_array[n-1]); + p_array[p_index] = p_item; + return p_index; + } + template + void insert_array_t( array1_t & outArray, size_t insertAt, array2_t const & inArray, size_t inArraySize) { + const size_t oldSize = outArray.get_size(); + if (insertAt > oldSize) insertAt = oldSize; + const size_t newSize = oldSize + inArraySize; + outArray.set_size( newSize ); + for(size_t m = oldSize; m != insertAt; --m) { + move_t( outArray[ m - 1 + inArraySize], outArray[m - 1] ); + } + for(size_t w = 0; w < inArraySize; ++w) { + outArray[ insertAt + w ] = inArray[ w ]; + } + } + + template + inline t_size insert_multi_t(t_array & p_array,const in_array_t & p_items, size_t p_itemCount, t_size p_index) { + const t_size old_count = p_array.get_size(); + const size_t new_count = old_count + p_itemCount; + if (p_index > old_count) p_index = old_count; + p_array.set_size(new_count); + size_t toMove = old_count - p_index; + for(size_t w = 0; w < toMove; ++w) { + move_t( p_array[new_count - 1 - w], p_array[old_count - 1 - w] ); + } + + for(size_t w = 0; w < p_itemCount; ++w) { + p_array[p_index+w] = p_items[w]; + } + + return p_index; + } + template + inline t_size insert_swap_t(t_array & p_array,T & p_item,t_size p_index) { + t_size old_count = p_array.get_size(); + if (p_index > old_count) p_index = old_count; + p_array.set_size(old_count + 1); + for(t_size n=old_count;n>p_index;n--) + swap_t(p_array[n],p_array[n-1]); + swap_t(p_array[p_index],p_item); + return p_index; + } + + + template + inline T max_t(const T & item1, const T & item2) {return item1 > item2 ? item1 : item2;}; + + template + inline T min_t(const T & item1, const T & item2) {return item1 < item2 ? item1 : item2;}; + + template + inline T abs_t(T item) {return item<0 ? -item : item;} + + template + inline T sqr_t(T item) {return item * item;} + + template + inline T clip_t(const T & p_item, const T & p_min, const T & p_max) { + if (p_item < p_min) return p_min; + else if (p_item <= p_max) return p_item; + else return p_max; + } + + + + + + template + inline void delete_t(T* ptr) {delete ptr;} + + template + inline void delete_array_t(T* ptr) {delete[] ptr;} + + template + inline T* clone_t(T* ptr) {return new T(*ptr);} + + + template + inline t_int mul_safe_t(t_int p_val1,t_int p_val2) { + if (p_val1 == 0 || p_val2 == 0) return 0; + t_int temp = (t_int) (p_val1 * p_val2); + if (temp / p_val1 != p_val2) throw t_exception(); + return temp; + } + template + t_int multiply_guarded(t_int v1, t_int v2) { + return mul_safe_t(v1, v2); + } + template t_int add_unsigned_clipped(t_int v1, t_int v2) { + t_int v = v1 + v2; + if (v < v1) return ~0; + return v; + } + template t_int sub_unsigned_clipped(t_int v1, t_int v2) { + t_int v = v1 - v2; + if (v > v1) return 0; + return v; + } + template void acc_unsigned_clipped(t_int & v1, t_int v2) { + v1 = add_unsigned_clipped(v1, v2); + } + + template + void memcpy_t(t_dst* p_dst,const t_src* p_src,t_size p_count) { + for(t_size n=0;n + void copy_array_loop_t(t_dst & p_dst,const t_src & p_src,t_size p_count) { + for(t_size n=0;n + void memcpy_backwards_t(t_dst * p_dst,const t_src * p_src,t_size p_count) { + p_dst += p_count; p_src += p_count; + for(t_size n=0;n + void memset_t(T * p_buffer,const t_val & p_val,t_size p_count) { + for(t_size n=0;n + void memset_t(T &p_buffer,const t_val & p_val) { + const t_size width = pfc::array_size_t(p_buffer); + for(t_size n=0;n + void memset_null_t(T * p_buffer,t_size p_count) { + for(t_size n=0;n + void memset_null_t(T &p_buffer) { + const t_size width = pfc::array_size_t(p_buffer); + for(t_size n=0;n + void memmove_t(T* p_dst,const T* p_src,t_size p_count) { + if (p_dst == p_src) {/*do nothing*/} + else if (p_dst > p_src && p_dst < p_src + p_count) memcpy_backwards_t(p_dst,p_src,p_count); + else memcpy_t(p_dst,p_src,p_count); + } + + template void memxor_t(TVal * out, const TVal * s1, const TVal * s2, t_size count) { + for(t_size walk = 0; walk < count; ++walk) out[walk] = s1[walk] ^ s2[walk]; + } + static void memxor(void * target, const void * source1, const void * source2, t_size size) { + memxor_t( reinterpret_cast(target), reinterpret_cast(source1), reinterpret_cast(source2), size); + } + + template + T* new_ptr_check_t(T* p_ptr) { + if (p_ptr == NULL) throw std::bad_alloc(); + return p_ptr; + } + + template + int sgn_t(const T & p_val) { + if (p_val < 0) return -1; + else if (p_val > 0) return 1; + else return 0; + } + + template const T* empty_string_t(); + + template<> inline const char * empty_string_t() {return "";} + template<> inline const wchar_t * empty_string_t() {return L"";} + + + template + t_type replace_t(t_type & p_var,const t_newval & p_newval) { + t_type oldval = p_var; + p_var = p_newval; + return oldval; + } + template + t_type replace_null_t(t_type & p_var) { + t_type ret = p_var; + p_var = 0; + return ret; + } + + template + inline bool is_ptr_aligned_t(const void * p_ptr) { + static_assert_t< (p_size_pow2 & (p_size_pow2 - 1)) == 0 >(); + return ( ((t_size)p_ptr) & (p_size_pow2-1) ) == 0; + } + + + template + void array_rangecheck_t(const t_array & p_array,t_size p_index) { + if (p_index >= pfc::array_size_t(p_array)) throw pfc::exception_overflow(); + } + + template + void array_rangecheck_t(const t_array & p_array,t_size p_from,t_size p_to) { + if (p_from > p_to) throw pfc::exception_overflow(); + array_rangecheck_t(p_array,p_from); array_rangecheck_t(p_array,p_to); + } + + t_int32 rint32(double p_val); + t_int64 rint64(double p_val); + + + + template + inline size_t remove_if_t( array_t & arr, pred_t pred ) { + const size_t inCount = arr.size(); + size_t walk = 0; + + + for( walk = 0; walk < inCount; ++ walk ) { + if ( pred(arr[walk]) ) break; + } + + size_t total = walk; + + for( ; walk < inCount; ++ walk ) { + if ( !pred(arr[walk] ) ) { + move_t(arr[total++], arr[walk]); + } + } + arr.resize(total); + + return total; + } + + template + inline t_size remove_mask_t(t_array & p_array,const bit_array & p_mask)//returns amount of items left + { + t_size n,count = p_array.size(), total = 0; + + n = total = p_mask.find(true,0,count); + + if (n + t_size find_duplicates_sorted_t(t_array p_array,t_size p_count,t_compare p_compare,bit_array_var & p_out) { + t_size ret = 0; + t_size n; + if (p_count > 0) + { + p_out.set(0,false); + for(n=1;n + t_size find_duplicates_sorted_permutation_t(t_array p_array,t_size p_count,t_compare p_compare,t_permutation const & p_permutation,bit_array_var & p_out) { + t_size ret = 0; + t_size n; + if (p_count > 0) { + p_out.set(p_permutation[0],false); + for(n=1;n + t_size strlen_t(const t_char * p_string,t_size p_length = ~0) { + for(t_size walk = 0;;walk++) { + if (walk >= p_length || p_string[walk] == 0) return walk; + } + } + + + template + class __list_to_array_enumerator { + public: + __list_to_array_enumerator(t_array & p_array) : m_walk(), m_array(p_array) {} + template + void operator() (const t_item & p_item) { + PFC_ASSERT(m_walk < m_array.get_size()); + m_array[m_walk++] = p_item; + } + void finalize() { + PFC_ASSERT(m_walk == m_array.get_size()); + } + private: + t_size m_walk; + t_array & m_array; + }; + + template + void list_to_array(t_array & p_array,const t_list & p_list) { + p_array.set_size(p_list.get_count()); + __list_to_array_enumerator enumerator(p_array); + p_list.enumerate(enumerator); + enumerator.finalize(); + } + + template + class enumerator_add_item { + public: + enumerator_add_item(t_receiver & p_receiver) : m_receiver(p_receiver) {} + template void operator() (const t_item & p_item) {m_receiver.add_item(p_item);} + private: + t_receiver & m_receiver; + }; + + template + void overwrite_list_enumerated(t_receiver & p_receiver,const t_giver & p_giver) { + enumerator_add_item wrapper(p_receiver); + p_giver.enumerate(wrapper); + } + + template + void copy_list_enumerated(t_receiver & p_receiver,const t_giver & p_giver) { + p_receiver.remove_all(); + overwrite_list_enumerated(p_receiver,p_giver); + } + + inline bool lxor(bool p_val1,bool p_val2) { + return p_val1 == !p_val2; + } + + template + inline void min_acc(t_val & p_acc,const t_val & p_val) { + if (p_val < p_acc) p_acc = p_val; + } + + template + inline void max_acc(t_val & p_acc,const t_val & p_val) { + if (p_val > p_acc) p_acc = p_val; + } + + t_uint64 pow_int(t_uint64 base, t_uint64 exp); + + + template + class incrementScope { + public: + incrementScope(t_val & i) : v(i) {++v;} + ~incrementScope() {--v;} + private: + t_val & v; + }; + + inline unsigned countBits32(uint32_t i) { + const uint32_t mask = 0x11111111; + uint32_t acc = i & mask; + acc += (i >> 1) & mask; + acc += (i >> 2) & mask; + acc += (i >> 3) & mask; + + const uint32_t mask2 = 0x0F0F0F0F; + uint32_t acc2 = acc & mask2; + acc2 += (acc >> 4) & mask2; + + const uint32_t mask3 = 0x00FF00FF; + uint32_t acc3 = acc2 & mask3; + acc3 += (acc2 >> 8) & mask3; + + return (acc3 & 0xFFFF) + ((acc3 >> 16) & 0xFFFF); + } + + // Forward declarations + template + void copy_array_t(t_to & p_to,const t_from & p_from); + + template + void fill_array_t(t_array & p_array,const t_value & p_value); + + // Generic no-op for breakpointing stuff + inline void nop() {} + + class onLeaving { + public: + onLeaving() {} + onLeaving( std::function f_ ) : f(f_) {} + ~onLeaving() { + if (f) f(); + } + std::function f; + private: + void operator=( onLeaving const & ) = delete; + onLeaving( const onLeaving & ) = delete; + }; + + template + class singleton { + public: + static obj_t instance; + }; + template + obj_t singleton::instance; + +}; +#define PFC_SINGLETON(X) ::pfc::singleton::instance + + +#define PFC_CLASS_NOT_COPYABLE(THISCLASSNAME,THISTYPE) \ + private: \ + THISCLASSNAME(const THISTYPE&) = delete; \ + const THISTYPE & operator=(const THISTYPE &) = delete; + +#define PFC_CLASS_NOT_COPYABLE_EX(THISTYPE) PFC_CLASS_NOT_COPYABLE(THISTYPE,THISTYPE) \ No newline at end of file diff --git a/pfc/primitives_part2.h b/pfc/primitives_part2.h new file mode 100644 index 0000000..1aaa73d --- /dev/null +++ b/pfc/primitives_part2.h @@ -0,0 +1,26 @@ +#pragma once + +namespace pfc { + template + static bool guess_reorder_pattern(pfc::array_t & out, const t_list1 & from, const t_list2 & to) { + typedef typename t_list1::t_item t_item; + const t_size count = from.get_size(); + if (count != to.get_size()) return false; + out.set_size(count); + for(t_size walk = 0; walk < count; ++walk) out[walk] = walk; + //required output: to[n] = from[out[n]]; + typedef pfc::chain_list_v2_t t_queue; + pfc::map_t content; + for(t_size walk = 0; walk < count; ++walk) { + content.find_or_add(from[walk]).add_item(walk); + } + for(t_size walk = 0; walk < count; ++walk) { + t_queue * q = content.query_ptr(to[walk]); + if (q == NULL) return false; + if (q->get_count() == 0) return false; + out[walk] = *q->first(); + q->remove(q->first()); + } + return true; + } +} diff --git a/pfc/printf.cpp b/pfc/printf.cpp new file mode 100644 index 0000000..3e65014 --- /dev/null +++ b/pfc/printf.cpp @@ -0,0 +1,121 @@ +#include "pfc.h" + +//implementations of deprecated string_printf methods, with a pragma to disable warnings when they reference other deprecated methods. + +#ifndef _MSC_VER +#define _itoa_s itoa +#define _ultoa_s ultoa +#endif + +#ifdef _MSC_VER +#pragma warning(disable:4996) +#endif + +namespace pfc { + +void string_printf::run(const char * fmt,va_list list) {g_run(*this,fmt,list);} + +string_printf_va::string_printf_va(const char * fmt,va_list list) {string_printf::g_run(*this,fmt,list);} + +void string_printf::g_run(string_base & out,const char * fmt,va_list list) +{ + out.reset(); + while(*fmt) + { + if (*fmt=='%') + { + fmt++; + if (*fmt=='%') + { + out.add_char('%'); + fmt++; + } + else + { + bool force_sign = false; + if (*fmt=='+') + { + force_sign = true; + fmt++; + } + char padchar = (*fmt == '0') ? '0' : ' '; + t_size pad = 0; + while(*fmt>='0' && *fmt<='9') + { + pad = pad * 10 + (*fmt - '0'); + fmt++; + } + + if (*fmt=='s' || *fmt=='S') + { + const char * ptr = va_arg(list,const char*); + t_size len = strlen(ptr); + if (pad>len) out.add_chars(padchar,pad-len); + out.add_string(ptr); + fmt++; + + } + else if (*fmt=='i' || *fmt=='I' || *fmt=='d' || *fmt=='D') + { + int val = va_arg(list,int); + if (force_sign && val>0) out.add_char('+'); + pfc::format_int temp( val ); + t_size len = strlen(temp); + if (pad>len) out.add_chars(padchar,pad-len); + out.add_string(temp); + fmt++; + } + else if (*fmt=='u' || *fmt=='U') + { + int val = va_arg(list,int); + if (force_sign && val>0) out.add_char('+'); + pfc::format_uint temp(val); + t_size len = strlen(temp); + if (pad>len) out.add_chars(padchar,pad-len); + out.add_string(temp); + fmt++; + } + else if (*fmt=='x' || *fmt=='X') + { + auto val = va_arg(list,unsigned); + if (force_sign && val>0) out.add_char('+'); + pfc::format_uint temp(val, 0, 16); + if (*fmt=='X') + { + char * t = const_cast< char* > ( temp.get_ptr() ); + while(*t) + { + if (*t>='a' && *t<='z') + *t += 'A' - 'a'; + t++; + } + } + t_size len = strlen(temp); + if (pad>len) out.add_chars(padchar,pad-len); + out.add_string(temp); + fmt++; + } + else if (*fmt=='c' || *fmt=='C') + { + out.add_char(va_arg(list,int)); + fmt++; + } + } + } + else + { + out.add_char(*(fmt++)); + } + } +} + +string_printf::string_printf(const char * fmt,...) +{ + va_list list; + va_start(list,fmt); + run(fmt,list); + va_end(list); +} + + +} diff --git a/pfc/ptr_list.h b/pfc/ptr_list.h new file mode 100644 index 0000000..dab0c2c --- /dev/null +++ b/pfc/ptr_list.h @@ -0,0 +1,43 @@ +#pragma once + +namespace pfc { + + template > + class ptr_list_t : public B + { + public: + ptr_list_t() {} + ptr_list_t(const ptr_list_t & p_source) {*this = p_source;} + + void free_by_idx(t_size n) {free_mask(bit_array_one(n));} + void free_all() {this->remove_all_ex(free);} + void free_mask(const bit_array & p_mask) {this->remove_mask_ex(p_mask,free);} + + void delete_item(T* ptr) {delete_by_idx(find_item(ptr));} + + void delete_by_idx(t_size p_index) { + delete_mask(bit_array_one(p_index)); + } + + void delete_all() { + this->remove_all_ex(pfc::delete_t); + } + + void delete_mask(const bit_array & p_mask) { + this->remove_mask_ex(p_mask,pfc::delete_t); + } + + T * operator[](t_size n) const {return this->get_item(n);} + }; + + template + class ptr_list_hybrid_t : public ptr_list_t > { + public: + ptr_list_hybrid_t() {} + ptr_list_hybrid_t(const ptr_list_hybrid_t & p_source) {*this = p_source;} + }; + + typedef ptr_list_t ptr_list; + + template class traits_t > : public traits_t {}; +} diff --git a/pfc/ptrholder.h b/pfc/ptrholder.h new file mode 100644 index 0000000..4b6860d --- /dev/null +++ b/pfc/ptrholder.h @@ -0,0 +1,3 @@ +#pragma once + +// added for compatibility with fb2k mobile \ No newline at end of file diff --git a/pfc/rcptr.h b/pfc/rcptr.h new file mode 100644 index 0000000..c69daa2 --- /dev/null +++ b/pfc/rcptr.h @@ -0,0 +1,177 @@ +#pragma once + +namespace pfc { + + struct _rcptr_null; + typedef _rcptr_null* t_rcptr_null; + + static const t_rcptr_null rcptr_null = NULL; + + class rc_container_base { + public: + long add_ref() throw() { + return ++m_counter; + } + long release() throw() { + long ret = --m_counter; + if (ret == 0) PFC_ASSERT_NO_EXCEPTION( delete this ); + return ret; + } + protected: + virtual ~rc_container_base() {} + private: + refcounter m_counter; + }; + + template + class rc_container_t : public rc_container_base { + public: + template + rc_container_t(arg_t && ... arg) : m_object(std::forward(arg) ...) {} + + t_object m_object; + }; + + template + class rcptr_t { + private: + typedef rcptr_t t_self; + typedef rc_container_base t_container; + typedef rc_container_t t_container_impl; + public: + rcptr_t(t_rcptr_null) throw() {_clear();} + rcptr_t() throw() {_clear();} + rcptr_t(const t_self & p_source) throw() {__init(p_source);} + t_self const & operator=(const t_self & p_source) throw() {__copy(p_source); return *this;} + + template + rcptr_t(const rcptr_t & p_source) throw() {__init(p_source);} + template + const t_self & operator=(const rcptr_t & p_source) throw() {__copy(p_source); return *this;} + + rcptr_t(t_self && p_source) throw() {_move(p_source);} + const t_self & operator=(t_self && p_source) throw() {release(); _move(p_source); return *this;} + + const t_self & operator=(t_rcptr_null) throw() {release(); return *this;} + +/* template + operator rcptr_t() const throw() { + rcptr_t temp; + if (is_valid()) temp.__set_from_cast(this->m_container,this->m_ptr); + return temp; + }*/ + + + template + bool operator==(const rcptr_t & p_other) const throw() { + return m_container == p_other.__container(); + } + + template + bool operator!=(const rcptr_t & p_other) const throw() { + return m_container != p_other.__container(); + } + + void __set_from_cast(t_container * p_container,t_object * p_ptr) throw() { + //addref first because in rare cases this is the same pointer as the one we currently own + if (p_container != NULL) p_container->add_ref(); + release(); + m_container = p_container; + m_ptr = p_ptr; + } + + bool is_valid() const throw() {return m_container != NULL;} + bool is_empty() const throw() {return m_container == NULL;} + + + ~rcptr_t() throw() {release();} + + void release() throw() { + t_container * temp = m_container; + m_ptr = NULL; + m_container = NULL; + if (temp != NULL) temp->release(); + } + + + template + rcptr_t static_cast_t() const throw() { + rcptr_t temp; + if (is_valid()) temp.__set_from_cast(this->m_container,static_cast(this->m_ptr)); + return temp; + } + + void new_t() { + on_new(new t_container_impl()); + } + + template + void new_t(arg_t && ... arg) { + on_new(new t_container_impl(std::forward(arg) ...)); + } + + static t_self g_new_t() { + t_self temp; + temp.new_t(); + return temp; + } + + template + static t_self g_new_t(arg_t && ... arg) { + t_self temp; + temp.new_t(std::forward(arg) ...); + return temp; + } + + t_object & operator*() const throw() {return *this->m_ptr;} + + t_object * operator->() const throw() {return this->m_ptr;} + + + t_container * __container() const throw() {return m_container;} + + // FOR INTERNAL USE ONLY + void _clear() throw() {m_container = NULL; m_ptr = NULL;} + private: + + template + void __init(const rcptr_t & p_source) throw() { + m_container = p_source.__container(); + m_ptr = &*p_source; + if (m_container != NULL) m_container->add_ref(); + } + template + void _move(rcptr_t & p_source) throw() { + m_container = p_source.__container(); + m_ptr = &*p_source; + p_source._clear(); + } + template + void __copy(const rcptr_t & p_source) throw() { + __set_from_cast(p_source.__container(),&*p_source); + } + void on_new(t_container_impl * p_container) throw() { + this->release(); + p_container->add_ref(); + this->m_ptr = &p_container->m_object; + this->m_container = p_container; + } + + t_container * m_container; + t_object * m_ptr; + }; + + template + rcptr_t rcnew_t(arg_t && ... arg) { + rcptr_t temp; + temp.new_t(std::forward(arg) ...); + return temp; + } + + class traits_rcptr : public traits_default { + public: + enum { realloc_safe = true, constructor_may_fail = false }; + }; + + template class traits_t > : public traits_rcptr {}; +} \ No newline at end of file diff --git a/pfc/ref_counter.h b/pfc/ref_counter.h new file mode 100644 index 0000000..a2f2a4c --- /dev/null +++ b/pfc/ref_counter.h @@ -0,0 +1,104 @@ +#pragma once + +namespace pfc { + + class NOVTABLE refcounted_object_root + { + public: + void refcount_add_ref() throw() {++m_counter;} + void refcount_release() throw() {if (--m_counter == 0) delete this;} + void _refcount_release_temporary() throw() {--m_counter;}//for internal use only! + protected: + refcounted_object_root() {} + virtual ~refcounted_object_root() {} + private: + refcounter m_counter; + }; + + template + class refcounted_object_ptr_t { + private: + typedef refcounted_object_ptr_t t_self; + public: + inline refcounted_object_ptr_t() throw() : m_ptr(NULL) {} + inline refcounted_object_ptr_t(T* p_ptr) throw() : m_ptr(NULL) {copy(p_ptr);} + inline refcounted_object_ptr_t(const t_self & p_source) throw() : m_ptr(NULL) {copy(p_source);} + inline refcounted_object_ptr_t(t_self && p_source) throw() { m_ptr = p_source.m_ptr; p_source.m_ptr = NULL; } + + template + inline refcounted_object_ptr_t(t_source * p_ptr) throw() : m_ptr(NULL) {copy(p_ptr);} + + template + inline refcounted_object_ptr_t(const refcounted_object_ptr_t & p_source) throw() : m_ptr(NULL) {copy(p_source);} + + inline ~refcounted_object_ptr_t() throw() {if (m_ptr != NULL) m_ptr->refcount_release();} + + template + inline void copy(t_source * p_ptr) throw() { + T* torel = pfc::replace_t(m_ptr,pfc::safe_ptr_cast(p_ptr)); + if (m_ptr != NULL) m_ptr->refcount_add_ref(); + if (torel != NULL) torel->refcount_release(); + + } + + template + inline void copy(const refcounted_object_ptr_t & p_source) throw() {copy(p_source.get_ptr());} + + + inline const t_self & operator=(const t_self & p_source) throw() {copy(p_source); return *this;} + inline const t_self & operator=(t_self && p_source) throw() {attach(p_source.detach()); return *this;} + inline const t_self & operator=(T * p_ptr) throw() {copy(p_ptr); return *this;} + + template inline t_self & operator=(const refcounted_object_ptr_t & p_source) throw() {copy(p_source); return *this;} + template inline t_self & operator=(t_source * p_ptr) throw() {copy(p_ptr); return *this;} + + inline void release() throw() { + T * temp = pfc::replace_t(m_ptr,(T*)NULL); + if (temp != NULL) temp->refcount_release(); + } + + + inline T& operator*() const throw() {return *m_ptr;} + + inline T* operator->() const throw() {PFC_ASSERT(m_ptr != NULL);return m_ptr;} + + inline T* get_ptr() const throw() {return m_ptr;} + + inline bool is_valid() const throw() {return m_ptr != NULL;} + inline bool is_empty() const throw() {return m_ptr == NULL;} + + inline bool operator==(const t_self & p_item) const throw() {return m_ptr == p_item.get_ptr();} + inline bool operator!=(const t_self & p_item) const throw() {return m_ptr != p_item.get_ptr();} + inline bool operator>(const t_self & p_item) const throw() {return m_ptr > p_item.get_ptr();} + inline bool operator<(const t_self & p_item) const throw() {return m_ptr < p_item.get_ptr();} + + + inline T* _duplicate_ptr() const throw()//should not be used ! temporary ! + { + if (m_ptr) m_ptr->refcount_add_ref(); + return m_ptr; + } + + inline T* detach() throw() {//should not be used ! temporary ! + T* ret = m_ptr; + m_ptr = 0; + return ret; + } + + inline void attach(T * p_ptr) throw() {//should not be used ! temporary ! + release(); + m_ptr = p_ptr; + } + inline t_self & operator<<(t_self & p_source) throw() {attach(p_source.detach());return *this;} + inline t_self & operator>>(t_self & p_dest) throw() {p_dest.attach(detach());return *this;} + private: + T* m_ptr; + }; + + template + class traits_t > : public traits_default { + public: + enum { realloc_safe = true, constructor_may_fail = false}; + }; + +}; \ No newline at end of file diff --git a/pfc/selftest.cpp b/pfc/selftest.cpp new file mode 100644 index 0000000..ae3f5d4 --- /dev/null +++ b/pfc/selftest.cpp @@ -0,0 +1,94 @@ +#include "pfc.h" + + +namespace { + class foo {}; +} + +inline pfc::string_base & operator<<(pfc::string_base & p_fmt,foo p_source) {p_fmt.add_string_("FOO"); return p_fmt;} + +namespace { + using namespace pfc; + class thread_selftest : public thread { + public: + void threadProc() { + pfc::event ev; + ev.wait_for(1); + m_event.set_state(true); + ev.wait_for(1); + } + pfc::event m_event; + + void selftest() { + lores_timer timer; timer.start(); + this->start(); + if (!m_event.wait_for(-1)) { + PFC_ASSERT(!"Should not get here"); + return; + } + PFC_ASSERT(fabs(timer.query() - 1.0) < 0.1); + this->waitTillDone(); + PFC_ASSERT(fabs(timer.query() - 2.0) < 0.1); + } + }; +} + +namespace pfc { + + + + // Self test routines that need to be executed to do their payload + void selftest_runtime() { + { + thread_selftest t; t.selftest(); + } + + { + pfc::map_t map; + map["1"] = 1; + map["2"] = 2; + map["3"] = 3; + PFC_ASSERT(map.get_count() == 3); + PFC_ASSERT(map["1"] == 1); + PFC_ASSERT(map["2"] == 2); + PFC_ASSERT(map["3"] == 3); + } + } + // Self test routines that fail at compile time if there's something seriously wrong + void selftest_static() { + PFC_STATIC_ASSERT(sizeof(t_uint8) == 1); + PFC_STATIC_ASSERT(sizeof(t_uint16) == 2); + PFC_STATIC_ASSERT(sizeof(t_uint32) == 4); + PFC_STATIC_ASSERT(sizeof(t_uint64) == 8); + + PFC_STATIC_ASSERT(sizeof(t_int8) == 1); + PFC_STATIC_ASSERT(sizeof(t_int16) == 2); + PFC_STATIC_ASSERT(sizeof(t_int32) == 4); + PFC_STATIC_ASSERT(sizeof(t_int64) == 8); + + PFC_STATIC_ASSERT(sizeof(t_float32) == 4); + PFC_STATIC_ASSERT(sizeof(t_float64) == 8); + + PFC_STATIC_ASSERT(sizeof(t_size) == sizeof(void*)); + PFC_STATIC_ASSERT(sizeof(t_ssize) == sizeof(void*)); + + PFC_STATIC_ASSERT(sizeof(wchar_t) == 2 || sizeof(wchar_t) == 4); + + PFC_STATIC_ASSERT(sizeof(GUID) == 16); + + typedef pfc::avltree_t t_asdf; + t_asdf asdf; asdf.add_item(1); + t_asdf::iterator iter = asdf._first_var(); + t_asdf::const_iterator iter2 = asdf._first_var(); + + PFC_string_formatter() << "foo" << 1337 << foo(); + + pfc::list_t l; l.add_item(3); + } + + void selftest() { + selftest_static(); selftest_runtime(); + + debugLog out; out << "PFC selftest OK"; + } +} diff --git a/pfc/sort.cpp b/pfc/sort.cpp new file mode 100644 index 0000000..912bc92 --- /dev/null +++ b/pfc/sort.cpp @@ -0,0 +1,268 @@ +#include "pfc.h" + +#if defined(_M_IX86) || defined(_M_IX64) +#include +#define PFC_HAVE_RDTSC +#endif + +namespace pfc { + +void swap_void(void * item1,void * item2,t_size width) +{ + unsigned char * ptr1 = (unsigned char*)item1, * ptr2 = (unsigned char*)item2; + t_size n; + unsigned char temp; + for(n=0;n done; + done.set_size(done_size); + pfc::memset_t(done,(unsigned char)0); + t_size n; + for(n=0;nn); + PFC_ASSERT(n done; + done.set_size(done_size); + pfc::memset_t(done,(unsigned char)0); + t_size n; + for(n=0;nn); + PFC_ASSERT(n 0) pfc::swap_t(p_elem1,p_elem2); +} + + +#ifdef PFC_HAVE_RDTSC +static inline t_uint64 uniqueVal() {return __rdtsc();} +#else +static counter uniqueValCounter; +static counter::t_val uniqueVal() { + return ++uniqueValCounter; +} +#endif + +static t_size myrand(t_size count) { + const uint64_t rm = (uint64_t)RAND_MAX + 1; + uint64_t m = 1; + uint64_t v = 0; + for(;;) { + v += rand() * m; + m *= rm; + if (m >= count) break; + } + v ^= uniqueVal(); + return (t_size)(v % count); +} + +inline static t_size __pivot_helper(pfc::sort_callback & p_callback,t_size const p_base,t_size const p_count) { + PFC_ASSERT(p_count > 2); + + //t_size val1 = p_base, val2 = p_base + (p_count / 2), val3 = p_base + (p_count - 1); + + t_size val1 = myrand(p_count), val2 = myrand(p_count-1), val3 = myrand(p_count-2); + if (val2 >= val1) val2++; + if (val3 >= val1) val3++; + if (val3 >= val2) val3++; + + val1 += p_base; val2 += p_base; val3 += p_base; + + __sort_2elem_helper(p_callback,val1,val2); + __sort_2elem_helper(p_callback,val1,val3); + __sort_2elem_helper(p_callback,val2,val3); + + return val2; +} + +static void newsort(pfc::sort_callback & p_callback,t_size const p_base,t_size const p_count) { + if (p_count <= 4) { + squaresort(p_callback,p_base,p_count); + return; + } + + t_size pivot = __pivot_helper(p_callback,p_base,p_count); + + { + const t_size target = p_base + p_count - 1; + if (pivot != target) { + p_callback.swap(pivot,target); pivot = target; + } + } + + + t_size partition = p_base; + { + bool asdf = false; + for(t_size walk = p_base; walk < pivot; ++walk) { + const int comp = p_callback.compare(walk,pivot); + bool trigger = false; + if (comp == 0) { + trigger = asdf; + asdf = !asdf; + } else if (comp < 0) { + trigger = true; + } + if (trigger) { + if (partition != walk) p_callback.swap(partition,walk); + partition++; + } + } + } + if (pivot != partition) { + p_callback.swap(pivot,partition); pivot = partition; + } + + newsort(p_callback,p_base,pivot-p_base); + newsort(p_callback,pivot+1,p_count-(pivot+1-p_base)); +} + +void sort(pfc::sort_callback & p_callback,t_size p_num) { + srand((unsigned int)(uniqueVal() ^ p_num)); + newsort(p_callback,0,p_num); +} + + +void sort_void(void * base,t_size num,t_size width,int (*comp)(const void *, const void *) ) +{ + sort_void_ex(base,num,width,comp,swap_void); +} + + + + +sort_callback_stabilizer::sort_callback_stabilizer(sort_callback & p_chain,t_size p_count) +: m_chain(p_chain) +{ + m_order.set_size(p_count); + t_size n; + for(n=0;n + class reorder_callback_impl_t : public reorder_callback + { + public: + reorder_callback_impl_t(t_container & p_data) : m_data(p_data) {} + void swap(t_size p_index1,t_size p_index2) + { + pfc::swap_t(m_data[p_index1],m_data[p_index2]); + } + private: + t_container & m_data; + }; + + class reorder_callback_impl_delta : public reorder_callback + { + public: + reorder_callback_impl_delta(reorder_callback & p_data,t_size p_delta) : m_data(p_data), m_delta(p_delta) {} + void swap(t_size p_index1,t_size p_index2) + { + m_data.swap(p_index1+m_delta,p_index2+m_delta); + } + private: + reorder_callback & m_data; + t_size m_delta; + }; + + template + void reorder_t(t_container & p_data,const t_size * p_order,t_size p_count) + { + reorder_callback_impl_t cb(p_data); + reorder(cb,p_order,p_count); + } + + template + void reorder_partial_t(t_container & p_data,t_size p_base,const t_size * p_order,t_size p_count) + { + reorder_callback_impl_t cb1(p_data); + reorder_callback_impl_delta cb2( cb1, p_base ); + reorder(cb2,p_order,p_count); +// reorder(reorder_callback_impl_delta(reorder_callback_impl_t(p_data),p_base),p_order,p_count); + } + + template + class reorder_callback_impl_ptr_t : public reorder_callback + { + public: + reorder_callback_impl_ptr_t(T * p_data) : m_data(p_data) {} + void swap(t_size p_index1,t_size p_index2) + { + pfc::swap_t(m_data[p_index1],m_data[p_index2]); + } + private: + T* m_data; + }; + + + template + void reorder_ptr_t(T* p_data,const t_size * p_order,t_size p_count) + { + reorder_callback_impl_ptr_t cb(p_data); + reorder(cb,p_order,p_count); + } + + + + class NOVTABLE sort_callback + { + public: + virtual int compare(t_size p_index1, t_size p_index2) const = 0; + virtual void swap(t_size p_index1, t_size p_index2) = 0; + void swap_check(t_size p_index1, t_size p_index2) {if (compare(p_index1,p_index2) > 0) swap(p_index1,p_index2);} + }; + + class sort_callback_stabilizer : public sort_callback + { + public: + sort_callback_stabilizer(sort_callback & p_chain,t_size p_count); + virtual int compare(t_size p_index1, t_size p_index2) const; + virtual void swap(t_size p_index1, t_size p_index2); + private: + sort_callback & m_chain; + array_t m_order; + }; + + void sort(sort_callback & p_callback,t_size p_count); + void sort_stable(sort_callback & p_callback,t_size p_count); + + void sort_void_ex(void *base,t_size num,t_size width, int (*comp)(const void *, const void *),void (*swap)(void *, void *, t_size) ); + void sort_void(void * base,t_size num,t_size width,int (*comp)(const void *, const void *) ); + + template + class sort_callback_impl_simple_wrap_t : public sort_callback + { + public: + sort_callback_impl_simple_wrap_t(t_container & p_data, t_compare p_compare) : m_data(p_data), m_compare(p_compare) {} + int compare(t_size p_index1, t_size p_index2) const + { + return m_compare(m_data[p_index1],m_data[p_index2]); + } + + void swap(t_size p_index1, t_size p_index2) + { + swap_t(m_data[p_index1],m_data[p_index2]); + } + private: + t_container & m_data; + t_compare m_compare; + }; + + template + class sort_callback_impl_auto_wrap_t : public sort_callback + { + public: + sort_callback_impl_auto_wrap_t(t_container & p_data) : m_data(p_data) {} + int compare(t_size p_index1, t_size p_index2) const + { + return compare_t(m_data[p_index1],m_data[p_index2]); + } + + void swap(t_size p_index1, t_size p_index2) + { + swap_t(m_data[p_index1],m_data[p_index2]); + } + private: + t_container & m_data; + }; + + template + class sort_callback_impl_permutation_wrap_t : public sort_callback + { + public: + sort_callback_impl_permutation_wrap_t(const t_container & p_data, t_compare p_compare,t_permutation const & p_permutation) : m_data(p_data), m_compare(p_compare), m_permutation(p_permutation) {} + int compare(t_size p_index1, t_size p_index2) const + { + return m_compare(m_data[m_permutation[p_index1]],m_data[m_permutation[p_index2]]); + } + + void swap(t_size p_index1, t_size p_index2) + { + swap_t(m_permutation[p_index1],m_permutation[p_index2]); + } + private: + const t_container & m_data; + t_compare m_compare; + t_permutation const & m_permutation; + }; + + template + static void sort_t(t_container & p_data,t_compare p_compare,t_size p_count) + { + sort_callback_impl_simple_wrap_t cb(p_data,p_compare); + sort(cb,p_count); + } + + template + static void sort_stable_t(t_container & p_data,t_compare p_compare,t_size p_count) + { + sort_callback_impl_simple_wrap_t cb(p_data,p_compare); + sort_stable(cb,p_count); + } + + template + static void sort_get_permutation_t(const t_container & p_data,t_compare p_compare,t_size p_count,t_permutation const & p_permutation) + { + sort_callback_impl_permutation_wrap_t cb(p_data,p_compare,p_permutation); + sort(cb,p_count); + } + + template + static void sort_stable_get_permutation_t(const t_container & p_data,t_compare p_compare,t_size p_count,t_permutation const & p_permutation) + { + sort_callback_impl_permutation_wrap_t cb(p_data,p_compare,p_permutation); + sort_stable(cb,p_count); + } + +} diff --git a/pfc/splitString.h b/pfc/splitString.h new file mode 100644 index 0000000..4b6860d --- /dev/null +++ b/pfc/splitString.h @@ -0,0 +1,3 @@ +#pragma once + +// added for compatibility with fb2k mobile \ No newline at end of file diff --git a/pfc/stdafx.cpp b/pfc/stdafx.cpp new file mode 100644 index 0000000..f2d9441 --- /dev/null +++ b/pfc/stdafx.cpp @@ -0,0 +1,2 @@ +//cpp used to generate precompiled header +#include "pfc.h" \ No newline at end of file diff --git a/pfc/stdsort.h b/pfc/stdsort.h new file mode 100644 index 0000000..bbfe9d0 --- /dev/null +++ b/pfc/stdsort.h @@ -0,0 +1,28 @@ +#pragma once + +// OPTIONAL pfc feature, include on need to use basis +// std sort interop methods + +#include +#include +#include + +namespace pfc { + + std::vector sort_identity( size_t count ) { + std::vector ret; ret.resize(count); + for( size_t walk = 0; walk < ret.size(); ++ walk) ret[walk] = walk; + return ret; + } + + template + std::vector sort_get_order(iterator_t i1, iterator_t i2, predicate_t pred ) { + auto ret = sort_identity( i2 - i1 ); + auto pred2 = [pred, i1] (size_t idx1, size_t idx2) { + return pred( *(i1+idx1), *(i1+idx2)); + }; + std::sort(ret.begin(), ret.end(), pred2); + return ret; + } + +} diff --git a/pfc/string8_impl.h b/pfc/string8_impl.h new file mode 100644 index 0000000..a7ef0ee --- /dev/null +++ b/pfc/string8_impl.h @@ -0,0 +1,119 @@ +#pragma once + +namespace pfc { + +template class t_alloc> +void string8_t::add_string(const char * ptr,t_size len) +{ + if (m_data.is_owned(ptr)) { + add_string(string8(ptr,len)); + } else { + len = strlen_max(ptr,len); + add_string_nc(ptr, len); + } +} + +template class t_alloc> +void string8_t::set_string(const char * ptr,t_size len) { + if (m_data.is_owned(ptr)) { + set_string_(string8(ptr,len)); + } else { + len = strlen_max(ptr,len); + set_string_nc(ptr, len); + } +} + +template class t_alloc> +void string8_t::set_char(unsigned offset,char c) +{ + if (!c) truncate(offset); + else if (offset class t_alloc> +void string8_t::fix_filename_chars(char def,char leave)//replace "bad" characters, leave parameter can be used to keep eg. path separators +{ + t_size n; + for(n=0;n class t_alloc> +void string8_t::remove_chars(t_size first,t_size count) +{ + if (first>used) first = used; + if (first+count>used) count = used-first; + if (count>0) + { + t_size n; + for(n=first+count;n<=used;n++) + m_data[n-count]=m_data[n]; + used -= count; + makespace(used+1); + } +} + +template class t_alloc> +void string8_t::insert_chars(t_size first,const char * src, t_size count) +{ + if (first > used) first = used; + + makespace(used+count+1); + t_size n; + for(n=used;(int)n>=(int)first;n--) + m_data[n+count] = m_data[n]; + for(n=0;n class t_alloc> +void string8_t::insert_chars(t_size first,const char * src) {insert_chars(first,src,strlen(src));} + + +template class t_alloc> +t_size string8_t::replace_nontext_chars(char p_replace) +{ + t_size ret = 0; + for(t_size n=0;n class t_alloc> +t_size string8_t::replace_byte(char c1,char c2,t_size start) +{ + PFC_ASSERT(c1 != 0); PFC_ASSERT(c2 != 0); + t_size n, ret = 0; + for(n=start;n class t_alloc> +t_size string8_t::replace_char(unsigned c1,unsigned c2,t_size start) +{ + if (c1 < 128 && c2 < 128) return replace_byte((char)c1,(char)c2,start); + + string8 temp(get_ptr()+start); + truncate(start); + const char * ptr = temp; + t_size rv = 0; + while(*ptr) + { + unsigned test; + t_size delta = utf8_decode_char(ptr,test); + if (delta==0 || test==0) break; + if (test == c1) {test = c2;rv++;} + add_char(test); + ptr += delta; + } + return rv; +} + +} diff --git a/pfc/stringNew.cpp b/pfc/stringNew.cpp new file mode 100644 index 0000000..d84141a --- /dev/null +++ b/pfc/stringNew.cpp @@ -0,0 +1,82 @@ +#include "pfc.h" + +namespace pfc { + +t_size string::indexOf(char c,t_size base) const { + return pfc::string_find_first(ptr(),c,base); +} +t_size string::lastIndexOf(char c,t_size base) const { + return pfc::string_find_last(ptr(),c,base); +} +t_size string::indexOf(stringp s,t_size base) const { + return pfc::string_find_first(ptr(),s.ptr(),base); +} +t_size string::lastIndexOf(stringp s,t_size base) const { + return pfc::string_find_last(ptr(),s.ptr(),base); +} +t_size string::indexOfAnyChar(stringp _s,t_size base) const { + string s ( _s ); + const t_size len = length(); + const char* content = ptr(); + for(t_size walk = 0; walk < len; ++walk) { + if (s.contains(content[walk])) return walk; + } + return ~0; +} +t_size string::lastIndexOfAnyChar(stringp _s,t_size base) const { + string s ( _s ); + const char* content = ptr(); + for(t_size _walk = length(); _walk > 0; --_walk) { + const t_size walk = _walk-1; + if (s.contains(content[walk])) return walk; + } + return ~0; +} +bool string::startsWith(char c) const { + return (*this)[0] == c; +} +bool string::startsWith(string s) const { + const char * walk = ptr(); + const char * subWalk = s.ptr(); + for(;;) { + if (*subWalk == 0) return true; + if (*walk != *subWalk) return false; + walk++; subWalk++; + } +} +bool string::endsWith(char c) const { + const t_size len = length(); + if (len == 0) return false; + return ptr()[len-1] == c; +} +bool string::endsWith(string s) const { + const t_size len = length(), subLen = s.length(); + if (subLen > len) return false; + return subString(len - subLen) == s; +} + +char string::firstChar() const { + return (*this)[0]; +} +char string::lastChar() const { + const t_size len = length(); + return len > 0 ? (*this)[len-1] : (char)0; +} + +string string::replace(stringp strOld, stringp strNew) const { + t_size walk = 0; + string ret; + for(;;) { + t_size next = indexOf(strOld, walk); + if (next == ~0) { + ret += subString(walk); break; + } + ret += subString(walk,next-walk) + strNew; + walk = next + strOld.length(); + } + return ret; +} +bool string::contains(char c) const {return indexOf(c) != ~0;} +bool string::contains(stringp s) const {return indexOf(s) != ~0;} +bool string::containsAnyChar(stringp s) const {return indexOfAnyChar(s) != ~0;} +} diff --git a/pfc/stringNew.h b/pfc/stringNew.h new file mode 100644 index 0000000..15e8a35 --- /dev/null +++ b/pfc/stringNew.h @@ -0,0 +1,262 @@ +#pragma once + +namespace pfc { + //helper, const methods only + class _stringEmpty : public string_base { + public: + const char * get_ptr() const {return "";} + void add_string(const char * ,t_size) {throw exception_not_implemented();} + void set_string(const char * ,t_size) {throw exception_not_implemented();} + void truncate(t_size) {throw exception_not_implemented();} + t_size get_length() const {return 0;} + char * lock_buffer(t_size) {throw exception_not_implemented();} + void unlock_buffer() {throw exception_not_implemented();} + }; + + class stringp; + + //! New EXPERIMENTAL string class, allowing efficient copies and returning from functions. \n + //! Does not implement the string_base interface so you still need string8 in many cases. \n + //! Safe to pass between DLLs, but since a reference is used, objects possibly created by other DLLs must be released before owning DLLs are unloaded. + class string { + public: + typedef rcptr_t t_data; + typedef rcptr_t t_dataImpl; + + string() : m_content(rcnew_t<_stringEmpty>()) {} + string(const char * p_source) : m_content(rcnew_t(p_source)) {} + string(const char * p_source, t_size p_sourceLen) : m_content(rcnew_t(p_source,p_sourceLen)) {} + string(char * p_source) : m_content(rcnew_t(p_source)) {} + string(char * p_source, t_size p_sourceLen) : m_content(rcnew_t(p_source,p_sourceLen)) {} + string(t_data const & p_source) : m_content(p_source) {} + string(string_part_ref source) : m_content(rcnew_t(source)) {} + template string(const TSource & p_source); + + string(const string& other) : m_content(other.m_content) {} + string(string&& other) : m_content(std::move(other.m_content)) {} + + const string& operator=(const string& other) {m_content = other.m_content; return *this;} + const string& operator=(string&& other) {m_content = std::move(other.m_content); return *this;} + + + string const & toString() const {return *this;} + + //warning, not length-checked anymore! + static string g_concatenateRaw(const char * item1, t_size len1, const char * item2, t_size len2) { + t_dataImpl impl; impl.new_t(); + char * buffer = impl->lock_buffer(len1+len2); + memcpy_t(buffer,item1,len1); + memcpy_t(buffer+len1,item2,len2); + impl->unlock_buffer(); + return string(t_data(impl)); + } + + string operator+(const string& p_item2) const { + return g_concatenateRaw(ptr(),length(),p_item2.ptr(),p_item2.length()); + } + string operator+(const char * p_item2) const { + return g_concatenateRaw(ptr(),length(),p_item2,strlen(p_item2)); + } + + template string operator+(const TSource & p_item2) const; + + template + const string & operator+=(const TSource & p_item) { + *this = *this + p_item; + return *this; + } + + string subString(t_size base) const { + if (base > length()) throw exception_overflow(); + return string(ptr() + base); + } + string subString(t_size base, t_size count) const { + return string(ptr() + base,count); + } + + string toLower() const { + string8_fastalloc temp; temp.prealloc(128); + stringToLowerAppend(temp,ptr(),SIZE_MAX); + return string(temp.get_ptr()); + } + string toUpper() const { + string8_fastalloc temp; temp.prealloc(128); + stringToUpperAppend(temp,ptr(),SIZE_MAX); + return string(temp.get_ptr()); + } + + string clone() const {return string(ptr());} + + //! @returns ~0 if not found. + t_size indexOf(char c,t_size base = 0) const; + //! @returns ~0 if not found. + t_size lastIndexOf(char c,t_size base = SIZE_MAX) const; + //! @returns ~0 if not found. + t_size indexOf(stringp s,t_size base = 0) const; + //! @returns ~0 if not found. + t_size lastIndexOf(stringp s,t_size base = SIZE_MAX) const; + //! @returns ~0 if not found. + t_size indexOfAnyChar(stringp s,t_size base = 0) const; + //! @returns ~0 if not found. + t_size lastIndexOfAnyChar(stringp s,t_size base = SIZE_MAX) const; + + bool contains(char c) const; + bool contains(stringp s) const; + + bool containsAnyChar(stringp s) const; + + bool startsWith(char c) const; + bool startsWith(string s) const; + bool endsWith(char c) const; + bool endsWith(string s) const; + + char firstChar() const; + char lastChar() const; + + string replace(stringp strOld, stringp strNew) const; + + static int g_compare(const string & p_item1, const string & p_item2) {return strcmp(p_item1.ptr(),p_item2.ptr());} + bool operator==(const string& p_other) const {return g_compare(*this,p_other) == 0;} + bool operator!=(const string& p_other) const {return g_compare(*this,p_other) != 0;} + bool operator<(const string& p_other) const {return g_compare(*this,p_other) < 0;} + bool operator>(const string& p_other) const {return g_compare(*this,p_other) > 0;} + bool operator<=(const string& p_other) const {return g_compare(*this,p_other) <= 0;} + bool operator>=(const string& p_other) const {return g_compare(*this,p_other) >= 0;} + + const char * ptr() const {return m_content->get_ptr();} + const char * get_ptr() const {return m_content->get_ptr();} + const char * c_str() const { return get_ptr(); } + t_size length() const {return m_content->get_length();} + t_size get_length() const {return m_content->get_length();} + + void set_string(const char * ptr, t_size len = SIZE_MAX) { + *this = string(ptr, len); + } + + static bool isNonTextChar(char c) {return c >= 0 && c < 32;} + + char operator[](t_size p_index) const { + PFC_ASSERT(p_index <= length()); + return ptr()[p_index]; + } + bool isEmpty() const {return length() == 0;} + + class _comparatorCommon { + protected: + template static const char * myStringToPtr(const T& val) {return stringToPtr(val);} + static const char * myStringToPtr(string_part_ref) { + PFC_ASSERT(!"Should never get here"); throw exception_invalid_params(); + } + }; + + class comparatorCaseSensitive : private _comparatorCommon { + public: + template + static int compare(T1 const& v1, T2 const& v2) { + if (is_same_type::value || is_same_type::value) { + return compare_ex(stringToRef(v1), stringToRef(v2)); + } else { + return strcmp(myStringToPtr(v1),myStringToPtr(v2)); + } + } + static int compare_ex(string_part_ref v1, string_part_ref v2) { + return strcmp_ex(v1.m_ptr, v1.m_len, v2.m_ptr, v2.m_len); + } + static int compare_ex(const char * v1, t_size l1, const char * v2, t_size l2) { + return strcmp_ex(v1, l1, v2, l2); + } + }; + class comparatorCaseInsensitive : private _comparatorCommon { + public: + template + static int compare(T1 const& v1, T2 const& v2) { + if (is_same_type::value || is_same_type::value) { + return stringCompareCaseInsensitiveEx(stringToRef(v1), stringToRef(v2)); + } else { + return stringCompareCaseInsensitive(myStringToPtr(v1),myStringToPtr(v2)); + } + } + }; + class comparatorCaseInsensitiveASCII : private _comparatorCommon { + public: + template + static int compare(T1 const& v1, T2 const& v2) { + if (is_same_type::value || is_same_type::value) { + return compare_ex(stringToRef(v1), stringToRef(v2)); + } else { + return stricmp_ascii(myStringToPtr(v1),myStringToPtr(v2)); + } + } + static int compare_ex(string_part_ref v1, string_part_ref v2) { + return stricmp_ascii_ex(v1.m_ptr, v1.m_len, v2.m_ptr, v2.m_len); + } + static int compare_ex(const char * v1, t_size l1, const char * v2, t_size l2) { + return stricmp_ascii_ex(v1, l1, v2, l2); + } + }; + + static bool g_equals(const string & p_item1, const string & p_item2) {return p_item1 == p_item2;} + static bool g_equalsCaseInsensitive(const string & p_item1, const string & p_item2) {return comparatorCaseInsensitive::compare(p_item1,p_item2) == 0;} + + t_data _content() const {return m_content;} + private: + t_data m_content; + }; + + template inline string toString(T const& val) {return val.toString();} + template<> inline string toString(t_int64 const& val) {return format_int(val).get_ptr();} + template<> inline string toString(t_int32 const& val) {return format_int(val).get_ptr();} + template<> inline string toString(t_int16 const& val) {return format_int(val).get_ptr();} + template<> inline string toString(t_uint64 const& val) {return format_uint(val).get_ptr();} + template<> inline string toString(t_uint32 const& val) {return format_uint(val).get_ptr();} + template<> inline string toString(t_uint16 const& val) {return format_uint(val).get_ptr();} + template<> inline string toString(float const& val) {return format_float(val).get_ptr();} + template<> inline string toString(double const& val) {return format_float(val).get_ptr();} + template<> inline string toString(char const& val) {return string(&val,1);} + inline const char * toString(std::exception const& val) {return val.what();} + + template string::string(const TSource & p_source) { + *this = pfc::toString(p_source); + } + template string string::operator+(const TSource & p_item2) const { + return *this + pfc::toString(p_item2); + } + + //! "String parameter" helper class, to use in function parameters, allowing functions to take any type of string as a parameter (const char*, string_base, string). + class stringp { + public: + stringp(const char * ptr) : m_ptr(ptr) {} + stringp(string const &s) : m_ptr(s.ptr()), m_s(s._content()) {} + stringp(string_base const &s) : m_ptr(s.get_ptr()) {} + template stringp(const TWhat& in) : m_ptr(in.toString()) {} + + operator const char*() const {return m_ptr;} + const char * ptr() const {return m_ptr;} + const char * get_ptr() const {return m_ptr;} + string str() const {return m_s.is_valid() ? string(m_s) : string(m_ptr);} + operator string() const {return str();} + string toString() const {return str();} + t_size length() const {return m_s.is_valid() ? m_s->length() : strlen(m_ptr);} + const char * c_str() const { return ptr(); } + private: + const char * const m_ptr; + string::t_data m_s; + }; + + template + string stringCombineList(const TList & list, stringp separator) { + typename TList::const_iterator iter = list.first(); + string acc; + if (iter.is_valid()) { + acc = *iter; + for(++iter; iter.is_valid(); ++iter) { + acc = acc + separator + *iter; + } + } + return acc; + } + + class string; + template<> class traits_t : public traits_default {}; + +} diff --git a/pfc/string_base.cpp b/pfc/string_base.cpp new file mode 100644 index 0000000..f77893a --- /dev/null +++ b/pfc/string_base.cpp @@ -0,0 +1,1367 @@ +#include "pfc.h" +#include + +namespace pfc { + +void string_receiver::add_char(t_uint32 p_char) +{ + char temp[8]; + t_size len = utf8_encode_char(p_char,temp); + if (len>0) add_string(temp,len); +} + +void string_base::skip_trailing_chars( const char * lstCharsStr ) { + std::set lstChars; + for ( ;; ) { + unsigned c; + auto delta = utf8_decode_char( lstCharsStr, c ); + if ( delta == 0 ) break; + lstCharsStr += delta; + lstChars.insert( c ); + } + + const char * str = get_ptr(); + t_size ptr,trunc = 0; + bool need_trunc = false; + for(ptr=0;str[ptr];) + { + unsigned c; + t_size delta = utf8_decode_char(str+ptr,c); + if (delta==0) break; + if ( lstChars.count( c ) > 0 ) + { + if (!need_trunc) { + need_trunc = true; + trunc = ptr; + } + } + else + { + need_trunc = false; + } + ptr += delta; + } + if (need_trunc) truncate(trunc); +} + +void string_base::skip_trailing_char(unsigned skip) +{ + const char * str = get_ptr(); + t_size ptr,trunc = 0; + bool need_trunc = false; + for(ptr=0;str[ptr];) + { + unsigned c; + t_size delta = utf8_decode_char(str+ptr,c); + if (delta==0) break; + if (c==skip) + { + if (!need_trunc) { + need_trunc = true; + trunc = ptr; + } + } + else + { + need_trunc = false; + } + ptr += delta; + } + if (need_trunc) truncate(trunc); +} + +format_time::format_time(t_uint64 p_seconds) { + t_uint64 length = p_seconds; + unsigned weeks,days,hours,minutes,seconds; + + weeks = (unsigned)( ( length / (60*60*24*7) ) ); + days = (unsigned)( ( length / (60*60*24) ) % 7 ); + hours = (unsigned) ( ( length / (60 * 60) ) % 24); + minutes = (unsigned) ( ( length / (60 ) ) % 60 ); + seconds = (unsigned) ( ( length ) % 60 ); + + if (weeks) { + m_buffer << weeks << "wk "; + } + if (days || weeks) { + m_buffer << days << "d "; + } + if (hours || days || weeks) { + m_buffer << hours << ":" << format_uint(minutes,2) << ":" << format_uint(seconds,2); + } else { + m_buffer << minutes << ":" << format_uint(seconds,2); + } +} + +bool is_path_separator(unsigned c) +{ + return c=='\\' || c=='/' || c=='|' || c==':'; +} + +bool is_path_bad_char(unsigned c) +{ +#ifdef _WINDOWS + return c=='\\' || c=='/' || c=='|' || c==':' || c=='*' || c=='?' || c=='\"' || c=='>' || c=='<'; +#else + return c=='/' || c=='*' || c=='?'; +#endif +} + + + +char * strdup_n(const char * src,t_size len) +{ + len = strlen_max(src,len); + char * ret = (char*)malloc(len+1); + if (ret) + { + memcpy(ret,src,len); + ret[len]=0; + } + return ret; +} + +string_filename::string_filename(const char * fn) +{ + fn += pfc::scan_filename(fn); + const char * ptr=fn,*dot=0; + while(*ptr && *ptr!='?') + { + if (*ptr=='.') dot=ptr; + ptr++; + } + + if (dot && dot>fn) set_string(fn,dot-fn); + else set_string(fn); +} + +const char * filename_ext_v2(const char * fn, char slash) { + if (slash == 0) { + slash = pfc::io::path::getDefaultSeparator(); + } + size_t split = pfc::string_find_last(fn, slash); + if (split == pfc_infinite) return fn; + return fn + split + 1; +} + +string_filename_ext::string_filename_ext(const char * fn) +{ + fn += pfc::scan_filename(fn); + const char * ptr = fn; + while(*ptr && *ptr!='?') ptr++; + set_string(fn,ptr-fn); +} + +size_t find_extension_offset(const char * src) { + const char * start = src + pfc::scan_filename(src); + const char * end = start + strlen(start); + const char * ptr = end - 1; + while (ptr > start && *ptr != '.') + { + if (*ptr == '?') end = ptr; + ptr--; + } + + if (ptr >= start && *ptr == '.') + { + return ptr - src; + } + + return SIZE_MAX; +} + +string_extension::string_extension(const char * src) +{ + buffer[0]=0; + const char * start = src + pfc::scan_filename(src); + const char * end = start + strlen(start); + const char * ptr = end-1; + while(ptr>start && *ptr!='.') + { + if (*ptr=='?') end=ptr; + ptr--; + } + + if (ptr>=start && *ptr=='.') + { + ptr++; + t_size len = end-ptr; + if (len temp; + t_size outptr; + + if (out_max == 0) return; + out_max--;//for null terminator + + outptr = 0; + + if (outptr == out_max) {out[outptr]=0;return;} + + if (val<0) {out[outptr++] = '-'; val = -val;} + else if (val > 0 && b_sign) {out[outptr++] = '+';} + + if (outptr == out_max) {out[outptr]=0;return;} + + + { + double powval = pow((double)10.0,(double)precision); + temp << (t_int64)floor(val * powval + 0.5); + //_i64toa(blargh,temp,10); + } + + const t_size temp_len = temp.length(); + if (temp_len <= precision) + { + out[outptr++] = '0'; + if (outptr == out_max) {out[outptr]=0;return;} + out[outptr++] = '.'; + if (outptr == out_max) {out[outptr]=0;return;} + t_size d; + for(d=precision-temp_len;d;d--) + { + out[outptr++] = '0'; + if (outptr == out_max) {out[outptr]=0;return;} + } + for(d=0;d='0' && *src<='9') + { + int d = *src - '0'; + val = val * 10 + d; + if (got_dot) div--; + src++; + } + else if (*src=='.' || *src==',') + { + if (got_dot) break; + got_dot = true; + src++; + } + else if (*src=='E' || *src=='e') + { + src++; + div += atoi(src); + break; + } + else break; + } + if (neg) val = -val; + return (double) val * exp_int(10, div); +} + +double string_to_float(const char * src,t_size max) { + //old function wants an oldstyle nullterminated string, and i don't currently care enough to rewrite it as it works appropriately otherwise + char blargh[128]; + if (max > 127) max = 127; + t_size walk; + for(walk = 0; walk < max && src[walk]; walk++) blargh[walk] = src[walk]; + blargh[walk] = 0; + return pfc_string_to_float_internal(blargh); +} + + + +void string_base::convert_to_lower_ascii(const char * src,char replace) +{ + reset(); + PFC_ASSERT(replace>0); + while(*src) + { + unsigned c; + t_size delta = utf8_decode_char(src,c); + if (delta==0) {c = replace; delta = 1;} + else if (c>=0x80) c = replace; + add_byte((char)c); + src += delta; + } +} + +void convert_to_lower_ascii(const char * src,t_size max,char * out,char replace) +{ + t_size ptr = 0; + PFC_ASSERT(replace>0); + while(ptr=0x80) c = replace; + *(out++) = (char)c; + ptr += delta; + } + *out = 0; +} + +t_size strstr_ex(const char * p_string,t_size p_string_len,const char * p_substring,t_size p_substring_len) throw() +{ + p_string_len = strlen_max(p_string,p_string_len); + p_substring_len = strlen_max(p_substring,p_substring_len); + t_size index = 0; + while(index + p_substring_len <= p_string_len) + { + if (memcmp(p_string+index,p_substring,p_substring_len) == 0) return index; + t_size delta = utf8_char_len(p_string+index,p_string_len - index); + if (delta == 0) break; + index += delta; + } + return ~0; +} + +unsigned atoui_ex(const char * p_string,t_size p_string_len) +{ + unsigned ret = 0; t_size ptr = 0; + while(ptr= '0' && c <= '9' ) ) break; + ret = ret * 10 + (unsigned)( c - '0' ); + ptr++; + } + return ret; +} + +int strcmp_nc(const char* p1, size_t n1, const char * p2, size_t n2) throw() { + t_size idx = 0; + for(;;) + { + if (idx == n1 && idx == n2) return 0; + else if (idx == n1) return -1;//end of param1 + else if (idx == n2) return 1;//end of param2 + + char c1 = p1[idx], c2 = p2[idx]; + if (c1c2) return 1; + + idx++; + } +} + +int strcmp_ex(const char* p1,t_size n1,const char* p2,t_size n2) throw() +{ + n1 = strlen_max(p1,n1); n2 = strlen_max(p2,n2); + return strcmp_nc(p1, n1, p2, n2); +} + +t_uint64 atoui64_ex(const char * src,t_size len) { + len = strlen_max(src,len); + t_uint64 ret = 0, mul = 1; + t_size ptr = len; + t_size start = 0; +// start += skip_spacing(src+start,len-start); + + while(ptr>start) + { + char c = src[--ptr]; + if (c>='0' && c<='9') + { + ret += (c-'0') * mul; + mul *= 10; + } + else + { + ret = 0; + mul = 1; + } + } + return ret; +} + + +t_int64 atoi64_ex(const char * src,t_size len) +{ + len = strlen_max(src,len); + t_int64 ret = 0, mul = 1; + t_size ptr = len; + t_size start = 0; + bool neg = false; +// start += skip_spacing(src+start,len-start); + if (start < len && src[start] == '-') {neg = true; start++;} +// start += skip_spacing(src+start,len-start); + + while(ptr>start) + { + char c = src[--ptr]; + if (c>='0' && c<='9') + { + ret += (c-'0') * mul; + mul *= 10; + } + else + { + ret = 0; + mul = 1; + } + } + return neg ? -ret : ret; +} + +int stricmp_ascii_partial( const char * str, const char * substr) throw() { + size_t walk = 0; + for(;;) { + char c1 = str[walk]; + char c2 = substr[walk]; + c1 = ascii_tolower(c1); c2 = ascii_tolower(c2); + if (c2 == 0) return 0; // substr terminated = ret0 regardless of str content + if (c1c2) return 1; // ret 1 early + // else c1 == c2 and c2 != 0 so c1 != 0 either + ++walk; // go on + } +} + +int stricmp_ascii_ex(const char * const s1,t_size const len1,const char * const s2,t_size const len2) throw() { + t_size walk1 = 0, walk2 = 0; + for(;;) { + char c1 = (walk1 < len1) ? s1[walk1] : 0; + char c2 = (walk2 < len2) ? s2[walk2] : 0; + c1 = ascii_tolower(c1); c2 = ascii_tolower(c2); + if (c1c2) return 1; + else if (c1 == 0) return 0; + walk1++; + walk2++; + } + +} + +int wstricmp_ascii( const wchar_t * s1, const wchar_t * s2 ) throw() { + for(;;) { + wchar_t c1 = *s1, c2 = *s2; + + if (c1 > 0 && c2 > 0 && c1 < 128 && c2 < 128) { + c1 = ascii_tolower_lookup((char)c1); + c2 = ascii_tolower_lookup((char)c2); + } else { + if (c1 == 0 && c2 == 0) return 0; + } + if (c1c2) return 1; + else if (c1 == 0) return 0; + + s1++; + s2++; + } +} + +int stricmp_ascii(const char * s1,const char * s2) throw() { + for(;;) { + char c1 = *s1, c2 = *s2; + + if (c1 > 0 && c2 > 0) { + c1 = ascii_tolower_lookup(c1); + c2 = ascii_tolower_lookup(c2); + } else { + if (c1 == 0 && c2 == 0) return 0; + } + if (c1c2) return 1; + else if (c1 == 0) return 0; + + s1++; + s2++; + } +} + +static int naturalSortCompareInternal( const char * s1, const char * s2, bool insensitive) throw() { + for( ;; ) { + unsigned c1, c2; + size_t d1 = utf8_decode_char( s1, c1 ); + size_t d2 = utf8_decode_char( s2, c2 ); + if (d1 == 0 && d2 == 0) { + return 0; + } + if (char_is_numeric( c1 ) && char_is_numeric( c2 ) ) { + // Numeric block in both strings, do natural sort magic here + size_t l1 = 1, l2 = 1; + while( char_is_numeric( s1[l1] ) ) ++l1; + while( char_is_numeric( s2[l2] ) ) ++l2; + + size_t l = max_t(l1, l2); + for(size_t w = 0; w < l; ++w) { + char digit1, digit2; + + t_ssize off; + + off = w + l1 - l; + if (off >= 0) { + digit1 = s1[w - l + l1]; + } else { + digit1 = 0; + } + off = w + l2 - l; + if (off >= 0) { + digit2 = s2[w - l + l2]; + } else { + digit2 = 0; + } + if (digit1 < digit2) return -1; + if (digit1 > digit2) return 1; + } + + s1 += l1; s2 += l2; + continue; + } + + + if (insensitive) { + c1 = charLower( c1 ); + c2 = charLower( c2 ); + } + if (c1 < c2) return -1; + if (c1 > c2) return 1; + + s1 += d1; s2 += d2; + } +} +int naturalSortCompare( const char * s1, const char * s2) throw() { + int v = naturalSortCompareInternal( s1, s2, true ); + if (v) return v; + v = naturalSortCompareInternal( s1, s2, false ); + if (v) return v; + return strcmp(s1, s2); +} + +int naturalSortCompareI( const char * s1, const char * s2) throw() { + return naturalSortCompareInternal( s1, s2, true ); +} + + +format_float::format_float(double p_val,unsigned p_width,unsigned p_prec) +{ + char temp[64]; + float_to_string(temp,64,p_val,p_prec,false); + temp[63] = 0; + t_size len = strlen(temp); + if (len < p_width) + m_buffer.add_chars(' ',p_width-len); + m_buffer += temp; +} + +char format_hex_char(unsigned p_val) +{ + PFC_ASSERT(p_val < 16); + return (p_val < 10) ? p_val + '0' : p_val - 10 + 'A'; +} + +format_hex::format_hex(t_uint64 p_val,unsigned p_width) +{ + if (p_width > 16) p_width = 16; + else if (p_width == 0) p_width = 1; + char temp[16]; + unsigned n; + for(n=0;n<16;n++) + { + temp[15-n] = format_hex_char((unsigned)(p_val & 0xF)); + p_val >>= 4; + } + + for(n=0;n<16 && temp[n] == '0';n++) {} + + if (n > 16 - p_width) n = 16 - p_width; + + char * out = m_buffer; + for(;n<16;n++) + *(out++) = temp[n]; + *out = 0; +} + +char format_hex_char_lowercase(unsigned p_val) +{ + PFC_ASSERT(p_val < 16); + return (p_val < 10) ? p_val + '0' : p_val - 10 + 'a'; +} + +format_hex_lowercase::format_hex_lowercase(t_uint64 p_val,unsigned p_width) +{ + if (p_width > 16) p_width = 16; + else if (p_width == 0) p_width = 1; + char temp[16]; + unsigned n; + for(n=0;n<16;n++) + { + temp[15-n] = format_hex_char_lowercase((unsigned)(p_val & 0xF)); + p_val >>= 4; + } + + for(n=0;n<16 && temp[n] == '0';n++) {} + + if (n > 16 - p_width) n = 16 - p_width; + + char * out = m_buffer; + for(;n<16;n++) + *(out++) = temp[n]; + *out = 0; +} + +format_uint::format_uint(t_uint64 val,unsigned p_width,unsigned p_base) +{ + + enum {max_width = PFC_TABSIZE(m_buffer) - 1}; + + if (p_width > max_width) p_width = max_width; + else if (p_width == 0) p_width = 1; + + char temp[max_width]; + + unsigned n; + for(n=0;n max_width - p_width) n = max_width - p_width; + + char * out = m_buffer; + + for(;n max_width) p_width = max_width; + else if (p_width == 0) p_width = 1; + + if (neg && p_width > 1) p_width --; + + char temp[max_width]; + + unsigned n; + for(n=0;n max_width - p_width) n = max_width - p_width; + + char * out = m_buffer; + + if (neg) *(out++) = '-'; + + for(;n 0 && p_spacing != 0) m_formatter << p_spacing; + m_formatter << format_hex_lowercase(buffer[n],2); + } +} + +format_hexdump::format_hexdump(const void * p_buffer,t_size p_bytes,const char * p_spacing) +{ + t_size n; + const t_uint8 * buffer = (const t_uint8*)p_buffer; + for(n=0;n 0 && p_spacing != 0) m_formatter << p_spacing; + m_formatter << format_hex(buffer[n],2); + } +} + + + +string_replace_extension::string_replace_extension(const char * p_path,const char * p_ext) +{ + m_data = p_path; + t_size dot = m_data.find_last('.'); + if (dot < m_data.scan_filename()) + {//argh + m_data += "."; + m_data += p_ext; + } + else + { + m_data.truncate(dot+1); + m_data += p_ext; + } +} + +string_directory::string_directory(const char * p_path) +{ + t_size ptr = scan_filename(p_path); + if (ptr > 1) { + if (is_path_separator(p_path[ptr-1]) && !is_path_separator(p_path[ptr-2])) --ptr; + } + m_data.set_string(p_path,ptr); +} + +t_size scan_filename(const char * ptr) +{ + t_size n; + t_size _used = strlen(ptr); + for(n=_used-1;n!=~0;n--) + { + if (is_path_separator(ptr[n])) return n+1; + } + return 0; +} + + + +t_size string_find_first(const char * p_string,char p_tofind,t_size p_start) { + for(t_size walk = p_start; p_string[walk]; ++walk) { + if (p_string[walk] == p_tofind) return walk; + } + return ~0; +} +t_size string_find_last(const char * p_string,char p_tofind,t_size p_start) { + return string_find_last_ex(p_string,~0,&p_tofind,1,p_start); +} +t_size string_find_first(const char * p_string,const char * p_tofind,t_size p_start) { + return string_find_first_ex(p_string,~0,p_tofind,~0,p_start); +} +t_size string_find_last(const char * p_string,const char * p_tofind,t_size p_start) { + return string_find_last_ex(p_string,~0,p_tofind,~0,p_start); +} + +t_size string_find_first_ex(const char * p_string,t_size p_string_length,char p_tofind,t_size p_start) { + for(t_size walk = p_start; walk < p_string_length && p_string[walk]; ++walk) { + if (p_string[walk] == p_tofind) return walk; + } + return ~0; +} +t_size string_find_last_ex(const char * p_string,t_size p_string_length,char p_tofind,t_size p_start) { + return string_find_last_ex(p_string,p_string_length,&p_tofind,1,p_start); +} +t_size string_find_first_ex(const char * p_string,t_size p_string_length,const char * p_tofind,t_size p_tofind_length,t_size p_start) { + p_string_length = strlen_max(p_string,p_string_length); p_tofind_length = strlen_max(p_tofind,p_tofind_length); + if (p_string_length >= p_tofind_length) { + t_size max = p_string_length - p_tofind_length; + for(t_size walk = p_start; walk <= max; walk++) { + if (_strcmp_partial_ex(p_string+walk,p_string_length-walk,p_tofind,p_tofind_length) == 0) return walk; + } + } + return ~0; +} +t_size string_find_last_ex(const char * p_string,t_size p_string_length,const char * p_tofind,t_size p_tofind_length,t_size p_start) { + p_string_length = strlen_max(p_string,p_string_length); p_tofind_length = strlen_max(p_tofind,p_tofind_length); + if (p_string_length >= p_tofind_length) { + t_size max = min_t(p_string_length - p_tofind_length,p_start); + for(t_size walk = max; walk != (t_size)(-1); walk--) { + if (_strcmp_partial_ex(p_string+walk,p_string_length-walk,p_tofind,p_tofind_length) == 0) return walk; + } + } + return ~0; +} + +t_size string_find_first_nc(const char * p_string,t_size p_string_length,char c,t_size p_start) { + for(t_size walk = p_start; walk < p_string_length; walk++) { + if (p_string[walk] == c) return walk; + } + return ~0; +} + +t_size string_find_first_nc(const char * p_string,t_size p_string_length,const char * p_tofind,t_size p_tofind_length,t_size p_start) { + if (p_string_length >= p_tofind_length) { + t_size max = p_string_length - p_tofind_length; + for(t_size walk = p_start; walk <= max; walk++) { + if (memcmp(p_string+walk, p_tofind, p_tofind_length) == 0) return walk; + } + } + return ~0; +} + + +bool string_is_numeric(const char * p_string,t_size p_length) throw() { + bool retval = false; + for(t_size walk = 0; walk < p_length && p_string[walk] != 0; walk++) { + if (!char_is_numeric(p_string[walk])) {retval = false; break;} + retval = true; + } + return retval; +} + + +void string_base::end_with(char p_char) { + if (!ends_with(p_char)) add_byte(p_char); +} +bool string_base::ends_with(char c) const { + t_size length = get_length(); + return length > 0 && get_ptr()[length-1] == c; +} + +void string_base::end_with_slash() { + end_with( io::path::getDefaultSeparator() ); +} + +char string_base::last_char() const { + size_t l = this->length(); + if (l == 0) return 0; + return this->get_ptr()[l-1]; +} +void string_base::truncate_last_char() { + size_t l = this->length(); + if (l > 0) this->truncate( l - 1 ); +} + +void string_base::truncate_number_suffix() { + size_t l = this->length(); + const char * const p = this->get_ptr(); + while( l > 0 && char_is_numeric( p[l-1] ) ) --l; + truncate( l ); +} + +bool is_multiline(const char * p_string,t_size p_len) { + for(t_size n = 0; n < p_len && p_string[n]; n++) { + switch(p_string[n]) { + case '\r': + case '\n': + return true; + } + } + return false; +} + +static t_uint64 pow10_helper(unsigned p_extra) { + t_uint64 ret = 1; + for(unsigned n = 0; n < p_extra; n++ ) ret *= 10; + return ret; +} + +static uint64_t safeMulAdd(uint64_t prev, unsigned scale, uint64_t add) { + if (add >= scale || scale == 0) throw pfc::exception_invalid_params(); + uint64_t v = prev * scale + add; + if (v / scale != prev) throw pfc::exception_invalid_params(); + return v; +} + +static size_t parseNumber(const char * in, uint64_t & outNumber) { + size_t walk = 0; + uint64_t total = 0; + for (;;) { + char c = in[walk]; + if (!pfc::char_is_numeric(c)) break; + unsigned v = (unsigned)(c - '0'); + uint64_t newVal = total * 10 + v; + if (newVal / 10 != total) throw pfc::exception_overflow(); + total = newVal; + ++walk; + } + outNumber = total; + return walk; +} + +double parse_timecode(const char * in) { + char separator = 0; + uint64_t seconds = 0; + unsigned colons = 0; + for (;;) { + uint64_t number = 0; + size_t digits = parseNumber(in, number); + if (digits == 0) throw pfc::exception_invalid_params(); + in += digits; + char nextSeparator = *in; + switch (separator) { // *previous* separator + case '.': + if (nextSeparator != 0) throw pfc::exception_bug_check(); + return (double)seconds + (double)pfc::exp_int(10, -(int)digits) * number; + case 0: // is first number in the string + seconds = number; + break; + case ':': + if (colons == 2) throw pfc::exception_invalid_params(); + ++colons; + seconds = safeMulAdd(seconds, 60, number); + break; + } + + if (nextSeparator == 0) { + // end of string + return (double)seconds; + } + + ++in; + separator = nextSeparator; + } +} + +format_time_ex::format_time_ex(double p_seconds,unsigned p_extra) { + if (p_seconds < 0) {m_buffer << "-"; p_seconds = -p_seconds;} + t_uint64 pow10 = pow10_helper(p_extra); + t_uint64 ticks = pfc::rint64(pow10 * p_seconds); + + m_buffer << pfc::format_time(ticks / pow10); + if (p_extra>0) { + m_buffer << "." << pfc::format_uint(ticks % pow10, p_extra); + } +} + +void stringToUpperAppend(string_base & out, const char * src, t_size len) { + while(len && *src) { + unsigned c; t_size d; + d = utf8_decode_char(src,c,len); + if (d==0 || d>len) break; + out.add_char(charUpper(c)); + src+=d; + len-=d; + } +} +void stringToLowerAppend(string_base & out, const char * src, t_size len) { + while(len && *src) { + unsigned c; t_size d; + d = utf8_decode_char(src,c,len); + if (d==0 || d>len) break; + out.add_char(charLower(c)); + src+=d; + len-=d; + } +} +int stringCompareCaseInsensitiveEx(string_part_ref s1, string_part_ref s2) { + t_size w1 = 0, w2 = 0; + for(;;) { + unsigned c1, c2; t_size d1, d2; + d1 = utf8_decode_char(s1.m_ptr + w1, c1, s1.m_len - w1); + d2 = utf8_decode_char(s2.m_ptr + w2, c2, s2.m_len - w2); + if (d1 == 0 && d2 == 0) return 0; + else if (d1 == 0) return -1; + else if (d2 == 0) return 1; + else { + c1 = charLower(c1); c2 = charLower(c2); + if (c1 < c2) return -1; + else if (c1 > c2) return 1; + } + w1 += d1; w2 += d2; + } +} +int stringCompareCaseInsensitive(const char * s1, const char * s2) { + for(;;) { + unsigned c1, c2; t_size d1, d2; + d1 = utf8_decode_char(s1,c1); + d2 = utf8_decode_char(s2,c2); + if (d1 == 0 && d2 == 0) return 0; + else if (d1 == 0) return -1; + else if (d2 == 0) return 1; + else { + c1 = charLower(c1); c2 = charLower(c2); + if (c1 < c2) return -1; + else if (c1 > c2) return 1; + } + s1 += d1; s2 += d2; + } +} + +void format_file_size_short::format(t_uint64 size) { + t_uint64 scale = 1; + const char * unit = "B"; + const char * const unitTable[] = {"B","KB","MB","GB","TB"}; + for(t_size walk = 1; walk < PFC_TABSIZE(unitTable); ++walk) { + t_uint64 next = scale * 1024; + if (size < next) break; + scale = next; unit = unitTable[walk]; + } + *this << ( size / scale ); + + if (scale > 1 && length() < 3) { + t_size digits = 3 - length(); + const t_uint64 mask = pow_int(10,digits); + t_uint64 remaining = ( (size * mask / scale) % mask ); + while(digits > 0 && (remaining % 10) == 0) { + remaining /= 10; --digits; + } + if (digits > 0) { + *this << "." << format_uint(remaining, (t_uint32)digits); + } + } + *this << " " << unit; + m_scale = scale; +} + +bool string_base::truncate_eol(t_size start) +{ + const char * ptr = get_ptr() + start; + for(t_size n=start;*ptr;n++) + { + if (*ptr==10 || *ptr==13) + { + truncate(n); + return true; + } + ptr++; + } + return false; +} + +bool string_base::fix_eol(const char * append,t_size start) +{ + const bool rv = truncate_eol(start); + if (rv) add_string(append); + return rv; +} + +bool string_base::limit_length(t_size length_in_chars,const char * append) +{ + bool rv = false; + const char * base = get_ptr(), * ptr = base; + while(length_in_chars && utf8_advance(ptr)) length_in_chars--; + if (length_in_chars==0) + { + truncate(ptr-base); + add_string(append); + rv = true; + } + return rv; +} + +void string_base::truncate_to_parent_path() { + size_t at = scan_filename(); +#ifdef _WIN32 + while(at > 0 && (*this)[at-1] == '\\') --at; + if (at > 0 && (*this)[at-1] == ':' && (*this)[at] == '\\') ++at; +#else + // Strip trailing / + while(at > 0 && (*this)[at-1] == '/') --at; + + // Hit empty? Bring root / back to life + if (at == 0 && (*this)[0] == '/') ++at; + + // Deal with proto:// + if (at > 0 && (*this)[at-1] == ':') { + while((*this)[at] == '/') ++at; + } +#endif + this->truncate( at ); +} + +size_t string_base::replace_string(const char * replace, const char * replaceWith, t_size start) { + string_formatter temp; + size_t ret = replace_string_ex(temp, replace, replaceWith, start); + if ( ret > 0 ) * this = temp; + return ret; +} +size_t string_base::replace_string_ex (string_base & temp, const char * replace, const char * replaceWith, t_size start) const { + size_t srcDone = 0, walk = start; + size_t occurances = 0; + const char * const source = this->get_ptr(); + bool clear = false; + const size_t replaceLen = strlen( replace ); + for(;;) { + const char * ptr = strstr( source + walk, replace ); + if (ptr == NULL) { + // end + if (srcDone == 0) { + return 0; // string not altered + } + temp.add_string( source + srcDone ); + break; + } + ++occurances; + walk = ptr - source; + if (! clear ) { + temp.reset(); + clear = true; + } + temp.add_string( source + srcDone, walk - srcDone ); + temp.add_string( replaceWith ); + walk += replaceLen; + srcDone = walk; + } + return occurances; +} + +void urlEncodeAppendRaw(pfc::string_base & out, const char * in, t_size inSize) { + for(t_size walk = 0; walk < inSize; ++walk) { + const char c = in[walk]; + if (c == ' ') out.add_byte('+'); + else if (pfc::char_is_ascii_alphanumeric(c) || c == '_') out.add_byte(c); + else out << "%" << pfc::format_hex((t_uint8)c, 2); + } +} +void urlEncodeAppend(pfc::string_base & out, const char * in) { + for(;;) { + const char c = *(in++); + if (c == 0) break; + else if (c == ' ') out.add_byte('+'); + else if (pfc::char_is_ascii_alphanumeric(c) || c == '_') out.add_byte(c); + else out << "%" << pfc::format_hex((t_uint8)c, 2); + } +} +void urlEncode(pfc::string_base & out, const char * in) { + out.reset(); urlEncodeAppend(out, in); +} + +unsigned char_to_dec(char c) { + if (c >= '0' && c <= '9') return (unsigned)(c - '0'); + else throw exception_invalid_params(); +} + +unsigned char_to_hex(char c) { + if (c >= '0' && c <= '9') return (unsigned)(c - '0'); + else if (c >= 'a' && c <= 'f') return (unsigned)(c - 'a' + 10); + else if (c >= 'A' && c <= 'F') return (unsigned)(c - 'A' + 10); + else throw exception_invalid_params(); +} + + +static const t_uint8 ascii_tolower_table[128] = {0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0A,0x0B,0x0C,0x0D,0x0E,0x0F,0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0x1A,0x1B,0x1C,0x1D,0x1E,0x1F,0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27,0x28,0x29,0x2A,0x2B,0x2C,0x2D,0x2E,0x2F,0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39,0x3A,0x3B,0x3C,0x3D,0x3E,0x3F,0x40,0x61,0x62,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6A,0x6B,0x6C,0x6D,0x6E,0x6F,0x70,0x71,0x72,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7A,0x5B,0x5C,0x5D,0x5E,0x5F,0x60,0x61,0x62,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6A,0x6B,0x6C,0x6D,0x6E,0x6F,0x70,0x71,0x72,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7A,0x7B,0x7C,0x7D,0x7E,0x7F}; + +uint32_t charLower(uint32_t param) +{ + if (param<128) { + return ascii_tolower_table[param]; + } +#ifdef PFC_WINDOWS_DESKTOP_APP + else if (param<0x10000) { + return (uint32_t)(size_t)CharLowerW((WCHAR*)(size_t)param); + } +#endif + else return param; +} + +uint32_t charUpper(uint32_t param) +{ + if (param<128) { + if (param>='a' && param<='z') param += 'A' - 'a'; + return param; + } +#ifdef PFC_WINDOWS_DESKTOP_APP + else if (param<0x10000) { + return (uint32_t)(size_t)CharUpperW((WCHAR*)(size_t)param); + } +#endif + else return param; +} + + +bool stringEqualsI_ascii(const char * p1,const char * p2) throw() { + for(;;) + { + char c1 = *p1; + char c2 = *p2; + if (c1 > 0 && c2 > 0) { + if (ascii_tolower_table[ (unsigned) c1 ] != ascii_tolower_table[ (unsigned) c2 ]) return false; + } else { + if (c1 == 0 && c2 == 0) return true; + if (c1 == 0 || c2 == 0) return false; + if (c1 != c2) return false; + } + ++p1; ++p2; + } +} + +bool stringEqualsI_utf8(const char * p1,const char * p2) throw() +{ + for(;;) + { + char c1 = *p1; + char c2 = *p2; + if (c1 > 0 && c2 > 0) { + if (ascii_tolower_table[ (unsigned) c1 ] != ascii_tolower_table[ (unsigned) c2 ]) return false; + ++p1; ++p2; + } else { + if (c1 == 0 && c2 == 0) return true; + if (c1 == 0 || c2 == 0) return false; + unsigned w1,w2; t_size d1,d2; + d1 = utf8_decode_char(p1,w1); + d2 = utf8_decode_char(p2,w2); + if (d1 == 0 || d2 == 0) return false; // bad UTF-8, bail + if (w1 != w2) { + if (charLower(w1) != charLower(w2)) return false; + } + p1 += d1; + p2 += d2; + } + } +} + +char ascii_tolower_lookup(char c) { + PFC_ASSERT( c >= 0); + return (char)ascii_tolower_table[ (unsigned) c ]; +} + +void string_base::fix_dir_separator(char c) { +#ifdef _WIN32 + end_with(c); +#else + end_with_slash(); +#endif +} + + + bool string_has_prefix( const char * string, const char * prefix ) { + for(size_t w = 0; ; ++w ) { + char c = prefix[w]; + if (c == 0) return true; + if (string[w] != c) return false; + } + } + bool string_has_prefix_i( const char * string, const char * prefix ) { + const char * p1 = string; const char * p2 = prefix; + for(;;) { + unsigned w1, w2; size_t d1, d2; + d1 = utf8_decode_char(p1, w1); + d2 = utf8_decode_char(p2, w2); + if (d2 == 0) return true; + if (d1 == 0) return false; + if (w1 != w2) { + if (charLower(w1) != charLower(w2)) return false; + } + p1 += d1; p2 += d2; + } + } + bool string_has_suffix( const char * string, const char * suffix ) { + size_t len = strlen( string ); + size_t suffixLen = strlen( suffix ); + if (suffixLen > len) return false; + size_t base = len - suffixLen; + return memcmp( string + base, suffix, suffixLen * sizeof(char)) == 0; + } + bool string_has_suffix_i( const char * string, const char * suffix ) { + for(;;) { + if (*string == 0) return false; + if (stringEqualsI_utf8( string, suffix )) return true; + if (!utf8_advance(string)) return false; + } + } + + char * strDup(const char * src) { +#ifdef _MSC_VER + return _strdup(src); +#else + return strdup(src); +#endif + } + + + string_part_ref string_part_ref::make(const char * ptr, t_size len) { + string_part_ref val = {ptr, len}; return val; + } + + string_part_ref string_part_ref::substring(t_size base) const { + PFC_ASSERT( base <= m_len ); + return make(m_ptr + base, m_len - base); + } + string_part_ref string_part_ref::substring(t_size base, t_size len) const { + PFC_ASSERT( base <= m_len && base + len <= m_len ); + return make(m_ptr + base, len); + } + + string_part_ref string_part_ref::make( const char * str ) {return make( str, strlen(str) ); } + + bool string_part_ref::equals( string_part_ref other ) const { + if ( other.m_len != this->m_len ) return false; + return memcmp( other.m_ptr, this->m_ptr, m_len ) == 0; + } + bool string_part_ref::equals( const char * str ) const { + return equals(make(str) ); + } + + string8 lineEndingsToWin(const char * str) { + string8 ret; + const char * walk = str; + for( ;; ) { + const char * eol = strchr( walk, '\n' ); + if ( eol == nullptr ) { + ret += walk; break; + } + const char * next = eol + 1; + if ( eol > walk ) { + if (eol[-1] == '\r') --eol; + if ( eol > walk ) ret.add_string_nc(walk, eol-walk); + } + ret.add_string_nc("\r\n",2); + walk = next; + } + return ret; + } + + string8 stringToUpper(const char * str, size_t len) { + string8 ret; + stringToUpperAppend(ret, str, len); + return ret; + } + string8 stringToLower(const char * str, size_t len) { + string8 ret; + stringToLowerAppend(ret, str, len); + return ret; + } + +} //namespace pfc diff --git a/pfc/string_base.h b/pfc/string_base.h new file mode 100644 index 0000000..33aea19 --- /dev/null +++ b/pfc/string_base.h @@ -0,0 +1,1112 @@ +#pragma once + +#include + +namespace pfc { + inline t_size _strParamLen(const char * str) { + return strlen(str); + } + + + struct string_part_ref { + const char * m_ptr; + t_size m_len; + + + static string_part_ref make(const char * ptr, t_size len); + string_part_ref substring(t_size base) const; + string_part_ref substring(t_size base, t_size len) const; + static string_part_ref make( const char * str ); + bool equals( string_part_ref other ) const; + bool equals( const char * str ) const; + }; + + inline string_part_ref string_part(const char * ptr, t_size len) { + string_part_ref val = {ptr, len}; return val; + } + + + class NOVTABLE string_receiver { + public: + virtual void add_string(const char * p_string,t_size p_string_size = SIZE_MAX) = 0; + inline void add_string_(const char * str) {add_string(str, _strParamLen(str));} + + void add_char(t_uint32 c);//adds unicode char to the string + void add_byte(char c) {add_string(&c,1);} + void add_chars(t_uint32 p_char,t_size p_count) {for(;p_count;p_count--) add_char(p_char);} + protected: + string_receiver() {} + ~string_receiver() {} + }; + + t_size scan_filename(const char * ptr); + + bool is_path_separator(unsigned c); + bool is_path_bad_char(unsigned c); + bool is_valid_utf8(const char * param,t_size max = SIZE_MAX); + bool is_lower_ascii(const char * param); + bool is_multiline(const char * p_string,t_size p_len = SIZE_MAX); + bool has_path_bad_chars(const char * param); + void recover_invalid_utf8(const char * src,char * out,unsigned replace);//out must be enough to hold strlen(char) + 1, or appropiately bigger if replace needs multiple chars + void convert_to_lower_ascii(const char * src,t_size max,char * out,char replace = '?');//out should be at least strlen(src)+1 long + + template inline char_t ascii_tolower(char_t c) {if (c >= 'A' && c <= 'Z') c += 'a' - 'A'; return c;} + template inline char_t ascii_toupper(char_t c) {if (c >= 'a' && c <= 'z') c += 'A' - 'a'; return c;} + + t_size string_find_first(const char * p_string,char p_tofind,t_size p_start = 0); //returns infinite if not found + t_size string_find_last(const char * p_string,char p_tofind,t_size p_start = SIZE_MAX); //returns infinite if not found + t_size string_find_first(const char * p_string,const char * p_tofind,t_size p_start = 0); //returns infinite if not found + t_size string_find_last(const char * p_string,const char * p_tofind,t_size p_start = SIZE_MAX); //returns infinite if not found + + t_size string_find_first_ex(const char * p_string,t_size p_string_length,char p_tofind,t_size p_start = 0); //returns infinite if not found + t_size string_find_last_ex(const char * p_string,t_size p_string_length,char p_tofind,t_size p_start = SIZE_MAX); //returns infinite if not found + t_size string_find_first_ex(const char * p_string,t_size p_string_length,const char * p_tofind,t_size p_tofind_length,t_size p_start = 0); //returns infinite if not found + t_size string_find_last_ex(const char * p_string,t_size p_string_length,const char * p_tofind,t_size p_tofind_length,t_size p_start = SIZE_MAX); //returns infinite if not found + + + t_size string_find_first_nc(const char * p_string,t_size p_string_length,char c,t_size p_start = 0); // lengths MUST be valid, no checks are performed (faster than the other flavour) + t_size string_find_first_nc(const char * p_string,t_size p_string_length,const char * p_tofind,t_size p_tofind_length,t_size p_start = 0); // lengths MUST be valid, no checks are performed (faster than the other falvour); + + + bool string_has_prefix( const char * string, const char * prefix ); + bool string_has_prefix_i( const char * string, const char * prefix ); + bool string_has_suffix( const char * string, const char * suffix ); + bool string_has_suffix_i( const char * string, const char * suffix ); + + template + t_size strlen_max_t(const t_char * ptr,t_size max) { + PFC_ASSERT( ptr != NULL || max == 0 ); + t_size n = 0; + while(n inline bool char_is_numeric(char_t p_char) throw() {return p_char >= '0' && p_char <= '9';} + inline bool char_is_hexnumeric(char p_char) throw() {return char_is_numeric(p_char) || (p_char >= 'a' && p_char <= 'f') || (p_char >= 'A' && p_char <= 'F');} + inline bool char_is_ascii_alpha_upper(char p_char) throw() {return p_char >= 'A' && p_char <= 'Z';} + inline bool char_is_ascii_alpha_lower(char p_char) throw() {return p_char >= 'a' && p_char <= 'z';} + inline bool char_is_ascii_alpha(char p_char) throw() {return char_is_ascii_alpha_lower(p_char) || char_is_ascii_alpha_upper(p_char);} + inline bool char_is_ascii_alphanumeric(char p_char) throw() {return char_is_ascii_alpha(p_char) || char_is_numeric(p_char);} + + unsigned atoui_ex(const char * ptr,t_size max); + t_int64 atoi64_ex(const char * ptr,t_size max); + t_uint64 atoui64_ex(const char * ptr,t_size max); + + //Throws exception_invalid_params on failure. + unsigned char_to_hex(char c); + unsigned char_to_dec(char c); + + //Throws exception_invalid_params or exception_overflow on failure. + template t_uint atohex(const char * in, t_size inLen) { + t_uint ret = 0; + const t_uint guard = (t_uint)0xF << (sizeof(t_uint) * 8 - 4); + for(t_size walk = 0; walk < inLen; ++walk) { + if (ret & guard) throw exception_overflow(); + ret = (ret << 4) | char_to_hex(in[walk]); + } + return ret; + } + template t_uint atodec(const char * in, t_size inLen) { + t_uint ret = 0; + for(t_size walk = 0; walk < inLen; ++walk) { + const t_uint prev = ret; + ret = (ret * 10) + char_to_dec(in[walk]); + if ((ret / 10) != prev) throw exception_overflow(); + } + return ret; + } + + t_size strlen_utf8(const char * s,t_size num = SIZE_MAX) throw();//returns number of characters in utf8 string; num - no. of bytes (optional) + t_size utf8_char_len(const char * s,t_size max = SIZE_MAX) throw();//returns size of utf8 character pointed by s, in bytes, 0 on error + t_size utf8_char_len_from_header(char c) throw(); + t_size utf8_chars_to_bytes(const char * string,t_size count) throw(); + + t_size strcpy_utf8_truncate(const char * src,char * out,t_size maxbytes); + + template void strcpy_t( char_t * out, const char_t * in ) { + for(;;) { char_t c = *in++; *out++ = c; if (c == 0) break; } + } + + t_size utf8_decode_char(const char * src,unsigned & out,t_size src_bytes) throw();//returns length in bytes + t_size utf8_decode_char(const char * src,unsigned & out) throw();//returns length in bytes + + t_size utf8_encode_char(unsigned c,char * out) throw();//returns used length in bytes, max 6 + + + t_size utf16_decode_char(const char16_t * p_source,unsigned * p_out,t_size p_source_length = SIZE_MAX) throw(); + t_size utf16_encode_char(unsigned c,char16_t * out) throw(); + +#ifdef _MSC_VER + t_size utf16_decode_char(const wchar_t * p_source,unsigned * p_out,t_size p_source_length = SIZE_MAX) throw(); + t_size utf16_encode_char(unsigned c,wchar_t * out) throw(); +#endif + + t_size wide_decode_char(const wchar_t * p_source,unsigned * p_out,t_size p_source_length = SIZE_MAX) throw(); + t_size wide_encode_char(unsigned c,wchar_t * out) throw(); + + + t_size strstr_ex(const char * p_string,t_size p_string_len,const char * p_substring,t_size p_substring_len) throw(); + + + t_size skip_utf8_chars(const char * ptr,t_size count) throw(); + char * strdup_n(const char * src,t_size len); + int wstricmp_ascii( const wchar_t * s1, const wchar_t * s2 ) throw(); + int stricmp_ascii(const char * s1,const char * s2) throw(); + int stricmp_ascii_ex(const char * s1,t_size len1,const char * s2,t_size len2) throw(); + int naturalSortCompare( const char * s1, const char * s2) throw(); + int naturalSortCompareI( const char * s1, const char * s2) throw(); + + int strcmp_ex(const char* p1,t_size n1,const char* p2,t_size n2) throw(); + int strcmp_nc(const char* p1, size_t n1, const char * p2, size_t n2) throw(); + + unsigned utf8_get_char(const char * src); + + inline bool utf8_advance(const char * & var) throw() { + t_size delta = utf8_char_len(var); + var += delta; + return delta>0; + } + + inline bool utf8_advance(char * & var) throw() { + t_size delta = utf8_char_len(var); + var += delta; + return delta>0; + } + + inline const char * utf8_char_next(const char * src) throw() {return src + utf8_char_len(src);} + inline char * utf8_char_next(char * src) throw() {return src + utf8_char_len(src);} + + class NOVTABLE string_base : public pfc::string_receiver { + public: + virtual const char * get_ptr() const = 0; + const char * c_str() const { return get_ptr(); } + virtual void add_string(const char * p_string,t_size p_length = SIZE_MAX) = 0;//same as string_receiver method + virtual void set_string(const char * p_string,t_size p_length = SIZE_MAX) {reset();add_string(p_string,p_length);} + virtual void truncate(t_size len)=0; + virtual t_size get_length() const {return strlen(get_ptr());} + virtual char * lock_buffer(t_size p_requested_length) = 0; + virtual void unlock_buffer() = 0; + + void set_string_(const char * str) {set_string(str, _strParamLen(str));} + + inline const char * toString() const {return get_ptr();} + + //! For compatibility with old conventions. + inline t_size length() const {return get_length();} + + inline void reset() {truncate(0);} + inline void clear() {truncate(0);} + + inline bool is_empty() const {return *get_ptr()==0;} + + void skip_trailing_chars( const char * lstChars ); + void skip_trailing_char(unsigned c = ' '); + + bool is_valid_utf8() const {return pfc::is_valid_utf8(get_ptr());} + + void convert_to_lower_ascii(const char * src,char replace = '?'); + + inline const string_base & operator= (const char * src) {set_string_(src);return *this;} + inline const string_base & operator+= (const char * src) {add_string_(src);return *this;} + inline const string_base & operator= (const string_base & src) {set_string(src);return *this;} + inline const string_base & operator+= (const string_base & src) {add_string(src);return *this;} + + bool operator==(const string_base & p_other) const {return strcmp(*this,p_other) == 0;} + bool operator!=(const string_base & p_other) const {return strcmp(*this,p_other) != 0;} + bool operator>(const string_base & p_other) const {return strcmp(*this,p_other) > 0;} + bool operator<(const string_base & p_other) const {return strcmp(*this,p_other) < 0;} + bool operator>=(const string_base & p_other) const {return strcmp(*this,p_other) >= 0;} + bool operator<=(const string_base & p_other) const {return strcmp(*this,p_other) <= 0;} + + inline operator const char * () const {return get_ptr();} + + t_size scan_filename() const {return pfc::scan_filename(get_ptr());} + + t_size find_first(char p_char,t_size p_start = 0) const {return pfc::string_find_first(get_ptr(),p_char,p_start);} + t_size find_last(char p_char,t_size p_start = SIZE_MAX) const {return pfc::string_find_last(get_ptr(),p_char,p_start);} + t_size find_first(const char * p_string,t_size p_start = 0) const {return pfc::string_find_first(get_ptr(),p_string,p_start);} + t_size find_last(const char * p_string,t_size p_start = SIZE_MAX) const {return pfc::string_find_last(get_ptr(),p_string,p_start);} + + void fix_dir_separator(char c = '\\'); // Backwards compat function, "do what I mean" applied on non Windows + void end_with(char c); + void end_with_slash(); + bool ends_with(char c) const; + void delimit(const char* c) {if (length()>0) add_string(c);} + char last_char() const; + void truncate_last_char(); + void truncate_number_suffix(); + + bool truncate_eol(t_size start = 0); + bool fix_eol(const char * append = " (...)",t_size start = 0); + bool limit_length(t_size length_in_chars,const char * append = " (...)"); + + void truncate_filename() {truncate(scan_filename());} + void truncate_to_parent_path(); + void add_filename( const char * fn ) {end_with_slash(); *this += fn; } + + //! Replaces one string with another. Returns the number of occurances - zero if the string was not altered. + size_t replace_string ( const char * replace, const char * replaceWith, t_size start = 0); + //! Replaces one string with another, writing the output to another string object. \n + //! Returns the number of occurances replaced. \n + //! Special: returns zero if no occurances were found - and the target string is NOT modified if so. Use with care! + size_t replace_string_ex( pfc::string_base & target, const char * replace, const char * replaceWith, t_size start = 0) const; + + string_base & _formatter() const {return const_cast(*this);} + + bool has_prefix( const char * prefix) const { return string_has_prefix( get_ptr(), prefix ); } + bool has_prefix_i( const char * prefix ) const { return string_has_prefix_i( get_ptr(), prefix); } + bool has_suffix( const char * suffix ) const { return string_has_suffix( get_ptr(), suffix); } + bool has_suffix_i( const char * suffix ) const { return string_has_suffix_i( get_ptr(), suffix); } + + bool equals( const char * other ) const { return strcmp(*this, other) == 0; } + protected: + string_base() {} + ~string_base() {} + }; + + template + class string_fixed_t : public pfc::string_base { + public: + inline string_fixed_t() {init();} + inline string_fixed_t(const string_fixed_t & p_source) {init(); *this = p_source;} + inline string_fixed_t(const char * p_source) {init(); set_string(p_source);} + + inline const string_fixed_t & operator=(const string_fixed_t & p_source) {set_string(p_source);return *this;} + inline const string_fixed_t & operator=(const char * p_source) {set_string(p_source);return *this;} + + char * lock_buffer(t_size p_requested_length) { + if (p_requested_length >= max_length) return NULL; + memset(m_data,0,sizeof(m_data)); + return m_data; + } + void unlock_buffer() { + m_length = strlen(m_data); + } + + inline operator const char * () const {return m_data;} + + const char * get_ptr() const {return m_data;} + + void add_string(const char * ptr,t_size len) { + len = strlen_max(ptr,len); + if (m_length + len < m_length || m_length + len > max_length) throw pfc::exception_overflow(); + for(t_size n=0;n max_length) len = max_length; + if (m_length > len) { + m_length = len; + m_data[len] = 0; + } + } + t_size get_length() const {return m_length;} + private: + inline void init() { + PFC_STATIC_ASSERT(max_length>1); + m_length = 0; m_data[0] = 0; + } + t_size m_length; + char m_data[max_length+1]; + }; + + template class t_alloc> + class string8_t : public pfc::string_base { + private: + typedef string8_t t_self; + protected: + pfc::array_t m_data; + t_size used; + + inline void makespace(t_size s) { + if (t_alloc::alloc_prioritizes_speed) { + m_data.set_size(s); + } else { + const t_size old_size = m_data.get_size(); + if (old_size < s) + m_data.set_size(s + 16); + else if (old_size > s + 32) + m_data.set_size(s); + } + } + + inline const char * _get_ptr() const throw() {return used > 0 ? m_data.get_ptr() : "";} + + public: + inline void set_string_(const char * str) {set_string_nc(str, strlen(str));} + inline void add_string_(const char * str) {add_string_nc(str, strlen(str));} + void set_string_nc(const char * ptr, t_size len) { + PFC_ASSERT(! m_data.is_owned(ptr) ); + PFC_ASSERT( strlen_max(ptr, len) == len ); + makespace(len+1); + pfc::memcpy_t(m_data.get_ptr(),ptr,len); + used=len; + m_data[used]=0; + } + void add_string_nc(const char * ptr, t_size len) { + PFC_ASSERT(! m_data.is_owned(ptr) ); + PFC_ASSERT( strlen_max(ptr, len) == len ); + makespace(used+len+1); + pfc::memcpy_t(m_data.get_ptr() + used,ptr,len); + used+=len; + m_data[used]=0; + } + inline const t_self & operator= (const char * src) {set_string_(src);return *this;} + inline const t_self & operator+= (const char * src) {add_string_(src);return *this;} + inline const t_self & operator= (const string_base & src) {set_string(src);return *this;} + inline const t_self & operator+= (const string_base & src) {add_string(src);return *this;} + inline const t_self & operator= (const t_self & src) {set_string(src);return *this;} + inline const t_self & operator+= (const t_self & src) {add_string(src);return *this;} + + inline const t_self & operator= (string_part_ref src) {set_string(src);return *this;} + inline const t_self & operator+= (string_part_ref src) {add_string(src);return *this;} + + inline operator const char * () const throw() {return _get_ptr();} + + string8_t() : used(0) {} + string8_t(const char * p_string) : used(0) {set_string_(p_string);} + string8_t(const char * p_string,t_size p_length) : used(0) {set_string(p_string,p_length);} + string8_t(const t_self & p_string) : used(0) {set_string(p_string);} + string8_t(const string_base & p_string) : used(0) {set_string(p_string);} + string8_t(string_part_ref ref) : used(0) {set_string(ref);} + + void prealloc(t_size p_size) {m_data.prealloc(p_size+1);} + + const char * get_ptr() const throw() {return _get_ptr();} + + void add_string(const char * p_string,t_size p_length = SIZE_MAX); + void set_string(const char * p_string,t_size p_length = SIZE_MAX); + + void set_string(string_part_ref ref) {set_string_nc(ref.m_ptr, ref.m_len);} + void add_string(string_part_ref ref) {add_string_nc(ref.m_ptr, ref.m_len);} + + void truncate(t_size len) + { + if (used>len) {used=len;m_data[len]=0;makespace(used+1);} + } + + t_size get_length() const throw() {return used;} + + + void set_char(unsigned offset,char c); + + t_size replace_nontext_chars(char p_replace = '_'); + t_size replace_char(unsigned c1,unsigned c2,t_size start = 0); + t_size replace_byte(char c1,char c2,t_size start = 0); + void fix_filename_chars(char def = '_',char leave=0);//replace "bad" characters, leave parameter can be used to keep eg. path separators + void remove_chars(t_size first,t_size count); //slow + void insert_chars(t_size first,const char * src, t_size count);//slow + void insert_chars(t_size first,const char * src); + + //for string_buffer class + char * lock_buffer(t_size n) + { + if (n + 1 == 0) throw exception_overflow(); + makespace(n+1); + pfc::memset_t(m_data,(char)0); + return m_data.get_ptr();; + } + + void unlock_buffer() { + if (m_data.get_size() > 0) { + used=strlen(m_data.get_ptr()); + makespace(used+1); + } + } + + void force_reset() {used=0;m_data.force_reset();} + + inline static void g_swap(t_self & p_item1,t_self & p_item2) { + pfc::swap_t(p_item1.m_data,p_item2.m_data); + pfc::swap_t(p_item1.used,p_item2.used); + } + }; + + typedef string8_t string8; + typedef string8_t string8_fast; + typedef string8_t string8_fast_aggressive; + //for backwards compatibility + typedef string8_t string8_fastalloc; + + + template class t_alloc> class traits_t > : public pfc::combine_traits > > { + public: + enum { + needs_constructor = true, + }; + }; +} + + + +#include "string8_impl.h" + +#define PFC_DEPRECATE_PRINTF PFC_DEPRECATE("Use string8/string_fixed_t with operator<< overloads instead.") + +namespace pfc { + + class string_buffer { + private: + string_base & m_owner; + char * m_buffer; + public: + explicit string_buffer(string_base & p_string,t_size p_requested_length) : m_owner(p_string) {m_buffer = m_owner.lock_buffer(p_requested_length);} + ~string_buffer() {m_owner.unlock_buffer();} + char * get_ptr() {return m_buffer;} + operator char* () {return m_buffer;} + }; + + class PFC_DEPRECATE_PRINTF string_printf : public string8_fastalloc { + public: + static void g_run(string_base & out,const char * fmt,va_list list); + void run(const char * fmt,va_list list); + + explicit string_printf(const char * fmt,...); + }; + + class PFC_DEPRECATE_PRINTF string_printf_va : public string8_fastalloc { + public: + string_printf_va(const char * fmt,va_list list); + }; + + class format_time { + public: + format_time(t_uint64 p_seconds); + const char * get_ptr() const {return m_buffer;} + operator const char * () const {return m_buffer;} + protected: + string_fixed_t<127> m_buffer; + }; + + + class format_time_ex { + public: + format_time_ex(double p_seconds,unsigned p_extra = 3); + const char * get_ptr() const {return m_buffer;} + operator const char * () const {return m_buffer;} + private: + string_fixed_t<127> m_buffer; + }; + + + double parse_timecode( const char * tc ); + + class string_filename : public string8 { + public: + explicit string_filename(const char * fn); + }; + + class string_filename_ext : public string8 { + public: + explicit string_filename_ext(const char * fn); + }; + + class string_extension + { + char buffer[32]; + public: + inline const char * get_ptr() const {return buffer;} + inline t_size length() const {return strlen(buffer);} + inline operator const char * () const {return buffer;} + inline const char * toString() const {return buffer;} + explicit string_extension(const char * src); + }; + + size_t find_extension_offset( const char * path ); + + const char * filename_ext_v2(const char * fn, char slash = 0); + + class string_replace_extension + { + public: + string_replace_extension(const char * p_path,const char * p_ext); + inline operator const char*() const {return m_data;} + private: + string8 m_data; + }; + + class string_directory + { + public: + string_directory(const char * p_path); + inline operator const char*() const {return m_data;} + private: + string8 m_data; + }; + + void float_to_string(char * out,t_size out_max,double val,unsigned precision,bool force_sign = false);//doesnt add E+X etc, has internal range limits, useful for storing float numbers as strings without having to bother with international coma/dot settings BS + double string_to_float(const char * src,t_size len = SIZE_MAX); + + template<> + inline void swap_t(string8 & p_item1,string8 & p_item2) + { + string8::g_swap(p_item1,p_item2); + } + + class format_float + { + public: + format_float(double p_val,unsigned p_width = 0,unsigned p_prec = 7); + format_float(const format_float & p_source) {*this = p_source;} + + inline const char * get_ptr() const {return m_buffer.get_ptr();} + inline const char * toString() const {return m_buffer.get_ptr();} + inline operator const char*() const {return m_buffer.get_ptr();} + private: + string8 m_buffer; + }; + + class format_int + { + public: + format_int(t_int64 p_val,unsigned p_width = 0,unsigned p_base = 10); + format_int(const format_int & p_source) {*this = p_source;} + inline const char * get_ptr() const {return m_buffer;} + inline const char * toString() const {return m_buffer;} + inline operator const char*() const {return m_buffer;} + private: + char m_buffer[64]; + }; + + + class format_uint { + public: + format_uint(t_uint64 p_val,unsigned p_width = 0,unsigned p_base = 10); + format_uint(const format_uint & p_source) {*this = p_source;} + inline const char * get_ptr() const {return m_buffer;} + inline const char * toString() const {return m_buffer;} + inline operator const char*() const {return m_buffer;} + private: + char m_buffer[64]; + }; + + class format_hex + { + public: + format_hex(t_uint64 p_val,unsigned p_width = 0); + format_hex(const format_hex & p_source) {*this = p_source;} + inline const char * get_ptr() const {return m_buffer;} + inline const char * toString() const {return m_buffer;} + inline operator const char*() const {return m_buffer;} + private: + char m_buffer[17]; + }; + + class format_hex_lowercase + { + public: + format_hex_lowercase(t_uint64 p_val,unsigned p_width = 0); + format_hex_lowercase(const format_hex_lowercase & p_source) {*this = p_source;} + inline const char * get_ptr() const {return m_buffer;} + inline operator const char*() const {return m_buffer;} + inline const char * toString() const {return m_buffer;} + private: + char m_buffer[17]; + }; + + char format_hex_char_lowercase(unsigned p_val); + char format_hex_char(unsigned p_val); + + + typedef string8_fastalloc string_formatter; +#define PFC_string_formatter() ::pfc::string_formatter()._formatter() + + class format_hexdump + { + public: + format_hexdump(const void * p_buffer,t_size p_bytes,const char * p_spacing = " "); + + inline const char * get_ptr() const {return m_formatter;} + inline operator const char * () const {return m_formatter;} + inline const char * toString() const {return m_formatter;} + private: + string_formatter m_formatter; + }; + + class format_hexdump_lowercase + { + public: + format_hexdump_lowercase(const void * p_buffer,t_size p_bytes,const char * p_spacing = " "); + + inline const char * get_ptr() const {return m_formatter;} + inline operator const char * () const {return m_formatter;} + inline const char * toString() const {return m_formatter;} + + private: + string_formatter m_formatter; + }; + + class format_fixedpoint + { + public: + format_fixedpoint(t_int64 p_val,unsigned p_point); + inline const char * get_ptr() const {return m_buffer;} + inline operator const char*() const {return m_buffer;} + inline const char * toString() const {return m_buffer;} + private: + string_formatter m_buffer; + }; + + class format_char { + public: + format_char(char p_char) {m_buffer[0] = p_char; m_buffer[1] = 0;} + inline const char * get_ptr() const {return m_buffer;} + inline operator const char*() const {return m_buffer;} + inline const char * toString() const {return m_buffer;} + private: + char m_buffer[2]; + }; + + template + class format_pad_left { + public: + format_pad_left(t_size p_chars,t_uint32 p_padding /* = ' ' */,const char * p_string,t_size p_string_length = SIZE_MAX) { + t_size source_len = 0, source_walk = 0; + + while(source_walk < p_string_length && source_len < p_chars) { + unsigned dummy; + t_size delta = pfc::utf8_decode_char(p_string + source_walk, dummy, p_string_length - source_walk); + if (delta == 0) break; + source_len++; + source_walk += delta; + } + + m_buffer.add_string(p_string,source_walk); + m_buffer.add_chars(p_padding,p_chars - source_len); + } + inline const char * get_ptr() const {return m_buffer;} + inline operator const char*() const {return m_buffer;} + inline const char * toString() const {return m_buffer;} + private: + t_stringbuffer m_buffer; + }; + + template + class format_pad_right { + public: + format_pad_right(t_size p_chars,t_uint32 p_padding /* = ' ' */,const char * p_string,t_size p_string_length = SIZE_MAX) { + t_size source_len = 0, source_walk = 0; + + while(source_walk < p_string_length && source_len < p_chars) { + unsigned dummy; + t_size delta = pfc::utf8_decode_char(p_string + source_walk, dummy, p_string_length - source_walk); + if (delta == 0) break; + source_len++; + source_walk += delta; + } + + m_buffer.add_chars(p_padding,p_chars - source_len); + m_buffer.add_string(p_string,source_walk); + } + inline const char * get_ptr() const {return m_buffer;} + inline operator const char*() const {return m_buffer;} + inline const char * toString() const {return m_buffer;} + private: + t_stringbuffer m_buffer; + }; + + + + class format_file_size_short : public string_formatter { + public: + format_file_size_short(t_uint64 size) { format(size); } + format_file_size_short(t_uint64 size, uint64_t * usedScale) { format(size); *usedScale = m_scale; } + t_uint64 get_used_scale() const {return m_scale;} + private: + void format(uint64_t size); + t_uint64 m_scale; + }; + +} + +inline pfc::string_base & operator<<(pfc::string_base & p_fmt,const char * p_source) {p_fmt.add_string_(p_source); return p_fmt;} +inline pfc::string_base & operator<<(pfc::string_base & p_fmt,pfc::string_part_ref source) {p_fmt.add_string(source.m_ptr, source.m_len); return p_fmt;} +inline pfc::string_base & operator<<(pfc::string_base & p_fmt,short p_val) {p_fmt.add_string(pfc::format_int(p_val)); return p_fmt;} +inline pfc::string_base & operator<<(pfc::string_base & p_fmt,unsigned short p_val) {p_fmt.add_string(pfc::format_uint(p_val)); return p_fmt;} +inline pfc::string_base & operator<<(pfc::string_base & p_fmt,int p_val) {p_fmt.add_string(pfc::format_int(p_val)); return p_fmt;} +inline pfc::string_base & operator<<(pfc::string_base & p_fmt,unsigned p_val) {p_fmt.add_string(pfc::format_uint(p_val)); return p_fmt;} +inline pfc::string_base & operator<<(pfc::string_base & p_fmt,long p_val) {p_fmt.add_string(pfc::format_int(p_val)); return p_fmt;} +inline pfc::string_base & operator<<(pfc::string_base & p_fmt,unsigned long p_val) {p_fmt.add_string(pfc::format_uint(p_val)); return p_fmt;} +inline pfc::string_base & operator<<(pfc::string_base & p_fmt,long long p_val) {p_fmt.add_string(pfc::format_int(p_val)); return p_fmt;} +inline pfc::string_base & operator<<(pfc::string_base & p_fmt,unsigned long long p_val) {p_fmt.add_string(pfc::format_uint(p_val)); return p_fmt;} +inline pfc::string_base & operator<<(pfc::string_base & p_fmt,double p_val) {p_fmt.add_string(pfc::format_float(p_val)); return p_fmt;} +inline pfc::string_base & operator<<(pfc::string_base & p_fmt,std::exception const & p_exception) {p_fmt.add_string(p_exception.what()); return p_fmt;} + +template class t_alloc> inline pfc::string8_t & operator<< (pfc::string8_t & str, const char * src) {str.add_string_(src); return str;} +template class t_alloc> inline pfc::string8_t & operator<< (pfc::string8_t & str, pfc::string_base const & src) {str.add_string(src); return str;} +template class t_alloc> inline pfc::string8_t & operator<< (pfc::string8_t & str, pfc::string_part_ref src) {str.add_string(src); return str;} + + +namespace pfc { + + class format_array : public string_formatter { + public: + template format_array(t_source const & source, const char * separator = ", ") { + const t_size count = array_size_t(source); + if (count > 0) { + *this << source[0]; + for(t_size walk = 1; walk < count; ++walk) *this << separator << source[walk]; + } + } + }; + + + class format_hexdump_ex { + public: + template format_hexdump_ex(const TWord * buffer, t_size bufLen, const char * spacing = " ") { + for(t_size n = 0; n < bufLen; n++) { + if (n > 0 && spacing != NULL) m_formatter << spacing; + m_formatter << format_hex(buffer[n],sizeof(TWord) * 2); + } + } + inline const char * get_ptr() const {return m_formatter;} + inline operator const char * () const {return m_formatter;} + inline const char * toString() const {return m_formatter;} + private: + string_formatter m_formatter; + }; + + + + + + template + class string_simple_t { + private: + typedef string_simple_t t_self; + public: + t_size length() const { + t_size s = m_buffer.get_size(); + if (s == 0) return 0; else return s-1; + } + bool is_empty() const {return m_buffer.get_size() == 0;} + + void set_string_nc(const t_char * p_source, t_size p_length) { + if (p_length == 0) { + m_buffer.set_size(0); + } else { + m_buffer.set_size(p_length + 1); + pfc::memcpy_t(m_buffer.get_ptr(),p_source,p_length); + m_buffer[p_length] = 0; + } + } + void set_string(const t_char * p_source) { + set_string_nc(p_source, pfc::strlen_t(p_source)); + } + void set_string(const t_char * p_source, t_size p_length) { + set_string_nc(p_source, strlen_max_t(p_source, p_length)); + } + void add_string(const t_char * p_source, t_size p_length) { + add_string_nc(p_source, strlen_max_t(p_source, p_length)); + } + void add_string(const t_char * p_source) { + add_string_nc(p_source, strlen_t(p_source)); + } + void add_string_nc(const t_char * p_source, t_size p_length) { + if (p_length > 0) { + t_size base = length(); + m_buffer.set_size( base + p_length + 1 ); + memcpy_t(m_buffer.get_ptr() + base, p_source, p_length); + m_buffer[base + p_length] = 0; + } + } + string_simple_t() {} + string_simple_t(const t_char * p_source) {set_string(p_source);} + string_simple_t(const t_char * p_source, t_size p_length) {set_string(p_source, p_length);} + const t_self & operator=(const t_char * p_source) {set_string(p_source);return *this;} + operator const t_char* () const {return get_ptr();} + const t_char * get_ptr() const {return m_buffer.get_size() > 0 ? m_buffer.get_ptr() : pfc::empty_string_t();} + const t_char * c_str() const { return get_ptr(); } + private: + pfc::array_t m_buffer; + }; + + typedef string_simple_t string_simple; + + template class traits_t > : public traits_t > {}; +} + + +namespace pfc { + class comparator_strcmp { + public: + inline static int compare(const char * p_item1,const char * p_item2) {return strcmp(p_item1,p_item2);} + inline static int compare(const wchar_t * item1, const wchar_t * item2) {return wcscmp(item1, item2);} + + static int compare(const char * p_item1, string_part_ref p_item2) { + return strcmp_ex(p_item1, SIZE_MAX, p_item2.m_ptr, p_item2.m_len); + } + static int compare(string_part_ref p_item1, string_part_ref p_item2) { + return strcmp_ex(p_item1.m_ptr, p_item1.m_len, p_item2.m_ptr, p_item2.m_len); + } + static int compare(string_part_ref p_item1, const char * p_item2) { + return strcmp_ex(p_item1.m_ptr, p_item1.m_len, p_item2, SIZE_MAX); + } + }; + + class comparator_stricmp_ascii { + public: + inline static int compare(const char * p_item1,const char * p_item2) {return stricmp_ascii(p_item1,p_item2);} + }; + + + class comparator_naturalSort { + public: + inline static int compare( const char * i1, const char * i2 ) throw() {return naturalSortCompare(i1, i2); } + }; + + template static void stringCombine(pfc::string_base & out, t_source const & in, const char * separator, const char * separatorLast) { + out.reset(); + for(typename t_source::const_iterator walk = in.first(); walk.is_valid(); ++walk) { + if (!out.is_empty()) { + if (walk == in.last()) out << separatorLast; + else out << separator; + } + out << stringToPtr(*walk); + } + } + + template + void splitStringEx(t_output & p_output, const t_splitCheck & p_check, const char * p_string, t_size p_stringLen = SIZE_MAX) { + t_size walk = 0, splitBase = 0; + const t_size max = strlen_max(p_string,p_stringLen); + for(;walk < max;) { + t_size delta = p_check(p_string + walk,max - walk); + if (delta > 0) { + if (walk > splitBase) p_output(p_string + splitBase, walk - splitBase); + splitBase = walk + delta; + } else { + delta = utf8_char_len(p_string + walk, max - walk); + if (delta == 0) break; + } + walk += delta; + } + if (walk > splitBase) p_output(p_string + splitBase, walk - splitBase); + } + + class __splitStringSimple_calculateSubstringCount { + public: + __splitStringSimple_calculateSubstringCount() : m_count() {} + void operator() (const char *, t_size) {++m_count;} + t_size get() const {return m_count;} + private: + t_size m_count; + }; + + template class _splitStringSimple_check; + + template<> class _splitStringSimple_check { + public: + _splitStringSimple_check(const char * p_chars) { + m_chars.set_size(strlen_utf8(p_chars)); + for(t_size walk = 0, ptr = 0; walk < m_chars.get_size(); ++walk) { + ptr += utf8_decode_char(p_chars + ptr,m_chars[walk]); + } + } + t_size operator()(const char * p_string, t_size p_stringLen) const { + t_uint32 c; + t_size delta = utf8_decode_char(p_string, c, p_stringLen); + if (delta > 0) { + for(t_size walk = 0; walk < m_chars.get_size(); ++walk) { + if (m_chars[walk] == c) return delta; + } + } + return 0; + } + private: + array_t m_chars; + }; + template<> class _splitStringSimple_check { + public: + _splitStringSimple_check(char c) : m_char(c) {} + t_size operator()(const char * str, t_size len) const { + PFC_ASSERT( len > 0 ); + if (*str == m_char) return 1; + else return 0; + } + private: + const char m_char; + }; + template + class __splitStringSimple_arrayWrapper { + public: + __splitStringSimple_arrayWrapper(t_array & p_array) : m_walk(), m_array(p_array) {} + void operator()(const char * p_string, t_size p_stringLen) { + m_array[m_walk++] = string_part(p_string,p_stringLen); + } + private: + t_size m_walk; + t_array & m_array; + }; + template + class __splitStringSimple_listWrapper { + public: + __splitStringSimple_listWrapper(t_list & p_list) : m_list(p_list) {} + void operator()(const char * p_string, t_size p_stringLen) { + m_list += string_part(p_string, p_stringLen); + } + private: + t_list & m_list; + }; + + template + void splitStringSimple_toArray(t_array & p_output, t_split p_split, const char * p_string, t_size p_stringLen = SIZE_MAX) { + _splitStringSimple_check strCheck(p_split); + + { + __splitStringSimple_calculateSubstringCount wrapper; + splitStringEx(wrapper,strCheck,p_string,p_stringLen); + p_output.set_size(wrapper.get()); + } + + { + __splitStringSimple_arrayWrapper wrapper(p_output); + splitStringEx(wrapper,strCheck,p_string,p_stringLen); + } + } + template + void splitStringSimple_toList(t_list & p_output, t_split p_split, const char * p_string, t_size p_stringLen = SIZE_MAX) { + _splitStringSimple_check strCheck(p_split); + + __splitStringSimple_listWrapper wrapper(p_output); + splitStringEx(wrapper,strCheck,p_string,p_stringLen); + } + + template void splitStringByLines(t_out & out, const char * str) { + for(;;) { + const char * next = strchr(str, '\n'); + if (next == NULL) { + out += string_part(str, strlen(str)); break; + } + const char * walk = next; + while(walk > str && walk[-1] == '\r') --walk; + out += string_part(str, walk - str); + str = next + 1; + } + } + template void splitStringByChar(t_out & out, const char * str, char c) { + for(;;) { + const char * next = strchr(str, c); + if (next == NULL) { + out += string_part(str, strlen(str)); break; + } + out += string_part(str, next - str); + str = next + 1; + } + } + template void splitStringBySubstring(t_out & out, const char * str, const char * split) { + for(;;) { + const char * next = strstr(str, split); + if (next == NULL) { + out += string_part(str, strlen(str)); break; + } + out += string_part(str, next - str); + str = next + strlen(split); + } + } + + void stringToUpperAppend(string_base & p_out, const char * p_source, t_size p_sourceLen); + void stringToLowerAppend(string_base & p_out, const char * p_source, t_size p_sourceLen); + int stringCompareCaseInsensitive(const char * s1, const char * s2); + int stringCompareCaseInsensitiveEx(string_part_ref s1, string_part_ref s2); + t_uint32 charLower(t_uint32 param); + t_uint32 charUpper(t_uint32 param); + bool stringEqualsI_utf8(const char * p1,const char * p2) throw(); + bool stringEqualsI_ascii(const char * p1,const char * p2) throw(); + char ascii_tolower_lookup(char c); + + template inline const char * stringToPtr(T const& val) {return val.get_ptr();} + inline const char * stringToPtr(const char* val) {return val;} + + template static string_part_ref stringToRef(T const & val) {return string_part(stringToPtr(val), val.length());} + inline string_part_ref stringToRef(string_part_ref val) {return val;} + inline string_part_ref stringToRef(const char * val) {return string_part(val, strlen(val));} + + + + + class string_base_ref : public string_base { + public: + string_base_ref(const char * ptr) : m_ptr(ptr), m_len(strlen(ptr)) {} + const char * get_ptr() const {return m_ptr;} + t_size get_length() const {return m_len;} + private: + void add_string(const char *,t_size) {throw pfc::exception_not_implemented();} + void set_string(const char *,t_size) {throw pfc::exception_not_implemented();} + void truncate(t_size) {throw pfc::exception_not_implemented();} + char * lock_buffer(t_size) {throw pfc::exception_not_implemented();} + void unlock_buffer() {throw pfc::exception_not_implemented();} + private: + const char * const m_ptr; + t_size const m_len; + }; + + //! Writes a string to a fixed-size buffer. Truncates the string if necessary. Always writes a null terminator. + template + void stringToBuffer(TChar (&buffer)[len], const TSource & source) { + PFC_STATIC_ASSERT(len>0); + t_size walk; + for(walk = 0; walk < len - 1 && source[walk] != 0; ++walk) { + buffer[walk] = source[walk]; + } + buffer[walk] = 0; + } + + //! Same as stringToBuffer() but throws exception_overflow() if the string could not be fully written, including null terminator. + template + void stringToBufferGuarded(TChar (&buffer)[len], const TSource & source) { + t_size walk; + for(walk = 0; source[walk] != 0; ++walk) { + if (walk >= len) throw exception_overflow(); + buffer[walk] = source[walk]; + } + if (walk >= len) throw exception_overflow(); + buffer[walk] = 0; + } + + + template int _strcmp_partial_ex(const t_char * p_string,t_size p_string_length,const t_char * p_substring,t_size p_substring_length) throw() { + for(t_size walk=0;walk=p_string_length ? 0 : p_string[walk]); + t_char substringchar = p_substring[walk]; + int result = compare_t(stringchar,substringchar); + if (result != 0) return result; + } + return 0; + } + + template int strcmp_partial_ex_t(const t_char * p_string,t_size p_string_length,const t_char * p_substring,t_size p_substring_length) throw() { + p_string_length = strlen_max_t(p_string,p_string_length); p_substring_length = strlen_max_t(p_substring,p_substring_length); + return _strcmp_partial_ex(p_string,p_string_length,p_substring,p_substring_length); + } + + template + int strcmp_partial_t(const t_char * p_string,const t_char * p_substring) throw() {return strcmp_partial_ex_t(p_string,SIZE_MAX,p_substring,SIZE_MAX);} + + inline int strcmp_partial_ex(const char * str, t_size strLen, const char * substr, t_size substrLen) throw() {return strcmp_partial_ex_t(str, strLen, substr, substrLen); } + inline int strcmp_partial(const char * str, const char * substr) throw() {return strcmp_partial_t(str, substr); } + + int stricmp_ascii_partial( const char * str, const char * substr) throw(); + + void urlEncodeAppendRaw(pfc::string_base & out, const char * in, t_size inSize); + void urlEncodeAppend(pfc::string_base & out, const char * in); + void urlEncode(pfc::string_base & out, const char * in); + + + char * strDup(const char * src); // POSIX strdup() clone, prevent MSVC complaining + + string8 lineEndingsToWin( const char * str ); + + string8 stringToUpper( const char * str, size_t len = SIZE_MAX ); + string8 stringToLower( const char * str, size_t len = SIZE_MAX ); +} diff --git a/pfc/string_conv.cpp b/pfc/string_conv.cpp new file mode 100644 index 0000000..dd63acd --- /dev/null +++ b/pfc/string_conv.cpp @@ -0,0 +1,499 @@ +#include "pfc.h" + + + +namespace { + template + class string_writer_t { + public: + string_writer_t(t_char * p_buffer,t_size p_size) : m_buffer(p_buffer), m_size(p_size), m_writeptr(0) {} + + void write(t_char p_char) { + if (isChecked) { + if (m_writeptr < m_size) { + m_buffer[m_writeptr++] = p_char; + } + } else { + m_buffer[m_writeptr++] = p_char; + } + } + void write_multi(const t_char * p_buffer,t_size p_count) { + if (isChecked) { + const t_size delta = pfc::min_t(p_count,m_size-m_writeptr); + for(t_size n=0;n(m_writeptr,m_size-1); + m_buffer[terminator] = 0; + return terminator; + } else { + m_buffer[m_writeptr] = 0; + return m_writeptr; + } + } + bool is_overrun() const { + return m_writeptr >= m_size; + } + private: + t_char * m_buffer; + t_size m_size; + t_size m_writeptr; + }; + + + + + static const uint16_t mappings1252[] = { + /*0x80*/ 0x20AC, // #EURO SIGN + /*0x81*/ 0, // #UNDEFINED + /*0x82*/ 0x201A, // #SINGLE LOW-9 QUOTATION MARK + /*0x83*/ 0x0192, // #LATIN SMALL LETTER F WITH HOOK + /*0x84*/ 0x201E, // #DOUBLE LOW-9 QUOTATION MARK + /*0x85*/ 0x2026, // #HORIZONTAL ELLIPSIS + /*0x86*/ 0x2020, // #DAGGER + /*0x87*/ 0x2021, // #DOUBLE DAGGER + /*0x88*/ 0x02C6, // #MODIFIER LETTER CIRCUMFLEX ACCENT + /*0x89*/ 0x2030, // #PER MILLE SIGN + /*0x8A*/ 0x0160, // #LATIN CAPITAL LETTER S WITH CARON + /*0x8B*/ 0x2039, // #SINGLE LEFT-POINTING ANGLE QUOTATION MARK + /*0x8C*/ 0x0152, // #LATIN CAPITAL LIGATURE OE + /*0x8D*/ 0, // #UNDEFINED + /*0x8E*/ 0x017D, // #LATIN CAPITAL LETTER Z WITH CARON + /*0x8F*/ 0, // #UNDEFINED + /*0x90*/ 0, // #UNDEFINED + /*0x91*/ 0x2018, // #LEFT SINGLE QUOTATION MARK + /*0x92*/ 0x2019, // #RIGHT SINGLE QUOTATION MARK + /*0x93*/ 0x201C, // #LEFT DOUBLE QUOTATION MARK + /*0x94*/ 0x201D, // #RIGHT DOUBLE QUOTATION MARK + /*0x95*/ 0x2022, // #BULLET + /*0x96*/ 0x2013, // #EN DASH + /*0x97*/ 0x2014, // #EM DASH + /*0x98*/ 0x02DC, // #SMALL TILDE + /*0x99*/ 0x2122, // #TRADE MARK SIGN + /*0x9A*/ 0x0161, // #LATIN SMALL LETTER S WITH CARON + /*0x9B*/ 0x203A, // #SINGLE RIGHT-POINTING ANGLE QUOTATION MARK + /*0x9C*/ 0x0153, // #LATIN SMALL LIGATURE OE + /*0x9D*/ 0, // #UNDEFINED + /*0x9E*/ 0x017E, // #LATIN SMALL LETTER Z WITH CARON + /*0x9F*/ 0x0178, // #LATIN CAPITAL LETTER Y WITH DIAERESIS + }; + + static bool charImport1252(uint32_t & unichar, char c) { + uint8_t uc = (uint8_t) c; + if (uc == 0) return false; + else if (uc < 0x80) {unichar = uc; return true;} + else if (uc < 0xA0) { + uint32_t t = mappings1252[uc-0x80]; + if (t == 0) return false; + unichar = t; return true; + } else { + unichar = uc; return true; + } + } + + static bool charExport1252(char & c, uint32_t unichar) { + if (unichar == 0) return false; + else if (unichar < 0x80 || (unichar >= 0xa0 && unichar <= 0xFF)) {c = (char)(uint8_t)unichar; return true;} + for(size_t walk = 0; walk < PFC_TABSIZE(mappings1252); ++walk) { + if (unichar == mappings1252[walk]) { + c = (char)(uint8_t)(walk + 0x80); + return true; + } + } + return false; + } + struct asciiMap_t {uint16_t from; uint8_t to;}; + static const asciiMap_t g_asciiMap[] = { + {160,32},{161,33},{162,99},{164,36},{165,89},{166,124},{169,67},{170,97},{171,60},{173,45},{174,82},{178,50},{179,51},{183,46},{184,44},{185,49},{186,111},{187,62},{192,65},{193,65},{194,65},{195,65},{196,65},{197,65},{198,65},{199,67},{200,69},{201,69},{202,69},{203,69},{204,73},{205,73},{206,73},{207,73},{208,68},{209,78},{210,79},{211,79},{212,79},{213,79},{214,79},{216,79},{217,85},{218,85},{219,85},{220,85},{221,89},{224,97},{225,97},{226,97},{227,97},{228,97},{229,97},{230,97},{231,99},{232,101},{233,101},{234,101},{235,101},{236,105},{237,105},{238,105},{239,105},{241,110},{242,111},{243,111},{244,111},{245,111},{246,111},{248,111},{249,117},{250,117},{251,117},{252,117},{253,121},{255,121},{256,65},{257,97},{258,65},{259,97},{260,65},{261,97},{262,67},{263,99},{264,67},{265,99},{266,67},{267,99},{268,67},{269,99},{270,68},{271,100},{272,68},{273,100},{274,69},{275,101},{276,69},{277,101},{278,69},{279,101},{280,69},{281,101},{282,69},{283,101},{284,71},{285,103},{286,71},{287,103},{288,71},{289,103},{290,71},{291,103},{292,72},{293,104},{294,72},{295,104},{296,73},{297,105},{298,73},{299,105},{300,73},{301,105},{302,73},{303,105},{304,73},{305,105},{308,74},{309,106},{310,75},{311,107},{313,76},{314,108},{315,76},{316,108},{317,76},{318,108},{321,76},{322,108},{323,78},{324,110},{325,78},{326,110},{327,78},{328,110},{332,79},{333,111},{334,79},{335,111},{336,79},{337,111},{338,79},{339,111},{340,82},{341,114},{342,82},{343,114},{344,82},{345,114},{346,83},{347,115},{348,83},{349,115},{350,83},{351,115},{352,83},{353,115},{354,84},{355,116},{356,84},{357,116},{358,84},{359,116},{360,85},{361,117},{362,85},{363,117},{364,85},{365,117},{366,85},{367,117},{368,85},{369,117},{370,85},{371,117},{372,87},{373,119},{374,89},{375,121},{376,89},{377,90},{378,122},{379,90},{380,122},{381,90},{382,122},{384,98},{393,68},{401,70},{402,102},{407,73},{410,108},{415,79},{416,79},{417,111},{427,116},{430,84},{431,85},{432,117},{438,122},{461,65},{462,97},{463,73},{464,105},{465,79},{466,111},{467,85},{468,117},{469,85},{470,117},{471,85},{472,117},{473,85},{474,117},{475,85},{476,117},{478,65},{479,97},{484,71},{485,103},{486,71},{487,103},{488,75},{489,107},{490,79},{491,111},{492,79},{493,111},{496,106},{609,103},{697,39},{698,34},{700,39},{708,94},{710,94},{712,39},{715,96},{717,95},{732,126},{768,96},{770,94},{771,126},{782,34},{817,95},{818,95},{8192,32},{8193,32},{8194,32},{8195,32},{8196,32},{8197,32},{8198,32},{8208,45},{8209,45},{8211,45},{8212,45},{8216,39},{8217,39},{8218,44},{8220,34},{8221,34},{8222,34},{8226,46},{8230,46},{8242,39},{8245,96},{8249,60},{8250,62},{8482,84},{65281,33},{65282,34},{65283,35},{65284,36},{65285,37},{65286,38},{65287,39},{65288,40},{65289,41},{65290,42},{65291,43},{65292,44},{65293,45},{65294,46},{65295,47},{65296,48},{65297,49},{65298,50},{65299,51},{65300,52},{65301,53},{65302,54},{65303,55},{65304,56},{65305,57},{65306,58},{65307,59},{65308,60},{65309,61},{65310,62},{65312,64},{65313,65},{65314,66},{65315,67},{65316,68},{65317,69},{65318,70},{65319,71},{65320,72},{65321,73},{65322,74},{65323,75},{65324,76},{65325,77},{65326,78},{65327,79},{65328,80},{65329,81},{65330,82},{65331,83},{65332,84},{65333,85},{65334,86},{65335,87},{65336,88},{65337,89},{65338,90},{65339,91},{65340,92},{65341,93},{65342,94},{65343,95},{65344,96},{65345,97},{65346,98},{65347,99},{65348,100},{65349,101},{65350,102},{65351,103},{65352,104},{65353,105},{65354,106},{65355,107},{65356,108},{65357,109},{65358,110},{65359,111},{65360,112},{65361,113},{65362,114},{65363,115},{65364,116},{65365,117},{65366,118},{65367,119},{65368,120},{65369,121},{65370,122},{65371,123},{65372,124},{65373,125},{65374,126}}; + +} + +namespace pfc { + namespace stringcvt { + + char charToASCII( unsigned c ) { + if (c < 128) return (char)c; + unsigned lo = 0, hi = PFC_TABSIZE(g_asciiMap); + while( lo < hi ) { + const unsigned mid = (lo + hi) / 2; + const asciiMap_t entry = g_asciiMap[mid]; + if ( c > entry.from ) { + lo = mid + 1; + } else if (c < entry.from) { + hi = mid; + } else { + return (char)entry.to; + } + } + return '?'; + } + + t_size convert_utf8_to_wide(wchar_t * p_out,t_size p_out_size,const char * p_in,t_size p_in_size) { + const t_size insize = p_in_size; + t_size inptr = 0; + string_writer_t writer(p_out,p_out_size); + + while(inptr < insize && !writer.is_overrun()) { + unsigned newchar = 0; + t_size delta = utf8_decode_char(p_in + inptr,newchar,insize - inptr); + if (delta == 0 || newchar == 0) break; + PFC_ASSERT(inptr + delta <= insize); + inptr += delta; + writer.write_as_wide(newchar); + } + + return writer.finalize(); + } + + t_size convert_utf8_to_wide_unchecked(wchar_t * p_out,const char * p_in) { + t_size inptr = 0; + string_writer_t writer(p_out,~0); + + while(!writer.is_overrun()) { + unsigned newchar = 0; + t_size delta = utf8_decode_char(p_in + inptr,newchar); + if (delta == 0 || newchar == 0) break; + inptr += delta; + writer.write_as_wide(newchar); + } + + return writer.finalize(); + } + + t_size convert_wide_to_utf8(char * p_out,t_size p_out_size,const wchar_t * p_in,t_size p_in_size) { + const t_size insize = p_in_size; + t_size inptr = 0; + string_writer_t writer(p_out,p_out_size); + + while(inptr < insize && !writer.is_overrun()) { + unsigned newchar = 0; + t_size delta = wide_decode_char(p_in + inptr,&newchar,insize - inptr); + if (delta == 0 || newchar == 0) break; + PFC_ASSERT(inptr + delta <= insize); + inptr += delta; + writer.write_as_utf8(newchar); + } + + return writer.finalize(); + } + + t_size estimate_utf8_to_wide(const char * p_in) { + t_size inptr = 0; + t_size retval = 1;//1 for null terminator + for(;;) { + unsigned newchar = 0; + t_size delta = utf8_decode_char(p_in + inptr,newchar); + if (delta == 0 || newchar == 0) break; + inptr += delta; + + { + wchar_t temp[2]; + retval += wide_encode_char(newchar,temp); + } + } + return retval; + } + + t_size estimate_utf8_to_wide(const char * p_in,t_size p_in_size) { + const t_size insize = p_in_size; + t_size inptr = 0; + t_size retval = 1;//1 for null terminator + while(inptr < insize) { + unsigned newchar = 0; + t_size delta = utf8_decode_char(p_in + inptr,newchar,insize - inptr); + if (delta == 0 || newchar == 0) break; + PFC_ASSERT(inptr + delta <= insize); + inptr += delta; + + { + wchar_t temp[2]; + retval += wide_encode_char(newchar,temp); + } + } + return retval; + } + + t_size estimate_wide_to_utf8(const wchar_t * p_in,t_size p_in_size) { + const t_size insize = p_in_size; + t_size inptr = 0; + t_size retval = 1;//1 for null terminator + while(inptr < insize) { + unsigned newchar = 0; + t_size delta = wide_decode_char(p_in + inptr,&newchar,insize - inptr); + if (delta == 0 || newchar == 0) break; + PFC_ASSERT(inptr + delta <= insize); + inptr += delta; + + { + char temp[6]; + delta = utf8_encode_char(newchar,temp); + if (delta == 0) break; + retval += delta; + } + } + return retval; + } + + t_size estimate_wide_to_win1252( const wchar_t * p_in, t_size p_in_size ) { + const t_size insize = p_in_size; + t_size inptr = 0; + t_size retval = 1;//1 for null terminator + while(inptr < insize) { + unsigned newchar = 0; + t_size delta = wide_decode_char(p_in + inptr,&newchar,insize - inptr); + if (delta == 0 || newchar == 0) break; + PFC_ASSERT(inptr + delta <= insize); + inptr += delta; + + ++retval; + } + return retval; + + } + + t_size convert_wide_to_win1252( char * p_out, t_size p_out_size, const wchar_t * p_in, t_size p_in_size ) { + const t_size insize = p_in_size; + t_size inptr = 0; + string_writer_t writer(p_out,p_out_size); + + while(inptr < insize && !writer.is_overrun()) { + unsigned newchar = 0; + t_size delta = wide_decode_char(p_in + inptr,&newchar,insize - inptr); + if (delta == 0 || newchar == 0) break; + PFC_ASSERT(inptr + delta <= insize); + inptr += delta; + + char temp; + if (!charExport1252( temp, newchar )) temp = '?'; + writer.write( temp ); + } + + return writer.finalize(); + + } + + t_size estimate_win1252_to_wide( const char * p_source, t_size p_source_size ) { + return strlen_max_t( p_source, p_source_size ) + 1; + } + + t_size convert_win1252_to_wide( wchar_t * p_out, t_size p_out_size, const char * p_in, t_size p_in_size ) { + const t_size insize = p_in_size; + t_size inptr = 0; + string_writer_t writer(p_out,p_out_size); + + while(inptr < insize && !writer.is_overrun()) { + char inChar = p_in[inptr]; + if (inChar == 0) break; + ++inptr; + + unsigned out; + if (!charImport1252( out , inChar )) out = '?'; + writer.write_as_wide( out ); + } + + return writer.finalize(); + } + + t_size estimate_utf8_to_win1252( const char * p_in, t_size p_in_size ) { + const t_size insize = p_in_size; + t_size inptr = 0; + t_size retval = 1;//1 for null terminator + while(inptr < insize) { + unsigned newchar = 0; + t_size delta = utf8_decode_char(p_in + inptr,newchar,insize - inptr); + if (delta == 0 || newchar == 0) break; + PFC_ASSERT(inptr + delta <= insize); + inptr += delta; + + ++retval; + } + return retval; + } + t_size convert_utf8_to_win1252( char * p_out, t_size p_out_size, const char * p_in, t_size p_in_size ) { + const t_size insize = p_in_size; + t_size inptr = 0; + string_writer_t writer(p_out,p_out_size); + + while(inptr < insize && !writer.is_overrun()) { + unsigned newchar = 0; + t_size delta = utf8_decode_char(p_in + inptr,newchar,insize - inptr); + if (delta == 0 || newchar == 0) break; + PFC_ASSERT(inptr + delta <= insize); + inptr += delta; + + char temp; + if (!charExport1252( temp, newchar )) temp = '?'; + writer.write( temp ); + } + + return writer.finalize(); + } + + t_size estimate_win1252_to_utf8( const char * p_in, t_size p_in_size ) { + const size_t insize = p_in_size; + t_size inptr = 0; + t_size retval = 1; // 1 for null terminator + while(inptr < insize) { + unsigned newchar; + char c = p_in[inptr]; + if (c == 0) break; + ++inptr; + if (!charImport1252( newchar, c)) newchar = '?'; + + char temp[6]; + retval += pfc::utf8_encode_char( newchar, temp ); + } + return retval; + } + + t_size convert_win1252_to_utf8( char * p_out, t_size p_out_size, const char * p_in, t_size p_in_size ) { + const t_size insize = p_in_size; + t_size inptr = 0; + string_writer_t writer(p_out,p_out_size); + + while(inptr < insize && !writer.is_overrun()) { + char inChar = p_in[inptr]; + if (inChar == 0) break; + ++inptr; + + unsigned out; + if (!charImport1252( out , inChar )) out = '?'; + writer.write_as_utf8( out ); + } + + return writer.finalize(); + } + + t_size estimate_utf8_to_ascii( const char * p_source, t_size p_source_size ) { + return estimate_utf8_to_win1252( p_source, p_source_size ); + } + t_size convert_utf8_to_ascii( char * p_out, t_size p_out_size, const char * p_in, t_size p_in_size ) { + const t_size insize = p_in_size; + t_size inptr = 0; + string_writer_t writer(p_out,p_out_size); + + while(inptr < insize && !writer.is_overrun()) { + unsigned newchar = 0; + t_size delta = utf8_decode_char(p_in + inptr,newchar,insize - inptr); + if (delta == 0 || newchar == 0) break; + PFC_ASSERT(inptr + delta <= insize); + inptr += delta; + + writer.write( charToASCII(newchar) ); + } + + return writer.finalize(); + } + + + + // 2016-05-16 additions + // Explicit UTF-16 converters + t_size estimate_utf16_to_utf8( const char16_t * p_in, size_t p_in_size ) { + const t_size insize = p_in_size; + t_size inptr = 0; + t_size retval = 1;//1 for null terminator + while(inptr < insize) { + unsigned newchar = 0; + t_size delta = utf16_decode_char(p_in + inptr,&newchar,insize - inptr); + if (delta == 0 || newchar == 0) break; + PFC_ASSERT(inptr + delta <= insize); + inptr += delta; + + { + char temp[6]; + delta = utf8_encode_char(newchar,temp); + if (delta == 0) break; + retval += delta; + } + } + return retval; + } + t_size convert_utf16_to_utf8( char * p_out, size_t p_out_size, const char16_t * p_in, size_t p_in_size ) { + const t_size insize = p_in_size; + t_size inptr = 0; + string_writer_t writer(p_out,p_out_size); + + while(inptr < insize && !writer.is_overrun()) { + unsigned newchar = 0; + t_size delta = utf16_decode_char(p_in + inptr,&newchar,insize - inptr); + if (delta == 0 || newchar == 0) break; + PFC_ASSERT(inptr + delta <= insize); + inptr += delta; + writer.write_as_utf8(newchar); + } + + return writer.finalize(); + } + + } +} + +#ifdef _WINDOWS + + +namespace pfc { + namespace stringcvt { + + + t_size convert_codepage_to_wide(unsigned p_codepage,wchar_t * p_out,t_size p_out_size,const char * p_source,t_size p_source_size) { + if (p_out_size == 0) return 0; + memset(p_out,0,p_out_size * sizeof(*p_out)); + MultiByteToWideChar(p_codepage,0,p_source, pfc::downcast_guarded(p_source_size),p_out, pfc::downcast_guarded(p_out_size)); + p_out[p_out_size-1] = 0; + return wcslen(p_out); + } + + t_size convert_wide_to_codepage(unsigned p_codepage,char * p_out,t_size p_out_size,const wchar_t * p_source,t_size p_source_size) { + if (p_out_size == 0) return 0; + memset(p_out,0,p_out_size * sizeof(*p_out)); + WideCharToMultiByte(p_codepage,0,p_source,pfc::downcast_guarded(p_source_size),p_out,pfc::downcast_guarded(p_out_size),0,FALSE); + p_out[p_out_size-1] = 0; + return strlen(p_out); + } + + t_size estimate_codepage_to_wide(unsigned p_codepage,const char * p_source,t_size p_source_size) { + return MultiByteToWideChar(p_codepage,0,p_source, pfc::downcast_guarded(strlen_max(p_source,p_source_size)),0,0) + 1; + } + t_size estimate_wide_to_codepage(unsigned p_codepage,const wchar_t * p_source,t_size p_source_size) { + return WideCharToMultiByte(p_codepage,0,p_source, pfc::downcast_guarded(wcslen_max(p_source,p_source_size)),0,0,0,FALSE) + 1; + } + } + +} + +#endif //_WINDOWS + +pfc::string_base & operator<<(pfc::string_base & p_fmt, const wchar_t * p_str) { + p_fmt.add_string(pfc::stringcvt::string_utf8_from_wide(p_str) ); return p_fmt; +} diff --git a/pfc/string_conv.h b/pfc/string_conv.h new file mode 100644 index 0000000..f0d5adc --- /dev/null +++ b/pfc/string_conv.h @@ -0,0 +1,584 @@ +#pragma once + +namespace pfc { + + namespace stringcvt { + //! Converts UTF-8 characters to wide character. + //! @param p_out Output buffer, receives converted string, with null terminator. + //! @param p_out_size Size of output buffer, in characters. If converted string is too long, it will be truncated. Null terminator is always written, unless p_out_size is zero. + //! @param p_source String to convert. + //! @param p_source_size Number of characters to read from p_source. If reading stops if null terminator is encountered earlier. + //! @returns Number of characters written, not counting null terminator. + t_size convert_utf8_to_wide(wchar_t * p_out,t_size p_out_size,const char * p_source,t_size p_source_size); + + + //! Estimates buffer size required to convert specified UTF-8 string to widechar. + //! @param p_source String to be converted. + //! @param p_source_size Number of characters to read from p_source. If reading stops if null terminator is encountered earlier. + //! @returns Number of characters to allocate, including space for null terminator. + t_size estimate_utf8_to_wide(const char * p_source,t_size p_source_size); + + t_size estimate_utf8_to_wide(const char * p_source); + + //! Converts wide character string to UTF-8. + //! @param p_out Output buffer, receives converted string, with null terminator. + //! @param p_out_size Size of output buffer, in characters. If converted string is too long, it will be truncated. Null terminator is always written, unless p_out_size is zero. + //! @param p_source String to convert. + //! @param p_source_size Number of characters to read from p_source. If reading stops if null terminator is encountered earlier. + //! @returns Number of characters written, not counting null terminator. + t_size convert_wide_to_utf8(char * p_out,t_size p_out_size,const wchar_t * p_source,t_size p_source_size); + + //! Estimates buffer size required to convert specified wide character string to UTF-8. + //! @param p_source String to be converted. + //! @param p_source_size Number of characters to read from p_source. If reading stops if null terminator is encountered earlier. + //! @returns Number of characters to allocate, including space for null terminator. + t_size estimate_wide_to_utf8(const wchar_t * p_source,t_size p_source_size); + + t_size estimate_utf8_to_ascii( const char * p_source, t_size p_source_size ); + t_size convert_utf8_to_ascii( char * p_out, t_size p_out_size, const char * p_source, t_size p_source_size ); + + t_size estimate_wide_to_win1252( const wchar_t * p_source, t_size p_source_size ); + t_size convert_wide_to_win1252( char * p_out, t_size p_out_size, const wchar_t * p_source, t_size p_source_size ); + t_size estimate_win1252_to_wide( const char * p_source, t_size p_source_size ); + t_size convert_win1252_to_wide( wchar_t * p_out, t_size p_out_size, const char * p_source, t_size p_source_size ); + + t_size estimate_utf8_to_win1252( const char * p_source, t_size p_source_size ); + t_size convert_utf8_to_win1252( char * p_out, t_size p_out_size, const char * p_source, t_size p_source_size ); + t_size estimate_win1252_to_utf8( const char * p_source, t_size p_source_size ); + t_size convert_win1252_to_utf8( char * p_out, t_size p_out_size, const char * p_source, t_size p_source_size ); + + // 2016-05-16 additions + // Explicit UTF-16 converters + t_size estimate_utf16_to_utf8( const char16_t * p_source, size_t p_source_size ); + t_size convert_utf16_to_utf8( char * p_out, size_t p_out_size, const char16_t * p_source, size_t p_source_size ); + + + //! estimate_utf8_to_wide_quick() functions use simple math to determine buffer size required for the conversion. The result is not accurate length of output string - it's just a safe estimate of required buffer size, possibly bigger than what's really needed. \n + //! These functions are meant for scenarios when speed is more important than memory usage. + inline t_size estimate_utf8_to_wide_quick(t_size sourceLen) { + return sourceLen + 1; + } + inline t_size estimate_utf8_to_wide_quick(const char * source) { + return estimate_utf8_to_wide_quick(strlen(source)); + } + inline t_size estimate_utf8_to_wide_quick(const char * source, t_size sourceLen) { + return estimate_utf8_to_wide_quick(strlen_max(source, sourceLen)); + } + t_size convert_utf8_to_wide_unchecked(wchar_t * p_out,const char * p_source); + + template const t_char * null_string_t(); + template<> inline const char * null_string_t() {return "";} + template<> inline const wchar_t * null_string_t() {return L"";} + + template t_size strlen_t(const t_char * p_string,t_size p_string_size = ~0) { + for(t_size n=0;n bool string_is_empty_t(const t_char * p_string,t_size p_string_size = ~0) { + if (p_string_size == 0) return true; + return p_string[0] == 0; + } + + template class t_alloc = pfc::alloc_standard> class char_buffer_t { + public: + char_buffer_t() {} + char_buffer_t(const char_buffer_t & p_source) : m_buffer(p_source.m_buffer) {} + void set_size(t_size p_count) {m_buffer.set_size(p_count);} + t_char * get_ptr_var() {return m_buffer.get_ptr();} + const t_char * get_ptr() const { + return m_buffer.get_size() > 0 ? m_buffer.get_ptr() : null_string_t(); + } + private: + pfc::array_t m_buffer; + }; + + template class t_alloc = pfc::alloc_standard> + class string_utf8_from_wide_t { + public: + string_utf8_from_wide_t() {} + string_utf8_from_wide_t(const wchar_t * p_source,t_size p_source_size = ~0) {convert(p_source,p_source_size);} + + void convert(const wchar_t * p_source,t_size p_source_size = ~0) { + t_size size = estimate_wide_to_utf8(p_source,p_source_size); + m_buffer.set_size(size); + convert_wide_to_utf8( m_buffer.get_ptr_var(),size,p_source,p_source_size); + } + + operator const char * () const {return get_ptr();} + const char * get_ptr() const {return m_buffer.get_ptr();} + const char * toString() const {return get_ptr();} + bool is_empty() const {return string_is_empty_t(get_ptr());} + t_size length() const {return strlen_t(get_ptr());} + + private: + char_buffer_t m_buffer; + }; + typedef string_utf8_from_wide_t<> string_utf8_from_wide; + + template class t_alloc = pfc::alloc_standard> + class string_wide_from_utf8_t { + public: + string_wide_from_utf8_t() {} + string_wide_from_utf8_t(const char* p_source) {convert(p_source);} + string_wide_from_utf8_t(const char* p_source,t_size p_source_size) {convert(p_source,p_source_size);} + + void convert(const char* p_source,t_size p_source_size) { + const t_size size = estimate_size(p_source, p_source_size); + m_buffer.set_size(size); + convert_utf8_to_wide( m_buffer.get_ptr_var(),size,p_source,p_source_size ); + } + void convert(const char * p_source) { + m_buffer.set_size( estimate_size(p_source) ); + convert_utf8_to_wide_unchecked(m_buffer.get_ptr_var(), p_source); + } + + operator const wchar_t * () const {return get_ptr();} + const wchar_t * get_ptr() const {return m_buffer.get_ptr();} + bool is_empty() const {return string_is_empty_t(get_ptr());} + t_size length() const {return strlen_t(get_ptr());} + + void append(const char* p_source,t_size p_source_size) { + const t_size base = length(); + const t_size size = estimate_size(p_source, p_source_size); + m_buffer.set_size(base + size); + convert_utf8_to_wide( m_buffer.get_ptr_var() + base, size, p_source, p_source_size ); + } + void append(const char * p_source) { + const t_size base = length(); + m_buffer.set_size( base + estimate_size(p_source) ); + convert_utf8_to_wide_unchecked(m_buffer.get_ptr_var() + base, p_source); + } + + enum { alloc_prioritizes_speed = t_alloc::alloc_prioritizes_speed }; + private: + + inline t_size estimate_size(const char * source, t_size sourceLen) { + return alloc_prioritizes_speed ? estimate_utf8_to_wide_quick(source, sourceLen) : estimate_utf8_to_wide(source,sourceLen); + } + inline t_size estimate_size(const char * source) { + return alloc_prioritizes_speed ? estimate_utf8_to_wide_quick(source) : estimate_utf8_to_wide(source,~0); + } + char_buffer_t m_buffer; + }; + typedef string_wide_from_utf8_t<> string_wide_from_utf8; + typedef string_wide_from_utf8_t string_wide_from_utf8_fast; + + + template class t_alloc = pfc::alloc_standard> + class string_wide_from_win1252_t { + public: + string_wide_from_win1252_t() {} + string_wide_from_win1252_t(const char * p_source,t_size p_source_size = ~0) {convert(p_source,p_source_size);} + + void convert(const char * p_source,t_size p_source_size = ~0) { + t_size size = estimate_win1252_to_wide(p_source,p_source_size); + m_buffer.set_size(size); + convert_win1252_to_wide(m_buffer.get_ptr_var(),size,p_source,p_source_size); + } + + operator const wchar_t * () const {return get_ptr();} + const wchar_t * get_ptr() const {return m_buffer.get_ptr();} + bool is_empty() const {return string_is_empty_t(get_ptr());} + t_size length() const {return strlen_t(get_ptr());} + + private: + char_buffer_t m_buffer; + }; + typedef string_wide_from_win1252_t<> string_wide_from_win1252; + + + template class t_alloc = pfc::alloc_standard> + class string_win1252_from_wide_t { + public: + string_win1252_from_wide_t() {} + string_win1252_from_wide_t(const wchar_t * p_source,t_size p_source_size = ~0) {convert(p_source,p_source_size);} + + void convert(const wchar_t * p_source,t_size p_source_size = ~0) { + t_size size = estimate_wide_to_win1252(p_source,p_source_size); + m_buffer.set_size(size); + convert_wide_to_win1252(m_buffer.get_ptr_var(),size,p_source,p_source_size); + } + + operator const char * () const {return get_ptr();} + const char * get_ptr() const {return m_buffer.get_ptr();} + bool is_empty() const {return string_is_empty_t(get_ptr());} + t_size length() const {return strlen_t(get_ptr());} + + private: + char_buffer_t m_buffer; + }; + typedef string_win1252_from_wide_t<> string_win1252_from_wide; + + + template class t_alloc = pfc::alloc_standard> + class string_utf8_from_win1252_t { + public: + string_utf8_from_win1252_t() {} + string_utf8_from_win1252_t(const char * p_source,t_size p_source_size = ~0) {convert(p_source,p_source_size);} + + void convert(const char * p_source,t_size p_source_size = ~0) { + t_size size = estimate_win1252_to_utf8(p_source,p_source_size); + m_buffer.set_size(size); + convert_win1252_to_utf8(m_buffer.get_ptr_var(),size,p_source,p_source_size); + } + + operator const char * () const {return get_ptr();} + const char * get_ptr() const {return m_buffer.get_ptr();} + bool is_empty() const {return string_is_empty_t(get_ptr());} + t_size length() const {return strlen_t(get_ptr());} + + private: + char_buffer_t m_buffer; + }; + typedef string_utf8_from_win1252_t<> string_utf8_from_win1252; + + + template class t_alloc = pfc::alloc_standard> + class string_win1252_from_utf8_t { + public: + string_win1252_from_utf8_t() {} + string_win1252_from_utf8_t(const char * p_source,t_size p_source_size = ~0) {convert(p_source,p_source_size);} + + void convert(const char * p_source,t_size p_source_size = ~0) { + t_size size = estimate_utf8_to_win1252(p_source,p_source_size); + m_buffer.set_size(size); + convert_utf8_to_win1252(m_buffer.get_ptr_var(),size,p_source,p_source_size); + } + + operator const char * () const {return get_ptr();} + const char * get_ptr() const {return m_buffer.get_ptr();} + bool is_empty() const {return string_is_empty_t(get_ptr());} + t_size length() const {return strlen_t(get_ptr());} + + private: + char_buffer_t m_buffer; + }; + typedef string_win1252_from_utf8_t<> string_win1252_from_utf8; + + class string_ascii_from_utf8 { + public: + string_ascii_from_utf8() {} + string_ascii_from_utf8( const char * p_source, t_size p_source_size = ~0) { convert(p_source, p_source_size); } + + void convert( const char * p_source, t_size p_source_size = ~0) { + t_size size = estimate_utf8_to_ascii(p_source, p_source_size); + m_buffer.set_size(size); + convert_utf8_to_ascii(m_buffer.get_ptr_var(), size, p_source, p_source_size); + } + + operator const char * () const {return get_ptr();} + const char * get_ptr() const {return m_buffer.get_ptr();} + bool is_empty() const {return string_is_empty_t(get_ptr());} + t_size length() const {return strlen_t(get_ptr());} + + private: + char_buffer_t m_buffer; + }; + + class string_utf8_from_utf16 { + public: + string_utf8_from_utf16() {} + string_utf8_from_utf16( const char16_t * p_source, size_t p_source_size = ~0) {convert(p_source, p_source_size);} + + void convert( const char16_t * p_source, size_t p_source_size = ~0) { + size_t size = estimate_utf16_to_utf8(p_source, p_source_size); + m_buffer.set_size(size); + convert_utf16_to_utf8(m_buffer.get_ptr_var(), size, p_source, p_source_size ); + } + + operator const char * () const {return get_ptr();} + const char * get_ptr() const {return m_buffer.get_ptr();} + bool is_empty() const {return string_is_empty_t(get_ptr());} + t_size length() const {return strlen_t(get_ptr());} + + private: + char_buffer_t m_buffer; + }; + + } +#ifdef _WINDOWS + namespace stringcvt { + + enum { + codepage_system = CP_ACP, + codepage_ascii = 20127, + codepage_iso_8859_1 = 28591, + }; + + + + //! Converts string from specified codepage to wide character. + //! @param p_out Output buffer, receives converted string, with null terminator. + //! @param p_codepage Codepage ID of source string. + //! @param p_source String to convert. + //! @param p_source_size Number of characters to read from p_source. If reading stops if null terminator is encountered earlier. + //! @param p_out_size Size of output buffer, in characters. If converted string is too long, it will be truncated. Null terminator is always written, unless p_out_size is zero. + //! @returns Number of characters written, not counting null terminator. + t_size convert_codepage_to_wide(unsigned p_codepage,wchar_t * p_out,t_size p_out_size,const char * p_source,t_size p_source_size); + + //! Estimates buffer size required to convert specified string from specified codepage to wide character. + //! @param p_codepage Codepage ID of source string. + //! @param p_source String to be converted. + //! @param p_source_size Number of characters to read from p_source. If reading stops if null terminator is encountered earlier. + //! @returns Number of characters to allocate, including space for null terminator. + t_size estimate_codepage_to_wide(unsigned p_codepage,const char * p_source,t_size p_source_size); + + //! Converts string from wide character to specified codepage. + //! @param p_codepage Codepage ID of source string. + //! @param p_out Output buffer, receives converted string, with null terminator. + //! @param p_out_size Size of output buffer, in characters. If converted string is too long, it will be truncated. Null terminator is always written, unless p_out_size is zero. + //! @param p_source String to convert. + //! @param p_source_size Number of characters to read from p_source. If reading stops if null terminator is encountered earlier. + //! @returns Number of characters written, not counting null terminator. + t_size convert_wide_to_codepage(unsigned p_codepage,char * p_out,t_size p_out_size,const wchar_t * p_source,t_size p_source_size); + + //! Estimates buffer size required to convert specified wide character string to specified codepage. + //! @param p_codepage Codepage ID of source string. + //! @param p_source String to be converted. + //! @param p_source_size Number of characters to read from p_source. If reading stops if null terminator is encountered earlier. + //! @returns Number of characters to allocate, including space for null terminator. + t_size estimate_wide_to_codepage(unsigned p_codepage,const wchar_t * p_source,t_size p_source_size); + + + //! Converts string from system codepage to wide character. + //! @param p_out Output buffer, receives converted string, with null terminator. + //! @param p_source String to convert. + //! @param p_source_size Number of characters to read from p_source. If reading stops if null terminator is encountered earlier. + //! @param p_out_size Size of output buffer, in characters. If converted string is too long, it will be truncated. Null terminator is always written, unless p_out_size is zero. + //! @returns Number of characters written, not counting null terminator. + inline t_size convert_ansi_to_wide(wchar_t * p_out,t_size p_out_size,const char * p_source,t_size p_source_size) { + return convert_codepage_to_wide(codepage_system,p_out,p_out_size,p_source,p_source_size); + } + + //! Estimates buffer size required to convert specified system codepage string to wide character. + //! @param p_source String to be converted. + //! @param p_source_size Number of characters to read from p_source. If reading stops if null terminator is encountered earlier. + //! @returns Number of characters to allocate, including space for null terminator. + inline t_size estimate_ansi_to_wide(const char * p_source,t_size p_source_size) { + return estimate_codepage_to_wide(codepage_system,p_source,p_source_size); + } + + //! Converts string from wide character to system codepage. + //! @param p_out Output buffer, receives converted string, with null terminator. + //! @param p_out_size Size of output buffer, in characters. If converted string is too long, it will be truncated. Null terminator is always written, unless p_out_size is zero. + //! @param p_source String to convert. + //! @param p_source_size Number of characters to read from p_source. If reading stops if null terminator is encountered earlier. + //! @returns Number of characters written, not counting null terminator. + inline t_size convert_wide_to_ansi(char * p_out,t_size p_out_size,const wchar_t * p_source,t_size p_source_size) { + return convert_wide_to_codepage(codepage_system,p_out,p_out_size,p_source,p_source_size); + } + + //! Estimates buffer size required to convert specified wide character string to system codepage. + //! @param p_source String to be converted. + //! @param p_source_size Number of characters to read from p_source. If reading stops if null terminator is encountered earlier. + //! @returns Number of characters to allocate, including space for null terminator. + inline t_size estimate_wide_to_ansi(const wchar_t * p_source,t_size p_source_size) { + return estimate_wide_to_codepage(codepage_system,p_source,p_source_size); + } + + + template class t_alloc = pfc::alloc_standard> + class string_wide_from_codepage_t { + public: + string_wide_from_codepage_t() {} + string_wide_from_codepage_t(const string_wide_from_codepage_t & p_source) : m_buffer(p_source.m_buffer) {} + string_wide_from_codepage_t(unsigned p_codepage,const char * p_source,t_size p_source_size = ~0) {convert(p_codepage,p_source,p_source_size);} + + void convert(unsigned p_codepage,const char * p_source,t_size p_source_size = ~0) { + t_size size = estimate_codepage_to_wide(p_codepage,p_source,p_source_size); + m_buffer.set_size(size); + convert_codepage_to_wide(p_codepage, m_buffer.get_ptr_var(),size,p_source,p_source_size); + } + + operator const wchar_t * () const {return get_ptr();} + const wchar_t * get_ptr() const {return m_buffer.get_ptr();} + bool is_empty() const {return string_is_empty_t(get_ptr());} + t_size length() const {return strlen_t(get_ptr());} + + private: + char_buffer_t m_buffer; + }; + typedef string_wide_from_codepage_t<> string_wide_from_codepage; + + + template class t_alloc = pfc::alloc_standard> + class string_codepage_from_wide_t { + public: + string_codepage_from_wide_t() {} + string_codepage_from_wide_t(const string_codepage_from_wide_t & p_source) : m_buffer(p_source.m_buffer) {} + string_codepage_from_wide_t(unsigned p_codepage,const wchar_t * p_source,t_size p_source_size = ~0) {convert(p_codepage,p_source,p_source_size);} + + void convert(unsigned p_codepage,const wchar_t * p_source,t_size p_source_size = ~0) { + t_size size = estimate_wide_to_codepage(p_codepage,p_source,p_source_size); + m_buffer.set_size(size); + convert_wide_to_codepage(p_codepage, m_buffer.get_ptr_var(),size,p_source,p_source_size); + } + + operator const char * () const {return get_ptr();} + const char * get_ptr() const {return m_buffer.get_ptr();} + bool is_empty() const {return string_is_empty_t(get_ptr());} + t_size length() const {return strlen_t(get_ptr());} + + private: + char_buffer_t m_buffer; + }; + typedef string_codepage_from_wide_t<> string_codepage_from_wide; + + class string_codepage_from_utf8 { + public: + string_codepage_from_utf8() {} + string_codepage_from_utf8(const string_codepage_from_utf8 & p_source) : m_buffer(p_source.m_buffer) {} + string_codepage_from_utf8(unsigned p_codepage,const char * p_source,t_size p_source_size = ~0) {convert(p_codepage,p_source,p_source_size);} + + void convert(unsigned p_codepage,const char * p_source,t_size p_source_size = ~0) { + string_wide_from_utf8 temp; + temp.convert(p_source,p_source_size); + t_size size = estimate_wide_to_codepage(p_codepage,temp,SIZE_MAX); + m_buffer.set_size(size); + convert_wide_to_codepage(p_codepage,m_buffer.get_ptr_var(),size,temp,~0); + } + + operator const char * () const {return get_ptr();} + const char * get_ptr() const {return m_buffer.get_ptr();} + bool is_empty() const {return string_is_empty_t(get_ptr());} + t_size length() const {return strlen_t(get_ptr());} + + private: + char_buffer_t m_buffer; + }; + + class string_utf8_from_codepage { + public: + string_utf8_from_codepage() {} + string_utf8_from_codepage(const string_utf8_from_codepage & p_source) : m_buffer(p_source.m_buffer) {} + string_utf8_from_codepage(unsigned p_codepage,const char * p_source,t_size p_source_size = ~0) {convert(p_codepage,p_source,p_source_size);} + + void convert(unsigned p_codepage,const char * p_source,t_size p_source_size = ~0) { + string_wide_from_codepage temp; + temp.convert(p_codepage,p_source,p_source_size); + t_size size = estimate_wide_to_utf8(temp,SIZE_MAX); + m_buffer.set_size(size); + convert_wide_to_utf8( m_buffer.get_ptr_var(),size,temp,SIZE_MAX); + } + + operator const char * () const {return get_ptr();} + const char * get_ptr() const {return m_buffer.get_ptr();} + bool is_empty() const {return string_is_empty_t(get_ptr());} + t_size length() const {return strlen_t(get_ptr());} + + private: + char_buffer_t m_buffer; + }; + + + class string_utf8_from_ansi { + public: + string_utf8_from_ansi() {} + string_utf8_from_ansi(const string_utf8_from_ansi & p_source) : m_buffer(p_source.m_buffer) {} + string_utf8_from_ansi(const char * p_source,t_size p_source_size = ~0) : m_buffer(codepage_system,p_source,p_source_size) {} + operator const char * () const {return get_ptr();} + const char * get_ptr() const {return m_buffer.get_ptr();} + const char * toString() const {return get_ptr();} + bool is_empty() const {return string_is_empty_t(get_ptr());} + t_size length() const {return strlen_t(get_ptr());} + void convert(const char * p_source,t_size p_source_size = ~0) {m_buffer.convert(codepage_system,p_source,p_source_size);} + + private: + string_utf8_from_codepage m_buffer; + }; + + class string_ansi_from_utf8 { + public: + string_ansi_from_utf8() {} + string_ansi_from_utf8(const string_ansi_from_utf8 & p_source) : m_buffer(p_source.m_buffer) {} + string_ansi_from_utf8(const char * p_source,t_size p_source_size = ~0) : m_buffer(codepage_system,p_source,p_source_size) {} + operator const char * () const {return get_ptr();} + const char * get_ptr() const {return m_buffer.get_ptr();} + bool is_empty() const {return string_is_empty_t(get_ptr());} + t_size length() const {return strlen_t(get_ptr());} + + void convert(const char * p_source,t_size p_source_size = ~0) {m_buffer.convert(codepage_system,p_source,p_source_size);} + + private: + string_codepage_from_utf8 m_buffer; + }; + + class string_wide_from_ansi { + public: + string_wide_from_ansi() {} + string_wide_from_ansi(const string_wide_from_ansi & p_source) : m_buffer(p_source.m_buffer) {} + string_wide_from_ansi(const char * p_source,t_size p_source_size = ~0) : m_buffer(codepage_system,p_source,p_source_size) {} + operator const wchar_t * () const {return get_ptr();} + const wchar_t * get_ptr() const {return m_buffer.get_ptr();} + bool is_empty() const {return string_is_empty_t(get_ptr());} + t_size length() const {return strlen_t(get_ptr());} + + void convert(const char * p_source,t_size p_source_size = ~0) {m_buffer.convert(codepage_system,p_source,p_source_size);} + + private: + string_wide_from_codepage m_buffer; + }; + + class string_ansi_from_wide { + public: + string_ansi_from_wide() {} + string_ansi_from_wide(const string_ansi_from_wide & p_source) : m_buffer(p_source.m_buffer) {} + string_ansi_from_wide(const wchar_t * p_source,t_size p_source_size = ~0) : m_buffer(codepage_system,p_source,p_source_size) {} + operator const char * () const {return get_ptr();} + const char * get_ptr() const {return m_buffer.get_ptr();} + bool is_empty() const {return string_is_empty_t(get_ptr());} + t_size length() const {return strlen_t(get_ptr());} + + void convert(const wchar_t * p_source,t_size p_source_size = ~0) {m_buffer.convert(codepage_system,p_source,p_source_size);} + + private: + string_codepage_from_wide m_buffer; + }; + +#ifdef UNICODE + typedef string_wide_from_utf8 string_os_from_utf8; + typedef string_utf8_from_wide string_utf8_from_os; + typedef string_wide_from_utf8_fast string_os_from_utf8_fast; +#else + typedef string_ansi_from_utf8 string_os_from_utf8; + typedef string_utf8_from_ansi string_utf8_from_os; + typedef string_ansi_from_utf8 string_os_from_utf8_fast; +#endif + + class string_utf8_from_os_ex { + public: + template string_utf8_from_os_ex(const t_source * source, t_size sourceLen = ~0) { + convert(source,sourceLen); + } + + void convert(const char * source, t_size sourceLen = ~0) { + m_buffer = string_utf8_from_ansi(source,sourceLen); + } + void convert(const wchar_t * source, t_size sourceLen = ~0) { + m_buffer = string_utf8_from_wide(source,sourceLen); + } + + operator const char * () const {return get_ptr();} + const char * get_ptr() const {return m_buffer.get_ptr();} + bool is_empty() const {return m_buffer.is_empty();} + t_size length() const {return m_buffer.length();} + const char * toString() const {return get_ptr();} + private: + string8 m_buffer; + }; + } + +#else + namespace stringcvt { + typedef string_win1252_from_wide string_ansi_from_wide; + typedef string_wide_from_win1252 string_wide_from_ansi; + typedef string_win1252_from_utf8 string_ansi_from_utf8; + typedef string_utf8_from_win1252 string_utf8_from_ansi; + } +#endif +}; + +pfc::string_base & operator<<(pfc::string_base & p_fmt, const wchar_t * p_str); diff --git a/pfc/string_list.h b/pfc/string_list.h new file mode 100644 index 0000000..deeaf04 --- /dev/null +++ b/pfc/string_list.h @@ -0,0 +1,41 @@ +#pragma once + +namespace pfc { + + typedef list_base_const_t string_list_const; + + class string_list_impl : public string_list_const + { + public: + t_size get_count() const {return m_data.get_size();} + void get_item_ex(const char* & p_out, t_size n) const {p_out = m_data[n];} + + const char * operator[] (t_size n) const {return m_data[n];} + void add_item(const char * p_string) {pfc::append_t(m_data, p_string);} + + template void add_items(const t_what & p_source) {_append(p_source);} + + void remove_all() {m_data.set_size(0);} + + string_list_impl() {} + template string_list_impl(const t_what & p_source) {_copy(p_source);} + template string_list_impl & operator=(const t_what & p_source) {_copy(p_source); return *this;} + template string_list_impl & operator|=(const string_list_impl & p_source) {_append(p_source); return *this;} + template string_list_impl & operator+=(const t_what & p_source) {pfc::append_t(m_data, p_source); return *this;} + + private: + template void _append(const t_what & p_source) { + const t_size toadd = p_source.get_size(), base = m_data.get_size(); + m_data.set_size(base+toadd); + for(t_size n=0;n void _copy(const t_what & p_source) { + const t_size newcount = p_source.get_size(); + m_data.set_size(newcount); + for(t_size n=0;n m_data; + }; +} diff --git a/pfc/suppress_fb2k_hooks.h b/pfc/suppress_fb2k_hooks.h new file mode 100644 index 0000000..61d96ce --- /dev/null +++ b/pfc/suppress_fb2k_hooks.h @@ -0,0 +1,19 @@ +#pragma once + +/* +foobar2000 shared.dll hook implementations +If you're getting linker multiple-definition errors on these, change build configuration of PFC from "Debug" / "Release" to "Debug FB2K" / "Release FB2K" +Configurations with "FB2K" suffix disable compilation of pfc-fb2k-hooks.cpp allowing these methods to be redirected to shared.dll calls +*/ + +namespace pfc { + void crashImpl(); + BOOL winFormatSystemErrorMessageImpl(pfc::string_base & p_out, DWORD p_code); + + void crashHook() { + crashImpl(); + } + BOOL winFormatSystemErrorMessageHook(pfc::string_base & p_out, DWORD p_code) { + return winFormatSystemErrorMessageImpl(p_out, p_code); + } +} diff --git a/pfc/syncd_storage.h b/pfc/syncd_storage.h new file mode 100644 index 0000000..28282a6 --- /dev/null +++ b/pfc/syncd_storage.h @@ -0,0 +1,95 @@ +#pragma once + +namespace pfc { +// Read/write lock guarded object store for safe concurrent access +template +class syncd_storage { +private: + typedef syncd_storage t_self; +public: + syncd_storage() {} + template + syncd_storage(const t_source & p_source) : m_object(p_source) {} + template + void set(t_source const & p_in) { + inWriteSync(m_sync); + m_object = p_in; + } + template + void get(t_destination & p_out) const { + inReadSync(m_sync); + p_out = m_object; + } + t_object get() const { + inReadSync(m_sync); + return m_object; + } + template + const t_self & operator=(t_source const & p_source) {set(p_source); return *this;} +private: + mutable ::pfc::readWriteLock m_sync; + t_object m_object; +}; + +// Read/write lock guarded object store for safe concurrent access +// With 'has changed since last read' flag +template +class syncd_storage_flagged { +private: + typedef syncd_storage_flagged t_self; +public: + syncd_storage_flagged() : m_changed_flag(false) {} + template + syncd_storage_flagged(const t_source & p_source, bool initChanged = false) : m_changed_flag(initChanged), m_object(p_source) {} + void set_changed(bool p_flag = true) { + inWriteSync(m_sync); + m_changed_flag = p_flag; + } + template + void set(t_source const & p_in) { + inWriteSync(m_sync); + m_object = p_in; + m_changed_flag = true; + } + bool has_changed() const { + inReadSync(m_sync); + return m_changed_flag; + } + t_object peek() const {inReadSync(m_sync); return m_object;} + template + bool get_if_changed(t_destination & p_out) { + inReadSync(m_sync); + if (m_changed_flag) { + p_out = m_object; + m_changed_flag = false; + return true; + } else { + return false; + } + } + t_object get() { + inReadSync(m_sync); + m_changed_flag = false; + return m_object; + } + t_object get( bool & bHasChanged ) { + inReadSync(m_sync); + bHasChanged = m_changed_flag; + m_changed_flag = false; + return m_object; + } + template + void get(t_destination & p_out) { + inReadSync(m_sync); + p_out = m_object; + m_changed_flag = false; + } + template + const t_self & operator=(t_source const & p_source) {set(p_source); return *this;} +private: + mutable volatile bool m_changed_flag; + mutable ::pfc::readWriteLock m_sync; + t_object m_object; +}; + +} \ No newline at end of file diff --git a/pfc/synchro.h b/pfc/synchro.h new file mode 100644 index 0000000..73fac72 --- /dev/null +++ b/pfc/synchro.h @@ -0,0 +1,16 @@ +#pragma once +// added for compatibility with fb2k mobile + +namespace pfc { + class dummyLock { + public: + void enterRead() {} + void enterWrite() {} + void leaveRead() {} + void leaveWrite() {} + void enter() {} + void leave() {} + void lock() {} + void unlock() {} + }; +} \ No newline at end of file diff --git a/pfc/synchro_nix.cpp b/pfc/synchro_nix.cpp new file mode 100644 index 0000000..a667eb1 --- /dev/null +++ b/pfc/synchro_nix.cpp @@ -0,0 +1,33 @@ +#include "pfc.h" + +#ifndef _WIN32 + +namespace pfc { + + void mutexBase::create( const pthread_mutexattr_t * attr ) { + if (pthread_mutex_init( &obj, attr) != 0) { + throw exception_bug_check(); + } + } + void mutexBase::destroy() { + pthread_mutex_destroy( &obj ); + } + void mutexBase::createRecur() { + mutexAttr a; a.setRecursive(); create(&a.attr); + } + void mutexBase::create( const mutexAttr & a ) { + create( & a.attr ); + } + + void readWriteLockBase::create( const pthread_rwlockattr_t * attr ) { + if (pthread_rwlock_init( &obj, attr) != 0) { + throw exception_bug_check(); + } + } + void readWriteLockBase::create( const readWriteLockAttr & a) { + create(&a.attr); + } + +} + +#endif // _WIN32 diff --git a/pfc/synchro_nix.h b/pfc/synchro_nix.h new file mode 100644 index 0000000..45a4360 --- /dev/null +++ b/pfc/synchro_nix.h @@ -0,0 +1,146 @@ +#include + +namespace pfc { + class mutexAttr { + public: + mutexAttr() {pthread_mutexattr_init(&attr);} + ~mutexAttr() {pthread_mutexattr_destroy(&attr);} + void setType(int type) {pthread_mutexattr_settype(&attr, type);} + int getType() const {int rv = 0; pthread_mutexattr_gettype(&attr, &rv); return rv; } + void setRecursive() {setType(PTHREAD_MUTEX_RECURSIVE);} + bool isRecursive() {return getType() == PTHREAD_MUTEX_RECURSIVE;} + void setProcessShared() {pthread_mutexattr_setpshared(&attr, PTHREAD_PROCESS_SHARED);} + operator const pthread_mutexattr_t & () const {return attr;} + operator pthread_mutexattr_t & () {return attr;} + pthread_mutexattr_t attr; + private: + mutexAttr(const mutexAttr&); void operator=(const mutexAttr&); + }; + + class mutexBase { + public: + void lock() throw() {pthread_mutex_lock(&obj);} + void unlock() throw() {pthread_mutex_unlock(&obj);} + + void enter() throw() {lock();} + void leave() throw() {unlock();} + + void create( const pthread_mutexattr_t * attr ); + void create( const mutexAttr & ); + void createRecur(); + void destroy(); + protected: + mutexBase() {} + ~mutexBase() {} + private: + pthread_mutex_t obj; + + void operator=( const mutexBase & ); + mutexBase( const mutexBase & ); + }; + + + + class mutex : public mutexBase { + public: + mutex() {create(NULL);} + ~mutex() {destroy();} + }; + + class mutexRecur : public mutexBase { + public: + mutexRecur() {createRecur();} + ~mutexRecur() {destroy();} + }; + + + class mutexRecurStatic : public mutexBase { + public: + mutexRecurStatic() {createRecur();} + }; + + + class mutexScope { + public: + mutexScope( mutexBase * m ) throw() : m_mutex(m) { m_mutex->enter(); } + mutexScope( mutexBase & m ) throw() : m_mutex(&m) { m_mutex->enter(); } + ~mutexScope( ) throw() {m_mutex->leave();} + private: + void operator=( const mutexScope & ); mutexScope( const mutexScope & ); + mutexBase * m_mutex; + }; + + + class readWriteLockAttr { + public: + readWriteLockAttr() {pthread_rwlockattr_init( & attr ); } + ~readWriteLockAttr() {pthread_rwlockattr_destroy( & attr ) ;} + + void setProcessShared( bool val = true ) { + pthread_rwlockattr_setpshared( &attr, val ? PTHREAD_PROCESS_SHARED : PTHREAD_PROCESS_PRIVATE ); + } + + pthread_rwlockattr_t attr; + + private: + readWriteLockAttr( const readWriteLockAttr &); void operator=(const readWriteLockAttr & ); + }; + + class readWriteLockBase { + public: + void create( const pthread_rwlockattr_t * attr ); + void create( const readWriteLockAttr & ); + void destroy() {pthread_rwlock_destroy( & obj ); } + + void enterRead() {pthread_rwlock_rdlock( &obj ); } + void enterWrite() {pthread_rwlock_wrlock( &obj ); } + void leaveRead() {pthread_rwlock_unlock( &obj ); } + void leaveWrite() {pthread_rwlock_unlock( &obj ); } + protected: + readWriteLockBase() {} + ~readWriteLockBase() {} + private: + pthread_rwlock_t obj; + + void operator=( const readWriteLockBase & ); readWriteLockBase( const readWriteLockBase & ); + }; + + + class readWriteLock : public readWriteLockBase { + public: + readWriteLock() {create(NULL);} + ~readWriteLock() {destroy();} + }; + + + class _readWriteLock_scope_read { + public: + _readWriteLock_scope_read( readWriteLockBase & lock ) : m_lock( lock ) { m_lock.enterRead(); } + ~_readWriteLock_scope_read() {m_lock.leaveRead();} + private: + _readWriteLock_scope_read( const _readWriteLock_scope_read &); + void operator=( const _readWriteLock_scope_read &); + readWriteLockBase & m_lock; + }; + class _readWriteLock_scope_write { + public: + _readWriteLock_scope_write( readWriteLockBase & lock ) : m_lock( lock ) { m_lock.enterWrite(); } + ~_readWriteLock_scope_write() {m_lock.leaveWrite();} + private: + _readWriteLock_scope_write( const _readWriteLock_scope_write &); + void operator=( const _readWriteLock_scope_write &); + readWriteLockBase & m_lock; + }; +} + + + +typedef pfc::mutexRecur critical_section; +typedef pfc::mutexRecurStatic critical_section_static; +typedef pfc::mutexScope c_insync; + +#define insync(X) c_insync blah____sync(X) + + +#define inReadSync( X ) ::pfc::_readWriteLock_scope_read _asdf_l_readWriteLock_scope_read( X ) +#define inWriteSync( X ) ::pfc::_readWriteLock_scope_write _asdf_l_readWriteLock_scope_write( X ) diff --git a/pfc/synchro_win.h b/pfc/synchro_win.h new file mode 100644 index 0000000..fa0b5eb --- /dev/null +++ b/pfc/synchro_win.h @@ -0,0 +1,114 @@ +#pragma once + +class _critical_section_base { +protected: + CRITICAL_SECTION sec; +public: + _critical_section_base() {} + inline void enter() throw() {EnterCriticalSection(&sec);} + inline void leave() throw() {LeaveCriticalSection(&sec);} + inline void create() throw() { +#ifdef PFC_WINDOWS_DESKTOP_APP + InitializeCriticalSection(&sec); +#else + InitializeCriticalSectionEx(&sec,0,0); +#endif + } + inline void destroy() throw() {DeleteCriticalSection(&sec);} + inline bool tryEnter() throw() { return !!TryEnterCriticalSection(&sec); } +private: + _critical_section_base(const _critical_section_base&); + void operator=(const _critical_section_base&); +}; + +// Static-lifetime critical section, no cleanup - valid until process termination +class critical_section_static : public _critical_section_base { +public: + critical_section_static() {create();} +#if !PFC_LEAK_STATIC_OBJECTS + ~critical_section_static() {destroy();} +#endif +}; + +// Regular critical section, intended for any lifetime scopes +class critical_section : public _critical_section_base { +public: + critical_section() {create();} + ~critical_section() {destroy();} +}; + +template +class c_insync_ +{ +private: + lock_t& m_section; +public: + c_insync_(lock_t * p_section) throw() : m_section(*p_section) {m_section.enter();} + c_insync_(lock_t & p_section) throw() : m_section(p_section) {m_section.enter();} + ~c_insync_() throw() {m_section.leave();} +}; + +typedef c_insync_<_critical_section_base> c_insync; + +// Old typedef for backwards compat +#define insync(X) c_insync blah____sync(X) +// New typedef +#define PFC_INSYNC(X) c_insync_ blah____sync(X) + + +namespace pfc { + + +// Read write lock - Vista-and-newer friendly lock that allows concurrent reads from a resource that permits such +// Warning, non-recursion proof +class readWriteLock { +public: + readWriteLock() : theLock() { + } + + void enterRead() { + AcquireSRWLockShared( & theLock ); + } + void enterWrite() { + AcquireSRWLockExclusive( & theLock ); + } + void leaveRead() { + ReleaseSRWLockShared( & theLock ); + } + void leaveWrite() { + ReleaseSRWLockExclusive( &theLock ); + } + +private: + readWriteLock(const readWriteLock&) = delete; + void operator=(const readWriteLock&) = delete; + + SRWLOCK theLock; +}; + +class _readWriteLock_scope_read { +public: + _readWriteLock_scope_read( readWriteLock & lock ) : m_lock( lock ) { m_lock.enterRead(); } + ~_readWriteLock_scope_read() {m_lock.leaveRead();} +private: + _readWriteLock_scope_read( const _readWriteLock_scope_read &) = delete; + void operator=( const _readWriteLock_scope_read &) = delete; + readWriteLock & m_lock; +}; +class _readWriteLock_scope_write { +public: + _readWriteLock_scope_write( readWriteLock & lock ) : m_lock( lock ) { m_lock.enterWrite(); } + ~_readWriteLock_scope_write() {m_lock.leaveWrite();} +private: + _readWriteLock_scope_write( const _readWriteLock_scope_write &) = delete; + void operator=( const _readWriteLock_scope_write &) = delete; + readWriteLock & m_lock; +}; + +#define inReadSync( X ) ::pfc::_readWriteLock_scope_read _asdf_l_readWriteLock_scope_read( X ) +#define inWriteSync( X ) ::pfc::_readWriteLock_scope_write _asdf_l_readWriteLock_scope_write( X ) + +typedef ::critical_section mutex; +typedef ::c_insync mutexScope; + +} diff --git a/pfc/targetver.h b/pfc/targetver.h new file mode 100644 index 0000000..809eaad --- /dev/null +++ b/pfc/targetver.h @@ -0,0 +1,6 @@ +#pragma once + +#ifndef _WIN32_WINNT +#define _WIN32_WINNT 0x0601 +#include +#endif \ No newline at end of file diff --git a/pfc/threads.cpp b/pfc/threads.cpp new file mode 100644 index 0000000..c8e754c --- /dev/null +++ b/pfc/threads.cpp @@ -0,0 +1,210 @@ +#include "pfc.h" + +#ifdef _WIN32 +#include "pp-winapi.h" +#endif + +#ifdef __APPLE__ +#include +#include +#endif + +#include + +namespace pfc { + t_size getOptimalWorkerThreadCount() { + return std::thread::hardware_concurrency(); + } + + t_size getOptimalWorkerThreadCountEx(t_size taskCountLimit) { + if (taskCountLimit <= 1) return 1; + return pfc::min_t(taskCountLimit,getOptimalWorkerThreadCount()); + } + + + void thread::entry() { + try { + threadProc(); + } catch(...) {} + } + + void thread::couldNotCreateThread() { + // Something terminally wrong, better crash leaving a good debug trace + crash(); + } + +#ifdef _WIN32 + thread::thread() : m_thread(INVALID_HANDLE_VALUE) + { + } + + void thread::close() { + if (isActive()) { + + int ctxPriority = GetThreadPriority( GetCurrentThread() ); + if (ctxPriority > GetThreadPriority( m_thread ) ) SetThreadPriority( m_thread, ctxPriority ); + + if (WaitForSingleObject(m_thread,INFINITE) != WAIT_OBJECT_0) couldNotCreateThread(); + CloseHandle(m_thread); m_thread = INVALID_HANDLE_VALUE; + } + } + bool thread::isActive() const { + return m_thread != INVALID_HANDLE_VALUE; + } + + static HANDLE MyBeginThread( unsigned (__stdcall * proc)(void *) , void * arg, DWORD * outThreadID, int priority) { + HANDLE thread; +#ifdef PFC_WINDOWS_DESKTOP_APP + thread = (HANDLE)_beginthreadex(NULL, 0, proc, arg, CREATE_SUSPENDED, (unsigned int*)outThreadID); +#else + thread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)proc, arg, CREATE_SUSPENDED, outThreadID); +#endif + if (thread == NULL) thread::couldNotCreateThread(); + SetThreadPriority(thread, priority); + ResumeThread(thread); + return thread; + } + void thread::winStart(int priority, DWORD * outThreadID) { + close(); + m_thread = MyBeginThread(g_entry, reinterpret_cast(this), outThreadID, priority); + } + void thread::startWithPriority(int priority) { + winStart(priority, NULL); + } + void thread::setPriority(int priority) { + PFC_ASSERT(isActive()); + SetThreadPriority(m_thread, priority); + } + void thread::start() { + startWithPriority(GetThreadPriority(GetCurrentThread())); + } + + unsigned CALLBACK thread::g_entry(void* p_instance) { + reinterpret_cast(p_instance)->entry(); return 0; + } + + int thread::getPriority() { + PFC_ASSERT(isActive()); + return GetThreadPriority( m_thread ); + } + + int thread::currentPriority() { + return GetThreadPriority( GetCurrentThread() ); + } +#else + thread::thread() : m_thread(), m_threadValid() { + } + +#ifndef __APPLE__ // Apple specific entrypoint in obj-c.mm + void * thread::g_entry( void * arg ) { + reinterpret_cast( arg )->entry(); + return NULL; + } +#endif + + void thread::startWithPriority(int priority) { + close(); +#ifdef __APPLE__ + appleStartThreadPrologue(); +#endif + pthread_t thread; + pthread_attr_t attr; + pthread_attr_init(&attr); + + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); + + if (pthread_create(&thread, &attr, g_entry, reinterpret_cast(this)) < 0) couldNotCreateThread(); + + pthread_attr_destroy(&attr); + m_threadValid = true; + m_thread = thread; + } + + void thread::setPriority(int priority) { + PFC_ASSERT(isActive()); + // not avail + } + int thread::getPriority() { + return 0; // not avail + } + int thread::currentPriority() { + return 0; // not avail + } + + void thread::start() { + startWithPriority(currentPriority()); + } + + void thread::close() { + if (m_threadValid) { + void * rv = NULL; + pthread_join( m_thread, & rv ); m_thread = 0; + m_threadValid = false; + } + } + + bool thread::isActive() const { + return m_threadValid; + } +#endif +#ifdef _WIN32 + unsigned CALLBACK winSplitThreadProc(void* arg) { + auto f = reinterpret_cast *>(arg); + (*f)(); + delete f; + return 0; + } +#else + void * nixSplitThreadProc(void * arg) { + auto f = reinterpret_cast *>(arg); +#ifdef __APPLE__ + inAutoReleasePool([f] { + (*f)(); + delete f; + }); +#else + (*f)(); + delete f; +#endif + return NULL; + } +#endif + + void splitThread(std::function f) { + ptrholder_t< std::function > arg ( new std::function(f) ); +#ifdef _WIN32 + HANDLE h = MyBeginThread(winSplitThreadProc, arg.get_ptr(), NULL, GetThreadPriority(GetCurrentThread())); + CloseHandle(h); +#else +#ifdef __APPLE__ + thread::appleStartThreadPrologue(); +#endif + pthread_t thread; + + if (pthread_create(&thread, NULL, nixSplitThreadProc, arg.get_ptr()) < 0) thread::couldNotCreateThread(); + + pthread_detach(thread); +#endif + arg.detach(); + } +#ifndef __APPLE__ + // Stub for non Apple + void inAutoReleasePool(std::function f) { f(); } +#endif + + + void thread2::startHereWithPriority(std::function e, int priority) { + setEntry(e); startWithPriority(priority); + } + void thread2::startHere(std::function e) { + setEntry(e); start(); + } + void thread2::setEntry(std::function e) { + PFC_ASSERT(!isActive()); + m_entryPoint = e; + } + + void thread2::threadProc() { + m_entryPoint(); + } +} diff --git a/pfc/threads.h b/pfc/threads.h new file mode 100644 index 0000000..f06bfb0 --- /dev/null +++ b/pfc/threads.h @@ -0,0 +1,77 @@ +#pragma once + +#include + +#ifdef _WIN32 +#include +#else +#include +#endif +namespace pfc { + t_size getOptimalWorkerThreadCount(); + t_size getOptimalWorkerThreadCountEx(t_size taskCountLimit); + + //! IMPORTANT: all classes derived from thread must call waitTillDone() in their destructor, to avoid object destruction during a virtual function call! + class thread { + public: + + //! Critical error handler function, never returns + PFC_NORETURN static void couldNotCreateThread(); + + thread(); + ~thread() {PFC_ASSERT(!isActive()); waitTillDone();} + void startWithPriority(int priority); + void setPriority(int priority); + int getPriority(); + void start(); + bool isActive() const; + void waitTillDone() {close();} + static int currentPriority(); +#ifdef _WIN32 + void winStart(int priority, DWORD * outThreadID); + HANDLE winThreadHandle() { return m_thread; } +#else + pthread_t posixThreadHandle() { return m_thread; } +#endif +#ifdef __APPLE__ + static void appleStartThreadPrologue(); +#endif + protected: + virtual void threadProc() {PFC_ASSERT(!"Stub thread entry - should not get here");} + private: + void close(); +#ifdef _WIN32 + static unsigned CALLBACK g_entry(void* p_instance); +#else + static void * g_entry( void * arg ); +#endif + void entry(); + +#ifdef _WIN32 + HANDLE m_thread; +#else + pthread_t m_thread; + bool m_threadValid; // there is no invalid pthread_t, so we keep a separate 'valid' flag +#endif + PFC_CLASS_NOT_COPYABLE_EX(thread) + }; + + //! Thread class using lambda entrypoint rather than function override + class thread2 : public thread { + public: + ~thread2() { waitTillDone(); } + void startHereWithPriority(std::function e, int priority); + void startHere(std::function e); + void setEntry(std::function e); + private: + void threadProc(); + + std::function m_entryPoint; + }; + + void splitThread(std::function f); + + //! Apple specific; executes the function in a release pool scope. \n + //! On non Apple platforms it just invokes the function. + void inAutoReleasePool(std::function f); +} diff --git a/pfc/timers.cpp b/pfc/timers.cpp new file mode 100644 index 0000000..9bda731 --- /dev/null +++ b/pfc/timers.cpp @@ -0,0 +1,112 @@ +#include "pfc.h" + +#if defined(_WIN32) && defined(PFC_HAVE_PROFILER) +#include +#endif + +namespace pfc { + +#ifdef PFC_HAVE_PROFILER + +profiler_static::profiler_static(const char * p_name) +{ + name = p_name; + total_time = 0; + num_called = 0; +} + +static void profilerMsg(const char* msg) { +#ifdef _WIN32 + if (!IsDebuggerPresent()) { + static HANDLE hWriteTo = INVALID_HANDLE_VALUE; + static bool initialized = false; + if (!initialized) { + initialized = true; + wchar_t path[1024] = {}; + if (SUCCEEDED(SHGetFolderPath(NULL, CSIDL_DESKTOP, NULL, SHGFP_TYPE_CURRENT, path))) { + size_t l = wcslen(path); + if (l > 0) { + if (path[l - 1] != '\\') { + wcscat_s(path, L"\\"); + } + wchar_t fn[256]; + wsprintf(fn, L"profiler-%u.txt", GetProcessId(GetCurrentProcess())); + wcscat_s(path, fn); + hWriteTo = CreateFile(path, GENERIC_WRITE, 0, NULL, OPEN_ALWAYS, 0, NULL); + } + } + } + if (hWriteTo != INVALID_HANDLE_VALUE) { + SetFilePointer(hWriteTo, 0, 0, SEEK_END); + pfc::string8 temp = msg; + temp += "\r\n"; + DWORD written = 0; + WriteFile(hWriteTo, temp.c_str(), temp.length(), &written, NULL); + } + } +#endif + outputDebugLine(msg); +} + +profiler_static::~profiler_static() +{ + try { + pfc::string_fixed_t<511> message; + message << "profiler: " << pfc::format_pad_left >(48,' ',name) << " - " << + pfc::format_pad_right >(16,' ',pfc::format_uint(total_time) ) << " cycles"; + + if (num_called > 0) { + message << " (executed " << num_called << " times, " << (total_time / num_called) << " average)"; + } + profilerMsg(message); + } catch(...) { + //should never happen + OutputDebugString(_T("unexpected profiler failure\n")); + } +} +#endif + +#ifndef _WIN32 + + void hires_timer::start() { + m_start = nixGetTime(); + } + double hires_timer::query() const { + return nixGetTime() - m_start; + } + double hires_timer::query_reset() { + double t = nixGetTime(); + double r = t - m_start; + m_start = t; + return r; + } + pfc::string8 hires_timer::queryString(unsigned precision) const { + return format_time_ex( query(), precision ).get_ptr(); + } +#endif + + + uint64_t fileTimeWtoU(uint64_t ft) { + return (ft - 116444736000000000 + /*rounding*/10000000/2) / 10000000; + } + uint64_t fileTimeUtoW(uint64_t ft) { + return (ft * 10000000) + 116444736000000000; + } +#ifndef _WIN32 + uint64_t fileTimeUtoW(const timespec & ts) { + uint64_t ft = (uint64_t)ts.tv_sec * 10000000 + (uint64_t)ts.tv_nsec / 100; + return ft + 116444736000000000; + } +#endif + + uint64_t fileTimeNow() { +#ifdef _WIN32 + uint64_t ret; + GetSystemTimeAsFileTime((FILETIME*)&ret); + return ret; +#else + return fileTimeUtoW(time(NULL)); +#endif + } + +} diff --git a/pfc/timers.h b/pfc/timers.h new file mode 100644 index 0000000..8163bf0 --- /dev/null +++ b/pfc/timers.h @@ -0,0 +1,145 @@ +#pragma once + +#if defined(_MSC_VER) && (defined(_M_IX86) || defined(_M_X64)) + +#define PFC_HAVE_PROFILER + +#include +namespace pfc { + class profiler_static { + public: + profiler_static(const char * p_name); + ~profiler_static(); + void add_time(t_int64 delta) { total_time += delta;num_called++; } + private: + const char * name; + t_uint64 total_time, num_called; + }; + + class profiler_local { + public: + profiler_local(profiler_static * p_owner) { + owner = p_owner; + start = __rdtsc(); + } + ~profiler_local() { + t_int64 end = __rdtsc(); + owner->add_time(end - start); + } + private: + t_int64 start; + profiler_static * owner; + }; + +} +#define profiler(name) \ + static pfc::profiler_static profiler_static_##name(#name); \ + pfc::profiler_local profiler_local_##name(&profiler_static_##name); + + +#endif + +#ifdef _WIN32 + +namespace pfc { + // ALWAYS define 64bit tickcount - don't cause mayhem if different modules are compiled for different Windows versions + typedef uint64_t tickcount_t; + inline tickcount_t getTickCount() { return GetTickCount64(); } + +class hires_timer { +public: + hires_timer() : m_start() {} + void start() { + m_start = g_query(); + } + double query() const { + return _query( g_query() ); + } + double query_reset() { + t_uint64 current = g_query(); + double ret = _query(current); + m_start = current; + return ret; + } + pfc::string8 queryString(unsigned precision = 6) const { + return pfc::format_time_ex( query(), precision ).get_ptr(); + } +private: + double _query(t_uint64 p_val) const { + return (double)( p_val - m_start ) / (double) g_query_freq(); + } + static t_uint64 g_query() { + LARGE_INTEGER val; + if (!QueryPerformanceCounter(&val)) throw pfc::exception_not_implemented(); + return val.QuadPart; + } + static t_uint64 g_query_freq() { + LARGE_INTEGER val; + if (!QueryPerformanceFrequency(&val)) throw pfc::exception_not_implemented(); + return val.QuadPart; + } + t_uint64 m_start; +}; + +class lores_timer { +public: + lores_timer() {} + void start() { + _start(getTickCount()); + } + + double query() const { + return _query(getTickCount()); + } + double query_reset() { + tickcount_t time = getTickCount(); + double ret = _query(time); + _start(time); + return ret; + } + pfc::string8 queryString(unsigned precision = 3) const { + return pfc::format_time_ex( query(), precision ).get_ptr(); + } +private: + void _start(tickcount_t p_time) { + m_start = p_time; + } + double _query(tickcount_t p_time) const { + return (double)(p_time - m_start) / 1000.0; + } + t_uint64 m_start = 0; +}; +} +#else // not _WIN32 + +namespace pfc { + +class hires_timer { +public: + hires_timer() : m_start() {} + void start(); + double query() const; + double query_reset(); + pfc::string8 queryString(unsigned precision = 3) const; +private: + double m_start; +}; + +typedef hires_timer lores_timer; + +} + +#endif // _WIN32 + +#ifndef _WIN32 +struct timespec; +#endif + +namespace pfc { + uint64_t fileTimeWtoU(uint64_t ft); + uint64_t fileTimeUtoW(uint64_t ft); +#ifndef _WIN32 + uint64_t fileTimeUtoW( timespec const & ts ); +#endif + uint64_t fileTimeNow(); +} diff --git a/pfc/traits.h b/pfc/traits.h new file mode 100644 index 0000000..a991cea --- /dev/null +++ b/pfc/traits.h @@ -0,0 +1,85 @@ +#pragma once + +namespace pfc { + + class traits_default { + public: + enum { + realloc_safe = false, + needs_destructor = true, + needs_constructor = true, + constructor_may_fail = true + }; + }; + + class traits_default_movable { + public: + enum { + realloc_safe = true, + needs_destructor = true, + needs_constructor = true, + constructor_may_fail = true + }; + }; + + class traits_rawobject : public traits_default { + public: + enum { + realloc_safe = true, + needs_destructor = false, + needs_constructor = false, + constructor_may_fail = false + }; + }; + + class traits_vtable { + public: + enum { + realloc_safe = true, + needs_destructor = true, + needs_constructor = true, + constructor_may_fail = false + }; + }; + + template class traits_t : public traits_default {}; + + template + class combine_traits { + public: + enum { + realloc_safe = (traits1::realloc_safe && traits2::realloc_safe), + needs_destructor = (traits1::needs_destructor || traits2::needs_destructor), + needs_constructor = (traits1::needs_constructor || traits2::needs_constructor), + constructor_may_fail = (traits1::constructor_may_fail || traits2::constructor_may_fail), + }; + }; + + template + class traits_combined : public combine_traits,traits_t > {}; + + template class traits_t : public traits_rawobject {}; + + template<> class traits_t : public traits_rawobject {}; + template<> class traits_t : public traits_rawobject {}; + template<> class traits_t : public traits_rawobject {}; + template<> class traits_t : public traits_rawobject {}; + template<> class traits_t : public traits_rawobject {}; + template<> class traits_t : public traits_rawobject {}; + template<> class traits_t : public traits_rawobject {}; + template<> class traits_t : public traits_rawobject {}; + template<> class traits_t : public traits_rawobject {}; + template<> class traits_t : public traits_rawobject {}; + template<> class traits_t : public traits_rawobject {}; + template<> class traits_t : public traits_rawobject {}; + template<> class traits_t : public traits_rawobject {}; + + template<> class traits_t : public traits_rawobject {}; + template<> class traits_t : public traits_rawobject {}; + + template<> class traits_t : public traits_rawobject {}; + + template + class traits_t : public traits_t {}; + +} diff --git a/pfc/utf8.cpp b/pfc/utf8.cpp new file mode 100644 index 0000000..21ea3ca --- /dev/null +++ b/pfc/utf8.cpp @@ -0,0 +1,107 @@ +#include "pfc.h" + +namespace pfc { +//utf8 stuff +#include "pocket_char_ops.h" + +#ifdef _MSC_VER + t_size utf16_decode_char(const wchar_t * p_source,unsigned * p_out,t_size p_source_length) throw() { + PFC_STATIC_ASSERT( sizeof(wchar_t) == sizeof(char16_t) ); + return wide_decode_char( p_source, p_out, p_source_length ); + } + t_size utf16_encode_char(unsigned c,wchar_t * out) throw() { + PFC_STATIC_ASSERT( sizeof(wchar_t) == sizeof(char16_t) ); + return wide_encode_char( c, out ); + } +#endif + + t_size wide_decode_char(const wchar_t * p_source,unsigned * p_out,t_size p_source_length) throw() { + PFC_STATIC_ASSERT( sizeof( wchar_t ) == sizeof( char16_t ) || sizeof( wchar_t ) == sizeof( unsigned ) ); + if (sizeof( wchar_t ) == sizeof( char16_t ) ) { + return utf16_decode_char( reinterpret_cast< const char16_t *>(p_source), p_out, p_source_length ); + } else { + if (p_source_length == 0) { * p_out = 0; return 0; } + * p_out = p_source [ 0 ]; + return 1; + } + } + t_size wide_encode_char(unsigned c,wchar_t * out) throw() { + PFC_STATIC_ASSERT( sizeof( wchar_t ) == sizeof( char16_t ) || sizeof( wchar_t ) == sizeof( unsigned ) ); + if (sizeof( wchar_t ) == sizeof( char16_t ) ) { + return utf16_encode_char( c, reinterpret_cast< char16_t * >(out) ); + } else { + * out = (wchar_t) c; + return 1; + } + } + + + +bool is_lower_ascii(const char * param) +{ + while(*param) + { + if (*param<0) return false; + param++; + } + return true; +} + +static bool check_end_of_string(const char * ptr) +{ + return !*ptr; +} + +unsigned strcpy_utf8_truncate(const char * src,char * out,unsigned maxbytes) +{ + unsigned rv = 0 , ptr = 0; + if (maxbytes>0) + { + maxbytes--;//for null + while(!check_end_of_string(src) && maxbytes>0) + { + unsigned delta = (unsigned)utf8_char_len(src); + if (delta>maxbytes || delta==0) break; + maxbytes -= delta; + do + { + out[ptr++] = *(src++); + } while(--delta); + rv = ptr; + } + out[rv]=0; + } + return rv; +} + +t_size strlen_utf8(const char * p,t_size num) throw() +{ + unsigned w; + t_size d; + t_size ret = 0; + for(;num;) + { + d = utf8_decode_char(p,w); + if (w==0 || d<=0) break; + ret++; + p+=d; + num-=d; + } + return ret; +} + +t_size utf8_chars_to_bytes(const char * string,t_size count) throw() +{ + t_size bytes = 0; + while(count) + { + unsigned dummy; + t_size delta = utf8_decode_char(string+bytes,dummy); + if (delta==0) break; + bytes += delta; + count--; + } + return bytes; +} + +} \ No newline at end of file diff --git a/pfc/wait_queue.h b/pfc/wait_queue.h new file mode 100644 index 0000000..a8387d9 --- /dev/null +++ b/pfc/wait_queue.h @@ -0,0 +1,190 @@ +#pragma once + +#include +#include "synchro.h" + +namespace pfc { + + template + class waitQueue { + public: + waitQueue() : m_eof() {} + + void put( obj_t const & obj ) { + mutexScope guard( m_mutex ); + m_list.push_back( obj ); + if ( m_list.size() == 1 ) m_canRead.set_state( true ); + } + void put( obj_t && obj ) { + mutexScope guard( m_mutex ); + m_list.push_back( std::move(obj) ); + if ( m_list.size() == 1 ) m_canRead.set_state( true ); + } + + void set_eof() { + mutexScope guard(m_mutex); + m_eof = true; + m_canRead.set_state(true); + } + bool wait_read( double timeout ) { + return m_canRead.wait_for( timeout ); + } + eventHandle_t get_event_handle() { + return m_canRead.get_handle(); + } + + bool get( obj_t & out ) { + for ( ;; ) { + m_canRead.wait_for(-1); + mutexScope guard(m_mutex); + auto i = m_list.begin(); + if ( i == m_list.end() ) { + if (m_eof) return false; + continue; + } + out = std::move(*i); + m_list.erase( i ); + didGet(); + return true; + } + } + + bool get( obj_t & out, pfc::eventHandle_t hAbort, bool * didAbort = nullptr ) { + if (didAbort != nullptr) * didAbort = false; + for ( ;; ) { + if (pfc::event::g_twoEventWait( hAbort, m_canRead.get_handle(), -1) == 1) { + if (didAbort != nullptr) * didAbort = true; + return false; + } + mutexScope guard(m_mutex); + auto i = m_list.begin(); + if ( i == m_list.end() ) { + if (m_eof) return false; + continue; + } + out = std::move(*i); + m_list.erase( i ); + didGet(); + return true; + } + } + void clear() { + mutexScope guard(m_mutex); + m_eof = false; + m_list.clear(); + m_canRead.set_state(false); + } + private: + void didGet() { + if (!m_eof && m_list.size() == 0) m_canRead.set_state(false); + } + bool m_eof; + std::list m_list; + mutex m_mutex; + event m_canRead; + }; + + template + class waitQueue2 { + protected: + typedef obj_t_ obj_t; + typedef std::list list_t; + virtual bool canWriteCheck(list_t const &) { return true; } + + public: + void waitWrite() { + m_canWrite.wait_for(-1); + } + bool waitWrite(pfc::eventHandle_t hAbort) { + return event::g_twoEventWait( hAbort, m_canWrite.get_handle(), -1) == 2; + } + eventHandle_t waitWriteHandle() { + return m_canWrite.get_handle(); + } + + waitQueue2() : m_eof() { + m_canWrite.set_state(true); + } + + void put(obj_t const & obj) { + mutexScope guard(m_mutex); + m_list.push_back(obj); + if (m_list.size() == 1) m_canRead.set_state(true); + refreshCanWrite(); + } + void put(obj_t && obj) { + mutexScope guard(m_mutex); + m_list.push_back(std::move(obj)); + if (m_list.size() == 1) m_canRead.set_state(true); + refreshCanWrite(); + } + + void set_eof() { + mutexScope guard(m_mutex); + m_eof = true; + m_canRead.set_state(true); + m_canWrite.set_state(false); + } + + bool get(obj_t & out) { + for (;; ) { + m_canRead.wait_for(-1); + mutexScope guard(m_mutex); + auto i = m_list.begin(); + if (i == m_list.end()) { + if (m_eof) return false; + continue; + } + out = std::move(*i); + m_list.erase(i); + didGet(); + return true; + } + } + + bool get(obj_t & out, pfc::eventHandle_t hAbort, bool * didAbort = nullptr) { + if (didAbort != nullptr) * didAbort = false; + for (;; ) { + if (pfc::event::g_twoEventWait(hAbort, m_canRead.get_handle(), -1) == 1) { + if (didAbort != nullptr) * didAbort = true; + return false; + } + mutexScope guard(m_mutex); + auto i = m_list.begin(); + if (i == m_list.end()) { + if (m_eof) return false; + continue; + } + out = std::move(*i); + m_list.erase(i); + didGet(); + return true; + } + } + + void clear() { + mutexScope guard(m_mutex); + m_list.clear(); + m_eof = false; + m_canRead.set_state(false); + m_canWrite.set_state(true); + } + private: + void didGet() { + if (m_eof) return; + if (m_list.size() == 0) { + m_canRead.set_state(false); + m_canWrite.set_state(true); + } else { + m_canWrite.set_state(canWriteCheck(m_list)); + } + } + void refreshCanWrite() { + m_canWrite.set_state( !m_eof && canWriteCheck(m_list)); + } + bool m_eof; + std::list m_list; + mutex m_mutex; + event m_canRead, m_canWrite; + }; +} diff --git a/pfc/weakRef.h b/pfc/weakRef.h new file mode 100644 index 0000000..0c26686 --- /dev/null +++ b/pfc/weakRef.h @@ -0,0 +1,71 @@ +#pragma once + +// PFC weakRef class +// Create weak references to objects that automatically become invalidated upon destruction of the object +// Note that this is NOT thread safe and meant for single thread usage. If you require thread safety, provide your own means, such as mutexlocking of weakRef::get() and the destruction of your objects. + +#include // std::shared_ptr + +namespace pfc { + typedef std::shared_ptr weakRefKillSwitch_t; + + template + class weakRef { + typedef weakRef self_t; + public: + static self_t _make(target_t * target, weakRefKillSwitch_t ks) { + self_t ret; + ret.m_target = target; + ret.m_ks = ks; + return ret; + } + + + bool isValid() const { + return m_ks && !*m_ks; + } + + target_t * get() const { + if (!isValid()) return nullptr; + return m_target; + } + target_t * operator() () const { + return get(); + } + private: + target_t * m_target = nullptr; + weakRefKillSwitch_t m_ks; + }; + + template + class weakRefTarget { + public: + weakRefTarget() {} + + typedef weakRef weakRef_t; + + weakRef weakRef() { + PFC_ASSERT(!*m_ks); + class_t * ptr = static_cast(this); + return weakRef_t::_make(ptr, m_ks); + } + + // Optional: explicitly invalidates all weak references to this object. Called implicitly by the destructor, but you can do it yourself earlier. + void weakRefShutdown() { + *m_ks = true; + } + ~weakRefTarget() { + weakRefShutdown(); + } + + // Optional: obtain a reference to the killswitch if you wish to use it directly + weakRefKillSwitch_t weakRefKS() const { + return m_ks; + } + private: + std::shared_ptr m_ks = std::make_shared(false); + + weakRefTarget(const weakRefTarget &) = delete; + void operator=(const weakRefTarget &) = delete; + }; +} diff --git a/pfc/wildcard.cpp b/pfc/wildcard.cpp new file mode 100644 index 0000000..a5b87c2 --- /dev/null +++ b/pfc/wildcard.cpp @@ -0,0 +1,50 @@ +#include "pfc.h" + +static bool test_recur(const char * fn,const char * rm,bool b_sep) +{ + for(;;) + { + if ((b_sep && *rm==';') || *rm==0) return *fn==0; + else if (*rm=='*') + { + rm++; + do + { + if (test_recur(fn,rm,b_sep)) return true; + } while(pfc::utf8_advance(fn)); + return false; + } + else if (*fn==0) return false; + else if (*rm!='?' && pfc::charLower(pfc::utf8_get_char(fn))!=pfc::charLower(pfc::utf8_get_char(rm))) return false; + + fn = pfc::utf8_char_next(fn); rm = pfc::utf8_char_next(rm); + } +} + +bool wildcard_helper::test_path(const char * path,const char * pattern,bool b_sep) {return test(path + pfc::scan_filename(path),pattern,b_sep);} + +bool wildcard_helper::test(const char * fn,const char * pattern,bool b_sep) +{ + if (!b_sep) return test_recur(fn,pattern,false); + const char * rm=pattern; + while(*rm) + { + if (test_recur(fn,rm,true)) return true; + while(*rm && *rm!=';') rm++; + if (*rm==';') + { + while(*rm==';') rm++; + while(*rm==' ') rm++; + } + }; + + return false; +} + +bool wildcard_helper::has_wildcards(const char * str) {return strchr(str,'*') || strchr(str,'?');} + +const char * wildcard_helper::get_wildcard_list() {return "*?";} + +bool wildcard_helper::is_wildcard(char c) { + return c == '*' || c == '?'; +} diff --git a/pfc/wildcard.h b/pfc/wildcard.h new file mode 100644 index 0000000..d682917 --- /dev/null +++ b/pfc/wildcard.h @@ -0,0 +1,10 @@ +#pragma once + +namespace wildcard_helper +{ + bool test_path(const char * path,const char * pattern,bool b_separate_by_semicolon = false);//will extract filename from path first + bool test(const char * str,const char * pattern,bool b_separate_by_semicolon = false);//tests if str matches pattern + bool has_wildcards(const char * str); + const char * get_wildcard_list(); + bool is_wildcard(char c); +}; diff --git a/pfc/win-objects.cpp b/pfc/win-objects.cpp new file mode 100644 index 0000000..0939bb9 --- /dev/null +++ b/pfc/win-objects.cpp @@ -0,0 +1,431 @@ +#include "pfc.h" + +#include "pp-winapi.h" + +#ifdef _WIN32 + +#include "pfc-fb2k-hooks.h" + +namespace pfc { + +BOOL winFormatSystemErrorMessageImpl(pfc::string_base & p_out,DWORD p_code) { + switch(p_code) { + case ERROR_CHILD_NOT_COMPLETE: + p_out = "Application cannot be run in Win32 mode."; + return TRUE; + case ERROR_INVALID_ORDINAL: + p_out = "Invalid ordinal."; + return TRUE; + case ERROR_INVALID_STARTING_CODESEG: + p_out = "Invalid code segment."; + return TRUE; + case ERROR_INVALID_STACKSEG: + p_out = "Invalid stack segment."; + return TRUE; + case ERROR_INVALID_MODULETYPE: + p_out = "Invalid module type."; + return TRUE; + case ERROR_INVALID_EXE_SIGNATURE: + p_out = "Invalid executable signature."; + return TRUE; + case ERROR_BAD_EXE_FORMAT: + p_out = "Not a valid Win32 application."; + return TRUE; + case ERROR_EXE_MACHINE_TYPE_MISMATCH: + p_out = "Machine type mismatch."; + return TRUE; + case ERROR_EXE_CANNOT_MODIFY_SIGNED_BINARY: + case ERROR_EXE_CANNOT_MODIFY_STRONG_SIGNED_BINARY: + p_out = "Unable to modify a signed binary."; + return TRUE; + default: + { + TCHAR temp[512]; + if (FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM,0,p_code,0,temp,_countof(temp),0) == 0) return FALSE; + for(t_size n=0;n<_countof(temp);n++) { + switch(temp[n]) { + case '\n': + case '\r': + temp[n] = ' '; + break; + } + } + p_out = stringcvt::string_utf8_from_os(temp,_countof(temp)); + return TRUE; + } + break; + } +} +void winPrefixPath(pfc::string_base & out, const char * p_path) { + if (pfc::string_has_prefix(p_path, "..\\") || strstr(p_path, "\\..\\") ) { + // do not touch relative paths if we somehow got them here + out = p_path; + return; + } + const char * prepend_header = "\\\\?\\"; + const char * prepend_header_net = "\\\\?\\UNC\\"; + if (pfc::strcmp_partial( p_path, prepend_header ) == 0) { out = p_path; return; } + out.reset(); + if (pfc::strcmp_partial(p_path,"\\\\") != 0) { + out << prepend_header << p_path; + } else { + out << prepend_header_net << (p_path+2); + } +}; + +BOOL winFormatSystemErrorMessage(pfc::string_base & p_out, DWORD p_code) { + return winFormatSystemErrorMessageHook( p_out, p_code ); +} +void winUnPrefixPath(pfc::string_base & out, const char * p_path) { + const char * prepend_header = "\\\\?\\"; + const char * prepend_header_net = "\\\\?\\UNC\\"; + if (pfc::strcmp_partial(p_path, prepend_header_net) == 0) { + out = PFC_string_formatter() << "\\\\" << (p_path + strlen(prepend_header_net) ); + return; + } + if (pfc::strcmp_partial(p_path, prepend_header) == 0) { + out = (p_path + strlen(prepend_header)); + return; + } + out = p_path; +} + +string8 winPrefixPath(const char * in) { + string8 temp; winPrefixPath(temp, in); return temp; +} +string8 winUnPrefixPath(const char * in) { + string8 temp; winUnPrefixPath(temp, in); return temp; +} + +} // namespace pfc + +format_win32_error::format_win32_error(DWORD p_code) { + pfc::LastErrorRevertScope revert; + if (p_code == 0) m_buffer = "Undefined error"; + else if (!pfc::winFormatSystemErrorMessage(m_buffer,p_code)) m_buffer << "Unknown error code (" << (unsigned)p_code << ")"; +} + +format_hresult::format_hresult(HRESULT p_code) { + if (!pfc::winFormatSystemErrorMessage(m_buffer,(DWORD)p_code)) m_buffer = "Unknown error code"; + stamp_hex(p_code); +} +format_hresult::format_hresult(HRESULT p_code, const char * msgOverride) { + m_buffer = msgOverride; + stamp_hex(p_code); +} + +void format_hresult::stamp_hex(HRESULT p_code) { + m_buffer << " (0x" << pfc::format_hex((t_uint32)p_code, 8) << ")"; +} + +#ifdef PFC_WINDOWS_DESKTOP_APP + +namespace pfc { + HWND findOwningPopup(HWND p_wnd) + { + HWND walk = p_wnd; + while (walk != 0 && (GetWindowLong(walk, GWL_STYLE) & WS_CHILD) != 0) + walk = GetParent(walk); + return walk ? walk : p_wnd; + } + string8 getWindowClassName(HWND wnd) { + TCHAR temp[1024] = {}; + if (GetClassName(wnd, temp, PFC_TABSIZE(temp)) == 0) { + PFC_ASSERT(!"Should not get here"); + return ""; + } + return pfc::stringcvt::string_utf8_from_os(temp).get_ptr(); + } + void setWindowText(HWND wnd, const char * txt) { + SetWindowText(wnd, stringcvt::string_os_from_utf8(txt)); + } + string8 getWindowText(HWND wnd) { + PFC_ASSERT(wnd != NULL); + int len = GetWindowTextLength(wnd); + if (len >= 0) + { + len++; + pfc::array_t temp; + temp.set_size(len); + temp[0] = 0; + if (GetWindowText(wnd, temp.get_ptr(), len) > 0) + { + return stringcvt::string_utf8_from_os(temp.get_ptr(), len).get_ptr(); + } + } + return ""; + } +} + +void uAddWindowStyle(HWND p_wnd,LONG p_style) { + SetWindowLong(p_wnd,GWL_STYLE, GetWindowLong(p_wnd,GWL_STYLE) | p_style); +} + +void uRemoveWindowStyle(HWND p_wnd,LONG p_style) { + SetWindowLong(p_wnd,GWL_STYLE, GetWindowLong(p_wnd,GWL_STYLE) & ~p_style); +} + +void uAddWindowExStyle(HWND p_wnd,LONG p_style) { + SetWindowLong(p_wnd,GWL_EXSTYLE, GetWindowLong(p_wnd,GWL_EXSTYLE) | p_style); +} + +void uRemoveWindowExStyle(HWND p_wnd,LONG p_style) { + SetWindowLong(p_wnd,GWL_EXSTYLE, GetWindowLong(p_wnd,GWL_EXSTYLE) & ~p_style); +} + +unsigned MapDialogWidth(HWND p_dialog,unsigned p_value) { + RECT temp; + temp.left = 0; temp.right = p_value; temp.top = temp.bottom = 0; + if (!MapDialogRect(p_dialog,&temp)) return 0; + return temp.right; +} + +bool IsKeyPressed(unsigned vk) { + return (GetKeyState(vk) & 0x8000) ? true : false; +} + +//! Returns current modifier keys pressed, using win32 MOD_* flags. +unsigned GetHotkeyModifierFlags() { + unsigned ret = 0; + if (IsKeyPressed(VK_CONTROL)) ret |= MOD_CONTROL; + if (IsKeyPressed(VK_SHIFT)) ret |= MOD_SHIFT; + if (IsKeyPressed(VK_MENU)) ret |= MOD_ALT; + if (IsKeyPressed(VK_LWIN) || IsKeyPressed(VK_RWIN)) ret |= MOD_WIN; + return ret; +} + + + +bool CClipboardOpenScope::Open(HWND p_owner) { + Close(); + if (OpenClipboard(p_owner)) { + m_open = true; + return true; + } else { + return false; + } +} +void CClipboardOpenScope::Close() { + if (m_open) { + m_open = false; + CloseClipboard(); + } +} + + +CGlobalLockScope::CGlobalLockScope(HGLOBAL p_handle) : m_handle(p_handle), m_ptr(GlobalLock(p_handle)) { + if (m_ptr == NULL) throw std::bad_alloc(); +} +CGlobalLockScope::~CGlobalLockScope() { + if (m_ptr != NULL) GlobalUnlock(m_handle); +} + +bool IsPointInsideControl(const POINT& pt, HWND wnd) { + HWND walk = WindowFromPoint(pt); + for(;;) { + if (walk == NULL) return false; + if (walk == wnd) return true; + if (GetWindowLong(walk,GWL_STYLE) & WS_POPUP) return false; + walk = GetParent(walk); + } +} +bool IsPopupWindowChildOf(HWND child, HWND parent) { + HWND walk = child; + while (walk != parent && walk != NULL) { + walk = GetParent(walk); + } + return walk == parent; +} +bool IsWindowChildOf(HWND child, HWND parent) { + HWND walk = child; + while(walk != parent && walk != NULL && (GetWindowLong(walk,GWL_STYLE) & WS_CHILD) != 0) { + walk = GetParent(walk); + } + return walk == parent; +} +void ResignActiveWindow(HWND wnd) { + if (IsPopupWindowChildOf(GetActiveWindow(), wnd)) { + HWND parent = GetParent(wnd); + if ( parent != NULL ) SetActiveWindow(parent); + } +} +void win32_menu::release() { + if (m_menu != NULL) { + DestroyMenu(m_menu); + m_menu = NULL; + } +} + +void win32_menu::create_popup() { + release(); + SetLastError(NO_ERROR); + m_menu = CreatePopupMenu(); + if (m_menu == NULL) throw exception_win32(GetLastError()); +} + +#endif // #ifdef PFC_WINDOWS_DESKTOP_APP + +void win32_event::create(bool p_manualreset,bool p_initialstate) { + release(); + SetLastError(NO_ERROR); + m_handle = CreateEvent(NULL,p_manualreset ? TRUE : FALSE, p_initialstate ? TRUE : FALSE,NULL); + if (m_handle == NULL) throw exception_win32(GetLastError()); +} + +void win32_event::release() { + HANDLE temp = detach(); + if (temp != NULL) CloseHandle(temp); +} + + +DWORD win32_event::g_calculate_wait_time(double p_seconds) { + DWORD time = 0; + if (p_seconds> 0) { + time = pfc::rint32(p_seconds * 1000.0); + if (time == 0) time = 1; + } else if (p_seconds < 0) { + time = INFINITE; + } + return time; +} + +//! Returns true when signaled, false on timeout +bool win32_event::g_wait_for(HANDLE p_event,double p_timeout_seconds) { + SetLastError(NO_ERROR); + DWORD status = WaitForSingleObject(p_event,g_calculate_wait_time(p_timeout_seconds)); + switch(status) { + case WAIT_FAILED: + throw exception_win32(GetLastError()); + case WAIT_OBJECT_0: + return true; + case WAIT_TIMEOUT: + return false; + default: + pfc::crash(); + } +} + +void win32_event::set_state(bool p_state) { + PFC_ASSERT(m_handle != NULL); + if (p_state) SetEvent(m_handle); + else ResetEvent(m_handle); +} + +int win32_event::g_twoEventWait( HANDLE ev1, HANDLE ev2, double timeout ) { + HANDLE h[2] = {ev1, ev2}; + switch(WaitForMultipleObjects(2, h, FALSE, g_calculate_wait_time( timeout ) )) { + default: + pfc::crash(); + case WAIT_TIMEOUT: + return 0; + case WAIT_OBJECT_0: + return 1; + case WAIT_OBJECT_0 + 1: + return 2; + } +} + +int win32_event::g_twoEventWait( win32_event & ev1, win32_event & ev2, double timeout ) { + return g_twoEventWait( ev1.get_handle(), ev2.get_handle(), timeout ); +} + +#ifdef PFC_WINDOWS_DESKTOP_APP + +void win32_icon::release() { + HICON temp = detach(); + if (temp != NULL) DestroyIcon(temp); +} + + +void win32_accelerator::load(HINSTANCE p_inst,const TCHAR * p_id) { + release(); + SetLastError(NO_ERROR); + m_accel = LoadAccelerators(p_inst,p_id); + if (m_accel == NULL) { + throw exception_win32(GetLastError()); + } +} + +void win32_accelerator::release() { + if (m_accel != NULL) { + DestroyAcceleratorTable(m_accel); + m_accel = NULL; + } +} + +#endif // #ifdef PFC_WINDOWS_DESKTOP_APP + +void uSleepSeconds(double p_time,bool p_alertable) { + SleepEx(win32_event::g_calculate_wait_time(p_time),p_alertable ? TRUE : FALSE); +} + + +#ifdef PFC_WINDOWS_DESKTOP_APP + +WORD GetWindowsVersionCode() throw() { + const DWORD ver = GetVersion(); + return (WORD)HIBYTE(LOWORD(ver)) | ((WORD)LOBYTE(LOWORD(ver)) << 8); +} + + +namespace pfc { + bool isShiftKeyPressed() { + return IsKeyPressed(VK_SHIFT); + } + bool isCtrlKeyPressed() { + return IsKeyPressed(VK_CONTROL); + } + bool isAltKeyPressed() { + return IsKeyPressed(VK_MENU); + } + + void winSetThreadDescription(HANDLE hThread, const wchar_t * desc) { +#if _WIN32_WINNT >= 0xA00 + SetThreadDescription(hThread, desc); +#else + auto proc = GetProcAddress(GetModuleHandle(L"kernel32.dll"), "SetThreadDescription"); + if (proc != nullptr) { + typedef HRESULT(__stdcall * pSetThreadDescription_t)(HANDLE hThread, PCWSTR lpThreadDescription); + auto proc2 = reinterpret_cast(proc); + proc2(hThread, desc); + } +#endif + } +} + +#else +// If unknown / not available on this architecture, return false always +namespace pfc { + bool isShiftKeyPressed() { + return false; + } + bool isCtrlKeyPressed() { + return false; + } + bool isAltKeyPressed() { + return false; + } +} + +#endif // #ifdef PFC_WINDOWS_DESKTOP_APP + +namespace pfc { + void winSleep( double seconds ) { + DWORD ms = INFINITE; + if (seconds > 0) { + ms = rint32(seconds * 1000); + if (ms < 1) ms = 1; + } else if (seconds == 0) { + ms = 0; + } + Sleep(ms); + } + void sleepSeconds(double seconds) { + winSleep(seconds); + } + void yield() { + Sleep(1); + } +} + +#endif // _WIN32 diff --git a/pfc/win-objects.h b/pfc/win-objects.h new file mode 100644 index 0000000..df93d3a --- /dev/null +++ b/pfc/win-objects.h @@ -0,0 +1,341 @@ +#pragma once + +namespace pfc { + BOOL winFormatSystemErrorMessage(pfc::string_base & p_out,DWORD p_code); + + // Prefix filesystem paths with \\?\ and \\?\UNC where appropriate. + void winPrefixPath(pfc::string_base & out, const char * p_path); + // Reverse winPrefixPath + void winUnPrefixPath(pfc::string_base & out, const char * p_path); + + string8 winPrefixPath( const char * in ); + string8 winUnPrefixPath( const char * in ); + + class LastErrorRevertScope { + public: + LastErrorRevertScope() : m_val(GetLastError()) {} + ~LastErrorRevertScope() { SetLastError(m_val); } + + private: + const DWORD m_val; + }; + + string8 getWindowText(HWND wnd); + void setWindowText(HWND wnd, const char * txt); + string8 getWindowClassName( HWND wnd ); + HWND findOwningPopup(HWND wnd); +} + + + +class format_win32_error { +public: + format_win32_error(DWORD p_code); + + const char * get_ptr() const {return m_buffer.get_ptr();} + operator const char*() const {return m_buffer.get_ptr();} +private: + pfc::string8 m_buffer; +}; + +class format_hresult { +public: + format_hresult(HRESULT p_code); + format_hresult(HRESULT p_code, const char * msgOverride); + + const char * get_ptr() const {return m_buffer.get_ptr();} + operator const char*() const {return m_buffer.get_ptr();} +private: + void stamp_hex(HRESULT p_code); + pfc::string_formatter m_buffer; +}; + +class exception_win32 : public std::exception { +public: + exception_win32(DWORD p_code) : std::exception(format_win32_error(p_code)), m_code(p_code) {} + DWORD get_code() const {return m_code;} +private: + DWORD m_code; +}; + +#ifdef PFC_WINDOWS_DESKTOP_APP + +void uAddWindowStyle(HWND p_wnd,LONG p_style); +void uRemoveWindowStyle(HWND p_wnd,LONG p_style); +void uAddWindowExStyle(HWND p_wnd,LONG p_style); +void uRemoveWindowExStyle(HWND p_wnd,LONG p_style); +unsigned MapDialogWidth(HWND p_dialog,unsigned p_value); +bool IsKeyPressed(unsigned vk); + +//! Returns current modifier keys pressed, using win32 MOD_* flags. +unsigned GetHotkeyModifierFlags(); + +class CClipboardOpenScope { +public: + CClipboardOpenScope() : m_open(false) {} + ~CClipboardOpenScope() {Close();} + bool Open(HWND p_owner); + void Close(); +private: + bool m_open; + + PFC_CLASS_NOT_COPYABLE_EX(CClipboardOpenScope) +}; + +class CGlobalLockScope { +public: + CGlobalLockScope(HGLOBAL p_handle); + ~CGlobalLockScope(); + void * GetPtr() const {return m_ptr;} + t_size GetSize() const {return GlobalSize(m_handle);} +private: + void * m_ptr; + HGLOBAL m_handle; + + PFC_CLASS_NOT_COPYABLE_EX(CGlobalLockScope) +}; + +template class CGlobalLockScopeT { +public: + CGlobalLockScopeT(HGLOBAL handle) : m_scope(handle) {} + TItem * GetPtr() const {return reinterpret_cast(m_scope.GetPtr());} + t_size GetSize() const { + const t_size val = m_scope.GetSize(); + PFC_ASSERT( val % sizeof(TItem) == 0 ); + return val / sizeof(TItem); + } +private: + CGlobalLockScope m_scope; +}; + +//! Resigns active window status passing it to the parent window, if wnd or a child popup of is active. \n +//! Use this to mitigate Windows 10 1809 active window handling bugs - call prior to DestroyWindow() +void ResignActiveWindow(HWND wnd); +//! Is point inside a control? +bool IsPointInsideControl(const POINT& pt, HWND wnd); +//! Is a control inside window? Also returns true if child==parent. +bool IsWindowChildOf(HWND child, HWND parent); +//! Is window a child (popup or control) of window? Also returns true if child==parent. +bool IsPopupWindowChildOf(HWND child, HWND parent); + +class win32_menu { +public: + win32_menu(HMENU p_initval) : m_menu(p_initval) {} + win32_menu() : m_menu(NULL) {} + ~win32_menu() {release();} + void release(); + void set(HMENU p_menu) {release(); m_menu = p_menu;} + void create_popup(); + HMENU get() const {return m_menu;} + HMENU detach() {return pfc::replace_t(m_menu,(HMENU)NULL);} + + bool is_valid() const {return m_menu != NULL;} +private: + win32_menu(const win32_menu &) = delete; + void operator=(const win32_menu &) = delete; + + HMENU m_menu; +}; + +#endif + +class win32_event { +public: + win32_event() : m_handle(NULL) {} + ~win32_event() {release();} + + void create(bool p_manualreset,bool p_initialstate); + + void set(HANDLE p_handle) {release(); m_handle = p_handle;} + HANDLE get() const {return m_handle;} + HANDLE get_handle() const {return m_handle;} + HANDLE detach() {return pfc::replace_t(m_handle,(HANDLE)NULL);} + bool is_valid() const {return m_handle != NULL;} + + void release(); + + //! Returns true when signaled, false on timeout + bool wait_for(double p_timeout_seconds) {return g_wait_for(get(),p_timeout_seconds);} + + static DWORD g_calculate_wait_time(double p_seconds); + + //! Returns true when signaled, false on timeout + static bool g_wait_for(HANDLE p_event,double p_timeout_seconds); + + void set_state(bool p_state); + bool is_set() { return wait_for(0); } + + // Two-wait event functions, return 0 on timeout, 1 on evt1 set, 2 on evt2 set + static int g_twoEventWait( win32_event & ev1, win32_event & ev2, double timeout ); + static int g_twoEventWait( HANDLE ev1, HANDLE ev2, double timeout ); +private: + win32_event(const win32_event&) = delete; + void operator=(const win32_event &) = delete; + + HANDLE m_handle; +}; + +void uSleepSeconds(double p_time,bool p_alertable); + +#ifdef PFC_WINDOWS_DESKTOP_APP + +class win32_icon { +public: + win32_icon(HICON p_initval) : m_icon(p_initval) {} + win32_icon() : m_icon(NULL) {} + ~win32_icon() {release();} + + void release(); + + void set(HICON p_icon) {release(); m_icon = p_icon;} + HICON get() const {return m_icon;} + HICON detach() {return pfc::replace_t(m_icon,(HICON)NULL);} + + bool is_valid() const {return m_icon != NULL;} + +private: + win32_icon(const win32_icon&) = delete; + const win32_icon & operator=(const win32_icon &) = delete; + + HICON m_icon; +}; + +class win32_accelerator { +public: + win32_accelerator() : m_accel(NULL) {} + ~win32_accelerator() {release();} + HACCEL get() const {return m_accel;} + + void load(HINSTANCE p_inst,const TCHAR * p_id); + void release(); +private: + HACCEL m_accel; + PFC_CLASS_NOT_COPYABLE_EX(win32_accelerator); +}; + +class SelectObjectScope { +public: + SelectObjectScope(HDC p_dc,HGDIOBJ p_obj) throw() : m_dc(p_dc), m_obj(SelectObject(p_dc,p_obj)) {} + ~SelectObjectScope() throw() {SelectObject(m_dc,m_obj);} +private: + PFC_CLASS_NOT_COPYABLE_EX(SelectObjectScope) + HDC m_dc; + HGDIOBJ m_obj; +}; + +class OffsetWindowOrgScope { +public: + OffsetWindowOrgScope(HDC dc, const POINT & pt) throw() : m_dc(dc), m_pt(pt) { + OffsetWindowOrgEx(m_dc, m_pt.x, m_pt.y, NULL); + } + ~OffsetWindowOrgScope() throw() { + OffsetWindowOrgEx(m_dc, -m_pt.x, -m_pt.y, NULL); + } + +private: + const HDC m_dc; + const POINT m_pt; +}; +class DCStateScope { +public: + DCStateScope(HDC p_dc) throw() : m_dc(p_dc) { + m_state = SaveDC(m_dc); + } + ~DCStateScope() throw() { + RestoreDC(m_dc,m_state); + } +private: + const HDC m_dc; + int m_state; +}; +#endif // #ifdef PFC_WINDOWS_DESKTOP_APP + +class exception_com : public std::exception { +public: + exception_com(HRESULT p_code) : std::exception(format_hresult(p_code)), m_code(p_code) {} + exception_com(HRESULT p_code, const char * msg) : std::exception(format_hresult(p_code, msg)), m_code(p_code) {} + HRESULT get_code() const {return m_code;} +private: + HRESULT m_code; +}; + +#ifdef PFC_WINDOWS_DESKTOP_APP + +// Same format as _WIN32_WINNT macro. +WORD GetWindowsVersionCode() throw(); + +#endif + +//! Simple implementation of a COM reference counter. The initial reference count is zero, so it can be used with pfc::com_ptr_t<> with plain operator=/constructor rather than attach(). +template class ImplementCOMRefCounter : public TBase { +public: + template ImplementCOMRefCounter(arg_t && ... arg) : TBase(std::forward(arg) ...) {} + + ULONG STDMETHODCALLTYPE AddRef() override { + return ++m_refcounter; + } + ULONG STDMETHODCALLTYPE Release() override { + long val = --m_refcounter; + if (val == 0) delete this; + return val; + } +protected: + virtual ~ImplementCOMRefCounter() {} +private: + pfc::refcounter m_refcounter; +}; + + + +template +class CoTaskMemObject { +public: + CoTaskMemObject() : m_ptr() {} + + ~CoTaskMemObject() {CoTaskMemFree(m_ptr);} + void Reset() {CoTaskMemFree(pfc::replace_null_t(m_ptr));} + TPtr * Receive() {Reset(); return &m_ptr;} + + TPtr m_ptr; + PFC_CLASS_NOT_COPYABLE(CoTaskMemObject, CoTaskMemObject ); +}; + + +namespace pfc { + bool isShiftKeyPressed(); + bool isCtrlKeyPressed(); + bool isAltKeyPressed(); + + class winHandle { + public: + winHandle(HANDLE h_ = INVALID_HANDLE_VALUE) : h(h_) {} + ~winHandle() { Close(); } + void Close() { + if (h != INVALID_HANDLE_VALUE && h != NULL ) { CloseHandle(h); h = INVALID_HANDLE_VALUE; } + } + + void Attach(HANDLE h_) { Close(); h = h_; } + HANDLE Detach() { HANDLE t = h; h = INVALID_HANDLE_VALUE; return t; } + + HANDLE Get() const { return h; } + operator HANDLE() const { return h; } + + HANDLE h; + private: + winHandle(const winHandle&) = delete; + void operator=(const winHandle&) = delete; + }; + + void winSleep( double seconds ); + void sleepSeconds(double seconds); + void yield(); + +#ifdef PFC_WINDOWS_DESKTOP_APP + void winSetThreadDescription(HANDLE hThread, const wchar_t * desc); + +#if PFC_DEBUG +#define PFC_SET_THREAD_DESCRIPTION(msg) ::pfc::winSetThreadDescription(GetCurrentThread(), L##msg); +#define PFC_SET_THREAD_DESCRIPTION_SUPPORTED +#endif // PFC_DEBUG +#endif // PFC_WINDOWS_DESKTOP_APP +} diff --git a/sdk-license.txt b/sdk-license.txt new file mode 100644 index 0000000..c1b6542 --- /dev/null +++ b/sdk-license.txt @@ -0,0 +1,12 @@ +foobar2000 1.6 SDK +Copyright (c) 2001-2021, Peter Pawlowski +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +Neither the name of the author nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +Note that separate less restrictive licenses apply to the included 'pfc' and 'libPPUI' libraries. See license files included in respective folders for details. diff --git a/sdk-readme.css b/sdk-readme.css new file mode 100644 index 0000000..1020d9b --- /dev/null +++ b/sdk-readme.css @@ -0,0 +1,1291 @@ +a.interwiki { +padding-left : 16px; +} +a.iw_wp { +} +a.iw_wpde { +} +a.iw_wpmeta { +} +a.iw_doku { +} +a.iw_sb { +} +a.iw_amazon { +} +a.iw_amazon_de { +} +a.iw_amazon_uk { +} +a.iw_phpfn { +} +a.iw_dokubug { +} +a.iw_coral { +} +a.iw_google { +} +a.iw_meatball { +} +a.iw_wiki { +} +a.mediafile { +padding-left : 18px; +padding-bottom : 1px; +} +a.mf_jpg { +} +a.mf_jpeg { +} +a.mf_gif { +} +a.mf_png { +} +a.mf_tgz { +} +a.mf_tar { +} +a.mf_gz { +} +a.mf_zip { +} +a.mf_rar { +} +a.mf_pdf { +} +a.mf_ps { +} +a.mf_doc { +} +a.mf_xls { +} +a.mf_ppt { +} +a.mf_rtf { +} +a.mf_swf { +} +a.mf_rpm { +} +a.mf_deb { +} +a.mf_sxw { +} +a.mf_sxc { +} +a.mf_sxi { +} +a.mf_sxd { +} +a.mf_odc { +} +a.mf_odf { +} +a.mf_odg { +} +a.mf_odi { +} +a.mf_odp { +} +a.mf_ods { +} +a.mf_odt { +} +div.clearer { +clear : both; +line-height : 0; +height : 0; +overflow : hidden; +} +div.no { +display : inline; +margin : 0; +padding : 0; +} +.hidden { +display : none; +} +div.error { +background : #fcc; +color : #000; +border-bottom : 1px solid #faa; +font-size : 90%; +margin : 0; +padding-left : 3em; +overflow : hidden; +} +div.info { +background : #ccf; +color : #000; +border-bottom : 1px solid #aaf; +font-size : 90%; +margin : 0; +padding-left : 3em; +overflow : hidden; +} +div.success { +background : #cfc; +color : #000; +border-bottom : 1px solid #afa; +font-size : 90%; +margin : 0; +padding-left : 3em; +overflow : hidden; +} +div.notify { +background : #ffc; +color : #000; +border-bottom : 1px solid #ffa; +font-size : 90%; +margin : 0; +padding-left : 3em; +overflow : hidden; +} +.medialeft { +float : left; +} +.mediaright { +float : right; +} +.mediacenter { +display : block; +margin-left : auto; +margin-right : auto; +} +.leftalign { +text-align : left; +} +.centeralign { +text-align : center; +} +.rightalign { +text-align : right; +} +em.u { +font-style : normal; +text-decoration : underline; +} +em em.u { +font-style : italic; +} +.code .br0 { +color : #6c6; +} +.code .co1 { +color : #808080; +font-style : italic; +} +.code .co2 { +color : #808080; +font-style : italic; +} +.code .co3 { +color : #808080; +} +.code .coMULTI { +color : #808080; +font-style : italic; +} +.code .es0 { +color : #009; +font-weight : bold; +} +.code .kw1 { +color : #b1b100; +} +.code .kw2 { +color : #000; +font-weight : bold; +} +.code .kw3 { +color : #006; +} +.code .kw4 { +color : #933; +} +.code .kw5 { +color : #00f; +} +.code .me1 { +color : #060; +} +.code .me2 { +color : #060; +} +.code .nu0 { +color : #c6c; +} +.code .re0 { +color : #00f; +} +.code .re1 { +color : #00f; +} +.code .re2 { +color : #00f; +} +.code .re3 { +color : #f33; +font-weight : bold; +} +.code .re4 { +color : #099; +} +.code .st0 { +color : #f00; +} +.code .sy0 { +color : #6c6; +} +#acl__manager label { +text-align : left; +font-weight : normal; +display : inline; +} +#acl__manager table { +margin-left : 10%; +width : 80%; +} +#config__manager div.success, #config__manager div.error, #config__manager div.info { +background-position : 0.5em 0%; +padding : 0.5em; +text-align : center; +} +#config__manager fieldset { +margin : 1em; +width : auto; +margin-bottom : 2em; +background-color : #dee7ec; +color : #000; +padding : 0 1em; +} +#config__manager legend { +font-size : 1.25em; +} +#config__manager table { +margin : 1em 0; +width : 100%; +} +#config__manager fieldset td { +text-align : left; +} +#config__manager fieldset td.value { +width : 30em; +} +#config__manager td input.edit { +width : 30em; +} +#config__manager td textarea.edit { +width : 27.5em; +height : 4em; +} +#config__manager tr .input, #config__manager tr input, #config__manager tr textarea, #config__manager tr select { +background-color : #fff; +color : #000; +} +#config__manager tr.default .input, #config__manager tr.default input, #config__manager tr.default textarea, #config__manager tr.default select, #config__manager .selectiondefault { +background-color : #cdf; +color : #000; +} +#config__manager tr.protected .input, #config__manager tr.protected input, #config__manager tr.protected textarea, #config__manager tr.protected select, #config__manager tr.protected .selection { +background-color : #fcc !important ; +color : #000 !important ; +} +#config__manager td.error { +background-color : red; +color : #000; +} +#config__manager .selection { +width : 14.8em; +float : left; +margin : 0 0.3em 2px 0; +} +#config__manager .selection label { +float : right; +width : 14em; +font-size : 90%; +} +* html #config__manager .selection label { +padding-top : 2px; +} +#config__manager .selection input.checkbox { +padding-left : 0.7em; +} +#config__manager .other { +clear : both; +padding-top : 0.5em; +} +#config__manager .other label { +padding-left : 2px; +font-size : 90%; +} +#plugin__manager h2 { +margin-left : 0; +} +#plugin__manager form { +display : block; +margin : 0; +padding : 0; +} +#plugin__manager legend { +display : none; +} +#plugin__manager fieldset { +width : auto; +} +#plugin__manager .button { +margin : 0; +} +#plugin__manager p, #plugin__manager label { +text-align : left; +} +#plugin__manager .hidden { +display : none; +} +#plugin__manager .new { +background : #dee7ec; +} +#plugin__manager input[disabled] { +color : #ccc; +border-color : #ccc; +} +#plugin__manager .pm_menu, #plugin__manager .pm_info { +margin-left : 0; +text-align : left; +} +#plugin__manager .pm_menu { +float : left; +width : 48%; +} +#plugin__manager .pm_info { +float : right; +width : 50%; +} +#plugin__manager .common fieldset { +margin : 0; +padding : 0 0 1em 0; +text-align : left; +border : none; +} +#plugin__manager .common label { +padding : 0 0 0.5em 0; +} +#plugin__manager .common input.edit { +width : 24em; +margin : 0.5em; +} +#plugin__manager .plugins fieldset { +color : #000; +background : #fff; +text-align : right; +border-top : none; +border-right : none; +border-left : none; +} +#plugin__manager .plugins fieldset.protected { +background : #fdd; +color : #000; +} +#plugin__manager .plugins fieldset.disabled { +background : #e0e0e0; +color : #a8a8a8; +} +#plugin__manager .plugins .legend { +color : #000; +background : inherit; +display : block; +margin : 0; +padding : 0; +font-size : 1em; +line-height : 1.4em; +font-weight : normal; +text-align : left; +float : left; +padding : 0; +clear : none; +} +#plugin__manager .plugins .button { +font-size : 95%; +} +#plugin__manager .plugins fieldset.buttons { +border : none; +} +#plugin__manager .plugins fieldset.buttons .button { +float : left; +} +#plugin__manager .pm_info h3 { +margin-left : 0; +} +#plugin__manager .pm_info dl { +margin : 1em 0; +padding : 0; +} +#plugin__manager .pm_info dt { +width : 6em; +float : left; +clear : left; +margin : 0; +padding : 0; +} +#plugin__manager .pm_info dd { +margin : 0 0 0 7em; +padding : 0; +background : none; +} +#plugin__manager .plugins .enable { +float : left; +width : auto; +margin-right : 0.5em; +} +#user__manager tr.disabled { +color : #6f6f6f; +background : #e4e4e4; +} +#user__manager tr.user_info { +vertical-align : top; +} +#user__manager div.edit_user { +width : 46%; +float : left; +} +#user__manager table { +margin-bottom : 1em; +} +#user__manager input.button[disabled] { +color : #ccc !important ; +border-color : #ccc !important ; +} +div.dokuwiki .header { +padding : 3px 0 0 2px; +} +div.dokuwiki .pagename { +float : left; +font-size : 200%; +font-weight : bolder; +color : #dee7ec; +text-align : left; +vertical-align : middle; +} +div.dokuwiki .pagename a { +color : #436976 !important ; +text-decoration : none !important ; +} +div.dokuwiki .logo { +float : right; +font-size : 220%; +font-weight : bolder; +text-align : right; +vertical-align : middle; +} +div.dokuwiki .logo a { +color : #dee7ec !important ; +text-decoration : none !important ; +font-variant : small-caps; +letter-spacing : 2pt; +} +div.dokuwiki .bar { +border-top : 1px solid #8cacbb; +border-bottom : 1px solid #8cacbb; +background : #dee7ec; +padding : 0.1em 0.15em; +clear : both; +} +div.dokuwiki .bar-left { +float : left; +} +div.dokuwiki .bar-right { +float : right; +text-align : right; +} +div.dokuwiki #bar__bottom { +margin-bottom : 3px; +} +div.dokuwiki div.meta { +clear : both; +margin-top : 1em; +color : #638c9c; +font-size : 70%; +} +div.dokuwiki div.meta div.user { +float : left; +} +div.dokuwiki div.meta div.doc { +text-align : right; +} +* { +padding : 0; +margin : 0; +} +body { +font : 80% "Lucida Grande", Verdana, Lucida, Helvetica, Arial, sans-serif; +background-color : #fff; +color : #000; +} +div.dokuwiki div.page { +margin : 4px 2em 0 1em; +text-align : justify; +} +div.dokuwiki table { +font-size : 100%; +} +div.dokuwiki img { +border : 0; +} +div.dokuwiki p, div.dokuwiki blockquote, div.dokuwiki table, div.dokuwiki pre { +margin : 0 0 1em 0; +} +div.dokuwiki hr { +border : 0; +border-top : 1px solid #8cacbb; +text-align : center; +height : 0; +} +div.dokuwiki div.nothing { +text-align : center; +margin : 2em; +} +div.dokuwiki form { +border : none; +display : inline; +} +div.dokuwiki label.block { +display : block; +text-align : right; +font-weight : bold; +} +div.dokuwiki label.simple { +display : block; +text-align : left; +font-weight : normal; +} +div.dokuwiki label.block input.edit { +width : 50%; +} +div.dokuwiki fieldset { +width : 300px; +text-align : center; +border : 1px solid #8cacbb; +padding : 0.5em; +margin : auto; +} +div.dokuwiki textarea.edit { +font-family : monospace; +font-size : 14px; +color : #000; +background-color : #fff; +border : 1px solid #8cacbb; +padding : 0.3em 0 0 0.3em; +width : 100%; +} +html > body div.dokuwiki textarea.edit { +background : #fff; +} +div.dokuwiki input.edit, div.dokuwiki select.edit { +font-size : 100%; +border : 1px solid #8cacbb; +color : #000; +background-color : #fff; +vertical-align : middle; +margin : 1px; +padding : 0.2em 0.3em; +display : inline; +} +html > body div.dokuwiki input.edit, html > body div.dokuwiki select.edit { +background : #fff; +} +div.dokuwiki select.edit { +padding : 0.1em 0; +} +div.dokuwiki input.missing { +font-size : 100%; +border : 1px solid #8cacbb; +color : #000; +background-color : #fcc; +vertical-align : middle; +margin : 1px; +padding : 0.2em 0.3em; +display : inline; +} +div.dokuwiki textarea.edit[disabled], div.dokuwiki textarea.edit[readonly], div.dokuwiki input.edit[disabled], div.dokuwiki input.edit[readonly], div.dokuwiki select.edit[disabled] { +background-color : #f5f5f5 !important ; +color : #666 !important ; +} +div.dokuwiki div.toolbar, div.dokuwiki div#wiki__editbar { +margin : 2px 0; +text-align : left; +} +div.dokuwiki div#size__ctl { +float : right; +width : 60px; +height : 2.7em; +} +div.dokuwiki #size__ctl img { +cursor : pointer; +} +div.dokuwiki div#wiki__editbar div.editButtons { +float : left; +padding : 0 1em 0.7em 0; +} +div.dokuwiki div#wiki__editbar div.summary { +float : left; +} +div.dokuwiki .nowrap { +white-space : nowrap; +} +div.dokuwiki div#draft__status { +float : right; +color : #638c9c; +} +div.dokuwiki input.button, div.dokuwiki button.button { +border : 1px solid #8cacbb; +color : #000; +background-color : #fff; +vertical-align : middle; +text-decoration : none; +font-size : 100%; +cursor : pointer; +margin : 1px; +padding : 0.125em 0.4em; +} +html > body div.dokuwiki input.button, html > body div.dokuwiki button.button { +background : #fff; +} +* html div.dokuwiki input.button, * html div.dokuwiki button.button { +height : 1.8em; +} +div.dokuwiki div.secedit input.button { +border : 1px solid #8cacbb; +color : #000; +background-color : #fff; +vertical-align : middle; +text-decoration : none; +margin : 0; +padding : 0; +font-size : 10px; +cursor : pointer; +float : right; +display : inline; +} +div.dokuwiki div.pagenav { +margin : 1em 0 0 0; +} +div.dokuwiki div.pagenav-prev { +text-align : right; +float : left; +width : 49%; +} +div.dokuwiki div.pagenav-next { +text-align : left; +float : right; +width : 49%; +} +div.dokuwiki a:link, div.dokuwiki a:visited { +color : #436976; +text-decoration : none; +} +div.dokuwiki a:hover, div.dokuwiki a:active { +color : #000; +text-decoration : underline; +} +div.dokuwiki h1 a, div.dokuwiki h2 a, div.dokuwiki h3 a, div.dokuwiki h4 a, div.dokuwiki h5 a, div.dokuwiki a.nolink { +color : #000 !important ; +text-decoration : none !important ; +} +div.dokuwiki a.urlextern { +background : transparent; +padding : 1px 0 1px 16px; +} +div.dokuwiki a.windows { +background : transparent; +padding : 1px 0 1px 16px; +} +div.dokuwiki a.urlextern:link, div.dokuwiki a.windows:link, div.dokuwiki a.interwiki:link { +color : #436976; +} +div.dokuwiki a.urlextern:visited, div.dokuwiki a.windows:visited, div.dokuwiki a.interwiki:visited { +color : purple; +} +div.dokuwiki a.urlextern:hover, div.dokuwiki a.urlextern:active, div.dokuwiki a.windows:hover, div.dokuwiki a.windows:active, div.dokuwiki a.interwiki:hover, div.dokuwiki a.interwiki:active { +color : #000; +} +div.dokuwiki a.mail { +background : transparent; +padding : 1px 0 1px 16px; +} +div.dokuwiki a.wikilink1 { +color : #090 !important ; +} +div.dokuwiki a.wikilink2 { +color : #f30 !important ; +text-decoration : none !important ; +border-bottom : 1px dashed #f30 !important ; +} +div.dokuwiki div.preview { +background-color : #f5f5f5; +margin : 0 0 0 2em; +padding : 4px; +border : 1px dashed #000; +} +div.dokuwiki div.breadcrumbs { +background-color : #f5f5f5; +color : #666; +font-size : 80%; +padding : 0 0 0 4px; +} +div.dokuwiki span.user { +color : #ccc; +font-size : 90%; +} +div.dokuwiki li.minor { +color : #666; +font-style : italic; +} +div.dokuwiki img.media { +margin : 3px; +} +div.dokuwiki img.medialeft { +border : 0; +float : left; +margin : 0 1.5em 0 0; +} +div.dokuwiki img.mediaright { +border : 0; +float : right; +margin : 0 0 0 1.5em; +} +div.dokuwiki img.mediacenter { +border : 0; +display : block; +margin : 0 auto; +} +div.dokuwiki img.middle { +vertical-align : middle; +} +div.dokuwiki acronym { +cursor : help; +border-bottom : 1px dotted #000; +} +div.dokuwiki h1, div.dokuwiki h2, div.dokuwiki h3, div.dokuwiki h4, div.dokuwiki h5 { +color : #000; +background-color : inherit; +font-size : 100%; +font-weight : normal; +margin : 0 0 1em 0; +padding : 0.5em 0 0 0; +border-bottom : 1px solid #8cacbb; +clear : left; +} +div.dokuwiki h1 { +font-size : 160%; +margin-left : 0; +font-weight : bold; +} +div.dokuwiki h2 { +font-size : 150%; +margin-left : 20px; +} +div.dokuwiki h3 { +font-size : 140%; +margin-left : 40px; +border-bottom : none; +font-weight : bold; +} +div.dokuwiki h4 { +font-size : 120%; +margin-left : 60px; +border-bottom : none; +font-weight : bold; +} +div.dokuwiki h5 { +font-size : 100%; +margin-left : 80px; +border-bottom : none; +font-weight : bold; +} +div.dokuwiki div.level1 { +margin-left : 3px; +} +div.dokuwiki div.level2 { +margin-left : 23px; +} +div.dokuwiki div.level3 { +margin-left : 43px; +} +div.dokuwiki div.level4 { +margin-left : 63px; +} +div.dokuwiki div.level5 { +margin-left : 83px; +} +div.dokuwiki ul { +line-height : 1.5em; +list-style-type : square; +list-style-image : none; +margin : 0 0 0.5em 1.5em; +color : #638c9c; +} +div.dokuwiki ol { +line-height : 1.5em; +list-style-image : none; +margin : 0 0 0.5em 1.5em; +color : #638c9c; +font-weight : bold; +} +div.dokuwiki .li { +color : #000; +font-weight : normal; +} +div.dokuwiki ol { +list-style-type : decimal; +} +div.dokuwiki ol ol { +list-style-type : upper-roman; +} +div.dokuwiki ol ol ol { +list-style-type : lower-alpha; +} +div.dokuwiki ol ol ol ol { +list-style-type : lower-greek; +} +div.dokuwiki li.open { +} +div.dokuwiki li.closed { +} +div.dokuwiki blockquote { +border-left : 2px solid #8cacbb; +padding-left : 3px; +} +div.dokuwiki pre { +font-size : 120%; +padding : 0.5em; +border : 1px dashed #8cacbb; +color : #000; +overflow : auto; +} +div.dokuwiki pre.pre { +background-color : #f7f9fa; +} +div.dokuwiki pre.code { +background-color : #f7f9fa; +} +div.dokuwiki code { +font-size : 120%; +} +div.dokuwiki pre.file { +background-color : #dee7ec; +} +div.dokuwiki table.inline { +background-color : #fff; +border-spacing : 0; +border-collapse : collapse; +} +div.dokuwiki table.inline th { +padding : 3px; +border : 1px solid #8cacbb; +background-color : #dee7ec; +} +div.dokuwiki table.inline td { +padding : 3px; +border : 1px solid #8cacbb; +} +div.dokuwiki div.toc { +margin : 1.2em 0 0 2em; +float : right; +width : 200px; +font-size : 80%; +clear : both; +} +div.dokuwiki div.tocheader { +border : 1px solid #8cacbb; +background-color : #dee7ec; +text-align : left; +font-weight : bold; +padding : 3px; +margin-bottom : 2px; +} +div.dokuwiki span.toc_open, div.dokuwiki span.toc_close { +border : 0.4em solid #dee7ec; +float : right; +display : block; +margin : 0.4em 3px 0 0; +} +div.dokuwiki span.toc_open span, div.dokuwiki span.toc_close span { +display : none; +} +div.dokuwiki span.toc_open { +margin-top : 0.4em; +border-top : 0.4em solid #000; +} +div.dokuwiki span.toc_close { +margin-top : 0; +border-bottom : 0.4em solid #000; +} +div.dokuwiki #toc__inside { +border : 1px solid #8cacbb; +background-color : #fff; +text-align : left; +padding : 0.5em 0 0.7em 0; +} +div.dokuwiki ul.toc { +list-style-type : none; +list-style-image : none; +line-height : 1.2em; +padding-left : 1em; +margin : 0; +} +div.dokuwiki ul.toc li { +background : transparent; +padding-left : 0.4em; +} +div.dokuwiki ul.toc li.clear { +background-image : none; +padding-left : 0.4em; +} +div.dokuwiki a.toc:link, div.dokuwiki a.toc:visited { +color : #436976; +} +div.dokuwiki a.toc:hover, div.dokuwiki a.toc:active { +color : #000; +} +div.dokuwiki table.diff { +background-color : #fff; +width : 100%; +} +div.dokuwiki td.diff-blockheader { +font-weight : bold; +} +div.dokuwiki table.diff th { +border-bottom : 1px solid #8cacbb; +font-size : 120%; +width : 50%; +font-weight : normal; +text-align : left; +} +div.dokuwiki table.diff td { +font-family : monospace; +font-size : 100%; +} +div.dokuwiki td.diff-addedline { +background-color : #dfd; +} +div.dokuwiki td.diff-deletedline { +background-color : #ffb; +} +div.dokuwiki td.diff-context { +background-color : #f5f5f5; +} +div.dokuwiki table.diff td.diff-addedline strong, div.dokuwiki table.diff td.diff-deletedline strong { +color : red; +} +div.dokuwiki div.footnotes { +clear : both; +border-top : 1px solid #8cacbb; +padding-left : 1em; +margin-top : 1em; +} +div.dokuwiki div.fn { +font-size : 90%; +} +div.dokuwiki a.fn_top { +vertical-align : super; +font-size : 80%; +} +div.dokuwiki a.fn_bot { +vertical-align : super; +font-size : 80%; +font-weight : bold; +} +div.insitu-footnote { +font-size : 80%; +line-height : 1.2em; +border : 1px solid #8cacbb; +background-color : #f7f9fa; +text-align : left; +padding : 4px; +max-width : 40%; +} +* html .insitu-footnote pre.code, * html .insitu-footnote pre.file { +padding-bottom : 18px; +} +div.dokuwiki .search_result { +margin-bottom : 6px; +padding : 0 10px 0 30px; +} +div.dokuwiki .search_snippet { +color : #ccc; +font-size : 12px; +margin-left : 20px; +} +div.dokuwiki .search_sep { +color : #000; +} +div.dokuwiki .search_hit { +color : #000; +background-color : #ff9; +} +div.dokuwiki strong.search_hit { +font-weight : normal; +} +div.dokuwiki div.search_quickresult { +margin : 0 0 15px 30px; +padding : 0 10px 10px 0; +border-bottom : 1px dashed #8cacbb; +} +div.dokuwiki div.search_quickresult h3 { +margin : 0 0 1em 0; +font-size : 1em; +font-weight : bold; +} +div.dokuwiki ul.search_quickhits { +margin : 0 0 0.5em 1em; +} +div.dokuwiki ul.search_quickhits li { +margin : 0 1em 0 1em; +float : left; +width : 30%; +} +div.footerinc { +text-align : center; +} +.footerinc a img { +border : 0; +} +div.dokuwiki div.ajax_qsearch { +position : absolute; +right : 237px; +width : 200px; +display : none; +font-size : 80%; +line-height : 1.2em; +border : 1px solid #8cacbb; +background-color : #f7f9fa; +text-align : left; +padding : 4px; +} +button.toolbutton { +background-color : #fff; +padding : 0; +margin : 0 1px 0 0; +border : 1px solid #8cacbb; +cursor : pointer; +} +html > body button.toolbutton { +background : #fff; +} +div.picker { +width : 250px; +border : 1px solid #8cacbb; +background-color : #dee7ec; +} +button.pickerbutton { +padding : 0; +margin : 0 1px 1px 0; +border : 0; +background-color : transparent; +font-size : 80%; +cursor : pointer; +} +div.dokuwiki a.spell_error { +color : #f00; +text-decoration : underline; +} +div.dokuwiki div#spell__suggest { +background-color : #fff; +padding : 2px; +border : 1px solid #000; +font-size : 80%; +display : none; +} +div.dokuwiki div#spell__result { +border : 1px solid #8cacbb; +color : #000; +font-size : 14px; +padding : 3px; +background-color : #f7f9fa; +display : none; +} +div.dokuwiki span.spell_noerr { +color : #093; +} +div.dokuwiki span.spell_wait { +color : #06c; +} +div.dokuwiki div.img_big { +float : left; +margin-right : 0.5em; +} +div.dokuwiki dl.img_tags dt { +font-weight : bold; +background-color : #dee7ec; +} +div.dokuwiki dl.img_tags dd { +background-color : #f5f5f5; +} +div.dokuwiki div.imagemeta { +color : #666; +font-size : 70%; +line-height : 95%; +} +div.dokuwiki div.imagemeta img.thumb { +float : left; +margin-right : 0.1em; +} +#media__manager { +height : 100%; +overflow : hidden; +} +#media__left { +width : 30%; +border-right : 1px solid #8cacbb; +height : 100%; +overflow : auto; +position : absolute; +left : 0; +} +#media__right { +width : 69.7%; +height : 100%; +overflow : auto; +position : absolute; +right : 0; +} +#media__manager h1 { +margin : 0; +padding : 0; +margin-bottom : 0.5em; +} +#media__tree img { +float : left; +padding : 0.5em 0.3em 0 0; +} +#media__tree ul { +list-style-type : none; +list-style-image : none; +} +#media__tree li { +clear : left; +list-style-type : none; +list-style-image : none; +} +* html #media__tree li { +border : 1px solid #fff; +} +#media__opts { +padding-left : 1em; +margin-bottom : 0.5em; +} +#media__opts input { +float : left; +position : absolute; +} +* html #media__opts input { +position : static; +} +#media__opts label { +display : block; +float : left; +margin-left : 30px; +} +* html #media__opts label { +margin-left : 10px; +} +#media__opts br { +clear : left; +} +#media__content img.load { +margin : 1em auto; +} +#media__content #scroll__here { +border : 1px dashed #8cacbb; +} +#media__content .odd { +background-color : #f7f9fa; +padding : 0.4em; +} +#media__content .even { +padding : 0.4em; +} +#media__content a.mediafile { +margin-right : 1.5em; +font-weight : bold; +} +#media__content div.detail { +padding : 0.3em 0 0.3em 2em; +} +#media__content div.detail div.thumb { +float : left; +width : 130px; +text-align : center; +margin-right : 0.4em; +} +#media__content img.btn { +vertical-align : text-bottom; +} +#media__content div.example { +color : #666; +margin-left : 1em; +} +#media__content div.upload { +font-size : 90%; +padding : 0 0.5em 0.5em 0.5em; +} +#media__content form.upload { +display : block; +border-bottom : 1px solid #8cacbb; +padding : 0 0.5em 1em 0.5em; +} +#media__content form.upload fieldset { +padding : 0; +margin : 0; +border : none; +width : auto; +} +#media__content form.upload p { +clear : left; +text-align : left; +padding : 0.25em 0; +margin : 0; +line-height : 1em; +} +#media__content form.upload label { +float : left; +width : 30%; +} +#media__content form.upload label.check { +float : none; +width : auto; +} +#media__content form.upload input.check { +margin-left : 30%; +} +#media__content form.meta { +display : block; +padding : 0 0 1em 0; +} +#media__content form.meta label { +display : block; +width : 25%; +float : left; +font-weight : bold; +margin-left : 1em; +clear : left; +} +#media__content form.meta .edit { +font : 100% "Lucida Grande", Verdana, Lucida, Helvetica, Arial, sans-serif; +float : left; +width : 70%; +padding-right : 0; +padding-left : 0.2em; +margin : 2px; +} +#media__content form.meta textarea.edit { +height : 8em; +} +#media__content form.meta div.metafield { +clear : left; +} +#media__content form.meta div.buttons { +clear : left; +margin-left : 20%; +padding-left : 1em; +} \ No newline at end of file diff --git a/sdk-readme.html b/sdk-readme.html new file mode 100644 index 0000000..f91d8b9 --- /dev/null +++ b/sdk-readme.html @@ -0,0 +1,724 @@ + + + + + foobar2000 SDK Readme + + + + + +
+ + + + +

foobar2000 v1.6 SDK readme

+
+ +
+ +

Compatibility

+
+ +

+ Components built with this SDK are compatible with foobar2000 1.4 and newer. They are not compatible with any earlier versions (will fail to load), and not guaranteed to be compatible with any future versions, though upcoming releases will aim to maintain compatibility as far as possible without crippling newly added functionality. +

+ +

+You can alter the targeted foobar2000 API level, edit SDK/foobar2000.h and change the value of FOOBAR2000_TARGET_VERSION. +

+ +

+Currently supported values are 79 (for 1.4 series) and 80 (for 1.5 & 1.6 series). +

+ +
+ +

Microsoft Visual Studio compatibility

+
+ +

+ This SDK contains project files for Visual Studio 2017. +

+ +
+ +

Ill behavior of Visual C whole program optimization

+
+ +

+Visual Studio versions from 2012 up produce incorrect output with default release settings on foobar2000 code - see forum thread for details. You can mitigate this issue by compiling all affected code (foobar2000 SDK code along with everything that uses foobar2000 SDK classes) with /d2notypeopt option. This is set by default on project files provided with the SDK; please make sure that you set this option on any projects of your own. +

+ +

+If you're aware of a better workaround - such as a source code change rather than setting an obscure compiler flag - please let us know; posting on the forum is preferred for the benefit of other users of this SDK. +

+ +
+ +

"virtual memory range for PCH exceeded" error

+
+ +

+By convention, foobar2000 code used to #include every single SDK and utility header in a precompiled header (PCH) file. This became an issue with certain Visual Studio setups, as the PCH payload became extremely large. +

+ +

+The SDK has been changed to reduce the amount of unnecessary code shoved into #included headers; errors of this type are yet to be seen with this version of the foobar2000 SDK compiled under VS2017. +

+ +

+If you run into this error, we recommend trimming down the #includes and only referencing specific headers from helpers instead of #including all of them (via old helpers.h). +

+ +
+ +

Version 1.6 notes

+
+ +
+ +

Windows XP support

+
+ +

+The SDK project files are configured for targetting Windows 7 and newer, with SSE2 level instruction set. +

+ +

+If you really must compile components that run on 20 years old computers, you can still change relevant settings, or use an older SDK. Please test your components on actual hardware if you do so, VS2017 compiler has been known to output SSE opcodes when specifically told not to. +

+ +
+ +

Audio output API extensions

+
+ +

+New methods have been added to the output API, such as reporting whether your output is low-latency or high-latency, to interop with the new fading code. +

+ +

+foobar2000 now provides a suite of special fixes for problematic output implementations (guarenteed update() calls in regular intervals, silence injected at the end); use flag_needs_shims to enable with your output. While such feature might seem unnecessary, it allowed individual outputs to be greatly simplified, removing a lot of other special fixes implemented per output. +

+ +
+ +

WebP support, imageLoaderLite

+
+ +

+Use fb2k::imageLoaderLite API to load a Gdiplus image from a memory block, or to extract info without actually decoding the picture. It supports WebP payload and will be updated with any formats added in the future. +

+ +
+ +

File cache utilities

+
+ +

+New interface has been introduced - read_ahead_tools - to access advanced playback settings for read-ahead cache; primarily needed if your decoder needs to open additional files over the internet. +

+ +
+ +

Cuesheet wrapper fixes

+
+ +

+Wrapper code to support embedded cuesheets on arbitrary formats has been updated to deal with remote files gracefully, that is not present any chapters on such. +

+ +
+ +

libPPUI

+
+ +

+Notable changes: * WebP vs Gdiplus interop (in case you want to load WebP into Gdiplus in your own app - in fb2k, use imageLoaderLite) * Fixed horrible GdiplusImageFromMem() bug * Combo boxes in CListControl * CListControl graying/disabling of individual items +

+ +
+ +

Version 1.5 notes

+
+ +
+ +

libPPUI

+
+ +

+Various code from the helpers projects that was in no way foobar2000 specific became libPPUI. In addition, Default User Interface list control has been thrown in. libPPUI is released under a non-restrictive license. Reuse in other projects - including commercial projects - is encouraged. Credits in binary redistribution are not required. +

+ +

+Existing foobar2000 components that reference SDK helpers/ATLHelpers will need updating to reference libPPUI instead. Separate helpers/ATLHelpers projects are no more, as all projects depend on libPPUI which requires ATL/WTL. +

+ +
+ +

Version 1.4 notes

+
+ +
+ +

Namespace cleanup

+
+ +

+Some very old inconsistencies in the code have been cleaned up. Various bit_array classes are now in pfc namespace where they belong. Please use pfc::bit_array and so on in new code. If you have code that references bit_array classes without the pfc:: prefix, put “using pfc::bit_array” in your headers to work around it. +

+ +
+ +

Decoders

+
+ +
+ +

Invoking decoders

+
+ +

+Each new decoder (input_entry) now provides a get_guid() and get_name() to allow user to choose the order in which they're invoked as well as disable individual installed decoders. Because of this, you are no longer supposed to walk input_entry instances in your code; however many existing components do so because this was the way things were expected to work in all past versions before 1.4. +

+ +

+In most cases there's nothing that you need to do about this, unless you have code that talks to input_entry instance methods directly. +

+ +
+ +
input_manager
+
+ +

+The proper way to instantiate any input related classes is to call input_manager; it manages opening of decoders respecting user's decoder merit settings. Calling any of the helper methods to open decoders / read tags / etc in the SDK will call input_manager if available (v1.4) and fall back to walking input_entry services if not (v1.3). +

+ +
+ +
input_entry shim
+
+ +

+If your component targets API level lower than 79, all your attempts to walk input_entry services return a shim service that redirects all your calls to input_manager. You cannot walk actual input_entry services. +

+ +

+This is to keep existing components working as intended. +

+ +

+If your component targets API level 79 (which means it won't load on v1.3), the shim is not installed as your component is expected to be aware of the new semantics. +

+ +
+ +

Implementing decoders

+
+ +

+Many new input methods have been added and some are mandatory for all code, in particular reporting of decoder name and GUID; existing code not providing these will not compile. However some of the methods now required to be provided by an input class are mundane and will be left blank in majority of implementations; an input_stubs class has been introduced to help with these. In past SDK versions, your input class would not derive from another class; it is now recommended to derive from input_stubs to avoid having to supply mundane methods yourself. +

+ +
+ +
Specify supported interfaces
+
+ +

+You can now control which level of decoder API your instance supports from your input class instead of using multi parameter input_factory classes. +

+ +

+The input_stubs class provides these for your convenience: +

+
+typedef input_decoder_v4 interface_decoder_t;
+typedef input_info_reader interface_info_reader_t;
+typedef input_info_writer interface_info_writer_t;
+
+ +

+ Override these in your input class to indicate supported interfaces. +

+ +

+For an example, if your input supports remove_tags(), indicate that you implement input_info_writer_v2: +

+
+typedef input_info_writer_v2 interface_info_writer_t;
+
+ +
+ +

Dynamic runtime

+
+ +

+As of version 1.4, foobar2000 is compiled with dynamic Visual C runtime and redistributes Visual C runtime libraries with the installer, putting them in the foobar2000 installation folder if necessary. The benefits of this are: * Smaller component DLLs * Increased limit of how many component DLLs can be loaded. +

+ +

+This SDK comes configured for dynamic runtime by default. If you wish to support foobar2000 versions older than 1.4, change to static runtime or make sure that your users have the runtime installed. +

+ +
+ +

service_query()

+
+ +

+tl;dr if you don't know what this is about you probably don't care and your component isn't in any way affected by this. +

+ +

+The way service_query() is implemented has been redesigned for performance reasons - the old way ( implemented per interface class via FB2K_MAKE_SERVICE_INTERFACE macro ) resulted in every single class_guid from the entire SDK being included in your DLL, due to strange behaviors of Microsoft linker. +

+ +

+The service_query() function itself is now provided by service_impl_* classes, so it's materialized only for services that your code implements and spawns. +

+ +

+The default implementation calls a newly added static method of handle_service_query(), implemented in service_base to check against all supported interfaces by walking derived class chain. +

+ +

+If you wish to override it, create a function with a matching signature in your class and it will be called instead: +

+
+class myClass : public service_base {
+public:
+    static bool handle_service_query(service_ptr & out, const GUID & guid, myClass * in) {
+        if ( guid == myClassGUID ) {
+            out = in; return true;
+        }
+        return false;
+    }
+};
+
+ +
+ +

Multi-inheritance

+
+ +

+The above change to service_query() implementation rules obviously breaks any existing code where one class inherits from multiple service classes and supplies a custom service_query(). +

+ +

+While using multi inheritance is not recommended and very rarely done, a new template has been added to help with such cases: service_multi_inherit<class1, class2>. +

+ +

+You can use it to avoid having to supply service_query() code yourself and possibly change it if service_query() semantics change again in the future. +

+ +
+ +

Version 1.3 notes

+
+ +

+foobar2000 version 1.3 uses different metadb behavior semantics than older versions, to greatly improve the performance of multithreaded operation by reducing the time spent within global metadb locks. +

+ +

+Any methods that: +

+
    +
  • Lock the metadb - database_lock() etc
    +
  • +
  • Retrieve direct pointers to metadb info - get_info_locked() style
    +
  • +
+ +

+ .. are now marked deprecated and implemented only for backwards compatibility; they should not be used in any new code. +

+ +

+It is recommended that you change your existing code using these to obtain track information using new get_info_ref() style methods for much better performance as these methods have minimal overhead and require no special care when used in multiple concurrent threads. +

+ +
+ +

Basic usage

+
+ +

+ Each component must link against: +

+
    +
  • foobar2000_SDK project (contains declarations of services and various service-specific helper code)
    +
  • +
  • foobar2000_component_client project (contains DLL entrypoint)
    +
  • +
  • shared.dll (various helper code, mainly win32 function wrappers taking UTF-8 strings)
    +
  • +
  • PFC (non-OS-specific helper class library)
    +
  • +
+ +

+Optionally, components can use helper libraries with various non-critical code that is commonly reused across various foobar2000 components: +

+
    +
  • foobar2000_SDK_helpers - a library of various helper code commonly used by foobar2000 components.
    +
  • +
  • foobar2000_ATL_helpers - another library of various helper code commonly used by foobar2000 components; requires WTL.
    +
  • +
+ +

+ Foobar2000_SDK, foobar2000_component_client and PFC are included in sourcecode form; you can link against them by adding them to your workspace and using dependencies. To link against shared.dll, you must add “shared.lib” to linker input manually. +

+ +

+Component code should include the following header files: +

+
    +
  • foobar2000.h from SDK - do not include other headers from the SDK directory directly, they're meant to be referenced by foobar2000.h only; it also includes PFC headers and shared.dll helper declaration headers.
    +
  • +
  • Necessary headers from libPPUI and helpers, which both contain various code commonly used by fb2k components.
    +
  • +
+ +
+ +

Structure of a component

+
+ +

+ A component is a DLL that implements one or more entrypoint services and interacts with services provided by other components. +

+ +
+ +

Services

+
+ +

+ A service type is an interface class, deriving directly or indirectly from service_base class. A service type class must not have any data members; it can only provide virtual methods (to be overridden by service implementation), helper non-virtual methods built around virtual methods, static helper methods, and constants / enums. Each service interface class must have a static class_guid member, used for identification when enumerating services or querying for supported functionality. A service type declaration should declare a class with public virtual/helper/static methods, and use FB2K_MAKE_SERVICE_INTERFACE() / FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT() macro to implement standard service behaviors for the class; additionally, class_guid needs to be defined outside class declaration (e.g. const GUID someclass::class_guid = {….}; ). Note that most of components will not declare their own service types, they will only implement existing ones declared in the SDK. +

+ +

+A service implementation is a class derived from relevant service type class, implementing virtual methods declared by service type class. Note that service implementation class does not implement virtual methods declared by service_base; those are implemented by service type declaration framework (service_query) or by instantiation framework (service_add_ref / service_release). Service implementation classes are instantiated using service_factory templates in case of entrypoint services (see below), or using service_impl_t template and operator new: ”myserviceptr = new service_impl_t<myservice_impl>(params);”. +

+ +

+Each service object provides reference counter features and (service_add_ref() and service_release() methods) as well as a method to query for extended functionality (service_query() method). Those methods are implemented by service framework and should be never overridden by service implementations. These methods should also never be called directly - reference counter methods are managed by relevant autopointer templates, service_query_t function template should be used instead of calling service_query directly, to ensure type safety and correct type conversions. +

+ +
+ +

Entrypoint services

+
+ +

+ An entrypoint service type is a special case of a service type that can be registered using service_factory templates, and then accessed from any point of service system (excluding DLL startup/shutdown code, such as code inside static object constructors/destructors). An entrypoint service type class must directly derive from service_base. +

+ +

+Registered entrypoint services can be accessed using: +

+
    +
  • For services types with variable number of implementations registered: service_enum_t<T> template, service_class_helper_t<T> template, etc, e.g.
    service_enum_t<someclass> e; service_ptr_t<someclass> ptr; while(e.next(ptr)) ptr->dosomething();
    +
  • +
  • For services types with a single always-present implementation registered - such as core services like playlist_manager - using someclass::get(), e.g.:
    auto api = someclass::get(); api->dosomething(); api->dosomethingelse();
    +
  • +
  • Using per-service-type defined static helper functions, e.g. someclass::g_dosomething() - those use relevant service enumeration methods internally.
    +
  • +
+ +

+ An entrypoint service type must use FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT() macro to implement standard entrypoint service behaviors, as opposed to all other service types that use FB2K_MAKE_SERVICE_INTERFACE() macro instead. +

+ +

+You can register your own entrypoint service implementations using either service_factory_t or service_factory_single_t template - the difference between the two is that the former instantiates the class on demand, while the latter keeps a single static instance and returns references to it when requested; the latter is faster but usable only for things that have no per-instance member data to maintain. Each service_factory_t / service_factory_single_t instance should be a static variable, such as: ”static service_factory_t<myclass> g_myclass_factory;”. +

+ +

+Certain service types require custom service_factory helper templates to be used instead of standard service_factory_t / service_factory_single_t templates; see documentation of specific service type for exact info about registering. +

+ +

+A typical entrypoint service implementation looks like this: +

+
class myservice_impl : public myservice {
+public:
+	void dosomething() {....};
+	void dosomethingelse(int meh) {...};
+};
+static service_factory_single_t<myservice_impl> g_myservice_impl_factory;
+
+ +

Service extensions

+
+ +

+ Additional methods can be added to any service type, by declaring a new service type class deriving from service type class you want to extend. For example: +

+
class myservice : public service_base { public: virtual void dosomething() = 0; FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(myservice); };
+class myservice_v2 : public myservice { public: virtual void dosomethingelse() = 0; FB2K_MAKE_SERVICE_INTERFACE(myservice_v2, myservice); };
+

+ In such scenario, to query whether a myservice instance is a myservice_v2 and to retrieve myservice_v2 pointer, use service_query functionality: +

+
service_ptr_t<myservice> ptr;
+(...)
+service_ptr_t<myservice_v2> ptr_ex;
+if (ptr->service_query_t(ptr_ex)) { /* ptr_ex is a valid pointer to myservice_v2 interface of our myservice instance */ (...) }
+else {/* this myservice implementation does not implement myservice_v2 */ (...) }
+
+ +

Autopointer template use

+
+ +

+ When performing most kinds of service operations, service_ptr_t<T> template should be used rather than working with service pointers directly; it automatically manages reference counter calls, ensuring that the service object is deleted when it is no longer referenced. +

+ +

+For convenience, all service classes have myclass::ptr typedef'd to service_ptr_t<myclass>. +

+ +

+When working with pointers to core fb2k services, just use C++11 auto keyword and someclass::get(), e.g. auto myAPI = playlist_manager::get(); +

+ +
+ +

Exception use

+
+ +

+ Most of API functions use C++ exceptions to signal failure conditions. All used exception classes must derive from std::exception (which pfc::exception is typedef'd to); this design allows various instances of code to use single catch() line to get human-readable description of the problem to display. +

+ +

+Additionally, special subclasses of exceptions are defined for use in specific conditions, such as exception_io for I/O failures. As a result, you must provide an exception handler whenever you invoke any kind of I/O code that may fail, unless in specific case calling context already handles exceptions (e.g. input implementation code - any exceptions should be forwarded to calling context, since exceptions are a part of input API). +

+ +

+Implementations of global callback services such as playlist_callback, playback_callback or library_callback must not throw unhandled exceptions; behaviors in case they do are undefined (app termination is to be expected). +

+ +
+ +

Storing configuration

+
+ +

+ In order to create your entries in the configuration file, you must instantiate some objects that derive from cfg_var class. Those can be either predefined classes (cfg_int, cfg_string, etc) or your own classes implementing relevant methods. +

+ +

+Each cfg_var instance has a GUID assigned, to identify its configuration file entry. The GUID is passed to its constructor (which implementations must take care of, typically by providing a constructor that takes a GUID and forwards it to cfg_var constructor). +

+ +

+Note that cfg_var objects can only be instantiated statically (either directly as static objects, or as members of other static objects). Additionally, you can create configuration data objects that can be accessed by other components, by implementing config_object service. Some standard configuration variables can be also accessed using config_object interface. +

+ +
+ +

Use of global callback services

+
+ +

+ Multiple service classes presented by the SDK allow your component to receive notifications about various events: +

+
    +
  • file_operation_callback - tracking file move/copy/delete operations.
    +
  • +
  • library_callback - tracking Media Library content changes.
    +
  • +
  • metadb_io_callback - tracking tag read / write operations altering cached/displayed media information.
    +
  • +
  • play_callback - tracking playback related events.
    +
  • +
  • playback_statistics_collector - collecting information about played tracks.
    +
  • +
  • playlist_callback, playlist_callback_single - tracking playlist changes (the latter tracks only active playlist changes).
    +
  • +
  • playback_queue_callback - tracking playback queue changes.
    +
  • +
  • titleformat_config_callback - tracking changes of title formatting configuration.
    +
  • +
  • ui_drop_item_callback - filtering items dropped into the UI.
    +
  • +
+ +

+All of global callbacks operate only within main app thread, allowing easy cooperation with windows GUI - for an example, you perform playlist view window repainting directly from your playlist_callback implementation. +

+ +
+ +

Global callback recursion issues

+
+ +

+There are restrictions on things that are legal to call from within global callbacks. For an example, you can't modify a playlist from inside a playlist callback, because there are other registered callbacks tracking playlist changes that haven't been notified about the change being currently processed yet. +

+ +

+You must not enter modal message loops from inside global callbacks, as those allow any unrelated code (queued messages, user input, etc.) to be executed, without being aware that a global callback is being processed. Certain global API methods such as metadb_io::load_info_multi or threaded_process::run_modal enter modal loops when called. Use main_thread_callback service to avoid this problem and delay execution of problematic code. +

+ +

+You should also avoid firing a cross-thread SendMessage() inside global callbacks as well as performing any operations that dispatch global callbacks when handling a message that was sent through a cross-thread SendMessage(). Doing so may result in rare unwanted recursions - SendMessage() call will block the calling thread and immediately process any incoming cross-thread SendMessage() messages. If you're handling a cross-thread SendMessage() and need to perform such operation, delay it using PostMessage() or main_thread_callback. +

+ +
+ +

Service class design guidelines (advanced)

+
+ +

+ This chapter describes things you should keep on your mind when designing your own service type classes. Since 99% of components will only implement existing service types rather than adding their own cross-DLL-communication protocols, you can probably skip reading this chapter. +

+ +
+ +

Cross-DLL safety

+
+ +

+ It is important that all function parameters used by virtual methods of services are cross-DLL safe (do not depend on compiler-specific or runtime-specific behaviors, so no unexpected behaviors occur when calling code is built with different compiler/runtime than callee). To achieve this, any classes passed around must be either simple objects with no structure that could possibly vary with different compilers/runtimes (i.e. make sure that any memory blocks are freed on the side that allocated them); easiest way to achieve this is to reduce all complex data objects or classes passed around to interfaces with virtual methods, with implementation details hidden from callee. For an example, use pfc::string_base& as parameter to a function that is meant to return variable-length strings. +

+ +
+ +

Entrypoint service efficiency

+
+ +

+ When designing an entrypoint service interface meant to have multiple different implementations, you should consider making it possible for all its implementations to use service_factory_single_t (i.e. no per-instance member data); by e.g. moving functionality that needs multi-instance operation to a separate service type class that is created on-demand by one of entrypoint service methods. For example: +

+
class myservice : public service_base {
+public:
+	//this method accesses per-instance member data of the implementation class
+	virtual void workerfunction(const void * p_databuffer,t_size p_buffersize) = 0;
+	//this method is used to determine which implementation can be used to process specific data stream.
+	virtual bool queryfunction(const char * p_dataformat) = 0;
+ 
+	FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(myservice);
+};
+(...)
+service_ptr_t<myservice> findservice(const char * p_dataformat) {
+	service_enum_t<myservice> e; service_ptr_t<myservice> ptr;
+	//BOTTLENECK, this dynamically instantiates the service for each query.
+	while(e.next(ptr)) {
+		if (ptr->queryfunction(p_dataformat)) return ptr;
+	}
+	throw exception_io_data();
+}
+

+.. should be changed to: +

+
//no longer an entrypoint service - use myservice::instantiate to get an instance instead.
+class myservice_instance : public service_base {
+public:
+	virtual void workerfunction(const void * p_databuffer,t_size p_buffersize) = 0;
+	FB2K_MAKE_SERVICE_INTERFACE(myservice_instance,service_base);
+};
+ 
+class myservice : public service_base {
+public:
+	//this method is used to determine which implementation can be used to process specific data stream.
+	virtual bool queryfunction(const char * p_dataformat) = 0;
+	virtual service_ptr_t<myservice_instance> instantiate() = 0;
+	FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(myservice);
+};
+ 
+template<typename t_myservice_instance_impl>
+class myservice_impl_t : public myservice {
+public:
+	//implementation of myservice_instance must provide static bool g_queryformatfunction(const char*);
+	bool queryfunction(const char * p_dataformat) {return t_myservice_instance_impl::g_queryfunction(p_dataformat);}
+	service_ptr_t<myservice_instance> instantiate() {return new service_impl_t<t_myservice_instance_impl>();}
+};
+ 
+template<typename t_myservice_instance_impl> class myservice_factory_t :
+	public service_factory_single_t<myservice_impl_t<t_myservice_instance_impl> > {};
+//usage: static myservice_factory_t<myclass> g_myclass_factory;
+ 
+(...)
+ 
+service_ptr_t<myservice_instance> findservice(const char * p_dataformat) {
+	service_enum_t<myservice> e; service_ptr_t<myservice> ptr;
+	//no more bottleneck, enumerated service does not perform inefficient operations when requesting an instance.
+	while(e.next(ptr)) {
+		//"inefficient" part is used only once, with implementation that actually supports what we request.
+		if (ptr->queryfunction(p_dataformat)) return ptr->instantiate();
+	}
+	throw exception_io_data();
+}
+
+