diff --git a/libraries/ZWidget/include/zwidget/window/window.h b/libraries/ZWidget/include/zwidget/window/window.h index 63c65ff37..df852d2b8 100644 --- a/libraries/ZWidget/include/zwidget/window/window.h +++ b/libraries/ZWidget/include/zwidget/window/window.h @@ -172,4 +172,7 @@ public: virtual void SetCaptionTextColor(uint32_t bgra8) = 0; virtual void PresentBitmap(int width, int height, const uint32_t* pixels) = 0; + + virtual std::string GetClipboardText() = 0; + virtual void SetClipboardText(const std::string& text) = 0; }; diff --git a/libraries/ZWidget/src/core/widget.cpp b/libraries/ZWidget/src/core/widget.cpp index feac769a6..9a1082f48 100644 --- a/libraries/ZWidget/src/core/widget.cpp +++ b/libraries/ZWidget/src/core/widget.cpp @@ -379,11 +379,18 @@ void Widget::ReleaseMouseCapture() std::string Widget::GetClipboardText() { - return {}; + Widget* w = Window(); + if (w) + return w->DispWindow->GetClipboardText(); + else + return {}; } void Widget::SetClipboardText(const std::string& text) { + Widget* w = Window(); + if (w) + w->DispWindow->SetClipboardText(text); } Widget* Widget::Window() diff --git a/libraries/ZWidget/src/window/win32/win32window.cpp b/libraries/ZWidget/src/window/win32/win32window.cpp index e041a1cc8..6ece5f8db 100644 --- a/libraries/ZWidget/src/window/win32/win32window.cpp +++ b/libraries/ZWidget/src/window/win32/win32window.cpp @@ -254,6 +254,77 @@ double Win32Window::GetDpiScale() const return GetDpiForWindow(WindowHandle) / 96.0; } +std::string Win32Window::GetClipboardText() +{ + BOOL result = OpenClipboard(WindowHandle); + if (result == FALSE) + throw std::runtime_error("Unable to open clipboard"); + + HANDLE handle = GetClipboardData(CF_UNICODETEXT); + if (handle == 0) + { + CloseClipboard(); + return std::string(); + } + + std::wstring::value_type* data = (std::wstring::value_type*)GlobalLock(handle); + if (data == 0) + { + CloseClipboard(); + return std::string(); + } + std::string str = from_utf16(data); + GlobalUnlock(handle); + + CloseClipboard(); + return str; +} + +void Win32Window::SetClipboardText(const std::string& text) +{ + std::wstring text16 = to_utf16(text); + + BOOL result = OpenClipboard(WindowHandle); + if (result == FALSE) + throw std::runtime_error("Unable to open clipboard"); + + result = EmptyClipboard(); + if (result == FALSE) + { + CloseClipboard(); + throw std::runtime_error("Unable to empty clipboard"); + } + + unsigned int length = (text16.length() + 1) * sizeof(std::wstring::value_type); + HANDLE handle = GlobalAlloc(GMEM_MOVEABLE, length); + if (handle == 0) + { + CloseClipboard(); + throw std::runtime_error("Unable to allocate clipboard memory"); + } + + void* data = GlobalLock(handle); + if (data == 0) + { + GlobalFree(handle); + CloseClipboard(); + throw std::runtime_error("Unable to lock clipboard memory"); + } + memcpy(data, text16.c_str(), length); + GlobalUnlock(handle); + + HANDLE data_result = SetClipboardData(CF_UNICODETEXT, handle); + + if (data_result == 0) + { + GlobalFree(handle); + CloseClipboard(); + throw std::runtime_error("Unable to set clipboard data"); + } + + CloseClipboard(); +} + void Win32Window::PresentBitmap(int width, int height, const uint32_t* pixels) { BITMAPV5HEADER header = {}; diff --git a/libraries/ZWidget/src/window/win32/win32window.h b/libraries/ZWidget/src/window/win32/win32window.h index 316e228cd..2db141210 100644 --- a/libraries/ZWidget/src/window/win32/win32window.h +++ b/libraries/ZWidget/src/window/win32/win32window.h @@ -44,6 +44,9 @@ public: void SetCaptionColor(uint32_t bgra8) override; void SetCaptionTextColor(uint32_t bgra8) override; + std::string GetClipboardText() override; + void SetClipboardText(const std::string& text) override; + Point GetLParamPos(LPARAM lparam) const; static void ProcessEvents(); diff --git a/src/common/platform/win32/i_mainwindow.cpp b/src/common/platform/win32/i_mainwindow.cpp index d8c0d2d36..c660d5e23 100644 --- a/src/common/platform/win32/i_mainwindow.cpp +++ b/src/common/platform/win32/i_mainwindow.cpp @@ -104,7 +104,7 @@ void MainWindow::ShowErrorPane(const char* text) I_NetDone(); } - PrintStr(text); + // PrintStr(text); size_t totalsize = 0; for (const FString& line : bufferedConsoleStuff) @@ -115,7 +115,7 @@ void MainWindow::ShowErrorPane(const char* text) for (const FString& line : bufferedConsoleStuff) alltext.append(line.GetChars(), line.Len()); - ErrorWindow::ExecModal(alltext); + restartrequest = ErrorWindow::ExecModal(text, alltext); } void MainWindow::ShowNetStartPane(const char* message, int maxpos) diff --git a/src/common/widgets/errorwindow.cpp b/src/common/widgets/errorwindow.cpp index 30fc60a5b..e8b28b9de 100644 --- a/src/common/widgets/errorwindow.cpp +++ b/src/common/widgets/errorwindow.cpp @@ -1,20 +1,25 @@ #include "errorwindow.h" #include "version.h" -#include +#include "v_font.h" +#include "printf.h" +#include +#include -void ErrorWindow::ExecModal(const std::string& text) +bool ErrorWindow::ExecModal(const std::string& text, const std::string& log) { Size screenSize = GetScreenSize(); double windowWidth = 1200.0; double windowHeight = 700.0; - auto window = new ErrorWindow(); - window->SetText(text); + auto window = std::make_unique(); + window->SetText(text, log); window->SetFrameGeometry((screenSize.width - windowWidth) * 0.5, (screenSize.height - windowHeight) * 0.5, windowWidth, windowHeight); window->Show(); DisplayWindow::RunLoop(); + + return window->Restart; } ErrorWindow::ErrorWindow() : Widget(nullptr, WidgetType::Window) @@ -26,19 +31,175 @@ ErrorWindow::ErrorWindow() : Widget(nullptr, WidgetType::Window) SetWindowCaptionColor(Colorf::fromRgba8(33, 33, 33)); SetWindowCaptionTextColor(Colorf::fromRgba8(226, 223, 219)); - LogView = new TextEdit(this); + LogView = new LogViewer(this); + ClipboardButton = new PushButton(this); + RestartButton = new PushButton(this); + + ClipboardButton->OnClick = [=]() { OnClipboardButtonClicked(); }; + RestartButton->OnClick = [=]() { OnRestartButtonClicked(); }; + + ClipboardButton->SetText("Copy to clipboard"); + RestartButton->SetText("Restart"); LogView->SetFocus(); } -void ErrorWindow::SetText(const std::string& text) +void ErrorWindow::SetText(const std::string& text, const std::string& log) { - LogView->SetText(text); + LogView->SetText(text, log); + + clipboardtext.clear(); + clipboardtext.reserve(log.size() + text.size() + 100); + + // Strip the color escapes from the log + const uint8_t* cptr = (const uint8_t*)log.data(); + while (int chr = GetCharFromString(cptr)) + { + if (chr != TEXTCOLOR_ESCAPE) + { + // The bar characters, most commonly used to indicate map changes + if (chr >= 0x1D && chr <= 0x1F) + { + chr = 0x2550; // Box Drawings Double Horizontal + } + clipboardtext += MakeUTF8(chr); + } + } + + clipboardtext += "\nExecution could not continue.\n"; + clipboardtext += text; + clipboardtext += "\n"; +} + +void ErrorWindow::OnClipboardButtonClicked() +{ + SetClipboardText(clipboardtext); +} + +void ErrorWindow::OnRestartButtonClicked() +{ + Restart = true; + DisplayWindow::ExitLoop(); +} + +void ErrorWindow::OnClose() +{ + Restart = false; + DisplayWindow::ExitLoop(); } void ErrorWindow::OnGeometryChanged() { double w = GetWidth(); double h = GetHeight(); - LogView->SetFrameGeometry(Rect::xywh(20.0, 20.0, w - 40.0, h - 40.0)); + + double y = GetHeight() - 15.0 - ClipboardButton->GetPreferredHeight(); + ClipboardButton->SetFrameGeometry(20.0, y, 170.0, ClipboardButton->GetPreferredHeight()); + RestartButton->SetFrameGeometry(GetWidth() - 20.0 - 100.0, y, 100.0, RestartButton->GetPreferredHeight()); + y -= 20.0; + + LogView->SetFrameGeometry(Rect::xywh(0.0, 0.0, w, y)); +} + +///////////////////////////////////////////////////////////////////////////// + +LogViewer::LogViewer(Widget* parent) : Widget(parent) +{ + SetNoncontentSizes(8.0, 8.0, 8.0, 8.0); +} + +void LogViewer::SetText(const std::string& text, const std::string& log) +{ + lines.clear(); + + std::string::size_type start = 0; + std::string::size_type end = log.find('\n'); + while (end != std::string::npos) + { + lines.push_back(CreateLineLayout(log.substr(start, end - start))); + start = end + 1; + end = log.find('\n', start); + } + + lines.push_back(CreateLineLayout(log.substr(start))); + + // Add an empty line as a bit of spacing + lines.push_back(CreateLineLayout({})); + + SpanLayout layout; + //layout.AddImage(Image::LoadResource("widgets/erroricon.svg"), -8.0); + layout.AddText("Execution could not continue.", largefont, Colorf::fromRgba8(255, 170, 170)); + lines.push_back(layout); + + layout.Clear(); + layout.AddText(text, largefont, Colorf::fromRgba8(255, 255, 170)); + lines.push_back(layout); + + Update(); +} + +SpanLayout LogViewer::CreateLineLayout(const std::string& text) +{ + SpanLayout layout; + + Colorf curcolor = Colorf::fromRgba8(255, 255, 255); + std::string curtext; + + const uint8_t* cptr = (const uint8_t*)text.data(); + while (int chr = GetCharFromString(cptr)) + { + if (chr != TEXTCOLOR_ESCAPE) + { + // The bar characters, most commonly used to indicate map changes + if (chr >= 0x1D && chr <= 0x1F) + { + chr = 0x2550; // Box Drawings Double Horizontal + } + curtext += MakeUTF8(chr); + } + else + { + EColorRange range = V_ParseFontColor(cptr, CR_UNTRANSLATED, CR_YELLOW); + if (range != CR_UNDEFINED) + { + if (!curtext.empty()) + layout.AddText(curtext, font, curcolor); + curtext.clear(); + + PalEntry color = V_LogColorFromColorRange(range); + curcolor = Colorf::fromRgba8(color.r, color.g, color.b); + } + } + } + + curtext.push_back(' '); + layout.AddText(curtext, font, curcolor); + + return layout; +} + +void LogViewer::OnPaintFrame(Canvas* canvas) +{ + double w = GetFrameGeometry().width; + double h = GetFrameGeometry().height; + Colorf bordercolor = Colorf::fromRgba8(100, 100, 100); + canvas->fillRect(Rect::xywh(0.0, 0.0, w, h), Colorf::fromRgba8(38, 38, 38)); + //canvas->fillRect(Rect::xywh(0.0, 0.0, w, 1.0), bordercolor); + //canvas->fillRect(Rect::xywh(0.0, h - 1.0, w, 1.0), bordercolor); + //canvas->fillRect(Rect::xywh(0.0, 0.0, 1.0, h - 0.0), bordercolor); + //canvas->fillRect(Rect::xywh(w - 1.0, 0.0, 1.0, h - 0.0), bordercolor); +} + +void LogViewer::OnPaint(Canvas* canvas) +{ + double width = GetWidth(); + double y = GetHeight(); + for (size_t i = lines.size(); i > 0 && y > 0.0; i--) + { + SpanLayout& layout = lines[i - 1]; + layout.Layout(canvas, width); + layout.SetPosition(Point(0.0, y - layout.GetSize().height)); + layout.DrawLayout(canvas); + y -= layout.GetSize().height; + } } diff --git a/src/common/widgets/errorwindow.h b/src/common/widgets/errorwindow.h index 11cfbf6ed..5f2ecf069 100644 --- a/src/common/widgets/errorwindow.h +++ b/src/common/widgets/errorwindow.h @@ -1,21 +1,52 @@ #pragma once #include +#include -class TextEdit; +class LogViewer; +class PushButton; class ErrorWindow : public Widget { public: - static void ExecModal(const std::string& text); + static bool ExecModal(const std::string& text, const std::string& log); + + ErrorWindow(); + + bool Restart = false; protected: + void OnClose() override; void OnGeometryChanged() override; private: - ErrorWindow(); + void SetText(const std::string& text, const std::string& log); - void SetText(const std::string& text); + void OnClipboardButtonClicked(); + void OnRestartButtonClicked(); - TextEdit* LogView = nullptr; + LogViewer* LogView = nullptr; + PushButton* ClipboardButton = nullptr; + PushButton* RestartButton = nullptr; + + std::string clipboardtext; +}; + +class LogViewer : public Widget +{ +public: + LogViewer(Widget* parent); + + void SetText(const std::string& text, const std::string& log); + +protected: + void OnPaintFrame(Canvas* canvas) override; + void OnPaint(Canvas* canvas) override; + +private: + SpanLayout CreateLineLayout(const std::string& text); + + std::shared_ptr largefont = Font::Create("Poppins", 16.0); + std::shared_ptr font = Font::Create("Poppins", 12.0); + std::vector lines; };