Add a rewrite of the logger application.

This is compatible with the existing BeDC protocol but based on
ColumnListView, probably making the code a bit simpler. Since the
original app is not opensource, we have to use a replacement.

A few things are still missing, for example the ability to copypaste
messages or to put the application "offline", to clear the console from
the menu, etc. But it will be good enough for now.
diff --git a/src/LoggerApp.cpp b/src/LoggerApp.cpp
new file mode 100644
index 0000000..1c62c75
--- /dev/null
+++ b/src/LoggerApp.cpp
@@ -0,0 +1,291 @@
+/*
+ * Copyright (C) 2023 Adrien Destugues <pulkomandy@pulkomandy.tk>
+ *
+ * Distributed under terms of the MIT license.
+ */
+
+#include <Application.h>
+#include <private/interface/ColumnListView.h>
+#include <private/interface/ColumnTypes.h>
+#include <GroupLayout.h>
+#include <LayoutBuilder.h>
+#include <MenuBar.h>
+#include <Window.h>
+
+#include <ctype.h>
+
+#include "../BeDC.h"
+
+
+class BColorStringField: public BStringField
+{
+public:
+	BColorStringField(const char* string, rgb_color color)
+		: BStringField(string)
+		, fColor(color)
+	{
+	}
+
+	rgb_color fColor;
+};
+
+
+class BColorStringColumn: public BStringColumn
+{
+public:
+	BColorStringColumn(const char* title, float w, float minw, float maxw)
+		: BStringColumn(title, w, minw, maxw, false)
+	{
+	}
+
+	void DrawField(BField* field, BRect rect, BView* parent) override
+	{
+		BColorStringField* csField = dynamic_cast<BColorStringField*>(field);
+		if (csField) {
+			parent->SetHighColor(csField->fColor);
+		}
+		BStringColumn::DrawField(field, rect, parent);
+	}
+};
+
+
+static char sanechar(int32 input) {
+	input = input & 0xff;
+	if (isprint(input))
+		return (char)input;
+	else
+		return '.';
+}
+
+
+class LoggerWindow: public BWindow
+{
+public:
+	LoggerWindow()
+		: BWindow(BRect(100, 100, 900, 700), "Dev console", B_DOCUMENT_WINDOW,
+			B_QUIT_ON_WINDOW_CLOSE | B_AUTO_UPDATE_SIZE_LIMITS)
+	{
+		BGroupLayout* layout = new BGroupLayout(B_VERTICAL);
+		SetLayout(layout);
+		layout->SetSpacing(0);
+
+		fEventList = new BColumnListView("messages", 0, B_NO_BORDER);
+		fEventList->AddColumn(new BColorStringColumn("Name", 100, 50, 200), 0);
+		fEventList->AddColumn(new BColorStringColumn("Text", INT16_MAX, 50, INT16_MAX), 1);
+
+		BLayoutBuilder::Group<>(layout)
+			.SetInsets(0, -1, -1, -1)
+			.Add(new BMenuBar("main menu"))
+			.Add(fEventList)
+		.End();
+	}
+
+	void MessageReceived(BMessage* message) override
+	{
+		switch(message->what) {
+			case BEDC_MESSAGE:
+			{
+				// TODO parse all info: bedc_ver, bedc_main_type
+				// bedc_ver: "1.0" (can be changed in later versions if the message format changes)
+				// bedc_main_type: BEDC_BMESSAGE or BEDC_PLAIN_MESSAGE
+				BString name = message->FindString("bedc_name");
+				BString text = message->FindString("bedc_text");
+				int8 colorEnum = message->FindInt8("bedc_color");
+				int8 type = message->FindInt8("bedc_type");
+				int8 main_type = message->FindInt8("bedc_main_type");
+
+				if (type == DC_CLEAR) {
+					fEventList->Clear();
+					return;
+				}
+
+				if (main_type == BEDC_BMESSAGE && text.IsEmpty()) {
+					int32 what = message->FindInt32("bedc_what");
+
+					text.SetToFormat("BMessage(what=%#010x, %d, '%c%c%c%c')", what, what,
+						sanechar(what >> 24), sanechar(what >> 16),
+						sanechar(what >>  8), sanechar(what >>  0));
+				}
+
+				// TODO: generic BMessage handling (results in one line per item in the message)
+				// TODO: separators
+				//
+				//
+				BRow* row = new BRow();
+				if (colorEnum == 0) {
+					row->SetField(new BStringField(name), 0);
+				} else {
+					rgb_color color;
+					switch(colorEnum) {
+						case DC_WHITE:
+							color = make_color(128, 128, 128, 255);
+							break;
+						case DC_BLACK:
+							color = make_color(64, 64, 64, 255);
+							break;
+						case DC_BLUE:
+							color = make_color(0, 0, 128, 255);
+							break;
+						case DC_RED:
+							color = make_color(0, 128, 0, 255);
+							break;
+						case DC_YELLOW:
+							color = make_color(128, 128, 0, 255);
+							break;
+						case DC_GREEN:
+							color = make_color(128, 0, 0, 255);
+							break;
+					}
+					row->SetField(new BColorStringField(name, color), 0);
+				}
+
+				if (type == DC_SUCCESS)
+					row->SetField(new BColorStringField(text, ui_color(B_SUCCESS_COLOR)), 1);
+				else if (type == DC_ERROR)
+					row->SetField(new BColorStringField(text, ui_color(B_FAILURE_COLOR)), 1);
+				else if (type == DC_MESSAGE)
+					row->SetField(new BStringField(text), 1);
+				else if (type == DC_SEPARATOR)
+					row->SetField(new BStringField("-----"), 1);
+				else {
+					message->PrintToStream();
+				}
+				fEventList->AddRow(row, -1);
+
+				if (main_type == BEDC_BMESSAGE) {
+					int index = 0;
+					char* nameFound;
+					type_code typeFound;
+					int32 countFound;
+					while (message->GetInfo(B_ANY_TYPE, index++, &nameFound, &typeFound, &countFound) == B_OK) {
+						// Don't display all the bedc_ internal stuff
+						if (BString(nameFound).StartsWith("bedc_"))
+							continue;
+
+						for (int j = 0; j < countFound; j++) {
+							name.SetToFormat("%s[%d] (%c%c%c%c)", nameFound, j,
+								(typeFound >> 24) & 0xFF, (typeFound >> 16) & 0xFF,
+								(typeFound >>  8) & 0xFF, (typeFound >>  0) & 0xFF
+							);
+
+							switch(typeFound) {
+#if 0
+	B_AFFINE_TRANSFORM_TYPE			= 'AMTX',
+	B_ALIGNMENT_TYPE				= 'ALGN',
+	B_ANY_TYPE						= 'ANYT',
+	B_ATOM_TYPE						= 'ATOM',
+	B_ATOMREF_TYPE					= 'ATMR',
+	B_BOOL_TYPE						= 'BOOL',
+	B_CHAR_TYPE						= 'CHAR',
+	B_COLOR_8_BIT_TYPE				= 'CLRB',
+	B_DOUBLE_TYPE					= 'DBLE',
+	B_FLOAT_TYPE					= 'FLOT',
+	B_GRAYSCALE_8_BIT_TYPE			= 'GRYB',
+	B_INT16_TYPE					= 'SHRT',
+	B_INT64_TYPE					= 'LLNG',
+	B_INT8_TYPE						= 'BYTE',
+	B_LARGE_ICON_TYPE				= 'ICON',
+	B_MEDIA_PARAMETER_GROUP_TYPE	= 'BMCG',
+	B_MEDIA_PARAMETER_TYPE			= 'BMCT',
+	B_MEDIA_PARAMETER_WEB_TYPE		= 'BMCW',
+	B_MESSAGE_TYPE					= 'MSGG',
+	B_MESSENGER_TYPE				= 'MSNG',
+	B_MIME_TYPE						= 'MIME',
+	B_MINI_ICON_TYPE				= 'MICN',
+	B_MONOCHROME_1_BIT_TYPE			= 'MNOB',
+	B_OBJECT_TYPE					= 'OPTR',
+	B_OFF_T_TYPE					= 'OFFT',
+	B_PATTERN_TYPE					= 'PATN',
+	B_POINTER_TYPE					= 'PNTR',
+	B_PROPERTY_INFO_TYPE			= 'SCTD',
+	B_RAW_TYPE						= 'RAWT',
+	B_RECT_TYPE						= 'RECT',
+	B_REF_TYPE						= 'RREF',
+	B_NODE_REF_TYPE					= 'NREF',
+	B_RGB_32_BIT_TYPE				= 'RGBB',
+	B_RGB_COLOR_TYPE				= 'RGBC',
+	B_SIZE_TYPE						= 'SIZE',
+	B_SIZE_T_TYPE					= 'SIZT',
+	B_SSIZE_T_TYPE					= 'SSZT',
+	B_STRING_LIST_TYPE				= 'STRL',
+	B_TIME_TYPE						= 'TIME',
+	B_UINT16_TYPE					= 'USHT',
+	B_UINT32_TYPE					= 'ULNG',
+	B_UINT64_TYPE					= 'ULLG',
+	B_UINT8_TYPE					= 'UBYT',
+	B_VECTOR_ICON_TYPE				= 'VICN',
+	B_XATTR_TYPE					= 'XATR',
+	B_NETWORK_ADDRESS_TYPE			= 'NWAD',
+	B_MIME_STRING_TYPE				= 'MIMS',
+#endif
+								case B_STRING_TYPE:
+									text = message->FindString(nameFound, j);
+									break;
+								case B_INT32_TYPE:
+								{
+									int32 val = message->FindInt32(nameFound, j);
+									text.SetToFormat("%d (%#010x)", val, val);
+									break;
+								}
+								case B_POINT_TYPE:
+								{
+									BPoint val = message->FindPoint(nameFound, j);
+									text.SetToFormat("X: %f Y: %f", val.x, val.y);
+									break;
+								}
+								default:
+									text.SetTo("TODO: unhandled message content type");
+									break;
+							}
+
+							BRow* subRow = new BRow();
+							subRow->SetField(new BStringField(name), 0);
+							subRow->SetField(new BStringField(text), 1);
+							fEventList->AddRow(subRow, row);
+						}
+
+					}
+				}
+
+				break;
+			}
+			default:
+				BWindow::MessageReceived(message);
+				break;
+		}
+	}
+
+private:
+	BColumnListView* fEventList;
+};
+
+
+class LoggerApp: public BApplication
+{
+public:
+	LoggerApp()
+		: BApplication("application/x-vnd.ml-BeDCApp")
+	{
+		LoggerWindow* window = new LoggerWindow();
+		window->Show();
+	}
+
+	void MessageReceived(BMessage* message) override
+	{
+		switch(message->what) {
+			case BEDC_MESSAGE:
+				WindowAt(0)->PostMessage(message);
+				break;
+			default:
+				BApplication::MessageReceived(message);
+				break;
+		}
+	}
+};
+
+
+int main(void)
+{
+	LoggerApp app;
+	app.Run();
+}
diff --git a/src/build.sh b/src/build.sh
new file mode 100644
index 0000000..79d618b
--- /dev/null
+++ b/src/build.sh
@@ -0,0 +1 @@
+g++ LoggerApp.cpp -lbe -lcolumnlistview