diff options
Diffstat (limited to 'src/tracker_frame.cpp')
-rw-r--r-- | src/tracker_frame.cpp | 242 |
1 files changed, 112 insertions, 130 deletions
diff --git a/src/tracker_frame.cpp b/src/tracker_frame.cpp index 587d87b..e8d7ef6 100644 --- a/src/tracker_frame.cpp +++ b/src/tracker_frame.cpp | |||
@@ -5,9 +5,11 @@ | |||
5 | #include <wx/choicebk.h> | 5 | #include <wx/choicebk.h> |
6 | #include <wx/filedlg.h> | 6 | #include <wx/filedlg.h> |
7 | #include <wx/notebook.h> | 7 | #include <wx/notebook.h> |
8 | #include <wx/splitter.h> | ||
8 | #include <wx/stdpaths.h> | 9 | #include <wx/stdpaths.h> |
9 | #include <wx/webrequest.h> | 10 | #include <wx/webrequest.h> |
10 | 11 | ||
12 | #include <algorithm> | ||
11 | #include <nlohmann/json.hpp> | 13 | #include <nlohmann/json.hpp> |
12 | #include <sstream> | 14 | #include <sstream> |
13 | 15 | ||
@@ -16,6 +18,11 @@ | |||
16 | #include "connection_dialog.h" | 18 | #include "connection_dialog.h" |
17 | #include "ipc_dialog.h" | 19 | #include "ipc_dialog.h" |
18 | #include "ipc_state.h" | 20 | #include "ipc_state.h" |
21 | #include "items_pane.h" | ||
22 | #include "log_dialog.h" | ||
23 | #include "logger.h" | ||
24 | #include "options_pane.h" | ||
25 | #include "paintings_pane.h" | ||
19 | #include "settings_dialog.h" | 26 | #include "settings_dialog.h" |
20 | #include "subway_map.h" | 27 | #include "subway_map.h" |
21 | #include "tracker_config.h" | 28 | #include "tracker_config.h" |
@@ -44,14 +51,13 @@ enum TrackerFrameIds { | |||
44 | ID_SETTINGS = 3, | 51 | ID_SETTINGS = 3, |
45 | ID_ZOOM_IN = 4, | 52 | ID_ZOOM_IN = 4, |
46 | ID_ZOOM_OUT = 5, | 53 | ID_ZOOM_OUT = 5, |
47 | ID_OPEN_SAVE_FILE = 6, | ||
48 | ID_IPC_CONNECT = 7, | 54 | ID_IPC_CONNECT = 7, |
55 | ID_LOG_DIALOG = 8, | ||
49 | }; | 56 | }; |
50 | 57 | ||
51 | wxDEFINE_EVENT(STATE_RESET, wxCommandEvent); | 58 | wxDEFINE_EVENT(STATE_RESET, wxCommandEvent); |
52 | wxDEFINE_EVENT(STATE_CHANGED, wxCommandEvent); | 59 | wxDEFINE_EVENT(STATE_CHANGED, StateChangedEvent); |
53 | wxDEFINE_EVENT(STATUS_CHANGED, wxCommandEvent); | 60 | wxDEFINE_EVENT(STATUS_CHANGED, wxCommandEvent); |
54 | wxDEFINE_EVENT(REDRAW_POSITION, wxCommandEvent); | ||
55 | wxDEFINE_EVENT(CONNECT_TO_AP, ApConnectEvent); | 61 | wxDEFINE_EVENT(CONNECT_TO_AP, ApConnectEvent); |
56 | 62 | ||
57 | TrackerFrame::TrackerFrame() | 63 | TrackerFrame::TrackerFrame() |
@@ -62,16 +68,22 @@ TrackerFrame::TrackerFrame() | |||
62 | AP_SetTrackerFrame(this); | 68 | AP_SetTrackerFrame(this); |
63 | IPC_SetTrackerFrame(this); | 69 | IPC_SetTrackerFrame(this); |
64 | 70 | ||
71 | SetTheIconCache(&icons_); | ||
72 | |||
73 | updater_ = std::make_unique<Updater>(this); | ||
74 | updater_->Cleanup(); | ||
75 | |||
65 | wxMenu *menuFile = new wxMenu(); | 76 | wxMenu *menuFile = new wxMenu(); |
66 | menuFile->Append(ID_AP_CONNECT, "&Connect to Archipelago"); | 77 | menuFile->Append(ID_AP_CONNECT, "&Connect to Archipelago"); |
67 | menuFile->Append(ID_IPC_CONNECT, "&Connect to Lingo"); | 78 | menuFile->Append(ID_IPC_CONNECT, "&Connect to Lingo"); |
68 | menuFile->Append(ID_OPEN_SAVE_FILE, "&Open Save Data\tCtrl-O"); | ||
69 | menuFile->Append(ID_SETTINGS, "&Settings"); | 79 | menuFile->Append(ID_SETTINGS, "&Settings"); |
70 | menuFile->Append(wxID_EXIT); | 80 | menuFile->Append(wxID_EXIT); |
71 | 81 | ||
72 | wxMenu *menuView = new wxMenu(); | 82 | wxMenu *menuView = new wxMenu(); |
73 | zoom_in_menu_item_ = menuView->Append(ID_ZOOM_IN, "Zoom In\tCtrl-+"); | 83 | zoom_in_menu_item_ = menuView->Append(ID_ZOOM_IN, "Zoom In\tCtrl-+"); |
74 | zoom_out_menu_item_ = menuView->Append(ID_ZOOM_OUT, "Zoom Out\tCtrl--"); | 84 | zoom_out_menu_item_ = menuView->Append(ID_ZOOM_OUT, "Zoom Out\tCtrl--"); |
85 | menuView->AppendSeparator(); | ||
86 | menuView->Append(ID_LOG_DIALOG, "Show Log Window\tCtrl-L"); | ||
75 | 87 | ||
76 | zoom_in_menu_item_->Enable(false); | 88 | zoom_in_menu_item_->Enable(false); |
77 | zoom_out_menu_item_->Enable(false); | 89 | zoom_out_menu_item_->Enable(false); |
@@ -98,30 +110,43 @@ TrackerFrame::TrackerFrame() | |||
98 | ID_CHECK_FOR_UPDATES); | 110 | ID_CHECK_FOR_UPDATES); |
99 | Bind(wxEVT_MENU, &TrackerFrame::OnZoomIn, this, ID_ZOOM_IN); | 111 | Bind(wxEVT_MENU, &TrackerFrame::OnZoomIn, this, ID_ZOOM_IN); |
100 | Bind(wxEVT_MENU, &TrackerFrame::OnZoomOut, this, ID_ZOOM_OUT); | 112 | Bind(wxEVT_MENU, &TrackerFrame::OnZoomOut, this, ID_ZOOM_OUT); |
113 | Bind(wxEVT_MENU, &TrackerFrame::OnOpenLogWindow, this, ID_LOG_DIALOG); | ||
101 | Bind(wxEVT_NOTEBOOK_PAGE_CHANGED, &TrackerFrame::OnChangePage, this); | 114 | Bind(wxEVT_NOTEBOOK_PAGE_CHANGED, &TrackerFrame::OnChangePage, this); |
102 | Bind(wxEVT_MENU, &TrackerFrame::OnOpenFile, this, ID_OPEN_SAVE_FILE); | 115 | Bind(wxEVT_SPLITTER_SASH_POS_CHANGED, &TrackerFrame::OnSashPositionChanged, |
116 | this); | ||
103 | Bind(STATE_RESET, &TrackerFrame::OnStateReset, this); | 117 | Bind(STATE_RESET, &TrackerFrame::OnStateReset, this); |
104 | Bind(STATE_CHANGED, &TrackerFrame::OnStateChanged, this); | 118 | Bind(STATE_CHANGED, &TrackerFrame::OnStateChanged, this); |
105 | Bind(STATUS_CHANGED, &TrackerFrame::OnStatusChanged, this); | 119 | Bind(STATUS_CHANGED, &TrackerFrame::OnStatusChanged, this); |
106 | Bind(REDRAW_POSITION, &TrackerFrame::OnRedrawPosition, this); | ||
107 | Bind(CONNECT_TO_AP, &TrackerFrame::OnConnectToAp, this); | 120 | Bind(CONNECT_TO_AP, &TrackerFrame::OnConnectToAp, this); |
108 | 121 | ||
109 | wxChoicebook *choicebook = new wxChoicebook(this, wxID_ANY); | 122 | wxSize logicalSize = FromDIP(wxSize(1280, 728)); |
123 | |||
124 | splitter_window_ = new wxSplitterWindow(this, wxID_ANY); | ||
125 | splitter_window_->SetMinimumPaneSize(logicalSize.x / 5); | ||
126 | |||
127 | wxChoicebook *choicebook = new wxChoicebook(splitter_window_, wxID_ANY); | ||
128 | |||
110 | achievements_pane_ = new AchievementsPane(choicebook); | 129 | achievements_pane_ = new AchievementsPane(choicebook); |
111 | choicebook->AddPage(achievements_pane_, "Achievements"); | 130 | choicebook->AddPage(achievements_pane_, "Achievements"); |
112 | 131 | ||
113 | notebook_ = new wxNotebook(this, wxID_ANY); | 132 | items_pane_ = new ItemsPane(choicebook); |
133 | choicebook->AddPage(items_pane_, "Items"); | ||
134 | |||
135 | options_pane_ = new OptionsPane(choicebook); | ||
136 | choicebook->AddPage(options_pane_, "Options"); | ||
137 | |||
138 | paintings_pane_ = new PaintingsPane(choicebook); | ||
139 | choicebook->AddPage(paintings_pane_, "Paintings"); | ||
140 | |||
141 | notebook_ = new wxNotebook(splitter_window_, wxID_ANY); | ||
114 | tracker_panel_ = new TrackerPanel(notebook_); | 142 | tracker_panel_ = new TrackerPanel(notebook_); |
115 | subway_map_ = new SubwayMap(notebook_); | 143 | subway_map_ = new SubwayMap(notebook_); |
116 | notebook_->AddPage(tracker_panel_, "Map"); | 144 | notebook_->AddPage(tracker_panel_, "Map"); |
117 | notebook_->AddPage(subway_map_, "Subway"); | 145 | notebook_->AddPage(subway_map_, "Subway"); |
118 | 146 | ||
119 | wxBoxSizer *top_sizer = new wxBoxSizer(wxHORIZONTAL); | 147 | splitter_window_->SplitVertically(choicebook, notebook_, logicalSize.x / 4); |
120 | top_sizer->Add(choicebook, wxSizerFlags().Expand().Proportion(1)); | ||
121 | top_sizer->Add(notebook_, wxSizerFlags().Expand().Proportion(3)); | ||
122 | 148 | ||
123 | SetSizerAndFit(top_sizer); | 149 | SetSize(logicalSize); |
124 | SetSize(1280, 728); | ||
125 | 150 | ||
126 | if (!GetTrackerConfig().asked_to_check_for_updates) { | 151 | if (!GetTrackerConfig().asked_to_check_for_updates) { |
127 | GetTrackerConfig().asked_to_check_for_updates = true; | 152 | GetTrackerConfig().asked_to_check_for_updates = true; |
@@ -138,7 +163,7 @@ TrackerFrame::TrackerFrame() | |||
138 | } | 163 | } |
139 | 164 | ||
140 | if (GetTrackerConfig().should_check_for_updates) { | 165 | if (GetTrackerConfig().should_check_for_updates) { |
141 | CheckForUpdates(/*manual=*/false); | 166 | updater_->CheckForUpdates(/*invisible=*/true); |
142 | } | 167 | } |
143 | 168 | ||
144 | SetStatusText(GetStatusMessage()); | 169 | SetStatusText(GetStatusMessage()); |
@@ -158,15 +183,8 @@ void TrackerFrame::ResetIndicators() { | |||
158 | QueueEvent(new wxCommandEvent(STATE_RESET)); | 183 | QueueEvent(new wxCommandEvent(STATE_RESET)); |
159 | } | 184 | } |
160 | 185 | ||
161 | void TrackerFrame::UpdateIndicators(UpdateIndicatorsMode mode) { | 186 | void TrackerFrame::UpdateIndicators(StateUpdate state) { |
162 | auto evt = new wxCommandEvent(STATE_CHANGED); | 187 | QueueEvent(new StateChangedEvent(STATE_CHANGED, GetId(), std::move(state))); |
163 | evt->SetInt(static_cast<int>(mode)); | ||
164 | |||
165 | QueueEvent(evt); | ||
166 | } | ||
167 | |||
168 | void TrackerFrame::RedrawPosition() { | ||
169 | QueueEvent(new wxCommandEvent(REDRAW_POSITION)); | ||
170 | } | 188 | } |
171 | 189 | ||
172 | void TrackerFrame::OnAbout(wxCommandEvent &event) { | 190 | void TrackerFrame::OnAbout(wxCommandEvent &event) { |
@@ -231,15 +249,18 @@ void TrackerFrame::OnSettings(wxCommandEvent &event) { | |||
231 | GetTrackerConfig().should_check_for_updates = | 249 | GetTrackerConfig().should_check_for_updates = |
232 | dlg.GetShouldCheckForUpdates(); | 250 | dlg.GetShouldCheckForUpdates(); |
233 | GetTrackerConfig().hybrid_areas = dlg.GetHybridAreas(); | 251 | GetTrackerConfig().hybrid_areas = dlg.GetHybridAreas(); |
234 | GetTrackerConfig().show_hunt_panels = dlg.GetShowHuntPanels(); | 252 | GetTrackerConfig().visible_panels = dlg.GetVisiblePanels(); |
253 | GetTrackerConfig().track_position = dlg.GetTrackPosition(); | ||
235 | GetTrackerConfig().Save(); | 254 | GetTrackerConfig().Save(); |
236 | 255 | ||
237 | UpdateIndicators(); | 256 | UpdateIndicators(StateUpdate{.cleared_locations = true, |
257 | .player_position = true, | ||
258 | .changed_settings = true}); | ||
238 | } | 259 | } |
239 | } | 260 | } |
240 | 261 | ||
241 | void TrackerFrame::OnCheckForUpdates(wxCommandEvent &event) { | 262 | void TrackerFrame::OnCheckForUpdates(wxCommandEvent &event) { |
242 | CheckForUpdates(/*manual=*/true); | 263 | updater_->CheckForUpdates(/*invisible=*/false); |
243 | } | 264 | } |
244 | 265 | ||
245 | void TrackerFrame::OnZoomIn(wxCommandEvent &event) { | 266 | void TrackerFrame::OnZoomIn(wxCommandEvent &event) { |
@@ -254,132 +275,93 @@ void TrackerFrame::OnZoomOut(wxCommandEvent &event) { | |||
254 | } | 275 | } |
255 | } | 276 | } |
256 | 277 | ||
257 | void TrackerFrame::OnChangePage(wxBookCtrlEvent &event) { | 278 | void TrackerFrame::OnOpenLogWindow(wxCommandEvent &event) { |
258 | zoom_in_menu_item_->Enable(event.GetSelection() == 1); | 279 | if (log_dialog_ == nullptr) { |
259 | zoom_out_menu_item_->Enable(event.GetSelection() == 1); | 280 | log_dialog_ = new LogDialog(this); |
260 | } | 281 | log_dialog_->Show(); |
282 | TrackerSetLogDialog(log_dialog_); | ||
261 | 283 | ||
262 | void TrackerFrame::OnOpenFile(wxCommandEvent &event) { | 284 | log_dialog_->Bind(wxEVT_CLOSE_WINDOW, &TrackerFrame::OnCloseLogWindow, |
263 | wxFileDialog open_file_dialog( | 285 | this); |
264 | this, "Open Lingo Save File", | 286 | } else { |
265 | fmt::format("{}\\Godot\\app_userdata\\Lingo\\level1_stable", | 287 | log_dialog_->SetFocus(); |
266 | wxStandardPaths::Get().GetUserConfigDir().ToStdString()), | ||
267 | AP_GetSaveName(), "Lingo save file (*.save)|*.save", | ||
268 | wxFD_OPEN | wxFD_FILE_MUST_EXIST); | ||
269 | if (open_file_dialog.ShowModal() == wxID_CANCEL) { | ||
270 | return; | ||
271 | } | 288 | } |
289 | } | ||
272 | 290 | ||
273 | std::string savedata_path = open_file_dialog.GetPath().ToStdString(); | 291 | void TrackerFrame::OnCloseLogWindow(wxCloseEvent& event) { |
292 | TrackerSetLogDialog(nullptr); | ||
293 | log_dialog_ = nullptr; | ||
274 | 294 | ||
275 | if (panels_panel_ == nullptr) { | 295 | event.Skip(); |
276 | panels_panel_ = new TrackerPanel(notebook_); | 296 | } |
277 | notebook_->AddPage(panels_panel_, "Panels"); | ||
278 | } | ||
279 | 297 | ||
280 | notebook_->SetSelection(notebook_->FindPage(panels_panel_)); | 298 | void TrackerFrame::OnChangePage(wxBookCtrlEvent &event) { |
281 | panels_panel_->SetSavedataPath(savedata_path); | 299 | zoom_in_menu_item_->Enable(event.GetSelection() == 1); |
300 | zoom_out_menu_item_->Enable(event.GetSelection() == 1); | ||
301 | } | ||
302 | |||
303 | void TrackerFrame::OnSashPositionChanged(wxSplitterEvent& event) { | ||
304 | notebook_->Refresh(); | ||
282 | } | 305 | } |
283 | 306 | ||
284 | void TrackerFrame::OnStateReset(wxCommandEvent &event) { | 307 | void TrackerFrame::OnStateReset(wxCommandEvent &event) { |
285 | tracker_panel_->UpdateIndicators(); | 308 | tracker_panel_->UpdateIndicators(/*reset=*/true); |
286 | achievements_pane_->UpdateIndicators(); | 309 | achievements_pane_->UpdateIndicators(); |
310 | items_pane_->ResetIndicators(); | ||
311 | options_pane_->OnConnect(); | ||
312 | paintings_pane_->ResetIndicators(); | ||
287 | subway_map_->OnConnect(); | 313 | subway_map_->OnConnect(); |
288 | if (panels_panel_ != nullptr) { | ||
289 | notebook_->DeletePage(notebook_->FindPage(panels_panel_)); | ||
290 | panels_panel_ = nullptr; | ||
291 | } | ||
292 | Refresh(); | 314 | Refresh(); |
293 | } | 315 | } |
294 | 316 | ||
295 | void TrackerFrame::OnStateChanged(wxCommandEvent &event) { | 317 | void TrackerFrame::OnStateChanged(StateChangedEvent &event) { |
296 | UpdateIndicatorsMode mode = static_cast<UpdateIndicatorsMode>(event.GetInt()); | 318 | const StateUpdate &state = event.GetState(); |
297 | 319 | ||
298 | if (mode == kUPDATE_ALL_INDICATORS) { | 320 | bool hunt_panels = false; |
299 | tracker_panel_->UpdateIndicators(); | 321 | if (GetTrackerConfig().visible_panels == TrackerConfig::kHUNT_PANELS) { |
300 | achievements_pane_->UpdateIndicators(); | 322 | hunt_panels = std::any_of( |
323 | state.panels.begin(), state.panels.end(), [](int solve_index) { | ||
324 | return GD_GetPanel(GD_GetPanelBySolveIndex(solve_index)).hunt; | ||
325 | }); | ||
326 | } else if (GetTrackerConfig().visible_panels == TrackerConfig::kALL_PANELS) { | ||
327 | hunt_panels = true; | ||
328 | } | ||
329 | |||
330 | if (!state.items.empty() || !state.paintings.empty() || | ||
331 | state.cleared_locations || hunt_panels) { | ||
332 | // TODO: The only real reason to reset tracker_panel during an active | ||
333 | // connection is if the hunt panels setting changes. If we remove hunt | ||
334 | // panels later, we can get rid of this. | ||
335 | tracker_panel_->UpdateIndicators(/*reset=*/state.changed_settings); | ||
301 | subway_map_->UpdateIndicators(); | 336 | subway_map_->UpdateIndicators(); |
302 | if (panels_panel_ != nullptr) { | ||
303 | panels_panel_->UpdateIndicators(); | ||
304 | } | ||
305 | Refresh(); | 337 | Refresh(); |
306 | } else if (mode == kUPDATE_ONLY_PANELS) { | 338 | } else if (state.player_position && GetTrackerConfig().track_position) { |
307 | if (panels_panel_ == nullptr) { | 339 | if (notebook_->GetSelection() == 0) { |
308 | panels_panel_ = new TrackerPanel(notebook_); | 340 | tracker_panel_->Refresh(); |
309 | panels_panel_->SetPanelsMode(); | ||
310 | notebook_->AddPage(panels_panel_, "Panels"); | ||
311 | } | ||
312 | panels_panel_->UpdateIndicators(); | ||
313 | if (notebook_->GetSelection() == 2) { | ||
314 | Refresh(); | ||
315 | } | 341 | } |
316 | } | 342 | } |
317 | } | ||
318 | 343 | ||
319 | void TrackerFrame::OnStatusChanged(wxCommandEvent &event) { | 344 | if (std::any_of(state.panels.begin(), state.panels.end(), |
320 | SetStatusText(GetStatusMessage()); | 345 | [](int solve_index) { |
321 | } | 346 | return GD_GetPanel(GD_GetPanelBySolveIndex(solve_index)) |
322 | 347 | .achievement; | |
323 | void TrackerFrame::OnRedrawPosition(wxCommandEvent &event) { | 348 | })) { |
324 | if (notebook_->GetSelection() == 0) { | 349 | achievements_pane_->UpdateIndicators(); |
325 | tracker_panel_->Refresh(); | ||
326 | } else if (notebook_->GetSelection() == 2) { | ||
327 | panels_panel_->Refresh(); | ||
328 | } | 350 | } |
329 | } | ||
330 | |||
331 | void TrackerFrame::OnConnectToAp(ApConnectEvent &event) { | ||
332 | AP_Connect(event.GetServer(), event.GetUser(), event.GetPass()); | ||
333 | } | ||
334 | |||
335 | void TrackerFrame::CheckForUpdates(bool manual) { | ||
336 | wxWebRequest request = wxWebSession::GetDefault().CreateRequest( | ||
337 | this, "https://code.fourisland.com/lingo-ap-tracker/plain/VERSION"); | ||
338 | 351 | ||
339 | if (!request.IsOk()) { | 352 | if (!state.items.empty()) { |
340 | if (manual) { | 353 | items_pane_->UpdateIndicators(state.items); |
341 | wxMessageBox("Could not check for updates.", "Error", | 354 | } |
342 | wxOK | wxICON_ERROR); | ||
343 | } else { | ||
344 | SetStatusText("Could not check for updates."); | ||
345 | } | ||
346 | 355 | ||
347 | return; | 356 | if (!state.paintings.empty()) { |
357 | paintings_pane_->UpdateIndicators(state.paintings); | ||
348 | } | 358 | } |
359 | } | ||
349 | 360 | ||
350 | Bind(wxEVT_WEBREQUEST_STATE, [this, manual](wxWebRequestEvent &evt) { | 361 | void TrackerFrame::OnStatusChanged(wxCommandEvent &event) { |
351 | if (evt.GetState() == wxWebRequest::State_Completed) { | 362 | SetStatusText(wxString::FromUTF8(GetStatusMessage())); |
352 | std::string response = evt.GetResponse().AsString().ToStdString(); | 363 | } |
353 | |||
354 | Version latest_version(response); | ||
355 | if (kTrackerVersion < latest_version) { | ||
356 | std::ostringstream message_text; | ||
357 | message_text << "There is a newer version of Lingo AP Tracker " | ||
358 | "available. You have " | ||
359 | << kTrackerVersion.ToString() | ||
360 | << ", and the latest version is " | ||
361 | << latest_version.ToString() | ||
362 | << ". Would you like to update?"; | ||
363 | |||
364 | if (wxMessageBox(message_text.str(), "Update available", wxYES_NO) == | ||
365 | wxYES) { | ||
366 | wxLaunchDefaultBrowser( | ||
367 | "https://code.fourisland.com/lingo-ap-tracker/about/" | ||
368 | "CHANGELOG.md"); | ||
369 | } | ||
370 | } else if (manual) { | ||
371 | wxMessageBox("Lingo AP Tracker is up to date!", "Lingo AP Tracker", | ||
372 | wxOK); | ||
373 | } | ||
374 | } else if (evt.GetState() == wxWebRequest::State_Failed) { | ||
375 | if (manual) { | ||
376 | wxMessageBox("Could not check for updates.", "Error", | ||
377 | wxOK | wxICON_ERROR); | ||
378 | } else { | ||
379 | SetStatusText("Could not check for updates."); | ||
380 | } | ||
381 | } | ||
382 | }); | ||
383 | 364 | ||
384 | request.Start(); | 365 | void TrackerFrame::OnConnectToAp(ApConnectEvent &event) { |
366 | AP_Connect(event.GetServer(), event.GetUser(), event.GetPass()); | ||
385 | } | 367 | } |