latest SDK

This commit is contained in:
2021-12-14 00:28:25 -07:00
commit 68b10d413b
492 changed files with 80542 additions and 0 deletions

View File

@@ -0,0 +1,329 @@
#include "stdafx.h"
#include <helpers/file_list_helper.h>
#include <helpers/filetimetools.h>
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<tpc_copyFiles>(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.
}

View File

@@ -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.

View File

@@ -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<myFilter>();
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<myitem> 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");
}

View File

@@ -0,0 +1,77 @@
#include "stdafx.h"
#include <helpers/input_helpers.h>
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<threaded_process_callback> cb = new service_impl_t<calculate_peak_process>(data);
static_api_ptr_t<threaded_process>()->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);
}
}

View File

@@ -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<dsp_sample> g_dsp_sample_factory;
class CMyDSPPopup : public CDialogImpl<CMyDSPPopup> {
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<t_int32>( 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);
}
}

View File

@@ -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

View File

@@ -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

View File

@@ -0,0 +1,159 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|Win32">
<Configuration>Debug</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|Win32">
<Configuration>Release</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<ProjectGuid>{85FBFD09-0099-4FE9-9DB6-78DB6F60F817}</ProjectGuid>
<RootNamespace>foo_input_raw</RootNamespace>
<Keyword>Win32Proj</Keyword>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<CharacterSet>Unicode</CharacterSet>
<WholeProgramOptimization>true</WholeProgramOptimization>
<PlatformToolset>v141</PlatformToolset>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<CharacterSet>Unicode</CharacterSet>
<PlatformToolset>v141</PlatformToolset>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup>
<_ProjectFileVersion>10.0.30319.1</_ProjectFileVersion>
<OutDir Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(SolutionDir)$(Configuration)\</OutDir>
<IntDir Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(Configuration)\</IntDir>
<LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</LinkIncremental>
<OutDir Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">$(SolutionDir)$(Configuration)\</OutDir>
<IntDir Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">$(Configuration)\</IntDir>
<LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">false</LinkIncremental>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<ClCompile>
<Optimization>Disabled</Optimization>
<BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
<RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
<PrecompiledHeader>Use</PrecompiledHeader>
<PrecompiledHeaderFile>stdafx.h</PrecompiledHeaderFile>
<WarningLevel>Level3</WarningLevel>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
<TreatSpecificWarningsAsErrors>4715</TreatSpecificWarningsAsErrors>
<AdditionalIncludeDirectories>..;../..</AdditionalIncludeDirectories>
<FloatingPointModel>Fast</FloatingPointModel>
</ClCompile>
<Link>
<GenerateDebugInformation>true</GenerateDebugInformation>
<SubSystem>Windows</SubSystem>
<RandomizedBaseAddress>false</RandomizedBaseAddress>
<DataExecutionPrevention>
</DataExecutionPrevention>
<TargetMachine>MachineX86</TargetMachine>
<AdditionalDependencies>../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)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<ClCompile>
<RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
<PrecompiledHeader>Use</PrecompiledHeader>
<PrecompiledHeaderFile>stdafx.h</PrecompiledHeaderFile>
<WarningLevel>Level3</WarningLevel>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
<AdditionalOptions>/d2notypeopt %(AdditionalOptions)</AdditionalOptions>
<TreatSpecificWarningsAsErrors>4715</TreatSpecificWarningsAsErrors>
<BufferSecurityCheck>false</BufferSecurityCheck>
<StringPooling>true</StringPooling>
<OmitFramePointers>true</OmitFramePointers>
<AdditionalIncludeDirectories>..;../..</AdditionalIncludeDirectories>
<FloatingPointModel>Fast</FloatingPointModel>
<PreprocessorDefinitions>NDEBUG;_WINDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
<Link>
<GenerateDebugInformation>true</GenerateDebugInformation>
<SubSystem>Windows</SubSystem>
<OptimizeReferences>true</OptimizeReferences>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<RandomizedBaseAddress>false</RandomizedBaseAddress>
<DataExecutionPrevention>
</DataExecutionPrevention>
<TargetMachine>MachineX86</TargetMachine>
<AdditionalDependencies>../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)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="contextmenu.cpp" />
<ClCompile Include="decode.cpp" />
<ClCompile Include="dsp.cpp" />
<ClCompile Include="initquit.cpp" />
<ClCompile Include="input_raw.cpp" />
<ClCompile Include="listcontrol-advanced.cpp" />
<ClCompile Include="listcontrol-ownerdata.cpp" />
<ClCompile Include="listcontrol-simple.cpp" />
<ClCompile Include="main.cpp" />
<ClCompile Include="mainmenu-dynamic.cpp" />
<ClCompile Include="mainmenu.cpp" />
<ClCompile Include="IO.cpp" />
<ClCompile Include="PCH.cpp">
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">Create</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">Create</PrecompiledHeader>
</ClCompile>
<ClCompile Include="playback_state.cpp" />
<ClCompile Include="playback_stream_capture.cpp" />
<ClCompile Include="preferences.cpp" />
<ClCompile Include="rating.cpp" />
<ClCompile Include="ui_and_threads.cpp" />
<ClCompile Include="ui_element.cpp" />
<ClCompile Include="ui_element_dialog.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="playback_stream_capture.h" />
<ClInclude Include="resource.h" />
<ClInclude Include="stdafx.h" />
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="foo_sample.rc" />
</ItemGroup>
<ItemGroup>
<None Include="readme.txt" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\libPPUI\libPPUI.vcxproj">
<Project>{7729eb82-4069-4414-964b-ad399091a03f}</Project>
</ProjectReference>
<ProjectReference Include="..\..\pfc\pfc.vcxproj">
<Project>{ebfffb4e-261d-44d3-b89c-957b31a0bf9c}</Project>
</ProjectReference>
<ProjectReference Include="..\foobar2000_component_client\foobar2000_component_client.vcxproj">
<Project>{71ad2674-065b-48f5-b8b0-e1f9d3892081}</Project>
</ProjectReference>
<ProjectReference Include="..\helpers\foobar2000_sdk_helpers.vcxproj">
<Project>{ee47764e-a202-4f85-a767-abdab4aff35f}</Project>
</ProjectReference>
<ProjectReference Include="..\SDK\foobar2000_SDK.vcxproj">
<Project>{e8091321-d79d-4575-86ef-064ea1a4a20d}</Project>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<Image Include="..\..\libPPUI\IDI_SCROLL.ico" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>

