summaryrefslogtreecommitdiff
path: root/ui/progress_indicator.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'ui/progress_indicator.cpp')
-rw-r--r--ui/progress_indicator.cpp411
1 files changed, 369 insertions, 42 deletions
diff --git a/ui/progress_indicator.cpp b/ui/progress_indicator.cpp
index ebbc9edd..caf87428 100644
--- a/ui/progress_indicator.cpp
+++ b/ui/progress_indicator.cpp
@@ -10,12 +10,15 @@
#include <wx/stopwatch.h>
#include <wx/wupdlock.h>
#include <wx/sound.h>
+#include <wx/clipbrd.h>
+#include <wx/msgdlg.h>
#include <zen/basic_math.h>
#include <zen/format_unit.h>
#include <wx+/mouse_move_dlg.h>
#include <wx+/toggle_button.h>
#include <wx+/image_tools.h>
#include <wx+/graph.h>
+#include <wx+/context_menu.h>
#include <wx+/no_flicker.h>
#include <zen/file_handling.h>
#include "gui_generated.h"
@@ -272,7 +275,7 @@ namespace
inline
wxBitmap buttonPressed(const std::string& name)
{
- wxBitmap background = GlobalResources::getImage(wxT("log button pressed"));
+ wxBitmap background = GlobalResources::getImage(L"log button pressed");
return layOver(GlobalResources::getImage(utfCvrtTo<wxString>(name)), background);
}
@@ -287,17 +290,283 @@ wxBitmap buttonReleased(const std::string& name)
zen::move(output, 0, -1); //move image right one pixel
return output;
}
+
+
+//a vector-view on ErrorLog considering multi-line messages: prepare consumption by Grid
+class MessageView
+{
+public:
+ MessageView(const ErrorLog& log) : log_(log) {}
+
+ size_t rowsOnView() const { return viewRef.size(); }
+
+ struct LogEntryView
+ {
+ time_t time;
+ MessageType type;
+ MsgString messageLine;
+ bool firstLine; //if LogEntry::message spans multiple rows
+ };
+ bool getEntry(size_t row, LogEntryView& out) const
+ {
+ if (row < viewRef.size())
+ {
+ const Line& line = viewRef[row];
+ out.time = line.entry_->time;
+ out.type = line.entry_->type;
+ out.messageLine = extractLine(line.entry_->message, line.rowNumber_);
+ out.firstLine = line.rowNumber_ == 0; //this is virtually always correct, unless first line of the original message is empty!
+ return true;
+ }
+ return false;
+ }
+
+ void updateView(int includedTypes) //TYPE_INFO | TYPE_WARNING, ect. see error_log.h
+ {
+ viewRef.clear();
+
+ for (auto iter = log_.begin(); iter != log_.end(); ++iter)
+ if (iter->type & includedTypes)
+ {
+ assert_static((IsSameType<GetCharType<MsgString>::Type, wchar_t>::value));
+ assert(!startsWith(iter->message, L'\n'));
+
+ size_t rowNumber = 0;
+ bool lastCharNewline = true;
+ std::for_each(iter->message.begin(), iter->message.end(),
+ [&](wchar_t c)
+ {
+ typedef Line Line; //workaround MSVC compiler bug!
+
+ if (c == L'\n')
+ {
+ if (!lastCharNewline) //do not reference empty lines!
+ viewRef.push_back(Line(&*iter, rowNumber));
+ ++rowNumber;
+ lastCharNewline = true;
+ }
+ else
+ lastCharNewline = false;
+ });
+ if (!lastCharNewline)
+ viewRef.push_back(Line(&*iter, rowNumber));
+ }
+ }
+
+private:
+ static MsgString extractLine(const MsgString& message, size_t textRow)
+ {
+ auto iter1 = message.begin();
+ for (;;)
+ {
+ auto iter2 = std::find_if(iter1, message.end(), [](wchar_t c) { return c == L'\n'; });
+ if (textRow == 0)
+ return iter1 == message.end() ? MsgString() : MsgString(&*iter1, iter2 - iter1); //must not dereference iterator pointing to "end"!
+
+ if (iter2 == message.end())
+ {
+ assert(false);
+ return MsgString();
+ }
+
+ iter1 = iter2 + 1; //skip newline
+ --textRow;
+ }
+ }
+
+ struct Line
+ {
+ Line(const LogEntry* entry, size_t rowNumber) : entry_(entry), rowNumber_(rowNumber) {}
+ const LogEntry* entry_; //always bound!
+ size_t rowNumber_; //LogEntry::message may span multiple rows
+ };
+
+ std::vector<Line> viewRef; //partial view on log_
+ /* /|\
+ | updateView()
+ | */
+ const ErrorLog log_;
+};
+
+//-----------------------------------------------------------------------------
+
+enum ColumnTypeMsg
+{
+ COL_TYPE_MSG_TIME,
+ COL_TYPE_MSG_CATEGORY,
+ COL_TYPE_MSG_TEXT,
+};
+
+//Grid data implementation referencing MessageView
+class GridDataMessages : public GridData
+{
+ static const int COLUMN_BORDER_LEFT = 4; //for left-aligned text
+
+public:
+ GridDataMessages(const std::shared_ptr<MessageView>& msgView) : msgView_(msgView) {}
+
+ virtual size_t getRowCount() const { return msgView_ ? msgView_->rowsOnView() : 0; }
+
+ virtual wxString getValue(size_t row, ColumnType colType) const
+ {
+ MessageView::LogEntryView entry = {};
+ if (msgView_ && msgView_->getEntry(row, entry))
+ switch (static_cast<ColumnTypeMsg>(colType))
+ {
+ case COL_TYPE_MSG_TIME:
+ if (entry.firstLine)
+ return formatTime<wxString>(FORMAT_TIME, localTime(entry.time));
+ break;
+
+ case COL_TYPE_MSG_CATEGORY:
+ if (entry.firstLine)
+ switch (entry.type)
+ {
+ case TYPE_INFO:
+ return _("Info");
+ case TYPE_WARNING:
+ return _("Warning");
+ case TYPE_ERROR:
+ return _("Error");
+ case TYPE_FATAL_ERROR:
+ return _("Fatal Error");
+ }
+ break;
+
+ case COL_TYPE_MSG_TEXT:
+ return copyStringTo<wxString>(entry.messageLine);
+ }
+ return wxEmptyString;
+ }
+
+ virtual void renderCell(Grid& grid, wxDC& dc, const wxRect& rect, size_t row, ColumnType colType)
+ {
+ wxRect rectTmp = rect;
+
+ const wxColor colorGridLine = wxColour(192, 192, 192); //light grey
+
+ wxDCPenChanger dummy2(dc, wxPen(colorGridLine, 1, wxSOLID));
+ const bool drawBottomLine = [&]() -> bool //don't separate multi-line messages
+ {
+ MessageView::LogEntryView nextEntry = {};
+ if (msgView_ && msgView_->getEntry(row + 1, nextEntry))
+ return nextEntry.firstLine;
+ return true;
+ }();
+
+ if (drawBottomLine)
+ {
+ dc.DrawLine(rect.GetBottomLeft(), rect.GetBottomRight() + wxPoint(1, 0));
+ --rectTmp.height;
+ }
+
+ //--------------------------------------------------------
+
+ MessageView::LogEntryView entry = {};
+ if (msgView_ && msgView_->getEntry(row, entry))
+ switch (static_cast<ColumnTypeMsg>(colType))
+ {
+ case COL_TYPE_MSG_TIME:
+ drawCellText(dc, rectTmp, getValue(row, colType), grid.IsEnabled(), wxALIGN_CENTER);
+ break;
+
+ case COL_TYPE_MSG_CATEGORY:
+ if (entry.firstLine)
+ switch (entry.type)
+ {
+ case TYPE_INFO:
+ dc.DrawLabel(wxString(), GlobalResources::getImage(L"msg_small_info"), rectTmp, wxALIGN_CENTER);
+ break;
+ case TYPE_WARNING:
+ dc.DrawLabel(wxString(), GlobalResources::getImage(L"msg_small_warning"), rectTmp, wxALIGN_CENTER);
+ break;
+ case TYPE_ERROR:
+ case TYPE_FATAL_ERROR:
+ dc.DrawLabel(wxString(), GlobalResources::getImage(L"msg_small_error"), rectTmp, wxALIGN_CENTER);
+ break;
+ }
+ break;
+
+ case COL_TYPE_MSG_TEXT:
+ {
+ rectTmp.x += COLUMN_BORDER_LEFT;
+ rectTmp.width -= COLUMN_BORDER_LEFT;
+ drawCellText(dc, rectTmp, getValue(row, colType), grid.IsEnabled());
+ }
+ break;
+ }
+ }
+
+ virtual size_t getBestSize(wxDC& dc, size_t row, ColumnType colType)
+ {
+ // -> synchronize renderCell() <-> getBestSize()
+
+ MessageView::LogEntryView entry = {};
+ if (msgView_ && msgView_->getEntry(row, entry))
+ switch (static_cast<ColumnTypeMsg>(colType))
+ {
+ case COL_TYPE_MSG_TIME:
+ return 2 * COLUMN_BORDER_LEFT + dc.GetTextExtent(getValue(row, colType)).GetWidth();
+
+ case COL_TYPE_MSG_CATEGORY:
+ return GlobalResources::getImage(L"msg_small_info").GetWidth();
+
+ case COL_TYPE_MSG_TEXT:
+ return COLUMN_BORDER_LEFT + dc.GetTextExtent(getValue(row, colType)).GetWidth();
+ }
+ return 0;
+ }
+
+ static int getColumnTimeDefaultWidth(Grid& grid)
+ {
+ wxClientDC dc(&grid.getMainWin());
+ dc.SetFont(grid.getMainWin().GetFont());
+ return 2 * COLUMN_BORDER_LEFT + dc.GetTextExtent(formatTime<wxString>(FORMAT_TIME)).GetWidth();
+ }
+
+ static int getColumnCategoryDefaultWidth()
+ {
+ return GlobalResources::getImage(L"msg_small_info").GetWidth();
+ }
+
+ static int getRowDefaultHeight(const Grid& grid)
+ {
+ return std::max(GlobalResources::getImage(L"msg_small_info").GetHeight(), grid.getMainWin().GetCharHeight() + 2) + 1; //+ some space + bottom border
+ }
+
+ virtual wxString getToolTip(size_t row, ColumnType colType) const
+ {
+ MessageView::LogEntryView entry = {};
+ if (msgView_ && msgView_->getEntry(row, entry))
+ switch (static_cast<ColumnTypeMsg>(colType))
+ {
+ case COL_TYPE_MSG_TIME:
+ case COL_TYPE_MSG_TEXT:
+ break;
+
+ case COL_TYPE_MSG_CATEGORY:
+ return getValue(row, colType);
+ }
+ return wxEmptyString;
+ }
+
+ virtual wxString getColumnLabel(ColumnType colType) const { return wxEmptyString; }
+
+private:
+ const std::shared_ptr<MessageView> msgView_;
+};
}
class LogControl : public LogControlGenerated
{
public:
- LogControl(wxWindow* parent, const ErrorLog& log) : LogControlGenerated(parent), log_(log)
+ LogControl(wxWindow* parent, const ErrorLog& log) : LogControlGenerated(parent),
+ msgView(std::make_shared<MessageView>(log))
{
- const int errorCount = log_.getItemCount(TYPE_ERROR | TYPE_FATAL_ERROR);
- const int warningCount = log_.getItemCount(TYPE_WARNING);
- const int infoCount = log_.getItemCount(TYPE_INFO);
+ const int errorCount = log.getItemCount(TYPE_ERROR | TYPE_FATAL_ERROR);
+ const int warningCount = log.getItemCount(TYPE_WARNING);
+ const int infoCount = log.getItemCount(TYPE_INFO);
m_bpButtonErrors ->init(buttonPressed ("msg_error" ), buttonReleased("msg_error" ), _("Error" ) + wxString::Format(L" (%d)", errorCount ));
m_bpButtonWarnings->init(buttonPressed ("msg_warning"), buttonReleased("msg_warning"), _("Warning") + wxString::Format(L" (%d)", warningCount));
@@ -311,47 +580,49 @@ public:
m_bpButtonWarnings->Show(warningCount != 0);
m_bpButtonInfo ->Show(infoCount != 0);
- m_textCtrlInfo->SetMaxLength(0); //allow large entries!
+ //init grid, determine default sizes
+ const int rowHeight = GridDataMessages::getRowDefaultHeight(*m_gridMessages);
+ const int colMsgTimeWidth = GridDataMessages::getColumnTimeDefaultWidth(*m_gridMessages);
+ const int colMsgCategoryWidth = GridDataMessages::getColumnCategoryDefaultWidth();
+
+ m_gridMessages->setDataProvider(std::make_shared<GridDataMessages>(msgView));
+ m_gridMessages->setColumnLabelHeight(0);
+ m_gridMessages->showRowLabel(false);
+ m_gridMessages->setRowHeight(rowHeight);
+ std::vector<Grid::ColumnAttribute> attr;
+ attr.push_back(Grid::ColumnAttribute(static_cast<ColumnType>(COL_TYPE_MSG_TIME ), colMsgTimeWidth, 0));
+ attr.push_back(Grid::ColumnAttribute(static_cast<ColumnType>(COL_TYPE_MSG_CATEGORY), colMsgCategoryWidth, 0));
+ attr.push_back(Grid::ColumnAttribute(static_cast<ColumnType>(COL_TYPE_MSG_TEXT ), -colMsgTimeWidth - colMsgCategoryWidth, 1));
+ m_gridMessages->setColumnConfig(attr);
+
+ //support for CTRL + C
+ m_gridMessages->getMainWin().Connect(wxEVT_KEY_DOWN, wxKeyEventHandler(LogControl::onGridButtonEvent), nullptr, this);
- updateLogText();
+ m_gridMessages->Connect(EVENT_GRID_MOUSE_RIGHT_UP, GridClickEventHandler(LogControl::onMsgGridContext), nullptr, this);
- m_textCtrlInfo->Connect(wxEVT_KEY_DOWN, wxKeyEventHandler(LogControl::onKeyEvent), nullptr, this);
+ updateGrid();
}
private:
virtual void OnErrors(wxCommandEvent& event)
{
m_bpButtonErrors->toggle();
- updateLogText();
+ updateGrid();
}
virtual void OnWarnings(wxCommandEvent& event)
{
m_bpButtonWarnings->toggle();
- updateLogText();
+ updateGrid();
}
virtual void OnInfo(wxCommandEvent& event)
{
m_bpButtonInfo->toggle();
- updateLogText();
+ updateGrid();
}
- void onKeyEvent(wxKeyEvent& event)
- {
- const int keyCode = event.GetKeyCode();
-
- if (event.ControlDown())
- switch (keyCode)
- {
- case 'A': //CTRL + A
- m_textCtrlInfo->SetSelection(-1, -1); //select all
- return;
- }
- event.Skip();
- }
-
- void updateLogText()
+ void updateGrid()
{
int includedTypes = 0;
if (m_bpButtonErrors->isActive())
@@ -363,27 +634,83 @@ private:
if (m_bpButtonInfo->isActive())
includedTypes |= TYPE_INFO;
- //fast replacement for wxString modelling exponential growth
- MsgString logText;
+ msgView->updateView(includedTypes); //update MVC "model"
+ m_gridMessages->Refresh(); //update MVC "view"
+ }
+
- const auto& entries = log_.getEntries();
- for (auto iter = entries.begin(); iter != entries.end(); ++iter)
- if (iter->type & includedTypes)
+ void onGridButtonEvent(wxKeyEvent& event)
+ {
+ int keyCode = event.GetKeyCode();
+
+ if (event.ControlDown())
+ switch (keyCode)
{
- logText += formatMessage(*iter);
- logText += L'\n';
+ case 'C':
+ case WXK_INSERT: //CTRL + C || CTRL + INS
+ copySelectionToClipboard();
+ return; // -> swallow event! don't allow default grid commands!
}
- if (logText.empty()) //if no messages match selected view filter, at least show final status message
- if (!entries.empty())
- logText = formatMessage(entries.back());
+ event.Skip(); //unknown keypress: propagate
+ }
+
+ void onMsgGridContext(GridClickEvent& event)
+ {
+ const std::vector<size_t> selection = m_gridMessages->getSelectedRows();
+
+ ContextMenu menu;
+ menu.addItem(_("Copy") + L"\tCtrl+C", [this] { copySelectionToClipboard(); }, nullptr, !selection.empty(), wxID_COPY);
+ menu.popup(*this);
+ }
+
+ void copySelectionToClipboard()
+ {
+ try
+ {
+ typedef Zbase<wchar_t> zxString; //guaranteed exponential growth, unlike wxString
+ zxString clipboardString;
+
+ if (auto prov = m_gridMessages->getDataProvider())
+ {
+ std::vector<Grid::ColumnAttribute> colAttr = m_gridMessages->getColumnConfig();
+ vector_remove_if(colAttr, [](const Grid::ColumnAttribute& ca) { return !ca.visible_; });
+ if (!colAttr.empty())
+ {
+ const std::vector<size_t> selection = m_gridMessages->getSelectedRows();
+ std::for_each(selection.begin(), selection.end(),
+ [&](size_t row)
+ {
+#ifdef _MSC_VER
+ typedef zxString zxString; //workaround MSVC compiler bug!
+#endif
+ std::for_each(colAttr.begin(), --colAttr.end(),
+ [&](const Grid::ColumnAttribute& ca)
+ {
+ clipboardString += copyStringTo<zxString>(prov->getValue(row, ca.type_));
+ clipboardString += L'\t';
+ });
+ clipboardString += copyStringTo<zxString>(prov->getValue(row, colAttr.back().type_));
+ clipboardString += L'\n';
+ });
+ }
+ }
- wxWindowUpdateLocker dummy(m_textCtrlInfo);
- m_textCtrlInfo->ChangeValue(copyStringTo<wxString>(logText));
- m_textCtrlInfo->ShowPosition(m_textCtrlInfo->GetLastPosition());
+ //finally write to clipboard
+ if (!clipboardString.empty())
+ if (wxClipboard::Get()->Open())
+ {
+ ZEN_ON_SCOPE_EXIT(wxClipboard::Get()->Close());
+ wxClipboard::Get()->SetData(new wxTextDataObject(copyStringTo<wxString>(clipboardString))); //ownership passed
+ }
+ }
+ catch (const std::bad_alloc& e)
+ {
+ wxMessageBox(_("Out of memory!") + L" " + utfCvrtTo<std::wstring>(e.what()), _("Error"), wxOK | wxICON_ERROR);
+ }
}
- const ErrorLog log_;
+ std::shared_ptr<MessageView> msgView; //bound!
};
//########################################################################################
@@ -983,12 +1310,12 @@ void SyncStatus::SyncStatusImpl::updateProgress(bool allowYield)
if (paused_)
{
stopTimer();
+ ZEN_ON_SCOPE_EXIT(resumeTimer());
while (paused_)
{
wxMilliSleep(UI_UPDATE_INTERVAL);
updateUiNow(); //receive UI message that ends pause
}
- resumeTimer();
}
/*
/|\
@@ -1242,7 +1569,7 @@ void SyncStatus::SyncStatusImpl::processHasFinished(SyncResult resultId, const E
m_listbookResult->AddPage(logControl, _("Logging"), false);
//bSizerHoldStretch->Insert(0, logControl, 1, wxEXPAND);
- //show log instead of graph if errors occured! (not required for ignored warnings)
+ //show log instead of graph if errors occurred! (not required for ignored warnings)
if (log.getItemCount(TYPE_ERROR | TYPE_FATAL_ERROR) > 0)
m_listbookResult->ChangeSelection(posLog);
bgstack15