mirror of
https://github.com/cemu-project/Cemu.git
synced 2025-07-11 17:28:29 +12:00
Add all the files
This commit is contained in:
parent
e3db07a16a
commit
d60742f52b
1445 changed files with 430238 additions and 0 deletions
317
src/gui/components/TextList.cpp
Normal file
317
src/gui/components/TextList.cpp
Normal file
|
@ -0,0 +1,317 @@
|
|||
#include "gui/wxgui.h"
|
||||
#include "TextList.h"
|
||||
#include <wx/setup.h>
|
||||
#include <wx/tooltip.h>
|
||||
|
||||
TextList::~TextList()
|
||||
{
|
||||
m_tooltip_timer->Stop();
|
||||
|
||||
this->Unbind(wxEVT_MOTION, &TextList::OnMouseMoveEvent, this);
|
||||
this->Unbind(wxEVT_KEY_DOWN, &TextList::OnKeyDownEvent, this);
|
||||
this->Unbind(wxEVT_KEY_UP, &TextList::OnKeyUpEvent, this);
|
||||
this->Unbind(wxEVT_PAINT, &TextList::OnPaintEvent, this);
|
||||
this->Unbind(wxEVT_LEFT_DOWN, &TextList::OnMouseDownEvent, this);
|
||||
this->Unbind(wxEVT_LEFT_UP, &TextList::OnMouseUpEvent, this);
|
||||
this->Unbind(wxEVT_LEFT_DCLICK, &TextList::OnMouseDClickEvent, this);
|
||||
this->Unbind(wxEVT_CONTEXT_MENU, &TextList::OnContextMenu, this);
|
||||
this->Unbind(wxEVT_ERASE_BACKGROUND, &TextList::OnEraseBackground, this);
|
||||
}
|
||||
|
||||
TextList::TextList(wxWindow* parent, wxWindowID id, const wxPoint& pos, const wxSize& size, long style)
|
||||
: wxControl(parent, id, pos, size, style), wxScrollHelper(this)
|
||||
{
|
||||
m_font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
|
||||
wxWindowBase::SetBackgroundStyle(wxBG_STYLE_PAINT);
|
||||
|
||||
wxClientDC dc(this);
|
||||
m_line_height = dc.GetCharHeight();
|
||||
m_char_width = dc.GetCharWidth();
|
||||
|
||||
m_yScrollPixelsPerLine = m_line_height;
|
||||
|
||||
this->ShowScrollbars(wxSHOW_SB_DEFAULT, wxSHOW_SB_DEFAULT);
|
||||
|
||||
m_tooltip = new wxToolTip(wxEmptyString);
|
||||
|
||||
this->Bind(wxEVT_MOTION, &TextList::OnMouseMoveEvent, this);
|
||||
this->Bind(wxEVT_KEY_DOWN, &TextList::OnKeyDownEvent, this);
|
||||
this->Bind(wxEVT_KEY_UP, &TextList::OnKeyUpEvent, this);
|
||||
this->Bind(wxEVT_PAINT, &TextList::OnPaintEvent, this);
|
||||
this->Bind(wxEVT_LEFT_DOWN, &TextList::OnMouseDownEvent, this);
|
||||
this->Bind(wxEVT_LEFT_UP, &TextList::OnMouseUpEvent, this);
|
||||
this->Bind(wxEVT_LEFT_DCLICK, &TextList::OnMouseDClickEvent, this);
|
||||
this->Bind(wxEVT_CONTEXT_MENU, &TextList::OnContextMenu, this);
|
||||
this->Bind(wxEVT_ERASE_BACKGROUND, &TextList::OnEraseBackground, this);
|
||||
|
||||
m_tooltip_window = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxNO_BORDER);
|
||||
m_tooltip_window->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_INFOBK));
|
||||
m_tooltip_window->Hide();
|
||||
|
||||
m_tooltip_timer = new wxTimer(this);
|
||||
this->Bind(wxEVT_TIMER, &TextList::OnTooltipTimer, this);
|
||||
}
|
||||
|
||||
void TextList::RefreshControl(const wxRect* update_region)
|
||||
{
|
||||
if(update_region)
|
||||
Refresh(true, update_region);
|
||||
else
|
||||
{
|
||||
wxRect region = GetClientRect();
|
||||
update_region = ®ion;
|
||||
Refresh(true, update_region);
|
||||
}
|
||||
}
|
||||
|
||||
void TextList::RefreshLine(uint32 line)
|
||||
{
|
||||
wxRect update_region = GetClientRect();
|
||||
update_region.y = (sint32)line * m_line_height;
|
||||
update_region.height = m_line_height;
|
||||
CalcScrolledPosition(0, update_region.y, nullptr, &update_region.y);
|
||||
// debug_printf("update: <%x, %x>\n", update_region.y, update_region.height);
|
||||
Refresh(true, &update_region);
|
||||
}
|
||||
|
||||
wxSize TextList::DoGetVirtualSize() const
|
||||
{
|
||||
return {wxDefaultCoord, (int)(m_element_count + 1) * m_line_height};
|
||||
}
|
||||
|
||||
void TextList::DoSetSize(int x, int y, int width, int height, int sizeFlags)
|
||||
{
|
||||
wxControl::DoSetSize(x, y, width, height, sizeFlags);
|
||||
|
||||
m_elements_visible = (height + m_line_height - 1) / m_line_height;
|
||||
Refresh();
|
||||
}
|
||||
|
||||
void TextList::SetScrollPos(int orient, int pos, bool refresh)
|
||||
{
|
||||
wxControl::SetScrollPos(orient, pos, refresh);
|
||||
}
|
||||
|
||||
void TextList::DrawLineBackground(wxDC& dc, uint32 line, const wxColour& colour, uint32 lines) const
|
||||
{
|
||||
wxRect rect;
|
||||
rect.x = GetPosition().x;
|
||||
rect.y = line * m_line_height;
|
||||
rect.width = GetSize().x;
|
||||
rect.height = m_line_height * lines;
|
||||
|
||||
dc.SetBrush(colour);
|
||||
dc.DrawRectangle(rect);
|
||||
}
|
||||
|
||||
void TextList::DrawLineBackground(wxDC& dc, const wxPoint& position, const wxColour& colour, uint32 lines) const
|
||||
{
|
||||
wxRect rect;
|
||||
rect.x = position.x;
|
||||
rect.y = position.y;
|
||||
rect.width = this->GetSize().x;
|
||||
rect.height = m_line_height * lines;
|
||||
|
||||
dc.SetBrush(colour);
|
||||
dc.DrawRectangle(rect);
|
||||
}
|
||||
|
||||
bool TextList::SetElementCount(size_t element_count)
|
||||
{
|
||||
if (m_element_count == element_count)
|
||||
return false;
|
||||
|
||||
if (element_count > 0x7FFFFFFF)
|
||||
element_count = 0x7FFFFFFF;
|
||||
|
||||
m_element_count = element_count;
|
||||
this->AdjustScrollbars();
|
||||
return true;
|
||||
}
|
||||
|
||||
uint32 TextList::GetElementCount() const
|
||||
{
|
||||
return m_element_count;
|
||||
}
|
||||
|
||||
bool TextList::IsKeyDown(sint32 keycode)
|
||||
{
|
||||
return m_key_states[keycode];
|
||||
}
|
||||
|
||||
void TextList::WriteText(wxDC& dc, const wxString& text, wxPoint& position) const
|
||||
{
|
||||
dc.ResetBoundingBox();
|
||||
dc.DrawText(text, position);
|
||||
position.x += dc.MaxX() - dc.MinX();
|
||||
}
|
||||
|
||||
void TextList::WriteText(wxDC& dc, const wxString& text, wxPoint& position, const wxColour& color) const
|
||||
{
|
||||
dc.SetTextForeground(color);
|
||||
WriteText(dc, text, position);
|
||||
}
|
||||
|
||||
void TextList::NextLine(wxPoint& position, const wxPoint* start_position) const
|
||||
{
|
||||
position.y += m_line_height;
|
||||
|
||||
if (start_position)
|
||||
position.x = start_position->x;
|
||||
}
|
||||
|
||||
void TextList::OnMouseDown() {}
|
||||
void TextList::OnMouseUp() {}
|
||||
|
||||
bool TextList::OnShowTooltip(const wxPoint& position, uint32 line)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
void TextList::OnMouseMoveEvent(wxMouseEvent& event)
|
||||
{
|
||||
m_tooltip_timer->Stop();
|
||||
m_tooltip_timer->StartOnce(250);
|
||||
|
||||
wxPoint position = event.GetPosition();
|
||||
CalcUnscrolledPosition(position.x, position.y, &position.x, &position.y);
|
||||
|
||||
m_mouse_position = position;
|
||||
|
||||
if(m_mouse_down)
|
||||
m_selection.SetBottomRight(position);
|
||||
|
||||
const sint32 line = position.y / m_line_height;
|
||||
OnMouseMove(position, line);
|
||||
}
|
||||
|
||||
void TextList::OnKeyDownEvent(wxKeyEvent& event)
|
||||
{
|
||||
const auto key_code = event.GetKeyCode();
|
||||
const auto it = m_key_states.find(key_code);
|
||||
if(it == m_key_states.end() || !it->second)
|
||||
{
|
||||
m_key_states[key_code] = true;
|
||||
OnKeyPressed(key_code, event.GetPosition());
|
||||
}
|
||||
|
||||
event.Skip();
|
||||
}
|
||||
|
||||
void TextList::OnKeyUpEvent(wxKeyEvent& event)
|
||||
{
|
||||
m_key_states[event.GetKeyCode()] = false;
|
||||
}
|
||||
|
||||
void TextList::OnMouseDownEvent(wxMouseEvent& event)
|
||||
{
|
||||
m_mouse_down = true;
|
||||
|
||||
wxPoint position = event.GetPosition();
|
||||
CalcUnscrolledPosition(position.x, position.y, &position.x, &position.y);
|
||||
m_selection.SetPosition(position);
|
||||
m_selection.SetBottomRight(position);
|
||||
|
||||
OnMouseDown();
|
||||
|
||||
event.Skip();
|
||||
}
|
||||
|
||||
void TextList::OnMouseUpEvent(wxMouseEvent& event)
|
||||
{
|
||||
m_mouse_down = false;
|
||||
OnMouseUp();
|
||||
|
||||
event.Skip();
|
||||
}
|
||||
|
||||
void TextList::OnMouseDClickEvent(wxMouseEvent& event)
|
||||
{
|
||||
wxPoint position = event.GetPosition();
|
||||
CalcUnscrolledPosition(position.x, position.y, &position.x, &position.y);
|
||||
m_selection.SetPosition(position);
|
||||
m_selection.SetBottomRight(position);
|
||||
|
||||
const uint32 line = position.y / m_line_height;
|
||||
OnMouseDClick(position, line);
|
||||
}
|
||||
|
||||
void TextList::OnContextMenu(wxContextMenuEvent& event)
|
||||
{
|
||||
wxPoint position = event.GetPosition();
|
||||
if (position == wxDefaultPosition)
|
||||
return;
|
||||
|
||||
wxPoint clientPosition = ScreenToClient(position);
|
||||
|
||||
CalcUnscrolledPosition(clientPosition.x, clientPosition.y, &clientPosition.x, &clientPosition.y);
|
||||
m_selection.SetPosition(clientPosition);
|
||||
m_selection.SetBottomRight(clientPosition);
|
||||
|
||||
const uint32 line = clientPosition.y / m_line_height;
|
||||
OnContextMenu(clientPosition, line);
|
||||
}
|
||||
|
||||
|
||||
void TextList::OnTooltipTimer(wxTimerEvent& event)
|
||||
{
|
||||
m_tooltip_window->Hide();
|
||||
|
||||
const auto cursor_position = wxGetMousePosition();
|
||||
|
||||
const auto position = GetScreenPosition();
|
||||
if (cursor_position.x < position.x || cursor_position.y < position.y)
|
||||
return;
|
||||
|
||||
const auto size = GetSize();
|
||||
if (position.x + size.x < cursor_position.x || position.y + size.y < cursor_position.y)
|
||||
return;
|
||||
|
||||
const sint32 line = position.y / m_line_height;
|
||||
if(OnShowTooltip(m_mouse_position, line))
|
||||
{
|
||||
m_tooltip_window->SetPosition(wxPoint(m_mouse_position.x + 15, m_mouse_position.y + 15));
|
||||
m_tooltip_window->SendSizeEvent();
|
||||
m_tooltip_window->Show();
|
||||
}
|
||||
}
|
||||
|
||||
void TextList::OnPaintEvent(wxPaintEvent& event)
|
||||
{
|
||||
wxBufferedPaintDC dc(m_targetWindow, wxBUFFER_VIRTUAL_AREA);
|
||||
dc.SetFont(m_font);
|
||||
|
||||
// get window position
|
||||
auto position = GetPosition();
|
||||
|
||||
// get current real position
|
||||
wxRect rect_update = GetUpdateRegion().GetBox();
|
||||
const auto count = (uint32)std::ceil((float)rect_update.GetHeight() / m_line_height);
|
||||
|
||||
position.y = (rect_update.y / m_line_height) * m_line_height;
|
||||
|
||||
// paint background
|
||||
const wxColour window_colour = COLOR_WHITE;
|
||||
dc.SetBrush(window_colour);
|
||||
dc.SetPen(window_colour);
|
||||
dc.DrawRectangle(rect_update);
|
||||
|
||||
//// paint selection
|
||||
//if (!m_selected_text.eof())
|
||||
//{
|
||||
// dc.SetBrush(*wxBLUE_BRUSH);
|
||||
// dc.SetPen(*wxBLUE_PEN);
|
||||
// dc.DrawRectangle(m_selection);
|
||||
//}
|
||||
|
||||
sint32 start;
|
||||
CalcUnscrolledPosition(rect_update.x, rect_update.y, nullptr, &start);
|
||||
|
||||
start /= m_line_height;
|
||||
m_scrolled_to_end = (start + count) >= m_element_count;
|
||||
|
||||
OnDraw(dc, start, count, position);
|
||||
|
||||
|
||||
this->Update();
|
||||
}
|
79
src/gui/components/TextList.h
Normal file
79
src/gui/components/TextList.h
Normal file
|
@ -0,0 +1,79 @@
|
|||
#pragma once
|
||||
|
||||
#include <wx/wx.h>
|
||||
|
||||
#include <unordered_map>
|
||||
#include <sstream>
|
||||
|
||||
#define COLOR_BLACK 0xFF000000
|
||||
#define COLOR_GREY 0xFFA0A0A0
|
||||
#define COLOR_LIGHT_GREY 0xFFE0E0E0
|
||||
#define COLOR_WHITE 0xFFFFFFFF
|
||||
#define COLOR_RED 0xFF0000FF
|
||||
|
||||
|
||||
class TextList : public wxControl, public wxScrollHelper
|
||||
{
|
||||
public:
|
||||
virtual ~TextList();
|
||||
TextList(wxWindow* parent, wxWindowID id,
|
||||
const wxPoint& pos = wxDefaultPosition,
|
||||
const wxSize& size = wxDefaultSize, long style = 0);
|
||||
|
||||
void RefreshControl(const wxRect* update_region = nullptr);
|
||||
void RefreshLine(uint32 line);
|
||||
|
||||
wxSize DoGetVirtualSize() const override;
|
||||
void DoSetSize(int x, int y, int width, int height, int sizeFlags) override;
|
||||
void SetScrollPos(int orient, int pos, bool refresh) override;
|
||||
void DrawLineBackground(wxDC& dc, uint32 line, const wxColour& colour, uint32 lines = 1) const;
|
||||
void DrawLineBackground(wxDC& dc, const wxPoint& position, const wxColour& colour, uint32 lines = 1) const;
|
||||
|
||||
bool SetElementCount(size_t element_count);
|
||||
uint32 GetElementCount() const;
|
||||
bool IsKeyDown(sint32 keycode);
|
||||
|
||||
protected:
|
||||
void WriteText(wxDC& dc, const wxString& text, wxPoint& position) const;
|
||||
void WriteText(wxDC& dc, const wxString& text, wxPoint& position, const wxColour& color) const;
|
||||
void NextLine(wxPoint& position, const wxPoint* start_position = nullptr) const;
|
||||
|
||||
virtual void OnDraw(wxDC& dc, sint32 start, sint32 count, const wxPoint& start_position) {};
|
||||
virtual void OnMouseMove(const wxPoint& position, uint32 line) {}
|
||||
virtual void OnKeyPressed(sint32 key_code, const wxPoint& position) {}
|
||||
virtual void OnMouseDown();
|
||||
virtual void OnMouseUp();
|
||||
virtual void OnMouseDClick(const wxPoint& position, uint32 line) {}
|
||||
virtual void OnContextMenu(const wxPoint& position, uint32 line) {}
|
||||
virtual bool OnShowTooltip(const wxPoint& position, uint32 line);
|
||||
void OnEraseBackground(wxEraseEvent&) {}
|
||||
|
||||
sint32 m_line_height;
|
||||
sint32 m_char_width;
|
||||
size_t m_elements_visible = 0;
|
||||
size_t m_element_count = 0;
|
||||
bool m_scrolled_to_end = true;
|
||||
|
||||
std::wstringstream m_selected_text;
|
||||
bool m_mouse_down = false;
|
||||
wxRect m_selection;
|
||||
wxPanel* m_tooltip_window;
|
||||
|
||||
private:
|
||||
void OnPaintEvent(wxPaintEvent& event);
|
||||
void OnMouseMoveEvent(wxMouseEvent& event);
|
||||
void OnKeyDownEvent(wxKeyEvent& event);
|
||||
void OnKeyUpEvent(wxKeyEvent& event);
|
||||
void OnMouseDownEvent(wxMouseEvent& event);
|
||||
void OnMouseUpEvent(wxMouseEvent& event);
|
||||
void OnMouseDClickEvent(wxMouseEvent& event);
|
||||
void OnContextMenu(wxContextMenuEvent& event);
|
||||
void OnTooltipTimer(wxTimerEvent& event);
|
||||
|
||||
std::unordered_map<sint32, bool> m_key_states;
|
||||
|
||||
wxFont m_font;
|
||||
|
||||
wxTimer* m_tooltip_timer;
|
||||
wxPoint m_mouse_position;
|
||||
};
|
779
src/gui/components/wxDownloadManagerList.cpp
Normal file
779
src/gui/components/wxDownloadManagerList.cpp
Normal file
|
@ -0,0 +1,779 @@
|
|||
#include "gui/components/wxDownloadManagerList.h"
|
||||
|
||||
#include "gui/helpers/wxHelpers.h"
|
||||
#include "util/helpers/SystemException.h"
|
||||
#include "Cafe/TitleList/GameInfo.h"
|
||||
#include "gui/components/wxGameList.h"
|
||||
#include "gui/helpers/wxCustomEvents.h"
|
||||
|
||||
#include <wx/imaglist.h>
|
||||
#include <wx/wupdlock.h>
|
||||
#include <wx/menu.h>
|
||||
#include <wx/msgdlg.h>
|
||||
#include <wx/stattext.h>
|
||||
#include <wx/sizer.h>
|
||||
#include <wx/timer.h>
|
||||
#include <wx/panel.h>
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include "config/ActiveSettings.h"
|
||||
#include "gui/ChecksumTool.h"
|
||||
#include "Cemu/Tools/DownloadManager/DownloadManager.h"
|
||||
#include "Cafe/TitleList/TitleId.h"
|
||||
#include "gui/MainWindow.h"
|
||||
|
||||
wxDEFINE_EVENT(wxEVT_REMOVE_ENTRY, wxCommandEvent);
|
||||
|
||||
|
||||
wxDownloadManagerList::wxDownloadManagerList(wxWindow* parent, wxWindowID id)
|
||||
: wxListCtrl(parent, id, wxDefaultPosition, wxDefaultSize, wxLC_REPORT | wxLC_VIRTUAL)
|
||||
{
|
||||
AddColumns();
|
||||
|
||||
// tooltip TODO: extract class mb wxPanelTooltip
|
||||
m_tooltip_window = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxNO_BORDER);
|
||||
auto* tooltip_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
m_tooltip_text = new wxStaticText(m_tooltip_window, wxID_ANY, wxEmptyString);
|
||||
tooltip_sizer->Add(m_tooltip_text , 0, wxALL, 5);
|
||||
m_tooltip_window->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_INFOBK));
|
||||
m_tooltip_window->SetSizerAndFit(tooltip_sizer);
|
||||
m_tooltip_window->Hide();
|
||||
m_tooltip_timer = new wxTimer(this);
|
||||
|
||||
Bind(wxEVT_LIST_COL_CLICK, &wxDownloadManagerList::OnColumnClick, this);
|
||||
Bind(wxEVT_CONTEXT_MENU, &wxDownloadManagerList::OnContextMenu, this);
|
||||
Bind(wxEVT_LIST_ITEM_SELECTED, &wxDownloadManagerList::OnItemSelected, this);
|
||||
Bind(wxEVT_TIMER, &wxDownloadManagerList::OnTimer, this);
|
||||
Bind(wxEVT_REMOVE_ITEM, &wxDownloadManagerList::OnRemoveItem, this);
|
||||
Bind(wxEVT_REMOVE_ENTRY, &wxDownloadManagerList::OnRemoveEntry, this);
|
||||
Bind(wxEVT_CLOSE_WINDOW, &wxDownloadManagerList::OnClose, this);
|
||||
}
|
||||
|
||||
boost::optional<const wxDownloadManagerList::TitleEntry&> wxDownloadManagerList::GetSelectedTitleEntry() const
|
||||
{
|
||||
const auto selection = GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
|
||||
if (selection != wxNOT_FOUND)
|
||||
{
|
||||
const auto tmp = GetTitleEntry(selection);
|
||||
if (tmp.has_value())
|
||||
return tmp.value();
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
boost::optional<wxDownloadManagerList::TitleEntry&> wxDownloadManagerList::GetSelectedTitleEntry()
|
||||
{
|
||||
const auto selection = GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
|
||||
if (selection != wxNOT_FOUND)
|
||||
{
|
||||
const auto tmp = GetTitleEntry(selection);
|
||||
if (tmp.has_value())
|
||||
return tmp.value();
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
boost::optional<wxDownloadManagerList::TitleEntry&> wxDownloadManagerList::GetTitleEntry(uint64 titleId, uint16 titleVersion)
|
||||
{
|
||||
for(const auto& v : m_data)
|
||||
{
|
||||
if (v->entry.titleId == titleId && v->entry.version == titleVersion)
|
||||
return v->entry;
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
void wxDownloadManagerList::AddColumns()
|
||||
{
|
||||
wxListItem col0;
|
||||
col0.SetId(ColumnTitleId);
|
||||
col0.SetText(_("Title id"));
|
||||
col0.SetWidth(120);
|
||||
InsertColumn(ColumnTitleId, col0);
|
||||
|
||||
wxListItem col1;
|
||||
col1.SetId(ColumnName);
|
||||
col1.SetText(_("Name"));
|
||||
col1.SetWidth(260);
|
||||
InsertColumn(ColumnName, col1);
|
||||
|
||||
wxListItem col2;
|
||||
col2.SetId(ColumnVersion);
|
||||
col2.SetText(_("Version"));
|
||||
col2.SetWidth(55);
|
||||
InsertColumn(ColumnVersion, col2);
|
||||
|
||||
wxListItem col3;
|
||||
col3.SetId(ColumnType);
|
||||
col3.SetText(_("Type"));
|
||||
col3.SetWidth(60);
|
||||
InsertColumn(ColumnType, col3);
|
||||
|
||||
wxListItem col4;
|
||||
col4.SetId(ColumnProgress);
|
||||
col4.SetText(_("Progress"));
|
||||
col4.SetWidth(wxLIST_AUTOSIZE_USEHEADER);
|
||||
InsertColumn(ColumnProgress, col4);
|
||||
|
||||
wxListItem col5;
|
||||
col5.SetId(ColumnStatus);
|
||||
col5.SetText(_("Status"));
|
||||
col5.SetWidth(240);
|
||||
InsertColumn(ColumnStatus, col5);
|
||||
}
|
||||
|
||||
wxString wxDownloadManagerList::OnGetItemText(long item, long column) const
|
||||
{
|
||||
if (item >= GetItemCount())
|
||||
return wxEmptyString;
|
||||
|
||||
const auto entry = GetTitleEntry(item);
|
||||
if (entry.has_value())
|
||||
return GetTitleEntryText(entry.value(), (ItemColumn)column);
|
||||
|
||||
return wxEmptyString;
|
||||
}
|
||||
|
||||
wxItemAttr* wxDownloadManagerList::OnGetItemAttr(long item) const
|
||||
{
|
||||
const auto entry = GetTitleEntry(item);
|
||||
if (entry.has_value())
|
||||
{
|
||||
auto& entryData = entry.value();
|
||||
if (entryData.status == TitleDownloadStatus::Downloading ||
|
||||
entryData.status == TitleDownloadStatus::Verifying ||
|
||||
entryData.status == TitleDownloadStatus::Installing)
|
||||
{
|
||||
const wxColour kActiveColor{ 0xFFE0E0 };
|
||||
static wxListItemAttr s_error_attr(GetTextColour(), kActiveColor, GetFont());
|
||||
return &s_error_attr;
|
||||
}
|
||||
else if (entryData.status == TitleDownloadStatus::Installed && entryData.isPackage)
|
||||
{
|
||||
const wxColour kActiveColor{ 0xE0FFE0 };
|
||||
static wxListItemAttr s_error_attr(GetTextColour(), kActiveColor, GetFont());
|
||||
return &s_error_attr;
|
||||
}
|
||||
else if (entryData.status == TitleDownloadStatus::Error)
|
||||
{
|
||||
const wxColour kActiveColor{ 0xCCCCF2 };
|
||||
static wxListItemAttr s_error_attr(GetTextColour(), kActiveColor, GetFont());
|
||||
return &s_error_attr;
|
||||
}
|
||||
}
|
||||
|
||||
const wxColour kSecondColor{ 0xFDF9F2 };
|
||||
static wxListItemAttr s_coloured_attr(GetTextColour(), kSecondColor, GetFont());
|
||||
return item % 2 == 0 ? nullptr : &s_coloured_attr;
|
||||
}
|
||||
|
||||
boost::optional<wxDownloadManagerList::TitleEntry&> wxDownloadManagerList::GetTitleEntry(long item)
|
||||
{
|
||||
long counter = 0;
|
||||
for (const auto& data : m_sorted_data)
|
||||
{
|
||||
if (!data.get().visible)
|
||||
continue;
|
||||
|
||||
if (item != counter++)
|
||||
continue;
|
||||
|
||||
return data.get().entry;
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
boost::optional<const wxDownloadManagerList::TitleEntry&> wxDownloadManagerList::GetTitleEntry(long item) const
|
||||
{
|
||||
long counter = 0;
|
||||
for (const auto& data : m_sorted_data)
|
||||
{
|
||||
if (!data.get().visible)
|
||||
continue;
|
||||
|
||||
if (item != counter++)
|
||||
continue;
|
||||
|
||||
return data.get().entry;
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
void wxDownloadManagerList::OnClose(wxCloseEvent& event)
|
||||
{
|
||||
event.Skip();
|
||||
// wait until all tasks are complete
|
||||
if (m_context_worker.valid())
|
||||
m_context_worker.get();
|
||||
g_mainFrame->RequestGameListRefresh(); // todo: add games instead of fully refreshing game list if a game is downloaded
|
||||
}
|
||||
|
||||
void wxDownloadManagerList::OnColumnClick(wxListEvent& event)
|
||||
{
|
||||
const int column = event.GetColumn();
|
||||
|
||||
if (column == m_sort_by_column)
|
||||
{
|
||||
m_sort_less = !m_sort_less;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_sort_by_column = column;
|
||||
m_sort_less = true;
|
||||
}
|
||||
SortEntries();
|
||||
event.Skip();
|
||||
}
|
||||
|
||||
void wxDownloadManagerList::RemoveItem(long item)
|
||||
{
|
||||
const int item_count = GetItemCount();
|
||||
|
||||
const ItemData* ref = nullptr;
|
||||
long counter = 0;
|
||||
for(auto it = m_sorted_data.begin(); it != m_sorted_data.end(); ++it)
|
||||
{
|
||||
if (!it->get().visible)
|
||||
continue;
|
||||
|
||||
if (item != counter++)
|
||||
continue;
|
||||
|
||||
ref = &(it->get());
|
||||
m_sorted_data.erase(it);
|
||||
break;
|
||||
}
|
||||
|
||||
// shouldn't happen
|
||||
if (ref == nullptr)
|
||||
return;
|
||||
|
||||
for(auto it = m_data.begin(); it != m_data.end(); ++it)
|
||||
{
|
||||
if (ref != (*it).get())
|
||||
continue;
|
||||
|
||||
m_data.erase(it);
|
||||
break;
|
||||
}
|
||||
|
||||
SetItemCount(std::max(0, item_count - 1));
|
||||
RefreshPage();
|
||||
}
|
||||
|
||||
void wxDownloadManagerList::RemoveItem(const TitleEntry& entry)
|
||||
{
|
||||
const int item_count = GetItemCount();
|
||||
|
||||
const TitleEntry* ref = &entry;
|
||||
for (auto it = m_sorted_data.begin(); it != m_sorted_data.end(); ++it)
|
||||
{
|
||||
if (ref != &it->get().entry)
|
||||
continue;
|
||||
|
||||
m_sorted_data.erase(it);
|
||||
break;
|
||||
}
|
||||
|
||||
for (auto it = m_data.begin(); it != m_data.end(); ++it)
|
||||
{
|
||||
if (ref != &(*it).get()->entry)
|
||||
continue;
|
||||
|
||||
m_data.erase(it);
|
||||
break;
|
||||
}
|
||||
|
||||
SetItemCount(std::max(0, item_count - 1));
|
||||
RefreshPage();
|
||||
}
|
||||
|
||||
void wxDownloadManagerList::OnItemSelected(wxListEvent& event)
|
||||
{
|
||||
event.Skip();
|
||||
m_tooltip_timer->Stop();
|
||||
const auto selection = event.GetIndex();
|
||||
|
||||
if (selection == wxNOT_FOUND)
|
||||
{
|
||||
m_tooltip_window->Hide();
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
enum ContextMenuEntries
|
||||
{
|
||||
kContextMenuRetry,
|
||||
kContextMenuDownload,
|
||||
kContextMenuPause,
|
||||
kContextMenuResume,
|
||||
};
|
||||
void wxDownloadManagerList::OnContextMenu(wxContextMenuEvent& event)
|
||||
{
|
||||
// still doing work
|
||||
if (m_context_worker.valid() && !future_is_ready(m_context_worker))
|
||||
return;
|
||||
|
||||
wxMenu menu;
|
||||
menu.Bind(wxEVT_COMMAND_MENU_SELECTED, &wxDownloadManagerList::OnContextMenuSelected, this);
|
||||
|
||||
const auto selection = GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
|
||||
if (selection == wxNOT_FOUND)
|
||||
return;
|
||||
|
||||
const auto entry = GetTitleEntry(selection);
|
||||
if (!entry.has_value())
|
||||
return;
|
||||
|
||||
if (entry->isPaused)
|
||||
menu.Append(kContextMenuResume, _("&Resume"));
|
||||
else if (entry->status == TitleDownloadStatus::Error)
|
||||
menu.Append(kContextMenuRetry, _("&Retry"));
|
||||
else if(entry->status == TitleDownloadStatus::Available)
|
||||
menu.Append(kContextMenuDownload, _("&Download"));
|
||||
//else if(entry->status == TitleDownloadStatus::Downloading || entry->status == TitleDownloadStatus::Initializing)
|
||||
// menu.Append(kContextMenuPause, _("&Pause")); buggy!
|
||||
|
||||
PopupMenu(&menu);
|
||||
|
||||
// TODO: fix tooltip position
|
||||
}
|
||||
|
||||
void wxDownloadManagerList::SetCurrentDownloadMgr(DownloadManager* dlMgr)
|
||||
{
|
||||
std::unique_lock<std::mutex> _l(m_dlMgrMutex);
|
||||
m_dlMgr = dlMgr;
|
||||
}
|
||||
|
||||
bool wxDownloadManagerList::StartDownloadEntry(const TitleEntry& entry)
|
||||
{
|
||||
std::unique_lock<std::mutex> _l(m_dlMgrMutex);
|
||||
if (m_dlMgr)
|
||||
m_dlMgr->initiateDownload(entry.titleId, entry.version);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool wxDownloadManagerList::RetryDownloadEntry(const TitleEntry& entry)
|
||||
{
|
||||
StartDownloadEntry(entry);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool wxDownloadManagerList::PauseDownloadEntry(const TitleEntry& entry)
|
||||
{
|
||||
std::unique_lock<std::mutex> _l(m_dlMgrMutex);
|
||||
if (m_dlMgr)
|
||||
m_dlMgr->pauseDownload(entry.titleId, entry.version);
|
||||
return true;
|
||||
}
|
||||
|
||||
void wxDownloadManagerList::OnContextMenuSelected(wxCommandEvent& event)
|
||||
{
|
||||
// still doing work
|
||||
if (m_context_worker.valid() && !future_is_ready(m_context_worker))
|
||||
return;
|
||||
|
||||
const auto selection = GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
|
||||
if (selection == wxNOT_FOUND)
|
||||
return;
|
||||
|
||||
const auto entry = GetTitleEntry(selection);
|
||||
if (!entry.has_value())
|
||||
return;
|
||||
|
||||
switch (event.GetId())
|
||||
{
|
||||
case kContextMenuDownload:
|
||||
StartDownloadEntry(entry.value());
|
||||
break;
|
||||
case kContextMenuRetry:
|
||||
RetryDownloadEntry(entry.value());
|
||||
break;
|
||||
case kContextMenuResume:
|
||||
StartDownloadEntry(entry.value());
|
||||
break;
|
||||
case kContextMenuPause:
|
||||
PauseDownloadEntry(entry.value());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void wxDownloadManagerList::OnTimer(wxTimerEvent& event)
|
||||
{
|
||||
if(event.GetTimer().GetId() != m_tooltip_timer->GetId())
|
||||
{
|
||||
event.Skip();
|
||||
return;
|
||||
}
|
||||
|
||||
m_tooltip_window->Show();
|
||||
}
|
||||
|
||||
void wxDownloadManagerList::OnRemoveItem(wxCommandEvent& event)
|
||||
{
|
||||
RemoveItem(event.GetInt());
|
||||
}
|
||||
|
||||
void wxDownloadManagerList::OnRemoveEntry(wxCommandEvent& event)
|
||||
{
|
||||
wxASSERT(event.GetClientData() != nullptr);
|
||||
RemoveItem(*(TitleEntry*)event.GetClientData());
|
||||
}
|
||||
|
||||
wxString wxDownloadManagerList::GetTitleEntryText(const TitleEntry& entry, ItemColumn column)
|
||||
{
|
||||
switch (column)
|
||||
{
|
||||
case ColumnTitleId:
|
||||
return wxStringFormat2("{:08x}-{:08x}", (uint32)(entry.titleId >> 32), (uint32)(entry.titleId & 0xFFFFFFFF));
|
||||
case ColumnName:
|
||||
{
|
||||
return entry.name;
|
||||
}
|
||||
case ColumnType:
|
||||
return wxStringFormat2("{}", entry.type);
|
||||
case ColumnVersion:
|
||||
{
|
||||
// dont show version for base game unless it is not v0
|
||||
if (entry.type == EntryType::Base && entry.version == 0)
|
||||
return "";
|
||||
if (entry.type == EntryType::DLC && entry.version == 0)
|
||||
return "";
|
||||
return wxStringFormat2("v{}", entry.version);
|
||||
}
|
||||
case ColumnProgress:
|
||||
{
|
||||
if (entry.status == TitleDownloadStatus::Downloading)
|
||||
{
|
||||
if (entry.progress >= 1000)
|
||||
return "100%";
|
||||
return wxStringFormat2("{:.1f}%", (float)entry.progress / 10.0f); // one decimal
|
||||
}
|
||||
else if (entry.status == TitleDownloadStatus::Installing || entry.status == TitleDownloadStatus::Checking || entry.status == TitleDownloadStatus::Verifying)
|
||||
{
|
||||
return wxStringFormat2("{0}/{1}", entry.progress, entry.progressMax); // number of processed files/content files
|
||||
}
|
||||
return "";
|
||||
}
|
||||
case ColumnStatus:
|
||||
{
|
||||
if (entry.isPaused)
|
||||
return _("Paused");
|
||||
else if (entry.status == TitleDownloadStatus::Available)
|
||||
{
|
||||
if (entry.progress == 1)
|
||||
return _("Not installed (Partially downloaded)");
|
||||
if (entry.progress == 2)
|
||||
return _("Update available");
|
||||
return _("Not installed");
|
||||
}
|
||||
else if (entry.status == TitleDownloadStatus::Initializing)
|
||||
return _("Initializing");
|
||||
else if (entry.status == TitleDownloadStatus::Checking)
|
||||
return _("Checking");
|
||||
else if (entry.status == TitleDownloadStatus::Queued)
|
||||
return _("Queued");
|
||||
else if (entry.status == TitleDownloadStatus::Downloading)
|
||||
return _("Downloading");
|
||||
else if (entry.status == TitleDownloadStatus::Verifying)
|
||||
return _("Verifying");
|
||||
else if (entry.status == TitleDownloadStatus::Installing)
|
||||
return _("Installing");
|
||||
else if (entry.status == TitleDownloadStatus::Installed)
|
||||
return _("Installed");
|
||||
else if (entry.status == TitleDownloadStatus::Error)
|
||||
{
|
||||
wxString errorStatusMsg = _("Error:");
|
||||
errorStatusMsg.append(" ");
|
||||
errorStatusMsg.append(entry.errorMsg);
|
||||
return errorStatusMsg;
|
||||
}
|
||||
else
|
||||
return "Unknown status";
|
||||
}
|
||||
}
|
||||
|
||||
return wxEmptyString;
|
||||
}
|
||||
|
||||
void wxDownloadManagerList::AddOrUpdateTitle(TitleEntryData_t* obj)
|
||||
{
|
||||
const auto& data = obj->GetData();
|
||||
// if already in list only update
|
||||
auto entry = GetTitleEntry(data.titleId, data.version);
|
||||
if (entry.has_value())
|
||||
{
|
||||
// update item
|
||||
entry.value() = data;
|
||||
RefreshPage(); // more efficient way to do this?
|
||||
return;
|
||||
}
|
||||
|
||||
m_data.emplace_back(std::make_unique<ItemData>(true, data));
|
||||
m_sorted_data.emplace_back(*m_data[m_data.size() - 1]);
|
||||
SetItemCount(m_data.size());
|
||||
|
||||
// reapply sort
|
||||
Filter2(m_filterShowTitles, m_filterShowUpdates, m_filterShowInstalled);
|
||||
SortEntries();
|
||||
}
|
||||
|
||||
void wxDownloadManagerList::UpdateTitleStatusDepr(TitleEntryData_t* obj, const wxString& text)
|
||||
{
|
||||
const auto& data = obj->GetData();
|
||||
const auto entry = GetTitleEntry(data.titleId, data.version);
|
||||
// check if already added to list
|
||||
if (!entry.has_value())
|
||||
return;
|
||||
|
||||
// update gamelist text
|
||||
for(size_t i = 0; i < m_sorted_data.size(); ++i)
|
||||
{
|
||||
if (m_sorted_data[i].get().entry == data)
|
||||
{
|
||||
SetItem(i, ColumnStatus, text);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
forceLogDebug_printf("cant update title status of %llx", data.titleId);
|
||||
}
|
||||
|
||||
int wxDownloadManagerList::AddImage(const wxImage& image) const
|
||||
{
|
||||
return -1; // m_image_list->Add(image.Scale(kListIconWidth, kListIconWidth, wxIMAGE_QUALITY_BICUBIC));
|
||||
}
|
||||
|
||||
// return <
|
||||
bool wxDownloadManagerList::SortFunc(std::span<int> sortColumnOrder, const Type_t& v1, const Type_t& v2)
|
||||
{
|
||||
cemu_assert_debug(sortColumnOrder.size() == 5);
|
||||
|
||||
// visible have always priority
|
||||
if (!v1.get().visible && v2.get().visible)
|
||||
return false;
|
||||
else if (v1.get().visible && !v2.get().visible)
|
||||
return true;
|
||||
|
||||
const auto& entry1 = v1.get().entry;
|
||||
const auto& entry2 = v2.get().entry;
|
||||
|
||||
for (size_t i = 0; i < sortColumnOrder.size(); i++)
|
||||
{
|
||||
int sortByColumn = sortColumnOrder[i];
|
||||
if (sortByColumn == ColumnTitleId)
|
||||
{
|
||||
// ensure strong ordering -> use type since only one entry should be now (should be changed if every save for every user is displayed spearately?)
|
||||
if (entry1.titleId != entry2.titleId)
|
||||
return entry1.titleId < entry2.titleId;
|
||||
}
|
||||
else if (sortByColumn == ColumnName)
|
||||
{
|
||||
const int tmp = entry1.name.CmpNoCase(entry2.name);
|
||||
if (tmp != 0)
|
||||
return tmp < 0;
|
||||
}
|
||||
else if (sortByColumn == ColumnType)
|
||||
{
|
||||
if (std::underlying_type_t<EntryType>(entry1.type) != std::underlying_type_t<EntryType>(entry2.type))
|
||||
return std::underlying_type_t<EntryType>(entry1.type) < std::underlying_type_t<EntryType>(entry2.type);
|
||||
}
|
||||
else if (sortByColumn == ColumnVersion)
|
||||
{
|
||||
if (entry1.version != entry2.version)
|
||||
return entry1.version < entry2.version;
|
||||
}
|
||||
else if (sortByColumn == ColumnProgress)
|
||||
{
|
||||
if (entry1.progress != entry2.progress)
|
||||
return entry1.progress < entry2.progress;
|
||||
}
|
||||
else
|
||||
{
|
||||
cemu_assert_debug(false);
|
||||
return (uintptr_t)&entry1 < (uintptr_t)&entry2;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
#include <boost/container/small_vector.hpp>
|
||||
|
||||
void wxDownloadManagerList::SortEntries()
|
||||
{
|
||||
boost::container::small_vector<int, 12> s_SortColumnOrder{ ColumnName, ColumnType, ColumnVersion, ColumnTitleId, ColumnProgress };
|
||||
|
||||
if (m_sort_by_column != -1)
|
||||
{
|
||||
// prioritize column by moving it to first position in the column sort order list
|
||||
s_SortColumnOrder.erase(std::remove(s_SortColumnOrder.begin(), s_SortColumnOrder.end(), m_sort_by_column), s_SortColumnOrder.end());
|
||||
s_SortColumnOrder.insert(s_SortColumnOrder.begin(), m_sort_by_column);
|
||||
}
|
||||
|
||||
std::sort(m_sorted_data.begin(), m_sorted_data.end(),
|
||||
[this, &s_SortColumnOrder](const Type_t& v1, const Type_t& v2) -> bool
|
||||
{
|
||||
const bool result = SortFunc({ s_SortColumnOrder.data(), s_SortColumnOrder.size() }, v1, v2);
|
||||
return m_sort_less ? result : !result;
|
||||
});
|
||||
|
||||
RefreshPage();
|
||||
}
|
||||
|
||||
void wxDownloadManagerList::RefreshPage() { RefreshItems(GetTopItem(), GetTopItem() + GetCountPerPage() + 1); }
|
||||
|
||||
int wxDownloadManagerList::Filter(const wxString& filter, const wxString& prefix, ItemColumn column)
|
||||
{
|
||||
if (prefix.empty())
|
||||
return -1;
|
||||
|
||||
if (!filter.StartsWith(prefix))
|
||||
return -1;
|
||||
|
||||
int counter = 0;
|
||||
const auto tmp_filter = filter.substr(prefix.size() - 1).Trim(false);
|
||||
for (auto&& data : m_data)
|
||||
{
|
||||
if (GetTitleEntryText(data->entry, column).Upper().Contains(tmp_filter))
|
||||
{
|
||||
data->visible = true;
|
||||
++counter;
|
||||
}
|
||||
else
|
||||
data->visible = false;
|
||||
}
|
||||
return counter;
|
||||
}
|
||||
|
||||
void wxDownloadManagerList::Filter(const wxString& filter)
|
||||
{
|
||||
if(filter.empty())
|
||||
{
|
||||
std::for_each(m_data.begin(), m_data.end(), [](ItemDataPtr& data) { data->visible = true; });
|
||||
SetItemCount(m_data.size());
|
||||
RefreshPage();
|
||||
return;
|
||||
}
|
||||
|
||||
const auto filter_upper = filter.Upper().Trim(false).Trim(true);
|
||||
int counter = 0;
|
||||
|
||||
if (const auto result = Filter(filter_upper, "TITLEID:", ColumnTitleId) != -1)
|
||||
counter = result;
|
||||
else if (const auto result = Filter(filter_upper, "NAME:", ColumnName) != -1)
|
||||
counter = result;
|
||||
else if (const auto result = Filter(filter_upper, "TYPE:", ColumnType) != -1)
|
||||
counter = result;
|
||||
//else if (const auto result = Filter(filter_upper, "REGION:", ColumnRegion) != -1)
|
||||
// counter = result;
|
||||
else if (const auto result = Filter(filter_upper, "VERSION:", ColumnVersion) != -1)
|
||||
counter = result;
|
||||
else if(filter_upper == "ERROR")
|
||||
{
|
||||
for (auto&& data : m_data)
|
||||
{
|
||||
bool visible = false;
|
||||
//if (data->entry.error != TitleError::None)
|
||||
// visible = true;
|
||||
|
||||
data->visible = visible;
|
||||
if (visible)
|
||||
++counter;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (auto&& data : m_data)
|
||||
{
|
||||
bool visible = false;
|
||||
if (data->entry.name.Upper().Contains(filter_upper))
|
||||
visible = true;
|
||||
else if (GetTitleEntryText(data->entry, ColumnTitleId).Upper().Contains(filter_upper))
|
||||
visible = true;
|
||||
else if (GetTitleEntryText(data->entry, ColumnType).Upper().Contains(filter_upper))
|
||||
visible = true;
|
||||
|
||||
data->visible = visible;
|
||||
if (visible)
|
||||
++counter;
|
||||
}
|
||||
}
|
||||
|
||||
SetItemCount(counter);
|
||||
RefreshPage();
|
||||
}
|
||||
|
||||
void wxDownloadManagerList::Filter2(bool showTitles, bool showUpdates, bool showInstalled)
|
||||
{
|
||||
m_filterShowTitles = showTitles;
|
||||
m_filterShowUpdates = showUpdates;
|
||||
m_filterShowInstalled = showInstalled;
|
||||
if (showTitles && showUpdates && showInstalled)
|
||||
{
|
||||
std::for_each(m_data.begin(), m_data.end(), [](ItemDataPtr& data) { data->visible = true; });
|
||||
SetItemCount(m_data.size());
|
||||
RefreshPage();
|
||||
return;
|
||||
}
|
||||
|
||||
size_t counter = 0;
|
||||
|
||||
for (auto&& data : m_data)
|
||||
{
|
||||
bool visible = false;
|
||||
|
||||
TitleIdParser tParser(data->entry.titleId);
|
||||
bool isInstalled = data->entry.status == TitleDownloadStatus::Installed;
|
||||
if (tParser.IsBaseTitleUpdate())
|
||||
{
|
||||
if (showUpdates && (showInstalled || !isInstalled))
|
||||
visible = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (showTitles && (showInstalled || !isInstalled))
|
||||
visible = true;
|
||||
}
|
||||
|
||||
data->visible = visible;
|
||||
if (visible)
|
||||
++counter;
|
||||
}
|
||||
|
||||
SetItemCount(counter);
|
||||
RefreshPage();
|
||||
}
|
||||
|
||||
size_t wxDownloadManagerList::GetCountByType(EntryType type) const
|
||||
{
|
||||
size_t result = 0;
|
||||
for(const auto& data : m_data)
|
||||
{
|
||||
if (data->entry.type == type)
|
||||
++result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void wxDownloadManagerList::ClearItems()
|
||||
{
|
||||
m_sorted_data.clear();
|
||||
m_data.clear();
|
||||
SetItemCount(0);
|
||||
RefreshPage();
|
||||
}
|
||||
|
||||
void wxDownloadManagerList::AutosizeColumns()
|
||||
{
|
||||
wxAutosizeColumns(this, ColumnTitleId, ColumnMAX - 1);
|
||||
}
|
||||
|
174
src/gui/components/wxDownloadManagerList.h
Normal file
174
src/gui/components/wxDownloadManagerList.h
Normal file
|
@ -0,0 +1,174 @@
|
|||
#pragma once
|
||||
|
||||
#include "gui/helpers/wxCustomData.h"
|
||||
#include "config/CemuConfig.h"
|
||||
|
||||
#include <wx/listctrl.h>
|
||||
|
||||
#include <boost/optional.hpp> // std::optional doesn't support optional reference inner types yet
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
class wxDownloadManagerList : public wxListCtrl
|
||||
{
|
||||
friend class TitleManager;
|
||||
public:
|
||||
wxDownloadManagerList(wxWindow* parent, wxWindowID id = wxID_ANY);
|
||||
|
||||
enum ItemColumn
|
||||
{
|
||||
ColumnTitleId = 0,
|
||||
ColumnName,
|
||||
ColumnVersion,
|
||||
ColumnType,
|
||||
ColumnProgress,
|
||||
ColumnStatus,
|
||||
|
||||
ColumnMAX,
|
||||
};
|
||||
|
||||
enum class EntryType
|
||||
{
|
||||
Base,
|
||||
Update,
|
||||
DLC,
|
||||
};
|
||||
|
||||
enum class TitleDownloadStatus
|
||||
{
|
||||
None,
|
||||
Available, // available for download
|
||||
Error, // error state
|
||||
Queued, // queued for download
|
||||
Initializing, // downloading/parsing TMD
|
||||
Checking, // checking for previously downloaded files
|
||||
Downloading,
|
||||
Verifying, // verifying downloaded files
|
||||
Installing,
|
||||
Installed,
|
||||
// error state?
|
||||
};
|
||||
|
||||
void SortEntries();
|
||||
void RefreshPage();
|
||||
void Filter(const wxString& filter);
|
||||
void Filter2(bool showTitles, bool showUpdates, bool showInstalled);
|
||||
[[nodiscard]] size_t GetCountByType(EntryType type) const;
|
||||
void ClearItems();
|
||||
void AutosizeColumns();
|
||||
|
||||
struct TitleEntry
|
||||
{
|
||||
TitleEntry(const EntryType& type, bool isPackage, uint64 titleId, uint16 version, bool isPaused)
|
||||
: type(type), isPackage(isPackage), titleId(titleId), version(version), isPaused(isPaused) {}
|
||||
|
||||
EntryType type;
|
||||
|
||||
bool isPaused{};
|
||||
int icon = -1;
|
||||
bool isPackage;
|
||||
uint64 titleId;
|
||||
wxString name;
|
||||
uint32_t version{ 0 };
|
||||
uint32 progress; // downloading: in 1/10th of a percent, installing: number of files
|
||||
uint32 progressMax{ 0 };
|
||||
CafeConsoleRegion region;
|
||||
|
||||
TitleDownloadStatus status = TitleDownloadStatus::None;
|
||||
std::string errorMsg;
|
||||
|
||||
bool operator==(const TitleEntry& e) const
|
||||
{
|
||||
return type == e.type && titleId == e.titleId && version == e.version;
|
||||
}
|
||||
bool operator!=(const TitleEntry& e) const { return !(*this == e); }
|
||||
};
|
||||
boost::optional<const TitleEntry&> GetSelectedTitleEntry() const;
|
||||
|
||||
private:
|
||||
void AddColumns();
|
||||
int Filter(const wxString& filter, const wxString& prefix, ItemColumn column);
|
||||
boost::optional<TitleEntry&> GetSelectedTitleEntry();
|
||||
boost::optional<TitleEntry&> GetTitleEntry(uint64 titleId, uint16 titleVersion);
|
||||
|
||||
class wxPanel* m_tooltip_window;
|
||||
class wxStaticText* m_tooltip_text;
|
||||
class wxTimer* m_tooltip_timer;
|
||||
|
||||
void OnClose(wxCloseEvent& event);
|
||||
void OnColumnClick(wxListEvent& event);
|
||||
void OnContextMenu(wxContextMenuEvent& event);
|
||||
void OnItemSelected(wxListEvent& event);
|
||||
void OnContextMenuSelected(wxCommandEvent& event);
|
||||
void OnTimer(class wxTimerEvent& event);
|
||||
void OnRemoveItem(wxCommandEvent& event);
|
||||
void OnRemoveEntry(wxCommandEvent& event);
|
||||
|
||||
using TitleEntryData_t = wxCustomData<TitleEntry>;
|
||||
void AddOrUpdateTitle(TitleEntryData_t* obj);
|
||||
void UpdateTitleStatusDepr(TitleEntryData_t* obj, const wxString& text);
|
||||
int AddImage(const wxImage& image) const;
|
||||
wxString OnGetItemText(long item, long column) const override;
|
||||
wxItemAttr* OnGetItemAttr(long item) const override;
|
||||
[[nodiscard]] boost::optional<const TitleEntry&> GetTitleEntry(long item) const;
|
||||
[[nodiscard]] boost::optional<TitleEntry&> GetTitleEntry(long item);
|
||||
//[[nodiscard]] boost::optional<const TitleEntry&> GetTitleEntry(const fs::path& path) const;
|
||||
//[[nodiscard]] boost::optional<TitleEntry&> GetTitleEntry(const fs::path& path);
|
||||
|
||||
void SetCurrentDownloadMgr(class DownloadManager* dlMgr);
|
||||
|
||||
//bool FixEntry(TitleEntry& entry);
|
||||
//bool VerifyEntryFiles(TitleEntry& entry);
|
||||
bool StartDownloadEntry(const TitleEntry& entry);
|
||||
bool RetryDownloadEntry(const TitleEntry& entry);
|
||||
bool PauseDownloadEntry(const TitleEntry& entry);
|
||||
|
||||
void RemoveItem(long item);
|
||||
void RemoveItem(const TitleEntry& entry);
|
||||
|
||||
struct ItemData
|
||||
{
|
||||
ItemData(bool visible, const TitleEntry& entry)
|
||||
: visible(visible), entry(entry) {}
|
||||
|
||||
bool visible;
|
||||
TitleEntry entry;
|
||||
};
|
||||
using ItemDataPtr = std::unique_ptr<ItemData>;
|
||||
std::vector<ItemDataPtr> m_data;
|
||||
std::vector<std::reference_wrapper<ItemData>> m_sorted_data;
|
||||
|
||||
int m_sort_by_column = ItemColumn::ColumnName;
|
||||
bool m_sort_less = true;
|
||||
|
||||
bool m_filterShowTitles = true;
|
||||
bool m_filterShowUpdates = true;
|
||||
bool m_filterShowInstalled = true;
|
||||
DownloadManager* m_dlMgr{};
|
||||
std::mutex m_dlMgrMutex;
|
||||
using Type_t = std::reference_wrapper<const ItemData>;
|
||||
bool SortFunc(std::span<int> sortColumnOrder, const Type_t& v1, const Type_t& v2);
|
||||
|
||||
static wxString GetTitleEntryText(const TitleEntry& entry, ItemColumn column);
|
||||
std::future<bool> m_context_worker;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct fmt::formatter<wxDownloadManagerList::EntryType> : formatter<string_view>
|
||||
{
|
||||
using base = fmt::formatter<fmt::string_view>;
|
||||
template <typename FormatContext>
|
||||
auto format(const wxDownloadManagerList::EntryType& type, FormatContext& ctx)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case wxDownloadManagerList::EntryType::Base:
|
||||
return base::format("base", ctx);
|
||||
case wxDownloadManagerList::EntryType::Update:
|
||||
return base::format("update", ctx);
|
||||
case wxDownloadManagerList::EntryType::DLC:
|
||||
return base::format("DLC", ctx);
|
||||
}
|
||||
return base::format(std::to_string(static_cast<std::underlying_type_t<wxDownloadManagerList::EntryType>>(type)), ctx);
|
||||
}
|
||||
};
|
1098
src/gui/components/wxGameList.cpp
Normal file
1098
src/gui/components/wxGameList.cpp
Normal file
File diff suppressed because it is too large
Load diff
148
src/gui/components/wxGameList.h
Normal file
148
src/gui/components/wxGameList.h
Normal file
|
@ -0,0 +1,148 @@
|
|||
#pragma once
|
||||
|
||||
#include "config/CemuConfig.h"
|
||||
#include "Cafe/TitleList/TitleId.h"
|
||||
|
||||
#include <future>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
|
||||
#include <wx/listctrl.h>
|
||||
#include <wx/timer.h>
|
||||
#include <wx/panel.h>
|
||||
#include "util/helpers/Semaphore.h"
|
||||
|
||||
class wxTitleIdEvent : public wxCommandEvent
|
||||
{
|
||||
public:
|
||||
wxTitleIdEvent(int type, uint64 title_id)
|
||||
: wxCommandEvent(type), m_title_id(title_id) {}
|
||||
|
||||
[[nodiscard]] uint64 GetTitleId() const { return m_title_id; }
|
||||
|
||||
private:
|
||||
uint64 m_title_id;
|
||||
};
|
||||
|
||||
wxDECLARE_EVENT(wxEVT_OPEN_SETTINGS, wxCommandEvent);
|
||||
wxDECLARE_EVENT(wxEVT_OPEN_GRAPHIC_PACK, wxTitleIdEvent);
|
||||
wxDECLARE_EVENT(wxEVT_GAMELIST_BEGIN_UPDATE, wxCommandEvent);
|
||||
wxDECLARE_EVENT(wxEVT_GAMELIST_END_UPDATE, wxCommandEvent);
|
||||
|
||||
class wxGameList : public wxListCtrl
|
||||
{
|
||||
friend class MainWindow;
|
||||
public:
|
||||
enum class Style
|
||||
{
|
||||
kList,
|
||||
kIcons,
|
||||
kSmallIcons
|
||||
};
|
||||
|
||||
wxGameList(wxWindow* parent, wxWindowID id = wxID_ANY);
|
||||
~wxGameList();
|
||||
|
||||
void SetStyle(Style style, bool save = true);
|
||||
|
||||
void LoadConfig();
|
||||
void SaveConfig(bool flush = false);
|
||||
bool IsVisible(long item) const; // only available in wxwidgets 3.1.3
|
||||
|
||||
void ReloadGameEntries(bool cached = false);
|
||||
|
||||
long FindListItemByTitleId(uint64 title_id) const;
|
||||
void OnClose(wxCloseEvent& event);
|
||||
|
||||
private:
|
||||
std::atomic_bool m_exit = false;
|
||||
Style m_style;
|
||||
long GetStyleFlags(Style style) const;
|
||||
|
||||
inline static const wxColour kUpdateColor{ 0x3939c3 };
|
||||
inline static const wxColour kFavoriteColor{ 0xD3F6FD };
|
||||
inline static const wxColour kSecondColor{ 0xFDF9F2 };
|
||||
void UpdateItemColors(sint32 startIndex = 0);
|
||||
|
||||
enum ItemColumns
|
||||
{
|
||||
ColumnHiddenName = 0,
|
||||
ColumnIcon,
|
||||
ColumnName,
|
||||
ColumnVersion,
|
||||
ColumnDLC,
|
||||
ColumnGameTime,
|
||||
ColumnGameStarted,
|
||||
ColumnRegion,
|
||||
ColumnFavorite
|
||||
};
|
||||
|
||||
int s_last_column = ColumnName;
|
||||
int s_direction = 1;
|
||||
void SortEntries(int column = -1);
|
||||
struct SortData
|
||||
{
|
||||
wxGameList* thisptr;
|
||||
int column;
|
||||
int dir;
|
||||
};
|
||||
|
||||
int FindInsertPosition(TitleId titleId);
|
||||
int SortComparator(uint64 titleId1, uint64 titleId2, SortData* sortData);
|
||||
static int SortFunction(wxIntPtr item1, wxIntPtr item2, wxIntPtr sortData);
|
||||
|
||||
wxTimer* m_tooltip_timer;
|
||||
wxToolTip* m_tool_tip;
|
||||
wxPanel* m_tooltip_window;
|
||||
wxPoint m_mouse_position;
|
||||
|
||||
std::mutex m_async_worker_mutex;
|
||||
std::thread m_async_worker_thread;
|
||||
CounterSemaphore m_async_task_count;
|
||||
std::atomic_bool m_async_worker_active{false};
|
||||
|
||||
std::vector<TitleId> m_icon_load_queue;
|
||||
|
||||
uint64 m_callbackIdTitleList;
|
||||
|
||||
std::string GetNameByTitleId(uint64 titleId);
|
||||
|
||||
void HandleTitleListCallback(struct CafeTitleListCallbackEvent* evt);
|
||||
|
||||
void AsyncWorkerThread();
|
||||
void RequestLoadIconAsync(TitleId titleId);
|
||||
bool QueryIconForTitle(TitleId titleId, int& icon, int& iconSmall);
|
||||
|
||||
inline static constexpr int kListIconWidth = 64;
|
||||
inline static constexpr int kIconWidth = 128;
|
||||
wxImageList* m_image_list, *m_image_list_small;
|
||||
|
||||
std::mutex m_icon_cache_mtx;
|
||||
std::set<TitleId> m_icon_loaded;
|
||||
std::map<TitleId, std::pair<int, int>> m_icon_cache; // pair contains icon and small icon
|
||||
|
||||
std::map<TitleId, std::string> m_name_cache;
|
||||
|
||||
// list mode
|
||||
void CreateListColumns();
|
||||
|
||||
void OnColumnClick(wxListEvent& event);
|
||||
void OnColumnRightClick(wxListEvent& event);
|
||||
void ApplyGameListColumnWidths();
|
||||
void OnColumnBeginResize(wxListEvent& event);
|
||||
void OnColumnResize(wxListEvent& event);
|
||||
void OnColumnDrag(wxListEvent& event);
|
||||
|
||||
// generic events
|
||||
void OnKeyDown(wxListEvent& event);
|
||||
void OnContextMenu(wxContextMenuEvent& event);
|
||||
void OnContextMenuSelected(wxCommandEvent& event);
|
||||
void OnGameEntryUpdatedByTitleId(wxTitleIdEvent& event);
|
||||
void OnItemActivated(wxListEvent& event);
|
||||
void OnTimer(wxTimerEvent& event);
|
||||
void OnMouseMove(wxMouseEvent& event);
|
||||
void OnLeaveWindow(wxMouseEvent& event);
|
||||
|
||||
static inline std::once_flag s_launch_file_once;
|
||||
};
|
||||
|
118
src/gui/components/wxInputDraw.cpp
Normal file
118
src/gui/components/wxInputDraw.cpp
Normal file
|
@ -0,0 +1,118 @@
|
|||
#include "gui/components/wxInputDraw.h"
|
||||
|
||||
#include <wx/dcbuffer.h>
|
||||
|
||||
wxInputDraw::wxInputDraw(wxWindow* parent, wxWindowID id, const wxPoint& pos, const wxSize& size)
|
||||
: wxWindow(parent, id, pos, size, 0, wxPanelNameStr)
|
||||
{
|
||||
SetBackgroundStyle(wxBG_STYLE_PAINT);
|
||||
Bind(wxEVT_PAINT, &wxInputDraw::OnPaintEvent, this);
|
||||
}
|
||||
|
||||
wxInputDraw::~wxInputDraw() { Unbind(wxEVT_PAINT, &wxInputDraw::OnPaintEvent, this); }
|
||||
|
||||
void wxInputDraw::OnPaintEvent(wxPaintEvent& event)
|
||||
{
|
||||
wxAutoBufferedPaintDC dc(this);
|
||||
OnRender(dc);
|
||||
}
|
||||
|
||||
void wxInputDraw::OnRender(wxDC& dc)
|
||||
{
|
||||
dc.Clear();
|
||||
|
||||
glm::vec2 position;
|
||||
const wxPen *black, *red, *grey;
|
||||
const wxBrush *black_brush, *red_brush, *grey_brush;
|
||||
if(IsEnabled())
|
||||
{
|
||||
position = m_position;
|
||||
|
||||
black = wxBLACK_PEN;
|
||||
red = wxRED_PEN;
|
||||
grey = wxGREY_PEN;
|
||||
|
||||
black_brush = wxBLACK_BRUSH;
|
||||
red_brush = wxRED_BRUSH;
|
||||
grey_brush = wxGREY_BRUSH;
|
||||
}
|
||||
else
|
||||
{
|
||||
position = {};
|
||||
black = red = wxMEDIUM_GREY_PEN;
|
||||
grey = wxLIGHT_GREY_PEN;
|
||||
|
||||
black_brush = red_brush = wxMEDIUM_GREY_BRUSH;
|
||||
grey_brush = wxLIGHT_GREY_BRUSH;
|
||||
}
|
||||
|
||||
dc.SetBackgroundMode(wxSOLID);
|
||||
dc.SetBackground(*wxWHITE_BRUSH);
|
||||
|
||||
const auto size = GetSize();
|
||||
const auto min_size = (float)std::min(size.GetWidth(), size.GetHeight()) - 1.0f;
|
||||
const Vector2f middle{min_size / 2.0f, min_size / 2.0f};
|
||||
|
||||
// border
|
||||
const wxRect border{0, 0, (int)min_size, (int)min_size};
|
||||
dc.SetPen(*black);
|
||||
dc.DrawRectangle(border);
|
||||
|
||||
dc.SetPen(IsEnabled() ? wxPen(wxColour(0x336600)) : *grey); // dark green
|
||||
dc.DrawCircle((int)middle.x, (int)middle.y, (int)middle.x);
|
||||
|
||||
if (m_deadzone > 0)
|
||||
{
|
||||
dc.SetPen(*grey);
|
||||
dc.SetBrush(*wxLIGHT_GREY_BRUSH);
|
||||
const auto deadzone_size = m_deadzone * min_size / 2.0f;
|
||||
dc.DrawCircle(
|
||||
static_cast<int>(middle.x),
|
||||
static_cast<int>(middle.x),
|
||||
(int)deadzone_size);
|
||||
|
||||
if (length(position) >= m_deadzone)
|
||||
{
|
||||
dc.SetPen(*red);
|
||||
dc.SetBrush(*red_brush);
|
||||
|
||||
if (std::abs(1.0f - length(position)) < 0.05f)
|
||||
{
|
||||
dc.SetPen(wxPen(wxColour(0x336600)));
|
||||
dc.SetBrush(wxColour(0x336600));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
dc.SetPen(*black);
|
||||
dc.SetBrush(*black_brush);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
dc.SetPen(*red);
|
||||
dc.SetBrush(*red_brush);
|
||||
}
|
||||
|
||||
// draw axis
|
||||
const wxPoint pos
|
||||
{
|
||||
static_cast<int>(middle.x + position.x * middle.x),
|
||||
static_cast<int>(middle.y - position.y * middle.y)
|
||||
};
|
||||
dc.DrawCircle(pos.x, pos.y, 2);
|
||||
|
||||
dc.SetBrush(*wxTRANSPARENT_BRUSH);
|
||||
}
|
||||
|
||||
void wxInputDraw::SetAxisValue(const glm::vec2& position)
|
||||
{
|
||||
m_position = position;
|
||||
Refresh();
|
||||
}
|
||||
|
||||
void wxInputDraw::SetDeadzone(float deadzone)
|
||||
{
|
||||
m_deadzone = deadzone;
|
||||
Refresh();
|
||||
}
|
24
src/gui/components/wxInputDraw.h
Normal file
24
src/gui/components/wxInputDraw.h
Normal file
|
@ -0,0 +1,24 @@
|
|||
#pragma once
|
||||
|
||||
#include "input/api/ControllerState.h"
|
||||
#include "util/math/vector2.h"
|
||||
|
||||
#include <wx/window.h>
|
||||
|
||||
|
||||
class wxInputDraw : public wxWindow
|
||||
{
|
||||
public:
|
||||
wxInputDraw(wxWindow *parent, wxWindowID id, const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxDefaultSize);
|
||||
~wxInputDraw();
|
||||
|
||||
virtual void OnRender(wxDC& dc);
|
||||
void SetAxisValue(const glm::vec2& position);
|
||||
void SetDeadzone(float deadzone);
|
||||
|
||||
private:
|
||||
void OnPaintEvent(wxPaintEvent& event);
|
||||
|
||||
glm::vec2 m_position{};
|
||||
float m_deadzone{0};
|
||||
};
|
160
src/gui/components/wxLogCtrl.cpp
Normal file
160
src/gui/components/wxLogCtrl.cpp
Normal file
|
@ -0,0 +1,160 @@
|
|||
#include "gui/components/wxLogCtrl.h"
|
||||
|
||||
#include <boost/algorithm/string.hpp>
|
||||
|
||||
wxDEFINE_EVENT(EVT_ON_LIST_UPDATED, wxEvent);
|
||||
|
||||
wxLogCtrl::wxLogCtrl(wxWindow* parent, wxWindowID id, const wxPoint& pos, const wxSize& size, long style)
|
||||
: TextList(parent, id, pos, size, style)
|
||||
{
|
||||
m_timer = new wxTimer(this);
|
||||
this->Bind(wxEVT_TIMER, &wxLogCtrl::OnTimer, this);
|
||||
this->Bind(EVT_ON_LIST_UPDATED, &wxLogCtrl::OnActiveListUpdated, this);
|
||||
m_timer->Start(250);
|
||||
}
|
||||
|
||||
wxLogCtrl::~wxLogCtrl()
|
||||
{
|
||||
this->Unbind(wxEVT_TIMER, &wxLogCtrl::OnTimer, this);
|
||||
this->Unbind(EVT_ON_LIST_UPDATED, &wxLogCtrl::OnActiveListUpdated, this);
|
||||
|
||||
if(m_timer)
|
||||
m_timer->Stop();
|
||||
|
||||
if(m_update_worker.joinable())
|
||||
m_update_worker.join();
|
||||
}
|
||||
|
||||
void wxLogCtrl::SetActiveFilter(const std::string& active_filter)
|
||||
{
|
||||
if(m_active_filter == active_filter)
|
||||
return;
|
||||
|
||||
m_active_filter = active_filter;
|
||||
|
||||
if(m_update_worker.joinable())
|
||||
m_update_worker.join();
|
||||
|
||||
m_update_worker = std::thread(&wxLogCtrl::UpdateActiveEntries, this);
|
||||
}
|
||||
|
||||
const std::string& wxLogCtrl::GetActiveFilter() const
|
||||
{
|
||||
return m_active_filter;
|
||||
}
|
||||
|
||||
void wxLogCtrl::SetFilterMessage(bool state)
|
||||
{
|
||||
if(m_filter_messages == state)
|
||||
return;
|
||||
|
||||
m_filter_messages = state;
|
||||
|
||||
if(m_update_worker.joinable())
|
||||
m_update_worker.join();
|
||||
|
||||
m_update_worker = std::thread(&wxLogCtrl::UpdateActiveEntries, this);
|
||||
}
|
||||
|
||||
bool wxLogCtrl::GetFilterMessage() const
|
||||
{
|
||||
return m_filter_messages;
|
||||
}
|
||||
|
||||
void wxLogCtrl::PushEntry(const wxString& filter, const wxString& message)
|
||||
{
|
||||
std::unique_lock lock(m_mutex);
|
||||
m_log_entries.emplace_back(filter, message);
|
||||
ListIt_t it = m_log_entries.back();
|
||||
lock.unlock();
|
||||
|
||||
if(m_active_filter.empty() || filter == m_active_filter || (m_filter_messages && boost::icontains(message, m_active_filter)))
|
||||
{
|
||||
std::unique_lock active_lock(m_active_mutex);
|
||||
m_active_entries.emplace_back(std::cref(it));
|
||||
const auto entry_count = m_active_entries.size();
|
||||
active_lock.unlock();
|
||||
|
||||
if(entry_count <= m_elements_visible)
|
||||
{
|
||||
RefreshLine(entry_count - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void wxLogCtrl::OnDraw(wxDC& dc, sint32 start, sint32 count, const wxPoint& start_position)
|
||||
{
|
||||
wxPoint position = start_position;
|
||||
|
||||
std::scoped_lock lock(m_active_mutex);
|
||||
auto it = std::next(m_active_entries.cbegin(), start);
|
||||
if(it == m_active_entries.cend())
|
||||
return;
|
||||
|
||||
for (sint32 i = 0; i <= count && it != m_active_entries.cend(); ++i, ++it)
|
||||
{
|
||||
wxColour background_colour;
|
||||
if((start + i) % 2 == 0)
|
||||
background_colour = COLOR_WHITE;
|
||||
else
|
||||
background_colour = 0xFFFDF9F2;
|
||||
|
||||
DrawLineBackground(dc, position, background_colour);
|
||||
|
||||
dc.SetTextForeground(COLOR_BLACK);
|
||||
dc.DrawText(it->get().second, position);
|
||||
|
||||
NextLine(position, &start_position);
|
||||
}
|
||||
}
|
||||
|
||||
void wxLogCtrl::OnTimer(wxTimerEvent& event)
|
||||
{
|
||||
std::unique_lock lock(m_active_mutex);
|
||||
const auto count = m_active_entries.size();
|
||||
if(count == m_element_count)
|
||||
return;
|
||||
|
||||
lock.unlock();
|
||||
SetElementCount(count);
|
||||
|
||||
if(m_scrolled_to_end)
|
||||
{
|
||||
Scroll(0, count);
|
||||
RefreshControl();
|
||||
}
|
||||
}
|
||||
|
||||
void wxLogCtrl::OnActiveListUpdated(wxEvent& event)
|
||||
{
|
||||
std::unique_lock lock(m_active_mutex);
|
||||
const auto count = m_active_entries.size();
|
||||
lock.unlock();
|
||||
|
||||
SetElementCount(count);
|
||||
RefreshControl();
|
||||
}
|
||||
|
||||
void wxLogCtrl::UpdateActiveEntries()
|
||||
{
|
||||
{
|
||||
std::scoped_lock lock(m_mutex, m_active_mutex);
|
||||
m_active_entries.clear();
|
||||
if(m_active_filter.empty())
|
||||
{
|
||||
for (const auto& it : m_log_entries)
|
||||
m_active_entries.emplace_back(it);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (const auto& it : m_log_entries)
|
||||
{
|
||||
if(it.first == m_active_filter || (m_filter_messages && boost::icontains(it.second, m_active_filter)) )
|
||||
m_active_entries.emplace_back(it);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
wxQueueEvent(this, new wxCommandEvent(EVT_ON_LIST_UPDATED));
|
||||
}
|
||||
|
36
src/gui/components/wxLogCtrl.h
Normal file
36
src/gui/components/wxLogCtrl.h
Normal file
|
@ -0,0 +1,36 @@
|
|||
#pragma once
|
||||
#include "TextList.h"
|
||||
|
||||
class wxLogCtrl : public TextList
|
||||
{
|
||||
public:
|
||||
wxLogCtrl(wxWindow* parent, wxWindowID id, const wxPoint& pos, const wxSize& size, long style);
|
||||
~wxLogCtrl();
|
||||
|
||||
void SetActiveFilter(const std::string& active_filter);
|
||||
[[nodiscard]] const std::string& GetActiveFilter() const;
|
||||
void SetFilterMessage(bool state);
|
||||
[[nodiscard]] bool GetFilterMessage() const;
|
||||
|
||||
void PushEntry(const wxString& filter, const wxString& message);
|
||||
|
||||
protected:
|
||||
void OnDraw(wxDC& dc, sint32 start, sint32 count, const wxPoint& start_position);
|
||||
|
||||
private:
|
||||
void OnTimer(wxTimerEvent& event);
|
||||
void OnActiveListUpdated(wxEvent& event);
|
||||
|
||||
wxTimer* m_timer;
|
||||
|
||||
std::string m_active_filter;
|
||||
std::thread m_update_worker;
|
||||
bool m_filter_messages = false;
|
||||
|
||||
std::mutex m_mutex, m_active_mutex;
|
||||
using ListEntry_t = std::pair<wxString, wxString>;
|
||||
using ListIt_t = std::list<ListEntry_t>::const_reference;
|
||||
std::list<ListEntry_t> m_log_entries;
|
||||
std::list<std::reference_wrapper<const ListEntry_t>> m_active_entries;
|
||||
void UpdateActiveEntries();
|
||||
};
|
1277
src/gui/components/wxTitleManagerList.cpp
Normal file
1277
src/gui/components/wxTitleManagerList.cpp
Normal file
File diff suppressed because it is too large
Load diff
162
src/gui/components/wxTitleManagerList.h
Normal file
162
src/gui/components/wxTitleManagerList.h
Normal file
|
@ -0,0 +1,162 @@
|
|||
#pragma once
|
||||
|
||||
#include "gui/helpers/wxCustomData.h"
|
||||
#include "config/CemuConfig.h"
|
||||
|
||||
#include <wx/listctrl.h>
|
||||
|
||||
#include <boost/optional.hpp> // std::optional doesn't support optional reference inner types yet
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
class wxTitleManagerList : public wxListCtrl
|
||||
{
|
||||
friend class TitleManager;
|
||||
public:
|
||||
wxTitleManagerList(wxWindow* parent, wxWindowID id = wxID_ANY);
|
||||
~wxTitleManagerList();
|
||||
|
||||
enum ItemColumn
|
||||
{
|
||||
ColumnTitleId = 0,
|
||||
ColumnName,
|
||||
ColumnType,
|
||||
ColumnVersion,
|
||||
ColumnRegion,
|
||||
ColumnFormat,
|
||||
|
||||
ColumnMAX,
|
||||
};
|
||||
|
||||
enum class EntryType
|
||||
{
|
||||
Base,
|
||||
Update,
|
||||
Dlc,
|
||||
Save,
|
||||
System,
|
||||
};
|
||||
|
||||
enum class EntryFormat
|
||||
{
|
||||
Folder,
|
||||
WUD,
|
||||
WUA,
|
||||
};
|
||||
|
||||
// sort by column, if -1 will sort by last column or default (=titleid)
|
||||
void SortEntries(int column);
|
||||
void RefreshPage();
|
||||
void Filter(const wxString& filter);
|
||||
[[nodiscard]] size_t GetCountByType(EntryType type) const;
|
||||
void ClearItems();
|
||||
void AutosizeColumns();
|
||||
|
||||
struct TitleEntry
|
||||
{
|
||||
TitleEntry(const EntryType& type, const EntryFormat& format, fs::path path)
|
||||
: type(type), format(format), path(std::move(path)) {}
|
||||
|
||||
EntryType type;
|
||||
EntryFormat format;
|
||||
fs::path path;
|
||||
|
||||
int icon = -1;
|
||||
uint64 location_uid;
|
||||
uint64 title_id;
|
||||
wxString name;
|
||||
uint32_t version = 0;
|
||||
CafeConsoleRegion region;
|
||||
|
||||
std::vector<uint32> persistent_ids; // only used for save
|
||||
};
|
||||
boost::optional<const TitleEntry&> GetSelectedTitleEntry() const;
|
||||
|
||||
private:
|
||||
void AddColumns();
|
||||
int Filter(const wxString& filter, const wxString& prefix, ItemColumn column);
|
||||
boost::optional<TitleEntry&> GetSelectedTitleEntry();
|
||||
boost::optional<TitleEntry&> GetTitleEntryByUID(uint64 uid);
|
||||
|
||||
class wxPanel* m_tooltip_window;
|
||||
class wxStaticText* m_tooltip_text;
|
||||
class wxTimer* m_tooltip_timer;
|
||||
|
||||
void OnClose(wxCloseEvent& event);
|
||||
void OnColumnClick(wxListEvent& event);
|
||||
void OnContextMenu(wxContextMenuEvent& event);
|
||||
void OnItemSelected(wxListEvent& event);
|
||||
void OnContextMenuSelected(wxCommandEvent& event);
|
||||
void OnTimer(class wxTimerEvent& event);
|
||||
void OnRemoveItem(wxCommandEvent& event);
|
||||
void OnRemoveEntry(wxCommandEvent& event);
|
||||
|
||||
void OnTitleDiscovered(wxCommandEvent& event);
|
||||
void OnTitleRemoved(wxCommandEvent& event);
|
||||
void HandleTitleListCallback(struct CafeTitleListCallbackEvent* evt);
|
||||
void HandleSaveListCallback(struct CafeSaveListCallbackEvent* evt);
|
||||
|
||||
using TitleEntryData_t = wxCustomData<TitleEntry>;
|
||||
void AddTitle(TitleEntryData_t* obj);
|
||||
int AddImage(const wxImage& image) const;
|
||||
wxString OnGetItemText(long item, long column) const override;
|
||||
wxItemAttr* OnGetItemAttr(long item) const override;
|
||||
[[nodiscard]] boost::optional<const TitleEntry&> GetTitleEntry(long item) const;
|
||||
[[nodiscard]] boost::optional<TitleEntry&> GetTitleEntry(long item);
|
||||
[[nodiscard]] boost::optional<const TitleEntry&> GetTitleEntry(const fs::path& path) const;
|
||||
[[nodiscard]] boost::optional<TitleEntry&> GetTitleEntry(const fs::path& path);
|
||||
|
||||
bool VerifyEntryFiles(TitleEntry& entry);
|
||||
void OnConvertToCompressedFormat(uint64 titleId);
|
||||
bool DeleteEntry(long index, const TitleEntry& entry);
|
||||
|
||||
void RemoveItem(long item);
|
||||
void RemoveItem(const TitleEntry& entry);
|
||||
|
||||
struct ItemData
|
||||
{
|
||||
ItemData(bool visible, const TitleEntry& entry)
|
||||
: visible(visible), entry(entry) {}
|
||||
|
||||
bool visible;
|
||||
TitleEntry entry;
|
||||
};
|
||||
using ItemDataPtr = std::unique_ptr<ItemData>;
|
||||
std::vector<ItemDataPtr> m_data;
|
||||
std::vector<std::reference_wrapper<ItemData>> m_sorted_data;
|
||||
|
||||
int m_last_column_sorted = -1;
|
||||
bool m_sort_less = true;
|
||||
using Type_t = std::reference_wrapper<const ItemData>;
|
||||
bool SortFunc(int column, const Type_t& v1, const Type_t& v2);
|
||||
|
||||
static wxString GetTitleEntryText(const TitleEntry& entry, ItemColumn column);
|
||||
std::future<bool> m_context_worker;
|
||||
|
||||
uint64 m_callbackIdTitleList;
|
||||
uint64 m_callbackIdSaveList;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct fmt::formatter<wxTitleManagerList::EntryType> : formatter<string_view>
|
||||
{
|
||||
using base = fmt::formatter<fmt::string_view>;
|
||||
template <typename FormatContext>
|
||||
auto format(const wxTitleManagerList::EntryType& type, FormatContext& ctx)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case wxTitleManagerList::EntryType::Base:
|
||||
return base::format("base", ctx);
|
||||
case wxTitleManagerList::EntryType::Update:
|
||||
return base::format("update", ctx);
|
||||
case wxTitleManagerList::EntryType::Dlc:
|
||||
return base::format("DLC", ctx);
|
||||
case wxTitleManagerList::EntryType::Save:
|
||||
return base::format("save", ctx);
|
||||
case wxTitleManagerList::EntryType::System:
|
||||
return base::format("system", ctx);
|
||||
}
|
||||
return base::format(std::to_string(static_cast<std::underlying_type_t<wxTitleManagerList::EntryType>>(type)), ctx);
|
||||
}
|
||||
};
|
Loading…
Add table
Add a link
Reference in a new issue