mirror of
https://github.com/clangen/musikcube.git
synced 2025-03-14 04:18:36 +00:00
- Created IWindow interface
- Renamed BorderedWindow -> Window - Fixed more layout bugs in SimpleScrollAdapter -- I think it's stable now for reals.
This commit is contained in:
parent
b5a9a22020
commit
d96b44f236
@ -56,13 +56,25 @@ static const std::string TAG = "Indexer";
|
||||
|
||||
using namespace musik::core;
|
||||
|
||||
static std::string normalizeDir(std::string path) {
|
||||
path = boost::filesystem::path(path).make_preferred().string();
|
||||
|
||||
std::string sep(1, boost::filesystem::path::preferred_separator);
|
||||
if (path.substr(path.size() - 1, 1) != sep) {
|
||||
path += sep;
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
static std::string normalizePath(const std::string& path) {
|
||||
return boost::filesystem::path(path).make_preferred().string();
|
||||
}
|
||||
|
||||
Indexer::Indexer()
|
||||
: thread(NULL)
|
||||
, overallProgress(0)
|
||||
, currentProgress(0)
|
||||
, status(0)
|
||||
, restart(false)
|
||||
, nofFiles(0)
|
||||
, filesIndexed(0)
|
||||
, filesSaved(0) {
|
||||
}
|
||||
@ -82,27 +94,6 @@ Indexer::~Indexer(){
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////////////
|
||||
///\brief
|
||||
///Get the current status (text)
|
||||
//////////////////////////////////////////
|
||||
std::string Indexer::GetStatus() {
|
||||
boost::mutex::scoped_lock lock(this->progressMutex);
|
||||
|
||||
switch (status) {
|
||||
case 1: return boost::str(boost::format("counting... %1%") % this->nofFiles);
|
||||
case 2: return boost::str(boost::format("indexing... %.2f") % (this->overallProgress * 100)) + "%";
|
||||
case 3: return boost::str(boost::format("removing... %.2f") % (this->overallProgress * 100)) + "%";
|
||||
case 4: return "cleaning...";
|
||||
case 5: return "optimizing...";
|
||||
case 6: return boost::str(boost::format("running analyzers...: %.2f%% (current %.1f%%)")
|
||||
% (100.0 * this->overallProgress / (double) this->nofFiles)
|
||||
% (this->currentProgress * 100.0));
|
||||
}
|
||||
|
||||
return "doing something... (unknown)";
|
||||
}
|
||||
|
||||
//////////////////////////////////////////
|
||||
///\brief
|
||||
///Restart the sync
|
||||
@ -140,17 +131,10 @@ bool Indexer::Restarted() {
|
||||
///\remarks
|
||||
///If the path already exists it will not be added.
|
||||
//////////////////////////////////////////
|
||||
void Indexer::AddPath(std::string path) {
|
||||
boost::filesystem::path fsPath(path);
|
||||
path = fsPath.string(); /* canonicalize */
|
||||
|
||||
if (path.substr(path.size() -1, 1) != "/") {
|
||||
path += "/";
|
||||
}
|
||||
|
||||
void Indexer::AddPath(const std::string& path) {
|
||||
Indexer::AddRemoveContext context;
|
||||
context.add = true;
|
||||
context.path = path;
|
||||
context.path = normalizeDir(path);
|
||||
|
||||
{
|
||||
boost::mutex::scoped_lock lock(this->exitMutex);
|
||||
@ -167,11 +151,10 @@ void Indexer::AddPath(std::string path) {
|
||||
///\param sPath
|
||||
///Path to remove
|
||||
//////////////////////////////////////////
|
||||
void Indexer::RemovePath(std::string path) {
|
||||
|
||||
void Indexer::RemovePath(const std::string& path) {
|
||||
Indexer::AddRemoveContext context;
|
||||
context.add = false;
|
||||
context.path = path;
|
||||
context.path = normalizeDir(path);
|
||||
|
||||
{
|
||||
boost::mutex::scoped_lock lock(this->exitMutex);
|
||||
@ -195,7 +178,6 @@ void Indexer::Synchronize() {
|
||||
|
||||
{
|
||||
boost::mutex::scoped_lock lock(this->progressMutex);
|
||||
this->nofFiles = 0;
|
||||
this->filesIndexed = 0;
|
||||
}
|
||||
|
||||
@ -209,7 +191,7 @@ void Indexer::Synchronize() {
|
||||
{
|
||||
db::Statement stmt("SELECT id, path FROM paths", this->dbConnection);
|
||||
|
||||
while (stmt.Step()==db::Row) {
|
||||
while (stmt.Step() == db::Row) {
|
||||
try {
|
||||
DBID id = stmt.ColumnInt(0);
|
||||
std::string path = stmt.ColumnText(1);
|
||||
@ -225,31 +207,17 @@ void Indexer::Synchronize() {
|
||||
}
|
||||
}
|
||||
|
||||
/* count the files that need to be processed */
|
||||
|
||||
// {
|
||||
// boost::mutex::scoped_lock lock(this->progressMutex);
|
||||
// this->status = 1;
|
||||
// this->overallProgress = 0.0;
|
||||
// }
|
||||
|
||||
//for(std::size_t i = 0; i < paths.size(); ++i){
|
||||
// std::string path = paths[i];
|
||||
// this->CountFiles(path);
|
||||
// }
|
||||
|
||||
/* add new files */
|
||||
|
||||
{
|
||||
boost::mutex::scoped_lock lock(this->progressMutex);
|
||||
this->status = 2;
|
||||
this->overallProgress = 0.0;
|
||||
this->filesSaved = 0;
|
||||
}
|
||||
|
||||
for(std::size_t i = 0; i < paths.size(); ++i) {
|
||||
std::string path = paths[i];
|
||||
this->SyncDirectory(path ,0, pathIds[i], path);
|
||||
this->SyncDirectory(path, path ,0, pathIds[i]);
|
||||
}
|
||||
|
||||
/* remove undesired entries from db (files themselves will remain) */
|
||||
@ -259,7 +227,6 @@ void Indexer::Synchronize() {
|
||||
{
|
||||
boost::mutex::scoped_lock lock(this->progressMutex);
|
||||
this->status = 3;
|
||||
this->overallProgress = 0.0;
|
||||
}
|
||||
|
||||
if (!this->Restarted() && !this->Exited()) {
|
||||
@ -272,7 +239,6 @@ void Indexer::Synchronize() {
|
||||
|
||||
{
|
||||
boost::mutex::scoped_lock lock(this->progressMutex);
|
||||
this->overallProgress = 0.0;
|
||||
this->status = 4;
|
||||
}
|
||||
|
||||
@ -287,8 +253,7 @@ void Indexer::Synchronize() {
|
||||
|
||||
{
|
||||
boost::mutex::scoped_lock lock(this->progressMutex);
|
||||
this->overallProgress = 0.0;
|
||||
this->status = 5;
|
||||
this->status = 5;
|
||||
}
|
||||
|
||||
if(!this->Restarted() && !this->Exited()){
|
||||
@ -307,37 +272,6 @@ void Indexer::Synchronize() {
|
||||
musik::debug::info(TAG, "done!");
|
||||
}
|
||||
|
||||
//////////////////////////////////////////
|
||||
///\brief
|
||||
///Counts the number of files to synchronize
|
||||
///
|
||||
///\param dir
|
||||
///Folder to count files in.
|
||||
//////////////////////////////////////////
|
||||
void Indexer::CountFiles(const std::string &dir) {
|
||||
if (!this->Exited() && !this->Restarted()) {
|
||||
boost::filesystem::path path(dir);
|
||||
|
||||
try{
|
||||
boost::filesystem::directory_iterator invalidFile;
|
||||
boost::filesystem::directory_iterator file(path);
|
||||
|
||||
for ( ; file != invalidFile; ++file) {
|
||||
if (is_directory(file->status())) {
|
||||
this->CountFiles(file->path().string());
|
||||
}
|
||||
else {
|
||||
boost::mutex::scoped_lock lock(this->progressMutex);
|
||||
this->nofFiles++;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(...) {
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//////////////////////////////////////////
|
||||
///\brief
|
||||
///Reads all tracks in a folder
|
||||
@ -355,19 +289,19 @@ void Indexer::CountFiles(const std::string &dir) {
|
||||
//////////////////////////////////////////
|
||||
|
||||
void Indexer::SyncDirectory(
|
||||
const std::string &inDir,
|
||||
const std::string &syncRoot,
|
||||
const std::string ¤tPath,
|
||||
DBID parentDirId,
|
||||
DBID pathId,
|
||||
std::string &syncPath)
|
||||
DBID pathId)
|
||||
{
|
||||
if(this->Exited() || this->Restarted()) {
|
||||
if (this->Exited() || this->Restarted()) {
|
||||
return;
|
||||
}
|
||||
|
||||
boost::filesystem::path path(inDir);
|
||||
std::string dir = path.string(); /* canonicalize slash*/
|
||||
std::string normalizedSyncRoot = normalizeDir(syncRoot);
|
||||
std::string normalizedCurrentPath = normalizeDir(currentPath);
|
||||
std::string leaf = boost::filesystem::path(currentPath).leaf().string(); /* trailing subdir in currentPath */
|
||||
|
||||
std::string leaf(path.leaf().string());
|
||||
DBID dirId = 0;
|
||||
|
||||
/* get relative folder id */
|
||||
@ -378,17 +312,15 @@ void Indexer::SyncDirectory(
|
||||
stmt.BindInt(1, pathId);
|
||||
stmt.BindInt(2, parentDirId);
|
||||
|
||||
if(stmt.Step() == db::Row) {
|
||||
if (stmt.Step() == db::Row) {
|
||||
dirId = stmt.ColumnInt(0);
|
||||
}
|
||||
}
|
||||
|
||||
boost::thread::yield();
|
||||
|
||||
/* no ID yet? needs to be inserted... */
|
||||
|
||||
if (dirId == 0) {
|
||||
std::string relativePath(dir.substr(syncPath.size()));
|
||||
std::string relativePath(normalizedCurrentPath.substr(normalizedSyncRoot.size()));
|
||||
|
||||
db::CachedStatement stmt("INSERT INTO folders (name, path_id, parent_id, relative_path) VALUES(?,?,?,?)", this->dbConnection);
|
||||
stmt.BindText(0, leaf);
|
||||
@ -403,29 +335,25 @@ void Indexer::SyncDirectory(
|
||||
dirId = this->dbConnection.LastInsertedId();
|
||||
}
|
||||
|
||||
boost::thread::yield(); /* whistle... */
|
||||
|
||||
/* start recursive filesystem scan */
|
||||
|
||||
try { /* boost::filesystem may throw */
|
||||
|
||||
/* for each file in the current path... */
|
||||
|
||||
boost::filesystem::path path(currentPath);
|
||||
boost::filesystem::directory_iterator end;
|
||||
boost::filesystem::directory_iterator file(path);
|
||||
for( ; file != end && !this->Exited() && !this->Restarted();++file) {
|
||||
|
||||
for( ; file != end && !this->Exited() && !this->Restarted(); file++) {
|
||||
if (is_directory(file->status())) {
|
||||
/* recursion here */
|
||||
musik::debug::info(TAG, "scanning " + file->path().string());
|
||||
this->SyncDirectory(file->path().string(), dirId, pathId, syncPath);
|
||||
this->SyncDirectory(syncRoot, file->path().string(), dirId, pathId);
|
||||
}
|
||||
else {
|
||||
++this->filesIndexed;
|
||||
|
||||
/* update overallProgress every so often... */
|
||||
|
||||
if (this->filesIndexed % 25 == 0) {
|
||||
boost::mutex::scoped_lock lock(this->progressMutex);
|
||||
this->overallProgress = (double) this->filesIndexed / (double)this->nofFiles;
|
||||
}
|
||||
|
||||
musik::core::IndexerTrack track(0);
|
||||
|
||||
/* get cached filesize, parts, size, etc */
|
||||
@ -456,8 +384,6 @@ void Indexer::SyncDirectory(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
boost::thread::yield();
|
||||
}
|
||||
}
|
||||
catch(...) {
|
||||
@ -646,13 +572,6 @@ void Indexer::SyncDelete(const std::vector<DBID>& paths) {
|
||||
stmtSyncPath.Reset();
|
||||
|
||||
while(stmt.Step() == db::Row && !this->Exited() && !this->Restarted()) {
|
||||
{
|
||||
boost::mutex::scoped_lock lock(this->progressMutex);
|
||||
if (songs > 0) {
|
||||
this->overallProgress = 0.2+0.8*(double)count/(double)(songs);
|
||||
}
|
||||
}
|
||||
|
||||
bool remove = true;
|
||||
std::string file = stmt.ColumnText(1);
|
||||
|
||||
@ -732,20 +651,14 @@ void Indexer::SyncCleanup() {
|
||||
///\brief
|
||||
///Get a vector with all sync paths
|
||||
//////////////////////////////////////////
|
||||
std::vector<std::string> Indexer::GetPaths() {
|
||||
std::vector<std::string> paths;
|
||||
|
||||
db::Connection tempDB;
|
||||
|
||||
tempDB.Open(this->database.c_str());
|
||||
|
||||
db::Statement stmt("SELECT path FROM paths ORDER BY id", tempDB);
|
||||
void Indexer::GetPaths(std::vector<std::string>& paths) {
|
||||
db::Connection connection;
|
||||
connection.Open(this->database.c_str());
|
||||
db::Statement stmt("SELECT path FROM paths ORDER BY id", connection);
|
||||
|
||||
while (stmt.Step() == db::Row) {
|
||||
paths.push_back(stmt.ColumnText(0));
|
||||
}
|
||||
|
||||
return paths;
|
||||
}
|
||||
|
||||
static int optimize(
|
||||
@ -888,19 +801,9 @@ void Indexer::RunAnalyzers() {
|
||||
|
||||
{
|
||||
boost::mutex::scoped_lock lock(this->progressMutex);
|
||||
this->overallProgress = 0;
|
||||
this->currentProgress = 0;
|
||||
this->nofFiles = 0;
|
||||
this->status = 6;
|
||||
}
|
||||
|
||||
/* figure out how many files we need to process */
|
||||
|
||||
db::Statement totalTracks("SELECT count(*) FROM tracks", this->dbConnection);
|
||||
if (totalTracks.Step() == db::Row){
|
||||
this->nofFiles = totalTracks.ColumnInt(0);
|
||||
}
|
||||
|
||||
/* for each track... */
|
||||
|
||||
DBID trackId = 0;
|
||||
@ -942,11 +845,6 @@ void Indexer::RunAnalyzers() {
|
||||
audio::BufferPtr buffer;
|
||||
|
||||
while ((buffer = stream->GetNextProcessedOutputBuffer()) && !runningAnalyzers.empty()) {
|
||||
{
|
||||
boost::mutex::scoped_lock lock(this->progressMutex);
|
||||
this->currentProgress = stream->DecoderProgress();
|
||||
}
|
||||
|
||||
PluginVector::iterator plugin = runningAnalyzers.begin();
|
||||
while(plugin != runningAnalyzers.end()) {
|
||||
if ((*plugin)->Analyze(&track, buffer.get())) {
|
||||
@ -976,11 +874,6 @@ void Indexer::RunAnalyzers() {
|
||||
if(successPlugins>0) {
|
||||
track.Save(this->dbConnection, this->libraryPath,folderId);
|
||||
}
|
||||
|
||||
{
|
||||
boost::mutex::scoped_lock lock(this->progressMutex);
|
||||
this->currentProgress = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -990,11 +883,6 @@ void Indexer::RunAnalyzers() {
|
||||
return;
|
||||
}
|
||||
|
||||
{
|
||||
boost::mutex::scoped_lock lock(this->progressMutex);
|
||||
this->overallProgress++;
|
||||
}
|
||||
|
||||
getNextTrack.BindInt(0, trackId);
|
||||
}
|
||||
|
||||
|
@ -62,14 +62,13 @@ namespace musik { namespace core {
|
||||
Indexer();
|
||||
~Indexer();
|
||||
|
||||
void AddPath(std::string sPath);
|
||||
void RemovePath(std::string sPath);
|
||||
std::vector<std::string> GetPaths();
|
||||
void AddPath(const std::string& paths);
|
||||
void RemovePath(const std::string& paths);
|
||||
void GetPaths(std::vector<std::string>& paths);
|
||||
|
||||
bool Startup(std::string setLibraryPath);
|
||||
void ThreadLoop();
|
||||
|
||||
std::string GetStatus();
|
||||
void RestartSync(bool bNewRestart=true);
|
||||
bool Restarted();
|
||||
|
||||
@ -91,16 +90,17 @@ namespace musik { namespace core {
|
||||
boost::thread *thread;
|
||||
boost::mutex progressMutex;
|
||||
|
||||
double overallProgress;
|
||||
double currentProgress;
|
||||
int nofFiles;
|
||||
int filesIndexed;
|
||||
int filesSaved;
|
||||
|
||||
void CountFiles(const std::string &dir);
|
||||
|
||||
void Synchronize();
|
||||
void SyncDirectory(const std::string& dir, DBID parentDirId, DBID pathId, std::string &syncPath);
|
||||
|
||||
void SyncDirectory(
|
||||
const std::string& syncRoot,
|
||||
const std::string& currentPath,
|
||||
DBID parentDirId,
|
||||
DBID pathId);
|
||||
|
||||
void SyncDelete(const std::vector<DBID>& paths);
|
||||
void SyncCleanup();
|
||||
void ProcessAddRemoveQueue();
|
||||
|
@ -1,130 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "BorderedWindow.h"
|
||||
|
||||
BorderedWindow::BorderedWindow() {
|
||||
this->border = this->contents = 0;
|
||||
this->height = 0;
|
||||
this->width = 0;
|
||||
this->x = 0;
|
||||
this->y = 0;
|
||||
this->contentColor = -1;
|
||||
this->borderColor = -1;
|
||||
this->scrollable = false;
|
||||
}
|
||||
|
||||
BorderedWindow::~BorderedWindow() {
|
||||
this->Destroy();
|
||||
}
|
||||
|
||||
void BorderedWindow::SetSize(int width, int height) {
|
||||
this->width = width;
|
||||
this->height = height;
|
||||
}
|
||||
|
||||
void BorderedWindow::SetPosition(int x, int y) {
|
||||
this->x = x;
|
||||
this->y = y;
|
||||
}
|
||||
|
||||
int BorderedWindow::GetWidth() const {
|
||||
return this->width;
|
||||
}
|
||||
|
||||
int BorderedWindow::GetHeight() const {
|
||||
return this->height;
|
||||
}
|
||||
|
||||
int BorderedWindow::GetContentHeight() const {
|
||||
return this->height - 2;
|
||||
}
|
||||
|
||||
int BorderedWindow::GetContentWidth() const {
|
||||
return this->width - 2;
|
||||
}
|
||||
|
||||
int BorderedWindow::GetX() const {
|
||||
return this->x;
|
||||
}
|
||||
|
||||
int BorderedWindow::GetY() const {
|
||||
return this->y;
|
||||
}
|
||||
|
||||
void BorderedWindow::SetContentColor(int color) {
|
||||
this->contentColor = color;
|
||||
|
||||
if (this->contentColor != -1 && this->contents) {
|
||||
wbkgd(this->contents, COLOR_PAIR(this->contentColor));
|
||||
this->Repaint();
|
||||
}
|
||||
}
|
||||
|
||||
void BorderedWindow::SetBorderColor(int color) {
|
||||
this->borderColor = color;
|
||||
|
||||
if (this->borderColor != -1 && this->border) {
|
||||
wbkgd(this->border, COLOR_PAIR(this->borderColor));
|
||||
this->Repaint();
|
||||
}
|
||||
}
|
||||
|
||||
void BorderedWindow::SetScrollable(bool scrollable) {
|
||||
this->scrollable = scrollable;
|
||||
}
|
||||
|
||||
bool BorderedWindow::GetScrollable() const {
|
||||
return this->scrollable;
|
||||
}
|
||||
|
||||
WINDOW* BorderedWindow::GetContents() const {
|
||||
return this->contents;
|
||||
}
|
||||
|
||||
void BorderedWindow::Create() {
|
||||
this->Destroy();
|
||||
|
||||
this->border = newwin(this->height, this->width, this->y, this->x);
|
||||
box(this->border, 0, 0);
|
||||
wrefresh(this->border);
|
||||
|
||||
this->contents = derwin(
|
||||
this->border,
|
||||
this->height - 2,
|
||||
this->width - 2,
|
||||
1,
|
||||
1);
|
||||
|
||||
scrollok(this->contents, this->scrollable);
|
||||
|
||||
if (this->contentColor != -1) {
|
||||
wbkgd(this->contents, COLOR_PAIR(this->contentColor));
|
||||
}
|
||||
|
||||
if (this->borderColor != -1) {
|
||||
wbkgd(this->border, COLOR_PAIR(this->borderColor));
|
||||
}
|
||||
|
||||
touchwin(this->contents);
|
||||
wrefresh(this->contents);
|
||||
}
|
||||
|
||||
void BorderedWindow::Clear() {
|
||||
wclear(this->contents);
|
||||
}
|
||||
|
||||
void BorderedWindow::Repaint() {
|
||||
if (this->border && this->contents) {
|
||||
wrefresh(this->border);
|
||||
wrefresh(this->contents);
|
||||
}
|
||||
}
|
||||
|
||||
void BorderedWindow::Destroy() {
|
||||
if (this->border) {
|
||||
delwin(this->contents);
|
||||
delwin(this->border);
|
||||
this->border = this->contents = NULL;
|
||||
}
|
||||
}
|
@ -1,37 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "curses_config.h"
|
||||
|
||||
class BorderedWindow {
|
||||
public:
|
||||
BorderedWindow();
|
||||
virtual ~BorderedWindow();
|
||||
|
||||
void Create();
|
||||
void Destroy();
|
||||
virtual void Repaint();
|
||||
|
||||
virtual void SetContentColor(int color);
|
||||
virtual void SetBorderColor(int color);
|
||||
|
||||
protected:
|
||||
WINDOW* GetContents() const;
|
||||
void SetSize(int width, int height);
|
||||
void SetPosition(int x, int y);
|
||||
|
||||
void SetScrollable(bool scrollable);
|
||||
void Clear();
|
||||
int GetWidth() const;
|
||||
int GetHeight() const;
|
||||
int GetContentHeight() const;
|
||||
int GetContentWidth() const;
|
||||
int GetX() const;
|
||||
int GetY() const;
|
||||
bool GetScrollable() const;
|
||||
|
||||
private:
|
||||
WINDOW* border;
|
||||
WINDOW* contents;
|
||||
int width, height, x, y, contentColor, borderColor;
|
||||
bool scrollable;
|
||||
};
|
@ -46,15 +46,19 @@ CommandWindow::~CommandWindow() {
|
||||
delete[] buffer;
|
||||
}
|
||||
|
||||
void CommandWindow::Focus() {
|
||||
wmove(this->GetContent(), 0, bufferPosition);
|
||||
}
|
||||
|
||||
void CommandWindow::WriteChar(int ch) {
|
||||
if (bufferPosition >= MAX_SIZE) {
|
||||
return;
|
||||
}
|
||||
|
||||
waddch(this->GetContents(), ch);
|
||||
waddch(this->GetContent(), ch);
|
||||
|
||||
if (ch == 8) { /* backspace */
|
||||
wdelch(this->GetContents());
|
||||
wdelch(this->GetContent());
|
||||
|
||||
if (bufferPosition > 0) {
|
||||
--bufferPosition;
|
||||
@ -72,7 +76,7 @@ void CommandWindow::WriteChar(int ch) {
|
||||
}
|
||||
}
|
||||
|
||||
wclear(this->GetContents());
|
||||
wclear(this->GetContent());
|
||||
this->bufferPosition = 0;
|
||||
}
|
||||
else {
|
||||
@ -144,8 +148,8 @@ bool CommandWindow::ProcessCommand(const std::string& cmd) {
|
||||
library->Indexer()->RemovePath(path);
|
||||
}
|
||||
else if (name == "lsdirs") {
|
||||
/* should not be returning a vector, should be pass by ref */
|
||||
std::vector<std::string> paths = library->Indexer()->GetPaths();
|
||||
std::vector<std::string> paths;
|
||||
library->Indexer()->GetPaths(paths);
|
||||
this->output->WriteLine("paths:");
|
||||
for (size_t i = 0; i < paths.size(); i++) {
|
||||
this->output->WriteLine(" " + paths.at(i));
|
||||
|
@ -1,7 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "curses_config.h"
|
||||
#include "BorderedWindow.h"
|
||||
#include "Window.h"
|
||||
#include "OutputWindow.h"
|
||||
#include "IInput.h"
|
||||
#include <core/playback/Transport.h>
|
||||
@ -10,12 +10,13 @@
|
||||
using musik::core::LibraryPtr;
|
||||
using namespace musik::core::audio;
|
||||
|
||||
class CommandWindow : public BorderedWindow, public IInput {
|
||||
class CommandWindow : public Window, public IInput {
|
||||
public:
|
||||
CommandWindow(Transport& transport, OutputWindow& output);
|
||||
~CommandWindow();
|
||||
|
||||
virtual void WriteChar(int ch);
|
||||
virtual void Focus();
|
||||
|
||||
private:
|
||||
void ListPlugins() const;
|
||||
|
@ -5,4 +5,5 @@
|
||||
class IInput {
|
||||
public:
|
||||
virtual void WriteChar(int ch) = 0;
|
||||
virtual void Focus() = 0;
|
||||
};
|
8
src/musikbox/ILayout.h
Executable file
8
src/musikbox/ILayout.h
Executable file
@ -0,0 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include "IWindow.h"
|
||||
|
||||
class ILayout {
|
||||
virtual IWindow* focusNext() = 0;
|
||||
virtual IWindow* focusPrev() = 0;
|
||||
};
|
26
src/musikbox/IWindow.h
Executable file
26
src/musikbox/IWindow.h
Executable file
@ -0,0 +1,26 @@
|
||||
#pragma once
|
||||
|
||||
#include "curses_config.h"
|
||||
|
||||
class IWindow {
|
||||
public:
|
||||
virtual void Repaint() = 0;
|
||||
|
||||
virtual void SetContentColor(int color) = 0;
|
||||
virtual void SetFrameColor(int color) = 0;
|
||||
|
||||
virtual void SetSize(int width, int height) = 0;
|
||||
virtual void SetPosition(int x, int y) = 0;
|
||||
|
||||
virtual int GetWidth() const = 0;
|
||||
virtual int GetHeight() const = 0;
|
||||
|
||||
virtual int GetContentHeight() const = 0;
|
||||
virtual int GetContentWidth() const = 0;
|
||||
|
||||
virtual int GetX() const = 0;
|
||||
virtual int GetY() const = 0;
|
||||
|
||||
virtual WINDOW* GetFrame() const = 0;
|
||||
virtual WINDOW* GetContent() const = 0;
|
||||
};
|
@ -35,7 +35,7 @@ void LogWindow::Update() {
|
||||
return;
|
||||
}
|
||||
|
||||
WINDOW* contents = this->GetContents();
|
||||
WINDOW* contents = this->GetContent();
|
||||
|
||||
for (size_t i = 0; i < pending.size(); i++) {
|
||||
int64 attrs = COLOR_PAIR(BOX_COLOR_WHITE_ON_BLUE);
|
||||
|
@ -79,7 +79,6 @@ int main(int argc, char* argv[])
|
||||
start_color();
|
||||
use_default_colors();
|
||||
refresh();
|
||||
curs_set(0);
|
||||
|
||||
#ifdef __PDCURSES__
|
||||
PDC_set_title("musikbox ♫");
|
||||
@ -96,28 +95,51 @@ int main(int argc, char* argv[])
|
||||
CommandWindow command(tp, output);
|
||||
TransportWindow transport(tp);
|
||||
|
||||
std::vector<BorderedWindow*> order;
|
||||
std::vector<IWindow*> order;
|
||||
order.push_back(&command);
|
||||
order.push_back(&logs);
|
||||
order.push_back(&output);
|
||||
|
||||
/* set the initial state: select the command window and get
|
||||
the focused state painting properly. it will be done automatically
|
||||
every time after this */
|
||||
|
||||
size_t index = 0;
|
||||
BorderedWindow *focused = order.at(index);
|
||||
focused->SetBorderColor(BOX_COLOR_RED_ON_BLACK);
|
||||
IWindow *focused = order.at(index);
|
||||
ScrollableWindow *scrollable = NULL;
|
||||
IInput *input = &command;
|
||||
|
||||
focused->SetFrameColor(BOX_COLOR_RED_ON_BLACK);
|
||||
curs_set(1);
|
||||
input->Focus();
|
||||
wtimeout(focused->GetContent(), 500);
|
||||
|
||||
bool disable = false;
|
||||
|
||||
int ch;
|
||||
timeout(500);
|
||||
while (ch = getch()) {
|
||||
ScrollableWindow *scrollable = dynamic_cast<ScrollableWindow*>(focused);
|
||||
IInput *input = dynamic_cast<IInput*>(focused);
|
||||
bool quit = false;
|
||||
while (!quit) {
|
||||
/* if the focused item is an IInput, then get characters from it,
|
||||
so it can draw a pretty cursor if it wants */
|
||||
ch = (input != NULL) ? wgetch(focused->GetContent()) : getch();
|
||||
|
||||
if (ch == -1) { /* timeout */
|
||||
logs.Update();
|
||||
if (!disable) {
|
||||
logs.Update();
|
||||
}
|
||||
transport.Repaint();
|
||||
resources.Repaint();
|
||||
}
|
||||
else if (ch == 'f') {
|
||||
disable = true;
|
||||
}
|
||||
else if (ch == 9) { /* tab */
|
||||
focused->SetBorderColor(BOX_COLOR_WHITE_ON_BLACK);
|
||||
if (input != NULL) {
|
||||
wtimeout(focused->GetContent(), 0);
|
||||
}
|
||||
|
||||
focused->SetFrameColor(BOX_COLOR_WHITE_ON_BLACK);
|
||||
|
||||
index++;
|
||||
if (index >= order.size()) {
|
||||
@ -125,7 +147,19 @@ int main(int argc, char* argv[])
|
||||
}
|
||||
|
||||
focused = order.at(index);
|
||||
focused->SetBorderColor(BOX_COLOR_RED_ON_BLACK);
|
||||
focused->SetFrameColor(BOX_COLOR_RED_ON_BLACK);
|
||||
|
||||
scrollable = dynamic_cast<ScrollableWindow*>(focused);
|
||||
input = dynamic_cast<IInput*>(focused);
|
||||
|
||||
if (input != NULL) {
|
||||
curs_set(1);
|
||||
input->Focus();
|
||||
wtimeout(focused->GetContent(), 500);
|
||||
}
|
||||
else {
|
||||
curs_set(0);
|
||||
}
|
||||
}
|
||||
else if (ch >= KEY_F(0) && ch <= KEY_F(12)) {
|
||||
}
|
||||
|
@ -34,11 +34,11 @@ void ResourcesWindow::Repaint() {
|
||||
float physicalMemoryUsed = (float) systemInfo->GetUsedPhysicalMemory() / BYTES_PER_MEGABYTE;
|
||||
|
||||
wprintw(
|
||||
this->GetContents(),
|
||||
this->GetContent(),
|
||||
"cpu %.2f%% - virt %.2f (mb) - phys %.2f (mb)",
|
||||
systemInfo->GetCpuUsage(),
|
||||
virtualMemoryUsed,
|
||||
physicalMemoryUsed);
|
||||
|
||||
BorderedWindow::Repaint();
|
||||
Window::Repaint();
|
||||
}
|
@ -1,10 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
#include "curses_config.h"
|
||||
#include "BorderedWindow.h"
|
||||
#include "Window.h"
|
||||
#include "SystemInfo.h"
|
||||
|
||||
class ResourcesWindow : public BorderedWindow {
|
||||
class ResourcesWindow : public Window {
|
||||
public:
|
||||
ResourcesWindow();
|
||||
virtual ~ResourcesWindow();
|
||||
|
@ -8,7 +8,7 @@
|
||||
#include <core/debug.h>
|
||||
|
||||
ScrollableWindow::ScrollableWindow()
|
||||
: BorderedWindow() {
|
||||
: Window() {
|
||||
scrollPosition = 0;
|
||||
scrolledToBottom = true;
|
||||
}
|
||||
@ -22,7 +22,7 @@ void ScrollableWindow::OnAdapterChanged() {
|
||||
this->ScrollToBottom();
|
||||
}
|
||||
else {
|
||||
GetScrollAdapter().DrawPage(this->GetContents(), this->scrollPosition);
|
||||
GetScrollAdapter().DrawPage(this->GetContent(), this->scrollPosition);
|
||||
this->Repaint();
|
||||
}
|
||||
}
|
||||
@ -37,7 +37,7 @@ size_t ScrollableWindow::GetLastVisible() {
|
||||
}
|
||||
|
||||
void ScrollableWindow::ScrollToTop() {
|
||||
GetScrollAdapter().DrawPage(this->GetContents(), 0);
|
||||
GetScrollAdapter().DrawPage(this->GetContent(), 0);
|
||||
this->scrollPosition = 0;
|
||||
this->Repaint();
|
||||
this->CheckScrolledToBottom();
|
||||
@ -52,7 +52,7 @@ void ScrollableWindow::ScrollToBottom() {
|
||||
int actual = total - height;
|
||||
actual = (actual < 0) ? 0 : actual;
|
||||
|
||||
adapter->DrawPage(this->GetContents(), actual);
|
||||
adapter->DrawPage(this->GetContent(), actual);
|
||||
|
||||
this->scrollPosition = actual;
|
||||
this->Repaint();
|
||||
@ -63,7 +63,7 @@ void ScrollableWindow::ScrollUp(int delta) {
|
||||
int actual = (int) this->scrollPosition - delta;
|
||||
actual = (actual < 0) ? 0 : actual;
|
||||
|
||||
GetScrollAdapter().DrawPage(this->GetContents(), actual);
|
||||
GetScrollAdapter().DrawPage(this->GetContent(), actual);
|
||||
|
||||
this->scrollPosition = (size_t) actual;
|
||||
this->Repaint();
|
||||
@ -81,7 +81,7 @@ void ScrollableWindow::ScrollDown(int delta) {
|
||||
int actual = (int) this->scrollPosition + delta;
|
||||
actual = (actual > max) ? max : actual;
|
||||
|
||||
adapter->DrawPage(this->GetContents(), actual);
|
||||
adapter->DrawPage(this->GetContent(), actual);
|
||||
|
||||
this->scrollPosition = (size_t) actual;
|
||||
this->Repaint();
|
||||
|
@ -1,10 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
#include "curses_config.h"
|
||||
#include "BorderedWindow.h"
|
||||
#include "Window.h"
|
||||
#include "IScrollAdapter.h"
|
||||
|
||||
class ScrollableWindow : public BorderedWindow {
|
||||
class ScrollableWindow : public Window {
|
||||
public:
|
||||
ScrollableWindow();
|
||||
~ScrollableWindow();
|
||||
|
@ -6,6 +6,15 @@
|
||||
|
||||
#define MAX_ENTRY_COUNT 0xffffffff
|
||||
|
||||
inline static int utf8Length(const std::string& str) {
|
||||
try {
|
||||
return utf8::distance(str.begin(), str.end());
|
||||
}
|
||||
catch (...) {
|
||||
return str.length();
|
||||
}
|
||||
}
|
||||
|
||||
SimpleScrollAdapter::SimpleScrollAdapter() {
|
||||
this->lineCount = 0;
|
||||
|
||||
@ -66,7 +75,7 @@ void SimpleScrollAdapter::DrawPage(WINDOW* window, size_t lineNumber) {
|
||||
Iterator end = this->entries.end();
|
||||
size_t remaining = this->height;
|
||||
size_t w = this->width;
|
||||
size_t c = lineNumber - (*it)->GetIndex();
|
||||
size_t c = lineNumber - ((*it)->GetIndex() - removedOffset);
|
||||
|
||||
do {
|
||||
size_t count = (*it)->GetLineCount();
|
||||
@ -78,7 +87,12 @@ void SimpleScrollAdapter::DrawPage(WINDOW* window, size_t lineNumber) {
|
||||
|
||||
for (size_t i = c; i < count && remaining != 0; i++) {
|
||||
std::string line = (*it)->GetLine(i).c_str();
|
||||
wprintw(window, "%s\n", line.c_str());
|
||||
size_t len = utf8Length(line);
|
||||
|
||||
/* don't add a newline if we're going to hit the end of the line, the
|
||||
newline will be added automatically. */
|
||||
wprintw(window, "%s%s", line.c_str(), len >= this->width ? "" : "\n");
|
||||
|
||||
--remaining;
|
||||
}
|
||||
|
||||
@ -181,15 +195,6 @@ void SimpleScrollAdapter::Entry::SetAttrs(int64 attrs) {
|
||||
this->attrs = attrs;
|
||||
}
|
||||
|
||||
inline static int utf8Length(const std::string& str) {
|
||||
try {
|
||||
return utf8::distance(str.begin(), str.end());
|
||||
}
|
||||
catch (...) {
|
||||
return str.length();
|
||||
}
|
||||
}
|
||||
|
||||
inline static void breakIntoSubLines(
|
||||
std::string& line,
|
||||
size_t width,
|
||||
@ -222,7 +227,7 @@ inline static void breakIntoSubLines(
|
||||
std::vector<std::string> sanitizedWords;
|
||||
for (size_t i = 0; i < words.size(); i++) {
|
||||
std::string word = words.at(i);
|
||||
size_t len = std::distance(word.begin(), word.end());
|
||||
size_t len = utf8Length(word);
|
||||
|
||||
/* this word is fine, it'll easily fit on its own line of necessary */
|
||||
|
||||
@ -248,7 +253,7 @@ inline static void breakIntoSubLines(
|
||||
utf8::unchecked::next(end);
|
||||
++count;
|
||||
|
||||
if (count == width - 1 || end == word.end()) {
|
||||
if (count == width || end == word.end()) {
|
||||
sanitizedWords.push_back(std::string(begin, end));
|
||||
begin = end;
|
||||
count = 0;
|
||||
@ -268,7 +273,7 @@ inline static void breakIntoSubLines(
|
||||
for (size_t i = 0; i < sanitizedWords.size(); i++) {
|
||||
std::string word = sanitizedWords.at(i);
|
||||
size_t wordLength = utf8Length(word);
|
||||
size_t extra = (i != 0) && (sanitizedWords.size() - 1);
|
||||
size_t extra = (i != 0) && (i != sanitizedWords.size() - 1);
|
||||
|
||||
/* we have enough space for this new word. accumulate it. the
|
||||
+1 here is to take the space into account */
|
||||
@ -285,7 +290,9 @@ inline static void breakIntoSubLines(
|
||||
/* otherwise, flush the current line, and start a new one... */
|
||||
|
||||
else {
|
||||
output.push_back(accum);
|
||||
if (accum.size()) {
|
||||
output.push_back(accum);
|
||||
}
|
||||
|
||||
/* special case -- if the word is the exactly length of the
|
||||
width, just add it as a new line and reset... */
|
||||
@ -312,7 +319,6 @@ inline static void breakIntoSubLines(
|
||||
}
|
||||
|
||||
void SimpleScrollAdapter::Entry::SetWidth(size_t width) {
|
||||
width--;
|
||||
if (this->width != width) {
|
||||
this->width = width;
|
||||
|
||||
|
@ -35,12 +35,12 @@ TransportWindow::~TransportWindow() {
|
||||
|
||||
void TransportWindow::Repaint() {
|
||||
this->Clear();
|
||||
WINDOW *c = this->GetContents();
|
||||
WINDOW *c = this->GetContent();
|
||||
|
||||
float volume = (this->transport->Volume() * 100.0);
|
||||
|
||||
wprintw(c, "volume %.1f%%\n", volume);
|
||||
wprintw(c, "filename: ");
|
||||
|
||||
BorderedWindow::Repaint();
|
||||
Window::Repaint();
|
||||
}
|
@ -1,13 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#include "curses_config.h"
|
||||
#include "BorderedWindow.h"
|
||||
#include "Window.h"
|
||||
#include "OutputWindow.h"
|
||||
#include <core/playback/Transport.h>
|
||||
|
||||
using namespace musik::core::audio;
|
||||
|
||||
class TransportWindow : public BorderedWindow {
|
||||
class TransportWindow : public Window {
|
||||
public:
|
||||
TransportWindow(Transport& transport);
|
||||
~TransportWindow();
|
||||
|
123
src/musikbox/Window.cpp
Executable file
123
src/musikbox/Window.cpp
Executable file
@ -0,0 +1,123 @@
|
||||
#pragma once
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "Window.h"
|
||||
|
||||
Window::Window() {
|
||||
this->frame = this->content = 0;
|
||||
this->height = 0;
|
||||
this->width = 0;
|
||||
this->x = 0;
|
||||
this->y = 0;
|
||||
this->contentColor = -1;
|
||||
this->frameColor = -1;
|
||||
}
|
||||
|
||||
Window::~Window() {
|
||||
this->Destroy();
|
||||
}
|
||||
|
||||
void Window::SetSize(int width, int height) {
|
||||
this->width = width;
|
||||
this->height = height;
|
||||
}
|
||||
|
||||
void Window::SetPosition(int x, int y) {
|
||||
this->x = x;
|
||||
this->y = y;
|
||||
}
|
||||
|
||||
int Window::GetWidth() const {
|
||||
return this->width;
|
||||
}
|
||||
|
||||
int Window::GetHeight() const {
|
||||
return this->height;
|
||||
}
|
||||
|
||||
int Window::GetContentHeight() const {
|
||||
return this->height - 2;
|
||||
}
|
||||
|
||||
int Window::GetContentWidth() const {
|
||||
return this->width - 2;
|
||||
}
|
||||
|
||||
int Window::GetX() const {
|
||||
return this->x;
|
||||
}
|
||||
|
||||
int Window::GetY() const {
|
||||
return this->y;
|
||||
}
|
||||
|
||||
void Window::SetContentColor(int color) {
|
||||
this->contentColor = color;
|
||||
|
||||
if (this->contentColor != -1 && this->content) {
|
||||
wbkgd(this->content, COLOR_PAIR(this->contentColor));
|
||||
this->Repaint();
|
||||
}
|
||||
}
|
||||
|
||||
void Window::SetFrameColor(int color) {
|
||||
this->frameColor = color;
|
||||
|
||||
if (this->frameColor != -1 && this->frame) {
|
||||
wbkgd(this->frame, COLOR_PAIR(this->frameColor));
|
||||
this->Repaint();
|
||||
}
|
||||
}
|
||||
|
||||
WINDOW* Window::GetContent() const {
|
||||
return this->content;
|
||||
}
|
||||
|
||||
WINDOW* Window::GetFrame() const {
|
||||
return this->frame;
|
||||
}
|
||||
|
||||
void Window::Create() {
|
||||
this->Destroy();
|
||||
|
||||
this->frame = newwin(this->height, this->width, this->y, this->x);
|
||||
box(this->frame, 0, 0);
|
||||
wrefresh(this->frame);
|
||||
|
||||
this->content = derwin(
|
||||
this->frame,
|
||||
this->height - 2,
|
||||
this->width - 2,
|
||||
1,
|
||||
1);
|
||||
|
||||
if (this->contentColor != -1) {
|
||||
wbkgd(this->content, COLOR_PAIR(this->contentColor));
|
||||
}
|
||||
|
||||
if (this->frameColor != -1) {
|
||||
wbkgd(this->frame, COLOR_PAIR(this->frameColor));
|
||||
}
|
||||
|
||||
touchwin(this->content);
|
||||
wrefresh(this->content);
|
||||
}
|
||||
|
||||
void Window::Clear() {
|
||||
wclear(this->content);
|
||||
}
|
||||
|
||||
void Window::Repaint() {
|
||||
if (this->frame && this->content) {
|
||||
wrefresh(this->frame);
|
||||
wrefresh(this->content);
|
||||
}
|
||||
}
|
||||
|
||||
void Window::Destroy() {
|
||||
if (this->frame) {
|
||||
delwin(this->content);
|
||||
delwin(this->frame);
|
||||
this->frame = this->content = NULL;
|
||||
}
|
||||
}
|
38
src/musikbox/Window.h
Executable file
38
src/musikbox/Window.h
Executable file
@ -0,0 +1,38 @@
|
||||
#pragma once
|
||||
|
||||
#include "curses_config.h"
|
||||
#include "IWindow.h"
|
||||
|
||||
class Window : public IWindow {
|
||||
public:
|
||||
Window();
|
||||
virtual ~Window();
|
||||
|
||||
void Create();
|
||||
void Destroy();
|
||||
|
||||
virtual void Repaint();
|
||||
|
||||
virtual void SetContentColor(int color);
|
||||
virtual void SetFrameColor(int color);
|
||||
virtual void SetSize(int width, int height);
|
||||
virtual void SetPosition(int x, int y);
|
||||
|
||||
virtual int GetWidth() const;
|
||||
virtual int GetHeight() const;
|
||||
virtual int GetContentHeight() const;
|
||||
virtual int GetContentWidth() const;
|
||||
virtual int GetX() const;
|
||||
virtual int GetY() const;
|
||||
|
||||
virtual WINDOW* GetFrame() const;
|
||||
virtual WINDOW* GetContent() const;
|
||||
|
||||
protected:
|
||||
void Clear();
|
||||
|
||||
private:
|
||||
WINDOW* frame;
|
||||
WINDOW* content;
|
||||
int width, height, x, y, contentColor, frameColor;
|
||||
};
|
@ -115,7 +115,7 @@
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="BorderedWindow.cpp" />
|
||||
<ClCompile Include="Window.cpp" />
|
||||
<ClCompile Include="Colors.cpp" />
|
||||
<ClCompile Include="CommandWindow.cpp" />
|
||||
<ClCompile Include="IInput.h" />
|
||||
@ -136,10 +136,12 @@
|
||||
<ClCompile Include="TransportWindow.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="BorderedWindow.h" />
|
||||
<ClInclude Include="Window.h" />
|
||||
<ClInclude Include="Colors.h" />
|
||||
<ClInclude Include="CommandWindow.h" />
|
||||
<ClInclude Include="curses_config.h" />
|
||||
<ClInclude Include="ILayout.h" />
|
||||
<ClInclude Include="IWindow.h" />
|
||||
<ClInclude Include="LogWindow.h" />
|
||||
<ClInclude Include="OutputWindow.h" />
|
||||
<ClInclude Include="ResourcesWindow.h" />
|
||||
|
@ -4,9 +4,6 @@
|
||||
<ClCompile Include="TransportEvents.cpp" />
|
||||
<ClCompile Include="Main.cpp" />
|
||||
<ClCompile Include="stdafx.cpp" />
|
||||
<ClCompile Include="BorderedWindow.cpp">
|
||||
<Filter>curses</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Colors.cpp">
|
||||
<Filter>curses</Filter>
|
||||
</ClCompile>
|
||||
@ -43,13 +40,13 @@
|
||||
<ClCompile Include="IInput.h">
|
||||
<Filter>curses</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Window.cpp">
|
||||
<Filter>curses</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="TransportEvents.h" />
|
||||
<ClInclude Include="stdafx.h" />
|
||||
<ClInclude Include="BorderedWindow.h">
|
||||
<Filter>curses</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Colors.h">
|
||||
<Filter>curses</Filter>
|
||||
</ClInclude>
|
||||
@ -83,6 +80,15 @@
|
||||
<ClInclude Include="TransportWindow.h">
|
||||
<Filter>windows</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="ILayout.h">
|
||||
<Filter>curses</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="IWindow.h">
|
||||
<Filter>curses</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Window.h">
|
||||
<Filter>curses</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Filter Include="curses">
|
||||
|
Loading…
x
Reference in New Issue
Block a user