14#include <wx/artprov.h>
15#include <wx/bmpbuttn.h>
16#include <wx/filename.h>
21#include <wxSVG/imagsvg.h>
33 :
EmberUI::
SidePanel(parent, panelName, false), m_mainFrame(mainFrame), m_panelName(panelName),
34 m_tabBarButtonsEnabled(true), m_isInitializing(true),
35 m_descriptor(panelType, parentDescriptor ? parentDescriptor->GetHierarchyLevel() + 1 : 0, parentDescriptor) {
36 LOG_INFO(
"Panel", std::string(
"Creating side panel: ") + m_panelName.ToStdString() +
37 " (Type: " + m_descriptor.GetDisplayName().ToStdString() +
38 ", Level: " + std::to_string(m_descriptor.GetHierarchyLevel()) +
")");
55 wxBoxSizer *mainSizer =
new wxBoxSizer(wxVERTICAL);
61 mainSizer->Add(
m_notebook, 1, wxEXPAND | wxALL, 2);
66 LOG_INFO(
"Panel", std::string(
"Layout created for side panel: ") +
m_panelName.ToStdString());
70 LOG_INFO(
"Panel", std::string(
"Calling OnPanelSpecificSetup() for ") +
m_panelName.ToStdString() +
71 " (type: " + std::string(
typeid(*this).name()) +
")");
73 LOG_INFO(
"Panel", std::string(
"OnPanelSpecificSetup() completed for ") +
m_panelName.ToStdString());
76 LOG_INFO(
"Panel", std::string(
"Notebook has ") + std::to_string(
m_notebook->GetPageCount()) +
77 " pages after OnPanelSpecificSetup()");
84 long style = wxAUI_NB_DEFAULT_STYLE;
85 style &= ~wxAUI_NB_TAB_MOVE;
86 style &= ~wxAUI_NB_TAB_EXTERNAL_MOVE;
87 style &= ~wxAUI_NB_TAB_SPLIT;
95 " with integrated tab bar buttons");
105 LOG_INFO(
"Panel", std::string(
"Base OnPanelSpecificSetup() called for %s") +
m_panelName.ToStdString());
109 LOG_INFO(
"Panel", std::string(
"*** RestoreAndEnsureTabs() called for ") +
m_panelName.ToStdString() +
" ***");
113 if (!lastOpenTabs.empty()) {
114 LOG_INFO(
"Panel", wxString::Format(
"Restoring %zu saved tabs", lastOpenTabs.size()).ToStdString());
115 for (
const auto &tabTypeStr : lastOpenTabs) {
123 LOG_INFO(
"Panel",
"No tabs exist, trying to create a default tab");
126 for (
const auto &tabType : supportedTypes) {
131 if (countAfter > countBefore) {
132 LOG_INFO(
"Panel", wxString::Format(
"Created fallback tab: %s",
140 LOG_ERROR(
"Panel", std::string(
"Failed to create any default tab for ") +
m_panelName.ToStdString() +
141 " - all supported types at max instances");
154 for (
size_t i = 0; i <
m_notebook->GetPageCount(); ++i) {
155 if (
m_notebook->GetPageText(i) == targetTabName) {
156 targetIndex =
static_cast<int>(i);
162 LOG_INFO(
"Panel", wxString::Format(
"Set active tab to '%s' (index %d) [rememberLastTab=%s], total tabs: %d",
168 LOG_INFO(
"Panel", std::string(
"RestoreAndEnsureTabs() completed for ") +
m_panelName.ToStdString());
177 wxString tabTitle = tab->GetTitle();
182 LOG_INFO(
"Panel", std::string(
"Added tab '") + tabTitle.ToStdString() +
"' to " +
m_panelName.ToStdString() +
183 " at index " + std::to_string(index));
188 if (prefManager.GetPreferences().SaveToFile(configPath)) {
189 LOG_INFO(
"Panel",
"Saved tab state to disk after tab addition");
191 LOG_ERROR(
"Panel",
"Failed to save tab state to disk after tab addition");
195 std::string(
"Failed to add tab '") + tabTitle.ToStdString() +
"' to " +
m_panelName.ToStdString());
203 std::string(
"Invalid tab index ") + std::to_string(index) +
" for " +
m_panelName.ToStdString());
210 std::string(
"Removed tab at index ") + std::to_string(index) +
" from " +
m_panelName.ToStdString());
212 LOG_ERROR(
"Panel", std::string(
"Failed to remove tab at index ") + std::to_string(index) +
" from " +
222 std::string(
"Set active tab to index ") + std::to_string(index) +
" in " +
m_panelName.ToStdString());
234 " to default tab: " + defaultTabName.ToStdString());
236 wxWindow *defaultTabWindow =
nullptr;
237 for (
size_t i = 0; i <
m_notebook->GetPageCount(); ++i) {
238 if (
m_notebook->GetPageText(i) == defaultTabName) {
244 for (
int i =
static_cast<int>(
m_notebook->GetPageCount()) - 1; i >= 0; --i) {
245 if (
m_notebook->GetPage(i) != defaultTabWindow) {
250 if (defaultTabWindow ==
nullptr) {
260 " complete, tab count: " + std::to_string(
m_notebook->GetPageCount()));
269 std::to_string(defaultTabTypes.size()) +
" default tabs");
275 for (
const auto &tabType : defaultTabTypes) {
285 " complete, tab count: " + std::to_string(
m_notebook->GetPageCount()));
299 int newSelection =
event.GetSelection();
300 if (newSelection != wxNOT_FOUND) {
302 LOG_TRACE(
"Panel", std::string(
"Tab changed to index ") + std::to_string(newSelection) +
" in " +
310 if (prefManager.GetPreferences().SaveToFile(configPath)) {
311 LOG_TRACE(
"Panel",
"Saved active tab state to disk after tab change");
313 LOG_ERROR(
"Panel",
"Failed to save active tab state to disk after tab change");
321 int closedIndex =
event.GetSelection();
322 LOG_INFO(
"Panel", std::string(
"Tab at index ") + std::to_string(closedIndex) +
" closed");
324 if (closedIndex >= 0 && closedIndex <
static_cast<int>(
m_tabs.size())) {
325 if (
m_tabs[closedIndex]) {
326 m_tabs[closedIndex]->OnClosed();
328 m_tabs[closedIndex].release();
331 CallAfter([
this, closedIndex]() {
332 if (closedIndex >= 0 && closedIndex <
static_cast<int>(
m_tabs.size())) {
346 if (prefManager.GetPreferences().SaveToFile(configPath)) {
347 LOG_INFO(
"Panel",
"Saved tab state to disk after tab removal");
349 LOG_ERROR(
"Panel",
"Failed to save tab state to disk after tab removal");
358 LOG_INFO(
"Panel", std::string(
"Add tab button clicked in %s") +
m_panelName.ToStdString());
362 if (event.GetSkipped()) {
368 LOG_INFO(
"Panel", std::string(
"Tab bar add button clicked in %s") +
m_panelName.ToStdString());
370 wxCommandEvent addEvent;
375 LOG_INFO(
"Panel", std::string(
"Tab bar menu button clicked in %s") +
m_panelName.ToStdString());
377 wxCommandEvent menuEvent;
384 wxMenu *mainMenu =
new wxMenu();
387 mainMenu->AppendSeparator();
398 LOG_ERROR(
"Panel",
"Cannot hide panel - no MainFrame reference");
402 wxAuiManager *auiManager =
m_mainFrame->GetAuiManager();
404 LOG_ERROR(
"Panel",
"Cannot hide panel - no AUI manager");
410 wxAuiPaneInfo &pane = auiManager->GetPane(paneName);
413 auiManager->Update();
423 LOG_ERROR(
"Panel",
"Cannot open preferences - no MainFrame reference");
432 int result = dialog.ShowModal();
433 if (result == wxID_OK) {
442 std::string(
"Cannot create tab - missing notebook or mainframe in %s") +
m_panelName.ToStdString());
451 std::string(
"Tab '") + tabName.ToStdString() +
"' is not allowed in " +
m_panelName.ToStdString());
457 std::string(
"Tab '") + tabName.ToStdString() +
"' already exists in " +
m_panelName.ToStdString());
462 if (maxInstances > 0) {
463 int currentCount =
m_mainFrame->CountTabGlobally(tabName);
464 if (currentCount >= maxInstances) {
465 LOG_WARNING(
"Panel", std::string(
"Tab '") + tabName.ToStdString() +
"' has reached max instances (" +
466 std::to_string(maxInstances) +
")");
475 std::string(
"Created and added tab '") + tabName.ToStdString() +
"' to " +
m_panelName.ToStdString());
478 std::string(
"Failed to create tab '") + tabName.ToStdString() +
"' for " +
m_panelName.ToStdString());
484 if (supportedTypes.empty()) {
493 for (
size_t i = 0; i < supportedTypes.size(); ++i) {
504 wxMenuItem *item = menu.Append(menuId, displayName, tooltip);
507 bool existsInThisPanel =
511 int currentCount =
m_mainFrame->CountTabGlobally(displayName);
512 bool atMaxInstances = (maxInstances > 0) && (currentCount >= maxInstances);
514 if (existsInThisPanel || atMaxInstances) {
516 if (atMaxInstances && !existsInThisPanel) {
517 if (maxInstances == 1) {
518 item->SetItemLabel(displayName +
" (open in another panel)");
520 item->SetItemLabel(displayName +
" (max " + std::to_string(maxInstances) +
" reached)");
526 wxEVT_COMMAND_MENU_SELECTED, [
this, tabType](wxCommandEvent &) {
CreateAndAddTab(tabType); }, menuId);
529 if (validItems == 0) {
530 wxMenuItem *item = menu.Append(wxID_ANY,
"(No tabs available for this panel)");
536 LOG_TRACE(
"Panel", std::string(
"Showed tab creation menu for ") +
m_panelName.ToStdString() +
" with " +
537 std::to_string(validItems) +
" options");
549 LOG_INFO(
"Panel", std::string(
"Tab bar buttons ") + (enabled ?
"enabled" :
"disabled") +
" for " +
561 LOG_INFO(
"SidePanel", std::string(
"Base ApplyPreferences called for ") +
m_panelName.ToStdString() +
562 " - derived class should override this");
566 LOG_INFO(
"SidePanel", std::string(
"Base SaveState called for ") +
m_panelName.ToStdString() +
567 " - derived class should override this");
571 LOG_INFO(
"SidePanel", std::string(
"Base RestoreState called for ") +
m_panelName.ToStdString() +
572 " - derived class should override this");
BehaviorTreeProjectDialog::OnProjectNameChanged BehaviorTreeProjectDialog::OnRemoveFiles wxEND_EVENT_TABLE() BehaviorTreeProjectDialog
wxBEGIN_EVENT_TABLE(SidePanel, EmberUI::SidePanel) EVT_AUINOTEBOOK_PAGE_CHANGED(SidePanel
std::unique_ptr< ITab > ITabPtr
#define LOG_ERROR(category, message)
#define LOG_TRACE(category, message)
#define LOG_WARNING(category, message)
#define LOG_INFO(category, message)
MainFrame::OnExit EVT_MENU(wxID_ABOUT, MainFrame::OnAbout) EVT_MENU(ID_NewProject
Centralized resource path management for EmberForge.
static AppPreferencesManager & GetInstance()
static EmberCore::String GetDefaultConfigPath()
Custom wxAuiNotebook that integrates Add Tab and Menu buttons into the tab bar.
void SetTabBarButtonsEnabled(bool enabled)
Enable or disable the integrated tab bar buttons.
static wxString GetTabDisplayName(TabType type)
Get the display name for a tab type.
static wxString GetTabTooltip(TabType type)
Get the tooltip text for a tab type.
static std::unique_ptr< ITab > CreateTab(TabType type, wxWindow *parent, MainFrame *mainFrame=nullptr)
Create a tab of the specified type.
static int GetMaxGlobalInstances(TabType type)
Get the maximum number of instances allowed globally across all panels.
static bool IsTabAllowedInPanel(TabType tabType, EmberForge::PanelType panelType)
Check if a tab type is allowed in a specific panel type.
static bool AllowMultipleInstances(TabType type)
Check if a tab type can be created multiple times within a single panel.
static TabType StringToTabType(const std::string &str)
Convert string identifier to tab type.
Tabbed side panel using wxAuiNotebook.
virtual ITabPtr RemoveTab(int index)
Removes the tab at the given index; returns the removed tab.
bool HasTabWithName(const wxString &name) const
Returns true if a tab with the given name exists.
std::vector< ITabPtr > m_tabs
virtual void ClearAllTabs()
Removes all tabs.
virtual int AddTab(ITabPtr tab)
Adds a tab and returns its index.
virtual bool SetActiveTab(int index)
Sets the active tab by index; returns true on success.
wxAuiNotebook * m_notebook
Main application window for EmberForge.
Preferences dialog for configuring EmberForge application settings.
bool SelectSection(const wxString §ionName)
Select a section in the preferences tree by name.
void SetTabBarButtonsEnabled(bool enabled)
@ ID_BASE_PREFERENCES_MENU_ITEM
@ ID_BASE_HIDE_PANEL_MENU_ITEM
virtual void ApplyPreferences()
bool SetActiveTab(int index) override
Sets the active tab by index; returns true on success.
void OnTabChanged(wxAuiNotebookEvent &event)
virtual const std::vector< std::string > & GetLastOpenTabs() const =0
void OnHidePanelClicked(wxCommandEvent &event)
void ResetToDefaultTab(EmberForge::TabType defaultTabType)
void RestoreAndEnsureTabs()
bool m_tabBarButtonsEnabled
void ClearAllTabs() override
Removes all tabs.
virtual void SetupEventHandlers()
void OnPanelPreferencesClicked(wxCommandEvent &event)
EmberForge::CustomAuiNotebook * GetCustomNotebook() const
virtual void OnMenuButtonClicked(wxCommandEvent &event)
virtual void OnPanelSpecificSetup()
void DoSetupEventHandlers()
void OnAddTabButtonClickedInternal(wxCommandEvent &event)
void OnTabClosed(wxAuiNotebookEvent &event)
virtual std::string GetLastActiveTab() const =0
void ShowTabCreationMenu()
void ToggleTabBarButtons()
virtual void OnAddTabButtonClicked(wxCommandEvent &event)
virtual void RestoreState()
void OnTabBarMenuButtonClicked(wxCommandEvent &event)
virtual bool GetRememberLastTab() const =0
void CreateAndAddTab(EmberForge::TabType tabType)
EmberForge::PanelDescriptor m_descriptor
void ResetToDefaultTabs(const std::vector< EmberForge::TabType > &defaultTabTypes)
void CreateNotebook() override
Hook: creates the notebook control. Override to customize.
virtual std::vector< EmberForge::TabType > GetSupportedTabTypes() const
void OnTabBarAddButtonClicked(wxCommandEvent &event)
void CreateLayout() override
Creates the side panel layout with the notebook.
int AddTab(ITabPtr tab) override
Adds a tab and returns its index.
virtual std::string GetDefaultActiveTab() const =0
ITabPtr RemoveTab(int index) override
Removes the tab at the given index; returns the removed tab.
PanelType
Defines the type of panel in the UI hierarchy.
TabType
Enumeration of available tab types.