View File

@@ -0,0 +1,103 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Filter Include="Source Files">
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
<Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
</Filter>
<Filter Include="Header Files">
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
<Extensions>h;hpp;hxx;hm;inl;inc;xsd</Extensions>
</Filter>
<Filter Include="Resource Files">
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav</Extensions>
</Filter>
</ItemGroup>
<ItemGroup>
<ClCompile Include="contextmenu.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="decode.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="dsp.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="initquit.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="input_raw.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="main.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="mainmenu.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="PCH.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="playback_state.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="preferences.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="ui_element.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="ui_element_dialog.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="rating.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="IO.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="ui_and_threads.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="listcontrol-advanced.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="listcontrol-simple.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="listcontrol-ownerdata.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="mainmenu-dynamic.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="playback_stream_capture.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="resource.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="stdafx.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="playback_stream_capture.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="foo_sample.rc">
<Filter>Resource Files</Filter>
</ResourceCompile>
</ItemGroup>
<ItemGroup>
<None Include="readme.txt" />
</ItemGroup>
<ItemGroup>
<Image Include="..\..\libPPUI\IDI_SCROLL.ico">
<Filter>Resource Files</Filter>
</Image>
</ItemGroup>
</Project>

View File

@@ -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<myinitquit> g_myinitquit_factory;

View File

@@ -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<file> 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<file> m_file;
pfc::array_t<t_uint8> m_buffer;
};
static input_singletrack_factory_t<input_raw> 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");

View File

