#include "tracker_frame.h"
#include <fmt/core.h>
#include <wx/aboutdlg.h>
#include <wx/choicebk.h>
#include <wx/filedlg.h>
#include <wx/notebook.h>
#include <wx/splitter.h>
#include <wx/stdpaths.h>
#include <wx/webrequest.h>
#include <nlohmann/json.hpp>
#include <sstream>
#include "achievements_pane.h"
#include "ap_state.h"
#include "connection_dialog.h"
#include "ipc_dialog.h"
#include "ipc_state.h"
#include "items_pane.h"
#include "log_dialog.h"
#include "logger.h"
#include "options_pane.h"
#include "paintings_pane.h"
#include "settings_dialog.h"
#include "subway_map.h"
#include "tracker_config.h"
#include "tracker_panel.h"
#include "version.h"
namespace {
std::string GetStatusMessage() {
std::string msg = AP_GetStatusMessage();
std::optional<std::string> ipc_msg = IPC_GetStatusMessage();
if (ipc_msg) {
msg += " ";
msg += *ipc_msg;
}
return msg;
}
} // namespace
enum TrackerFrameIds {
ID_AP_CONNECT = 1,
ID_CHECK_FOR_UPDATES = 2,
ID_SETTINGS = 3,
ID_ZOOM_IN = 4,
ID_ZOOM_OUT = 5,
ID_OPEN_SAVE_FILE = 6,
ID_IPC_CONNECT = 7,
ID_LOG_DIALOG = 8,
};
wxDEFINE_EVENT(STATE_RESET, wxCommandEvent);
wxDEFINE_EVENT(STATE_CHANGED, StateChangedEvent);
wxDEFINE_EVENT(STATUS_CHANGED, wxCommandEvent);
wxDEFINE_EVENT(CONNECT_TO_AP, ApConnectEvent);
TrackerFrame::TrackerFrame()
: wxFrame(nullptr, wxID_ANY, "Lingo Archipelago Tracker", wxDefaultPosition,
wxDefaultSize, wxDEFAULT_FRAME_STYLE | wxFULL_REPAINT_ON_RESIZE) {
::wxInitAllImageHandlers();
AP_SetTrackerFrame(this);
IPC_SetTrackerFrame(this);
SetTheIconCache(&icons_);
updater_ = std::make_unique<Updater>(this);
updater_->Cleanup();
wxMenu *menuFile = new wxMenu();
menuFile->Append(ID_AP_CONNECT, "&Connect to Archipelago");
menuFile->Append(ID_IPC_CONNECT, "&Connect to Lingo");
menuFile->Append(ID_OPEN_SAVE_FILE, "&Open Save Data\tCtrl-O");
menuFile->Append(ID_SETTINGS, "&Settings");
menuFile->Append(wxID_EXIT);
wxMenu *menuView = new wxMenu();
zoom_in_menu_item_ = menuView->Append(ID_ZOOM_IN, "Zoom In\tCtrl-+");
zoom_out_menu_item_ = menuView->Append(ID_ZOOM_OUT, "Zoom Out\tCtrl--");
menuView->AppendSeparator();
menuView->Append(ID_LOG_DIALOG, "Show Log Window\tCtrl-L");
zoom_in_menu_item_->Enable(false);
zoom_out_menu_item_->Enable(false);
wxMenu *menuHelp = new wxMenu();
menuHelp->Append(wxID_ABOUT);
menuHelp->Append(ID_CHECK_FOR_UPDATES, "Check for Updates");
wxMenuBar *menuBar = new wxMenuBar();
menuBar->Append(menuFile, "&File");
menuBar->Append(menuView, "&View");
menuBar->Append(menuHelp, "&Help");
SetMenuBar(menuBar);
CreateStatusBar();
Bind(wxEVT_MENU, &TrackerFrame::OnAbout, this, wxID_ABOUT);
Bind(wxEVT_MENU, &TrackerFrame::OnExit, this, wxID_EXIT);
Bind(wxEVT_MENU, &TrackerFrame::OnApConnect, this, ID_AP_CONNECT);
Bind(wxEVT_MENU, &TrackerFrame::OnIpcConnect, this, ID_IPC_CONNECT);
Bind(wxEVT_MENU, &TrackerFrame::OnSettings, this, ID_SETTINGS);
Bind(wxEVT_MENU, &TrackerFrame::OnCheckForUpdates, this,
ID_CHECK_FOR_UPDATES);
Bind(wxEVT_MENU, &TrackerFrame::OnZoomIn, this, ID_ZOOM_IN);
Bind(wxEVT_MENU, &TrackerFrame::OnZoomOut, this, ID_ZOOM_OUT);
Bind(wxEVT_MENU, &TrackerFrame::OnOpenLogWindow, this, ID_LOG_DIALOG);
Bind(wxEVT_NOTEBOOK_PAGE_CHANGED, &TrackerFrame::OnChangePage, this);
Bind(wxEVT_MENU, &TrackerFrame::OnOpenFile, this, ID_OPEN_SAVE_FILE);
Bind(wxEVT_SPLITTER_SASH_POS_CHANGED, &TrackerFrame::OnSashPositionChanged,
this);
Bind(STATE_RESET, &TrackerFrame::OnStateReset, this);
Bind(STATE_CHANGED, &TrackerFrame::OnStateChanged, this);
Bind(STATUS_CHANGED, &TrackerFrame::OnStatusChanged, this);
Bind(CONNECT_TO_AP, &TrackerFrame::OnConnectToAp, this);
wxSize logicalSize = FromDIP(wxSize(1280, 728));
splitter_window_ = new wxSplitterWindow(this, wxID_ANY);
splitter_window_->SetMinimumPaneSize(logicalSize.x / 5);
wxChoicebook *choicebook = new wxChoicebook(splitter_window_, wxID_ANY);
achievements_pane_ = new AchievementsPane(choicebook);
choicebook->AddPage(achievements_pane_, "Achievements");
items_pane_ = new ItemsPane(choicebook);
choicebook->AddPage(items_pane_, "Items");
options_pane_ = new OptionsPane(choicebook);
choicebook->AddPage(options_pane_, "Options");
paintings_pane_ = new PaintingsPane(choicebook);
choicebook->AddPage(paintings_pane_, "Paintings");
notebook_ = new wxNotebook(splitter_window_, wxID_ANY);
tracker_panel_ = new TrackerPanel(notebook_);
subway_map_ = new SubwayMap(notebook_);
notebook_->AddPage(tracker_panel_, "Map");
notebook_->AddPage(subway_map_, "Subway");
splitter_window_->SplitVertically(choicebook, notebook_, logicalSize.x / 4);
SetSize(logicalSize);
if (!GetTrackerConfig().asked_to_check_for_updates) {
GetTrackerConfig().asked_to_check_for_updates = true;
if (wxMessageBox(
"Check for updates automatically when the tracker is opened?",
"Lingo AP Tracker", wxYES_NO) == wxYES) {
GetTrackerConfig().should_check_for_updates = true;
} else {
GetTrackerConfig().should_check_for_updates = false;
}
GetTrackerConfig().Save();
}
if (GetTrackerConfig().should_check_for_updates) {
updater_->CheckForUpdates(/*invisible=*/true);
}
SetStatusText(GetStatusMessage());
}
void TrackerFrame::ConnectToAp(std::string server, std::string user,
std::string pass) {
QueueEvent(new ApConnectEvent(CONNECT_TO_AP, GetId(), std::move(server),
std::move(user), std::move(pass)));
}
void TrackerFrame::UpdateStatusMessage() {
QueueEvent(new wxCommandEvent(STATUS_CHANGED));
}
void TrackerFrame::ResetIndicators() {
QueueEvent(new wxCommandEvent(STATE_RESET));
}
void TrackerFrame::UpdateIndicators(StateUpdate state) {
QueueEvent(new StateChangedEvent(STATE_CHANGED, GetId(), std::move(state)));
}
void TrackerFrame::OnAbout(wxCommandEvent &event) {
wxAboutDialogInfo about_info;
about_info.SetName("Lingo Archipelago Tracker");
about_info.SetVersion(kTrackerVersion.ToString());
about_info.AddDeveloper("hatkirby");
about_info.AddDeveloper("art0007i");
about_info.AddArtist("Brenton Wildes");
about_info.AddArtist("kinrah");
wxAboutBox(about_info);
}
void TrackerFrame::OnExit(wxCommandEvent &event) { Close(true); }
void TrackerFrame::OnApConnect(wxCommandEvent &event) {
ConnectionDialog dlg;
if (dlg.ShowModal() == wxID_OK) {
GetTrackerConfig().connection_details.ap_server = dlg.GetServerValue();
GetTrackerConfig().connection_details.ap_player = dlg.GetPlayerValue();
GetTrackerConfig().connection_details.ap_password = dlg.GetPasswordValue();
std::deque<ConnectionDetails> new_history;
new_history.push_back(GetTrackerConfig().connection_details);
for (const ConnectionDetails &details :
GetTrackerConfig().connection_history) {
if (details != GetTrackerConfig().connection_details) {
new_history.push_back(details);
}
}
while (new_history.size() > 10) {
new_history.pop_back();
}
GetTrackerConfig().connection_history = std::move(new_history);
GetTrackerConfig().Save();
AP_Connect(dlg.GetServerValue(), dlg.GetPlayerValue(),
dlg.GetPasswordValue());
}
}
void TrackerFrame::OnIpcConnect(wxCommandEvent &event) {
IpcDialog dlg;
if (dlg.ShowModal() == wxID_OK) {
GetTrackerConfig().ipc_address = dlg.GetIpcAddress();
GetTrackerConfig().Save();
IPC_Connect(dlg.GetIpcAddress());
}
}
void TrackerFrame::OnSettings(wxCommandEvent &event) {
SettingsDialog dlg;
if (dlg.ShowModal() == wxID_OK) {
GetTrackerConfig().should_check_for_updates =
dlg.GetShouldCheckForUpdates();
GetTrackerConfig().hybrid_areas = dlg.GetHybridAreas();
GetTrackerConfig().show_hunt_panels = dlg.GetShowHuntPanels();
GetTrackerConfig().track_position = dlg.GetTrackPosition();
GetTrackerConfig().Save();
UpdateIndicators(StateUpdate{.cleared_locations = true,
.player_position = true,
.changed_settings = true});
}
}
void TrackerFrame::OnCheckForUpdates(wxCommandEvent &event) {
updater_->CheckForUpdates(/*invisible=*/false);
}
void TrackerFrame::OnZoomIn(wxCommandEvent &event) {
if (notebook_->GetSelection() == 1) {
subway_map_->Zoom(true);
}
}
void TrackerFrame::OnZoomOut(wxCommandEvent &event) {
if (notebook_->GetSelection() == 1) {
subway_map_->Zoom(false);
}
}
void TrackerFrame::OnOpenLogWindow(wxCommandEvent &event) {
if (log_dialog_ == nullptr) {
log_dialog_ = new LogDialog(this);
log_dialog_->Show();
TrackerSetLogDialog(log_dialog_);
log_dialog_->Bind(wxEVT_CLOSE_WINDOW, &TrackerFrame::OnCloseLogWindow,
this);
} else {
log_dialog_->SetFocus();
}
}
void TrackerFrame::OnCloseLogWindow(wxCloseEvent& event) {
TrackerSetLogDialog(nullptr);
log_dialog_ = nullptr;
event.Skip();
}
void TrackerFrame::OnChangePage(wxBookCtrlEvent &event) {
zoom_in_menu_item_->Enable(event.GetSelection() == 1);
zoom_out_menu_item_->Enable(event.GetSelection() == 1);
}
void TrackerFrame::OnOpenFile(wxCommandEvent &event) {
wxFileDialog open_file_dialog(
this, "Open Lingo Save File",
fmt::format("{}\\Godot\\app_userdata\\Lingo\\level1_stable",
wxStandardPaths::Get().GetUserConfigDir().ToStdString()),
AP_GetSaveName(), "Lingo save file (*.save)|*.save",
wxFD_OPEN | wxFD_FILE_MUST_EXIST);
if (open_file_dialog.ShowModal() == wxID_CANCEL) {
return;
}
std::string savedata_path = open_file_dialog.GetPath().ToStdString();
if (panels_panel_ == nullptr) {
panels_panel_ = new TrackerPanel(notebook_);
notebook_->AddPage(panels_panel_, "Panels");
}
notebook_->SetSelection(notebook_->FindPage(panels_panel_));
panels_panel_->SetSavedataPath(savedata_path);
}
void TrackerFrame::OnSashPositionChanged(wxSplitterEvent& event) {
notebook_->Refresh();
}
void TrackerFrame::OnStateReset(wxCommandEvent &event) {
tracker_panel_->UpdateIndicators(/*reset=*/true);
achievements_pane_->UpdateIndicators();
items_pane_->ResetIndicators();
options_pane_->OnConnect();
paintings_pane_->ResetIndicators();
subway_map_->OnConnect();
if (panels_panel_ != nullptr) {
notebook_->DeletePage(notebook_->FindPage(panels_panel_));
panels_panel_ = nullptr;
}
Refresh();
}
void TrackerFrame::OnStateChanged(StateChangedEvent &event) {
const StateUpdate &state = event.GetState();
if (state.open_panels_tab) {
if (panels_panel_ == nullptr) {
panels_panel_ = new TrackerPanel(notebook_);
panels_panel_->SetPanelsMode();
notebook_->AddPage(panels_panel_, "Panels");
}
panels_panel_->UpdateIndicators(/*reset=*/false);
if (notebook_->GetSelection() == 2) {
Refresh();
}
return;
}
if (!state.items.empty() || !state.paintings.empty() ||
state.cleared_locations ||
(state.hunt_panels && GetTrackerConfig().show_hunt_panels)) {
// TODO: The only real reason to reset tracker_panel during an active
// connection is if the hunt panels setting changes. If we remove hunt
// panels later, we can get rid of this.
tracker_panel_->UpdateIndicators(/*reset=*/state.changed_settings);
subway_map_->UpdateIndicators();
if (panels_panel_ != nullptr) {
panels_panel_->UpdateIndicators(/*reset=*/false);
}
Refresh();
} else if (state.player_position && GetTrackerConfig().track_position) {
if (notebook_->GetSelection() == 0) {
tracker_panel_->Refresh();
} else if (notebook_->GetSelection() == 2) {
panels_panel_->Refresh();
}
}
if (state.achievements) {
achievements_pane_->UpdateIndicators();
}
if (!state.items.empty()) {
items_pane_->UpdateIndicators(state.items);
}
if (!state.paintings.empty()) {
paintings_pane_->UpdateIndicators(state.paintings);
}
}
void TrackerFrame::OnStatusChanged(wxCommandEvent &event) {
SetStatusText(wxString::FromUTF8(GetStatusMessage()));
}
void TrackerFrame::OnConnectToAp(ApConnectEvent &event) {
AP_Connect(event.GetServer(), event.GetUser(), event.GetPass());
}