Updated prototype to run queries in the background and update on the main

thread. Also, track filtering works properly.
This commit is contained in:
casey langen 2017-01-07 23:00:52 -08:00
parent 442697e9ce
commit 4474b96f28
5 changed files with 219 additions and 64 deletions

View File

@ -69,6 +69,8 @@ int APIENTRY _tWinMain(HINSTANCE instance, HINSTANCE previousInstance, LPTSTR co
mainWindow.MoveTo(200, 200);
LibraryPtr library = LibraryFactory::Libraries().at(0);
library->SetMessageQueue(mainWindow.Queue());
MasterTransport transport;
PlaybackService playback(mainWindow.Queue(), library, transport);

View File

@ -34,6 +34,7 @@
#include "MainController.h"
#include <core/library/LocalLibraryConstants.h>
#include <core/runtime/Message.h>
#include <glue/query/SearchTrackListQuery.h>
#include <glue/util/Playback.h>
#include <glue/util/Duration.h>
@ -41,18 +42,27 @@
using namespace musik::win;
using namespace musik::core;
using namespace musik::core::audio;
using namespace musik::core::runtime;
using namespace musik::glue;
using namespace musik::core::library;
using namespace win32cpp;
using ColumnRef = ListView::ColumnRef;
using CellRendererRef = ListView::CellRendererRef;
using TextCellRenderer = ListView::TextCellRenderer<uistring>;
using TextCellRendererRef = std::shared_ptr<TextCellRenderer>;
static ColumnRef sNumberColumn = ListView::Column::Create(_T("#"), 50);
static ColumnRef sTitleColumn = ListView::Column::Create(_T("title"));
static ColumnRef sDurationColumn = ListView::Column::Create(_T("duration"), 100, TextAlignment::TextAlignRight);
static ColumnRef sTitleColumn = ListView::Column::Create(_T("title"), 250);
static ColumnRef sDurationColumn = ListView::Column::Create(_T("time"), 50, TextAlignment::TextAlignRight);
static ColumnRef sAlbumColumn = ListView::Column::Create(_T("album"));
static ColumnRef sArtistColumn = ListView::Column::Create(_T("artist"));
class TrackListModel : public ListView::Model {
#define EDIT_VIEW_HEIGHT 22
#define UPDATE_SEARCH_MESSAGE 1001
#define UPDATE_SEARCH_DEBOUNCE_MS 750
class MainController::TrackListModel : public ListView::Model, public sigslot::has_slots<> {
public:
static std::shared_ptr<TrackListModel> Create(PlaybackService& playback) {
return std::shared_ptr<TrackListModel>(new TrackListModel(playback));
@ -60,30 +70,74 @@ class TrackListModel : public ListView::Model {
TrackListModel(PlaybackService& playback)
: playback(playback) {
this->SetRowCount(playback.Count());
playback.TrackChanged.connect(this, &TrackListModel::OnTrackChanged);
this->normal.reset(new TextCellRenderer());
this->bold.reset(new TextCellRenderer());
FontRef font = Font::Create();
font->SetBold(true);
this->bold->SetFont(font);
}
void SetTrackList(std::shared_ptr<TrackList> trackList) {
this->trackList = trackList;
this->SetRowCount(trackList ? trackList->Count() : 0);
this->InvalidateData();
}
virtual uistring CellValueToString(int rowIndex, ColumnRef column) {
TrackPtr track = playback.GetTrackAtIndex(rowIndex);
if (track) {
if (column == sNumberColumn) {
return u8to16(track->GetValue(constants::Track::TRACK_NUM));
}
else if (column == sTitleColumn) {
return u8to16(track->GetValue(constants::Track::TITLE));
}
else if (column == sDurationColumn) {
return u8to16(duration::Duration(track->GetValue(constants::Track::DURATION)));
}
else if (column == sArtistColumn) {
return u8to16(track->GetValue(constants::Track::ARTIST));
}
}
return _T("");
}
virtual CellRendererRef CellRenderer(int rowIndex, ColumnRef column) {
TrackPtr track = this->trackList->Get(rowIndex);
if (track) {
TextCellRendererRef renderer = this->normal;
if (playing && playing->Id() == track->Id()) {
renderer = this->bold;
}
uistring value;
if (column == sNumberColumn) {
value = u8to16(track->GetValue(constants::Track::TRACK_NUM));
}
else if (column == sTitleColumn) {
value = u8to16(track->GetValue(constants::Track::TITLE));
}
else if (column == sDurationColumn) {
value = u8to16(duration::Duration(track->GetValue(constants::Track::DURATION)));
}
else if (column == sAlbumColumn) {
value = u8to16(track->GetValue(constants::Track::ALBUM));
}
else if (column == sArtistColumn) {
value = u8to16(track->GetValue(constants::Track::ARTIST));
}
if (value.size()) {
renderer->Set(value, column->Alignment());
return renderer;
}
}
return TextCellRendererRef();
}
private:
void OnTrackChanged(size_t index, TrackPtr track) {
this->playing = track;
this->InvalidateData();
}
std::shared_ptr<TrackList> trackList;
PlaybackService& playback;
TextCellRendererRef normal;
TextCellRendererRef bold;
TrackPtr playing;
};
MainController::MainController(
@ -92,22 +146,26 @@ MainController::MainController(
LibraryPtr library)
: mainWindow(mainWindow)
, playback(playback)
, library(library) {
std::shared_ptr<SearchTrackListQuery> query =
std::shared_ptr<SearchTrackListQuery>(new SearchTrackListQuery(library, "o'o"));
, library(library)
, trackListDirty(true) {
library->QueryCompleted.connect(this, &MainController::OnLibraryQueryCompleted);
library->Enqueue(query, ILibrary::QuerySynchronous);
this->trackListQuery.reset(new SearchTrackListQuery(library, ""));
library->Enqueue(this->trackListQuery);
playback.Play(*query->GetResult(), 0);
this->editView = this->mainWindow.AddChild(new EditView());
this->editView->Changed.connect(this, &MainController::OnSearchEditChanged);
this->categoryList = this->mainWindow.AddChild(new ListView());
this->trackListView = this->mainWindow.AddChild(new ListView());
this->trackListView->RowActivated.connect(this, &MainController::OnTrackListRowActivated);
this->trackListView->AddColumn(sNumberColumn);
this->trackListView->AddColumn(sTitleColumn);
this->trackListView->AddColumn(sAlbumColumn);
this->trackListView->AddColumn(sArtistColumn);
this->trackListView->AddColumn(sDurationColumn);
this->trackList = this->mainWindow.AddChild(new ListView());
this->trackList->AddColumn(sNumberColumn);
this->trackList->AddColumn(sTitleColumn);
this->trackList->AddColumn(sDurationColumn);
this->trackList->AddColumn(sArtistColumn);
this->trackList->SetModel(TrackListModel::Create(this->playback));
this->trackListModel = TrackListModel::Create(this->playback);
this->trackListView->SetModel(this->trackListModel);
this->Layout();
@ -121,17 +179,57 @@ void MainController::Layout() {
auto size = this->mainWindow.ClientSize();
if (size.height > 0) {
static int padding = 8;
int categoryCx = size.width / 3;
int tracksCx = size.width - (categoryCx + padding);
int tracksX = categoryCx + padding;
this->categoryList->MoveTo(0, 0);
this->categoryList->Resize(categoryCx, size.height);
this->trackList->MoveTo(tracksX, 0);
this->trackList->Resize(tracksCx, size.height);
int editCx = (size.width * 2) / 3;
int editX = (size.width / 2) - (editCx / 2);
this->editView->MoveTo(editX, padding);
this->editView->Resize(editCx, EDIT_VIEW_HEIGHT);
int listY = padding + EDIT_VIEW_HEIGHT + padding;
int tracksCx = size.width;
int listCy = size.height - listY;
this->trackListView->MoveTo(0, listY);
this->trackListView->Resize(tracksCx, listCy);
}
}
void MainController::OnMainWindowResized(Window* window, Size size) {
this->Layout();
}
}
void MainController::OnTrackListRowActivated(ListView* list, int index) {
if (trackListDirty) {
this->trackListDirty = false;
if (this->trackList) {
this->playback.Play((*this->trackList), index);
}
}
else {
playback.Play(index);
}
}
void MainController::ProcessMessage(musik::core::runtime::IMessage &message) {
if (message.Type() == UPDATE_SEARCH_MESSAGE) {
std::string term = u16to8(this->editView->Caption());
this->trackListQuery.reset(new SearchTrackListQuery(library, term));
library->Enqueue(this->trackListQuery);
}
}
void MainController::OnLibraryQueryCompleted(IQueryPtr query) {
if (this->trackListQuery.get() == query.get()) {
this->trackListDirty = true;
this->trackList = ((TrackListQueryBase *)query.get())->GetResult();
this->trackListModel->SetTrackList(this->trackList);
}
}
void MainController::OnSearchEditChanged(win32cpp::EditView* editView) {
if (editView == this->editView) {
IMessageQueue& queue = mainWindow.Queue();
queue.Debounce(Message::Create(this, UPDATE_SEARCH_MESSAGE, 0, 0), UPDATE_SEARCH_DEBOUNCE_MS);
}
}

View File

@ -35,13 +35,21 @@
#pragma once
#include <app/view/MainWindow.h>
#include <core/runtime/IMessageQueue.h>
#include <core/audio/PlaybackService.h>
#include <core/library/ILibrary.h>
#include <glue/query/TrackListQueryBase.h>
#include <win32cpp/EditView.hpp>
#include <win32cpp/ListView.hpp>
namespace musik { namespace win {
class MainController : public sigslot::has_slots<> {
class MainController :
public sigslot::has_slots<>,
public musik::core::runtime::IMessageTarget
{
public:
MainController(
MainWindow& mainWindow,
@ -50,15 +58,30 @@ namespace musik { namespace win {
virtual ~MainController();
virtual void ProcessMessage(musik::core::runtime::IMessage &message);
private:
class TrackListModel;
void OnMainWindowResized(win32cpp::Window* window, win32cpp::Size size);
void OnTrackListRowActivated(win32cpp::ListView* list, int index);
void OnLibraryQueryCompleted(musik::core::IQueryPtr query);
void OnSearchEditChanged(win32cpp::EditView* editView);
void Layout();
MainWindow& mainWindow;
musik::core::audio::PlaybackService& playback;
musik::core::LibraryPtr library;
win32cpp::ListView* categoryList;
win32cpp::ListView* trackList;
MainWindow& mainWindow;
std::shared_ptr<musik::glue::TrackListQueryBase> trackListQuery;
std::shared_ptr<TrackListModel> trackListModel;
std::shared_ptr<musik::core::TrackList> trackList;
win32cpp::ListView* trackListView;
win32cpp::EditView* editView;
bool trackListDirty;
};
} }

View File

@ -8,31 +8,31 @@
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// * Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// * Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
//
// * Neither the name of the author nor the names of other contributors may
// be used to endorse or promote products derived from this software
// without specific prior written permission.
// * Neither the name of the author nor the names of other contributors may
// be used to endorse or promote products derived from this software
// without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
//
//////////////////////////////////////////////////////////////////////////////

View File

@ -44,6 +44,7 @@
#include <win32cpp/Window.hpp>
#include <win32cpp/Win32Exception.hpp>
#include <win32cpp/Color.hpp>
#include <win32cpp/Font.hpp>
#include <boost/format.hpp>
@ -363,6 +364,9 @@ public: // constructors
///\param value The value to render
///\param alignment The alignment to use
/*ctor*/ TextCellRenderer(const T& value, TextAlignment alignment = TextAlignLeft);
/*ctor*/ TextCellRenderer(TextAlignment aligntment = TextAlignLeft);
void SetFont(FontRef font);
void Set(const T& value, TextAlignment aligntment = TextAlignLeft);
public: // methods
void Render(const ListView& listView, RenderParams& renderParams);
@ -370,14 +374,33 @@ public: // methods
protected: // instance data
uistring textValue;
TextAlignment alignment;
FontRef font;
};
template <typename T>
/*ctor*/ ListView::TextCellRenderer<T>::TextCellRenderer(const T& value, TextAlignment alignment)
: alignment(alignment)
{
this->Set(value, alignment);
}
template <typename T>
/*ctor*/ ListView::TextCellRenderer<T>::TextCellRenderer(TextAlignment alignment)
{
this->alignment = alignment;
}
template <typename T>
void ListView::TextCellRenderer<T>::Set(const T& value, TextAlignment alignment)
{
typedef boost::basic_format<uichar> format;
this->textValue = (format(_T("%1%")) % value).str();
this->alignment = alignment;
}
template <typename T>
void ListView::TextCellRenderer<T>::SetFont(FontRef font)
{
this->font = font;
}
template <typename T>
@ -390,13 +413,18 @@ void ListView::TextCellRenderer<T>::Render(const ListView& listView, Rend
::SecureZeroMemory(&drawTextParams, sizeof(DRAWTEXTPARAMS));
drawTextParams.cbSize = sizeof(DRAWTEXTPARAMS);
drawTextParams.iLeftMargin = 6;
//
int bufferSize = (int) this->textValue.size() + 4; // DrawTextEx may add up to 4 additional characters
std::unique_ptr<uichar> buffer(new uichar[bufferSize]);
::wcsncpy_s(buffer.get(), bufferSize, this->textValue.c_str(), this->textValue.size());
//
RECT drawRect = renderParams.rect;
//
HGDIOBJ old = nullptr;
if (font) {
old = ::SelectObject(renderParams.hdc, font->GetHFONT());
}
::DrawTextEx(
renderParams.hdc,
buffer.get(),
@ -404,6 +432,10 @@ void ListView::TextCellRenderer<T>::Render(const ListView& listView, Rend
&drawRect,
this->alignment | DT_END_ELLIPSIS | DT_VCENTER | DT_SINGLELINE,
&drawTextParams);
if (font) {
::SelectObject(renderParams.hdc, old);
}
}
//////////////////////////////////////////////////////////////////////////////