@@ -0,0 +1,323 @@
// Advanced CListControl use demo
// Subclasses a CListControl to use all its features
#include "stdafx.h"
#include "resource.h"
#include <helpers/atl-misc.h>
#include <libPPUI/CListControlComplete.h>
#include <libPPUI/CListControl-Cells.h>
#include <string>
#include <algorithm>
#include <vector>
namespace {
struct listData_t {
std::string m_key, m_value;
bool m_checkState = false;
};
static std::vector<listData_t> makeListData() {
std::vector<listData_t> 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<CListControlAdvancedDemoDialog> {
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<CListControlAdvancedDemoDialog>();
}

View File

@@ -0,0 +1,204 @@
#include "stdafx.h"
// Owner-data CListControl use demo
// CListControlOwnerData with callbacks
#include "stdafx.h"
#include "resource.h"
#include <helpers/atl-misc.h>
#include <libPPUI/CListControlOwnerData.h>
#include <string>
#include <algorithm>
#include <vector>
namespace {
struct listData_t {
std::string m_key, m_value;
};
static std::vector<listData_t> makeListData() {
std::vector<listData_t> 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<CListControlOwnerDataDemoDialog>, 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<CListControlOwnerDataDemoDialog>();
}

View File

@@ -0,0 +1,163 @@
#include "stdafx.h"
// Simple CListControl use demo
// CListControlSimple
#include "stdafx.h"
#include "resource.h"
#include <helpers/atl-misc.h>
#include <libPPUI/CListControlSimple.h>
#include <string>
#include <algorithm>
#include <vector>
namespace {
struct listData_t {
std::string m_key, m_value;
};
static std::vector<listData_t> makeListData() {
std::vector<listData_t> 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<CListControlSimpleDemoDialog> {
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<CListControlSimpleDemoDialog>();
}

View File

@@ -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");

View File

@@ -0,0 +1,120 @@
#include "stdafx.h"
#include <vector>
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<service_base> 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<mainmenu_node_separator>();
} else {
auto cmdIndex = walk/2 + 1;
node = fb2k::service_new<sample_command>( 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<mainmenu_node::ptr> 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<service_base> 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<sample_group>();
}
bool dynamic_execute(t_uint32 index, const GUID & subID, service_ptr_t<service_base> 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<mainmenu_sample_dynamic> g_mainmenu_sample_dynamic;
} // namespace

View File

@@ -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<service_base> 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<mainmenu_commands_sample> g_mainmenu_commands_sample_factory;

View File

@@ -0,0 +1,155 @@
#include "stdafx.h"
#include "resource.h"
#include <helpers/WindowPositionUtils.h>
#include <helpers/atl-misc.h>
class CPlaybackStateDemo : public CDialogImpl<CPlaybackStateDemo>, 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<playback_control> 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<titleformat_compiler>()->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<contextmenu_manager> 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<ImplementModelessTracking<CPlaybackStateDemo> >(core_api::get_main_window());
} catch(std::exception const & e) {
popup_message::g_complain("Dialog creation failure", e);
}
}

View File

@@ -0,0 +1,121 @@
#include "stdafx.h"
#include <helpers/writer_wav.h>
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;
}

View File

@@ -0,0 +1,4 @@
#pragma once
void ToggleCapture();
bool IsCaptureRunning();

View File

@@ -0,0 +1,111 @@
#include "stdafx.h"
#include "resource.h"
#include <helpers/atl-misc.h>
// 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<CMyPreferences>, 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<CMyPreferences> {
// 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<preferences_page_myimpl> g_preferences_page_myimpl_factory;

View File

@@ -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<titleformat_compiler>()->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<hasher_md5>()->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<metadb_index_client_impl>(strTrackRatingPinTo);
static metadb_index_client_impl * g_clientAlbum = new service_impl_single_t<metadb_index_client_impl>(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<init_stage_callback_impl> g_init_stage_callback_impl;
static service_factory_single_t<initquit_impl> 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<metadb_display_field_provider_impl> 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<metadb_index_hash> 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<metadb_index_hash> 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<metadb_index_hash> 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<metadb_index_hash> 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<metadb_index_hash> 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<service_base> 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<mainmenu_rating> 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<metadb_index_hash> 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 = "<various>";
}
}
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<track_property_provider_impl> g_track_property_provider_impl;
}

View File

@@ -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.

View File

@@ -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

View File

@@ -0,0 +1,2 @@
#include <helpers/foobar2000+atl.h>

View File

@@ -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 <memory> // shared_ptr
#include <libPPUI/CDialogResizeHelper.h>
#include <helpers/filetimetools.h>
#include <helpers/duration_counter.h>
#include <helpers/atl-misc.h>
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<CDemoDialog> {
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<abort_callback_impl>();
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<abort_callback_impl> aborter, std::function<void ()> 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<sharedData_t> shared, std::shared_ptr<abort_callback_impl> 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<sharedData_t> shared, std::shared_ptr<abort_callback_impl> 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<abort_callback_impl> 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<CDemoDialog>( data );
}

View File

@@ -0,0 +1,82 @@
#include "stdafx.h"
#include <libPPUI/win32_op.h>
#include <helpers/BumpableElem.h>
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<CMyElemWindow> {
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<CMyElemWindow> {};
static service_factory_single_t<ui_element_myimpl> g_ui_element_myimpl_factory;
} // namespace

View File

@@ -0,0 +1,178 @@
#include "stdafx.h"
#include "resource.h"
#include <libPPUI/win32_utility.h>
#include <libPPUI/win32_op.h> // WIN32_OP()
#include <libPPUI/wtl-pp.h> // CCheckBox
#include <helpers/atl-misc.h> // 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<CDialogUIElem>, 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;
}