From 25ea14ad16e1bac8b917e91ea6ace5591d9a47cf Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 21 Oct 2025 12:39:19 +0000 Subject: [PATCH 1/3] Initial plan From f696d31067bc2f084ab3168a7016c0311b22baff Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 21 Oct 2025 12:46:59 +0000 Subject: [PATCH 2/3] Add FlowLayout and convert DebugControlsWidget to use wrapping layout - Created FlowLayout class for automatic button wrapping - Changed DebugControlsWidget from QToolBar to QWidget with FlowLayout - Updated button creation to use QToolButtons within the flow layout - Maintained all existing functionality while enabling button wrapping Co-authored-by: xusheng6 <94503187+xusheng6@users.noreply.github.com> --- ui/controlswidget.cpp | 227 +++++++++++++++++++++--------------------- ui/controlswidget.h | 22 +++- ui/flowlayout.cpp | 166 ++++++++++++++++++++++++++++++ ui/flowlayout.h | 50 ++++++++++ 4 files changed, 349 insertions(+), 116 deletions(-) create mode 100644 ui/flowlayout.cpp create mode 100644 ui/flowlayout.h diff --git a/ui/controlswidget.cpp b/ui/controlswidget.cpp index 5b054ab2..2b367491 100644 --- a/ui/controlswidget.cpp +++ b/ui/controlswidget.cpp @@ -17,6 +17,7 @@ limitations under the License. #include "controlswidget.h" #include "adaptersettings.h" #include "timestampnavigationdialog.h" +#include "flowlayout.h" #include #include #include @@ -34,7 +35,7 @@ using namespace BinaryNinja; DebugControlsWidget::DebugControlsWidget(QWidget* parent, const std::string name, BinaryViewRef data) : - QToolBar(parent), m_name(name) + QWidget(parent), m_name(name) { m_controller = DebuggerController::GetController(data); if (!m_controller) @@ -45,101 +46,101 @@ DebugControlsWidget::DebugControlsWidget(QWidget* parent, const std::string name auto red = getThemeColor(RedStandardHighlightColor); auto white = getThemeColor(WhiteStandardHighlightColor); - m_actionRun = addAction(getColoredIcon(":/debugger/start", red), "Launch", [this]() { - performLaunch(); - }); - m_actionRun->setToolTip(getToolTip("Launch")); - - m_actionPause = addAction(getColoredIcon(":/debugger/pause", white), "Pause", [this]() { - performPause(); - }); - m_actionPause->setToolTip(getToolTip("Pause")); - - m_actionResume = addAction(getColoredIcon(":/debugger/resume", green), "Resume", [this]() { - performResume(); - }); - m_actionResume->setToolTip(getToolTip("Resume")); - - // m_actionRun->setVisible(true); - m_actionPause->setVisible(false); - m_actionResume->setVisible(false); - - m_actionAttachPid = addAction(getColoredIcon(":/debugger/connect", white), "Attach To Process...", [this]() { - performAttachPID(); - }); - m_actionAttachPid->setToolTip(getToolTip("Attach To Process...")); - - m_actionDetach = addAction(getColoredIcon(":/debugger/disconnect", red), "Detach", [this]() { - performDetach(); - }); - m_actionDetach->setVisible(false); - m_actionDetach->setToolTip(getToolTip("Detach")); - - m_actionRestart = addAction(getColoredIcon(":/debugger/restart", red), "Restart", [this]() { - performRestart(); - }); - m_actionRestart->setToolTip(getToolTip("Restart")); - - m_actionQuit = addAction(getColoredIcon(":/debugger/cancel", red), "Kill", [this]() { - performQuit(); - }); - m_actionQuit->setToolTip(getToolTip("Kill")); - addSeparator(); - - m_actionStepInto = addAction(getColoredIcon(":/debugger/step-into", cyan), "Step Into", [this]() { - performStepInto(); - }); - m_actionStepInto->setToolTip(getToolTip("Step Into")); - - m_actionStepOver = addAction(getColoredIcon(":/debugger/step-over", cyan), "Step Over", [this]() { - performStepOver(); - }); - m_actionStepOver->setToolTip(getToolTip("Step Over")); - - m_actionStepReturn = addAction(getColoredIcon(":/debugger/step-out", cyan), "Step Return", [this]() { - performStepReturn(); - }); - m_actionStepReturn->setToolTip(getToolTip("Step Return")); - addSeparator(); - - m_actionSettings = addAction(getColoredIcon(":/debugger/settings", cyan), "Settings", [this]() { - performSettings(); - }); - m_actionSettings->setToolTip(getToolTip("Debug Adapter Settings")); - addSeparator(); - - m_actionToggleBreakpoint = addAction(getColoredIcon(":/debugger/breakpoint", red), "Breakpoint", [this]() { - toggleBreakpoint(); - }); - m_actionToggleBreakpoint->setToolTip(getToolTip("Toggle Breakpoint")); + FlowLayout* layout = new FlowLayout(this, 2, 2, 2); + setLayout(layout); + + auto createButton = [&](const QString& iconPath, const QColor& color, const QString& text, const QString& tooltip, auto callback) { + QToolButton* button = new QToolButton(this); + button->setIcon(getColoredIcon(iconPath, color)); + button->setToolTip(tooltip); + button->setIconSize(QSize(20, 20)); + button->setAutoRaise(true); + connect(button, &QToolButton::clicked, this, callback); + layout->addWidget(button); + return button; + }; + + auto createAction = [&](QToolButton* button) { + QAction* action = new QAction(this); + action->setIcon(button->icon()); + action->setToolTip(button->toolTip()); + connect(action, &QAction::triggered, button, &QToolButton::click); + button->setDefaultAction(action); + return action; + }; + + auto createSeparator = [&]() { + QFrame* line = new QFrame(this); + line->setFrameShape(QFrame::VLine); + line->setFrameShadow(QFrame::Sunken); + line->setFixedWidth(2); + line->setFixedHeight(24); + layout->addWidget(line); + }; + + m_buttonRun = createButton(":/debugger/start", red, "Launch", getToolTip("Launch"), &DebugControlsWidget::performLaunch); + m_actionRun = createAction(m_buttonRun); + + m_buttonPause = createButton(":/debugger/pause", white, "Pause", getToolTip("Pause"), &DebugControlsWidget::performPause); + m_actionPause = createAction(m_buttonPause); + m_buttonPause->setVisible(false); + + m_buttonResume = createButton(":/debugger/resume", green, "Resume", getToolTip("Resume"), &DebugControlsWidget::performResume); + m_actionResume = createAction(m_buttonResume); + m_buttonResume->setVisible(false); + + m_buttonAttachPid = createButton(":/debugger/connect", white, "Attach To Process...", getToolTip("Attach To Process..."), &DebugControlsWidget::performAttachPID); + m_actionAttachPid = createAction(m_buttonAttachPid); + + m_buttonDetach = createButton(":/debugger/disconnect", red, "Detach", getToolTip("Detach"), &DebugControlsWidget::performDetach); + m_actionDetach = createAction(m_buttonDetach); + m_buttonDetach->setVisible(false); + + m_buttonRestart = createButton(":/debugger/restart", red, "Restart", getToolTip("Restart"), &DebugControlsWidget::performRestart); + m_actionRestart = createAction(m_buttonRestart); + + m_buttonQuit = createButton(":/debugger/cancel", red, "Kill", getToolTip("Kill"), &DebugControlsWidget::performQuit); + m_actionQuit = createAction(m_buttonQuit); + + createSeparator(); + + m_buttonStepInto = createButton(":/debugger/step-into", cyan, "Step Into", getToolTip("Step Into"), &DebugControlsWidget::performStepInto); + m_actionStepInto = createAction(m_buttonStepInto); + + m_buttonStepOver = createButton(":/debugger/step-over", cyan, "Step Over", getToolTip("Step Over"), &DebugControlsWidget::performStepOver); + m_actionStepOver = createAction(m_buttonStepOver); + + m_buttonStepReturn = createButton(":/debugger/step-out", cyan, "Step Return", getToolTip("Step Return"), &DebugControlsWidget::performStepReturn); + m_actionStepReturn = createAction(m_buttonStepReturn); + + createSeparator(); + + m_buttonSettings = createButton(":/debugger/settings", cyan, "Settings", getToolTip("Debug Adapter Settings"), &DebugControlsWidget::performSettings); + m_actionSettings = createAction(m_buttonSettings); + + createSeparator(); + + m_buttonToggleBreakpoint = createButton(":/debugger/breakpoint", red, "Breakpoint", getToolTip("Toggle Breakpoint"), &DebugControlsWidget::toggleBreakpoint); + m_actionToggleBreakpoint = createAction(m_buttonToggleBreakpoint); if(m_controller->IsTTD()) - addSeparator(); //TODO: IsTTD only updates when the adapter is connected. This leaves the separator in place when the adapter is disconnected. + createSeparator(); - m_actionGoBack = addAction(getColoredIcon(":/debugger/resume-reverse", red), "Go Backwards", [this]() { - performGoReverse(); - }); - m_actionGoBack->setToolTip(getToolTip("Go Backwards")); + m_buttonGoBack = createButton(":/debugger/resume-reverse", red, "Go Backwards", getToolTip("Go Backwards"), &DebugControlsWidget::performGoReverse); + m_actionGoBack = createAction(m_buttonGoBack); - m_actionStepIntoBack = addAction(getColoredIcon(":/debugger/step-into-reverse", red), "Step Into Backwards", [this]() { - performStepIntoReverse(); - }); - m_actionStepIntoBack->setToolTip(getToolTip("Step Into Backwards")); - - m_actionStepOverBack = addAction(getColoredIcon(":/debugger/step-back", red), "Step Over Backwards", [this]() { - performStepOverReverse(); - }); - m_actionStepOverBack->setToolTip(getToolTip("Step Over Backwards")); - - m_actionStepReturnBack = addAction(getColoredIcon(":/debugger/step-out-reverse", red), "Step Return Backwards", [this]() { - performStepReturnReverse(); - }); - m_actionStepReturnBack->setToolTip(getToolTip("Step Return Backwards")); - - m_actionTimestampNavigation = addAction(getColoredIcon(":/debugger/ttd-timestamp", cyan), "Navigate to Timestamp", [this]() { - performTimestampNavigation(); - }); - m_actionTimestampNavigation->setToolTip(getToolTip("Navigate to TTD Timestamp...")); + m_buttonStepIntoBack = createButton(":/debugger/step-into-reverse", red, "Step Into Backwards", getToolTip("Step Into Backwards"), &DebugControlsWidget::performStepIntoReverse); + m_actionStepIntoBack = createAction(m_buttonStepIntoBack); + + m_buttonStepOverBack = createButton(":/debugger/step-back", red, "Step Over Backwards", getToolTip("Step Over Backwards"), &DebugControlsWidget::performStepOverReverse); + m_actionStepOverBack = createAction(m_buttonStepOverBack); + + m_buttonStepReturnBack = createButton(":/debugger/step-out-reverse", red, "Step Return Backwards", getToolTip("Step Return Backwards"), &DebugControlsWidget::performStepReturnReverse); + m_actionStepReturnBack = createAction(m_buttonStepReturnBack); + + m_buttonTimestampNavigation = createButton(":/debugger/ttd-timestamp", cyan, "Navigate to Timestamp", getToolTip("Navigate to TTD Timestamp..."), &DebugControlsWidget::performTimestampNavigation); + m_actionTimestampNavigation = createAction(m_buttonTimestampNavigation); + updateButtons(); } @@ -484,7 +485,7 @@ void DebugControlsWidget::setStartingEnabled(bool enabled) m_actionRun->setEnabled(enabled && canExec()); // TODO: we need to support specifying whether the adapter supports attaching to a pid m_actionAttachPid->setEnabled(enabled && canExec()); - m_actionAttachPid->setVisible(enabled); + m_buttonAttachPid->setVisible(enabled); } @@ -493,7 +494,7 @@ void DebugControlsWidget::setStoppingEnabled(bool enabled) m_actionRestart->setEnabled(enabled); m_actionQuit->setEnabled(enabled); m_actionDetach->setEnabled(enabled); - m_actionDetach->setVisible(enabled); + m_buttonDetach->setVisible(enabled); } @@ -508,20 +509,18 @@ void DebugControlsWidget::setSteppingEnabled(bool enabled) void DebugControlsWidget::setReverseSteppingEnabled(bool enabled) { m_actionStepIntoBack->setEnabled(enabled); - m_actionStepIntoBack->setVisible(enabled); + m_buttonStepIntoBack->setVisible(enabled); m_actionStepOverBack->setEnabled(enabled); - m_actionStepOverBack->setVisible(enabled); + m_buttonStepOverBack->setVisible(enabled); m_actionStepReturnBack->setEnabled(enabled); - m_actionStepReturnBack->setVisible(enabled); + m_buttonStepReturnBack->setVisible(enabled); m_actionTimestampNavigation->setEnabled(enabled); - m_actionTimestampNavigation->setVisible(enabled); + m_buttonTimestampNavigation->setVisible(enabled); } void DebugControlsWidget::updateButtons() { - this->setIconSize(QSize(20, 20)); - DebugAdapterConnectionStatus connection = m_controller->GetConnectionStatus(); DebugAdapterTargetStatus status = m_controller->GetTargetStatus(); @@ -535,10 +534,10 @@ void DebugControlsWidget::updateButtons() m_actionResume->setEnabled(false); m_actionGoBack->setEnabled(false); - m_actionRun->setVisible(true); - m_actionPause->setVisible(false); - m_actionResume->setVisible(false); - m_actionGoBack->setVisible(false); + m_buttonRun->setVisible(true); + m_buttonPause->setVisible(false); + m_buttonResume->setVisible(false); + m_buttonGoBack->setVisible(false); } else if (status == DebugAdapterRunningStatus) { @@ -546,17 +545,17 @@ void DebugControlsWidget::updateButtons() setStoppingEnabled(true); setSteppingEnabled(false); setReverseSteppingEnabled(false); - m_actionStepIntoBack->setVisible(m_controller->IsTTD()); - m_actionStepOverBack->setVisible(m_controller->IsTTD()); + m_buttonStepIntoBack->setVisible(m_controller->IsTTD()); + m_buttonStepOverBack->setVisible(m_controller->IsTTD()); m_actionPause->setEnabled(true); m_actionResume->setEnabled(false); m_actionGoBack->setEnabled(false); - m_actionRun->setVisible(false); - m_actionPause->setVisible(true); - m_actionResume->setVisible(false); - m_actionGoBack->setVisible(false); + m_buttonRun->setVisible(false); + m_buttonPause->setVisible(true); + m_buttonResume->setVisible(false); + m_buttonGoBack->setVisible(false); } else // status == DebugAdapterPausedStatus { @@ -568,10 +567,10 @@ void DebugControlsWidget::updateButtons() m_actionResume->setEnabled(true); m_actionGoBack->setEnabled(m_controller->IsTTD()); - m_actionRun->setVisible(false); - m_actionPause->setVisible(false); - m_actionResume->setVisible(true); - m_actionGoBack->setVisible(m_controller->IsTTD()); + m_buttonRun->setVisible(false); + m_buttonPause->setVisible(false); + m_buttonResume->setVisible(true); + m_buttonGoBack->setVisible(m_controller->IsTTD()); } } diff --git a/ui/controlswidget.h b/ui/controlswidget.h index 3c023a5f..7dfa096d 100644 --- a/ui/controlswidget.h +++ b/ui/controlswidget.h @@ -16,7 +16,7 @@ limitations under the License. #pragma once -#include +#include #include #include #include @@ -28,7 +28,7 @@ limitations under the License. using namespace BinaryNinjaDebuggerAPI; -class DebugControlsWidget : public QToolBar +class DebugControlsWidget : public QWidget { Q_OBJECT @@ -36,6 +36,24 @@ class DebugControlsWidget : public QToolBar std::string m_name; DbgRef m_controller; + QToolButton* m_buttonRun; + QToolButton* m_buttonAttachPid; + QToolButton* m_buttonRestart; + QToolButton* m_buttonQuit; + QToolButton* m_buttonDetach; + QToolButton* m_buttonPause; + QToolButton* m_buttonResume; + QToolButton* m_buttonGoBack; + QToolButton* m_buttonStepInto; + QToolButton* m_buttonStepIntoBack; + QToolButton* m_buttonStepOver; + QToolButton* m_buttonStepOverBack; + QToolButton* m_buttonStepReturn; + QToolButton* m_buttonStepReturnBack; + QToolButton* m_buttonSettings; + QToolButton* m_buttonToggleBreakpoint; + QToolButton* m_buttonTimestampNavigation; + QAction* m_actionRun; QAction* m_actionAttachPid; QAction* m_actionRestart; diff --git a/ui/flowlayout.cpp b/ui/flowlayout.cpp new file mode 100644 index 00000000..879a247e --- /dev/null +++ b/ui/flowlayout.cpp @@ -0,0 +1,166 @@ +/* +Copyright 2020-2025 Vector 35 Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "flowlayout.h" +#include + +FlowLayout::FlowLayout(QWidget *parent, int margin, int hSpacing, int vSpacing) + : QLayout(parent), m_hSpace(hSpacing), m_vSpace(vSpacing) +{ + setContentsMargins(margin, margin, margin, margin); +} + +FlowLayout::FlowLayout(int margin, int hSpacing, int vSpacing) + : m_hSpace(hSpacing), m_vSpace(vSpacing) +{ + setContentsMargins(margin, margin, margin, margin); +} + +FlowLayout::~FlowLayout() +{ + QLayoutItem *item; + while ((item = takeAt(0))) + delete item; +} + +void FlowLayout::addItem(QLayoutItem *item) +{ + itemList.append(item); +} + +int FlowLayout::horizontalSpacing() const +{ + if (m_hSpace >= 0) + return m_hSpace; + else + return smartSpacing(QStyle::PM_LayoutHorizontalSpacing); +} + +int FlowLayout::verticalSpacing() const +{ + if (m_vSpace >= 0) + return m_vSpace; + else + return smartSpacing(QStyle::PM_LayoutVerticalSpacing); +} + +int FlowLayout::count() const +{ + return itemList.size(); +} + +QLayoutItem *FlowLayout::itemAt(int index) const +{ + return itemList.value(index); +} + +QLayoutItem *FlowLayout::takeAt(int index) +{ + if (index >= 0 && index < itemList.size()) + return itemList.takeAt(index); + return nullptr; +} + +Qt::Orientations FlowLayout::expandingDirections() const +{ + return {}; +} + +bool FlowLayout::hasHeightForWidth() const +{ + return true; +} + +int FlowLayout::heightForWidth(int width) const +{ + int height = doLayout(QRect(0, 0, width, 0), true); + return height; +} + +void FlowLayout::setGeometry(const QRect &rect) +{ + QLayout::setGeometry(rect); + doLayout(rect, false); +} + +QSize FlowLayout::sizeHint() const +{ + return minimumSize(); +} + +QSize FlowLayout::minimumSize() const +{ + QSize size; + for (const QLayoutItem *item : itemList) + size = size.expandedTo(item->minimumSize()); + + const QMargins margins = contentsMargins(); + size += QSize(margins.left() + margins.right(), margins.top() + margins.bottom()); + return size; +} + +int FlowLayout::doLayout(const QRect &rect, bool testOnly) const +{ + int left, top, right, bottom; + getContentsMargins(&left, &top, &right, &bottom); + QRect effectiveRect = rect.adjusted(+left, +top, -right, -bottom); + int x = effectiveRect.x(); + int y = effectiveRect.y(); + int lineHeight = 0; + + for (QLayoutItem *item : itemList) + { + const QWidget *wid = item->widget(); + int spaceX = horizontalSpacing(); + if (spaceX == -1) + spaceX = wid->style()->layoutSpacing(QSizePolicy::PushButton, QSizePolicy::PushButton, Qt::Horizontal); + int spaceY = verticalSpacing(); + if (spaceY == -1) + spaceY = wid->style()->layoutSpacing(QSizePolicy::PushButton, QSizePolicy::PushButton, Qt::Vertical); + + int nextX = x + item->sizeHint().width() + spaceX; + if (nextX - spaceX > effectiveRect.right() && lineHeight > 0) + { + x = effectiveRect.x(); + y = y + lineHeight + spaceY; + nextX = x + item->sizeHint().width() + spaceX; + lineHeight = 0; + } + + if (!testOnly) + item->setGeometry(QRect(QPoint(x, y), item->sizeHint())); + + x = nextX; + lineHeight = qMax(lineHeight, item->sizeHint().height()); + } + return y + lineHeight - rect.y() + bottom; +} + +int FlowLayout::smartSpacing(QStyle::PixelMetric pm) const +{ + QObject *parent = this->parent(); + if (!parent) + return -1; + else if (parent->isWidgetType()) + { + QWidget *pw = static_cast(parent); + return pw->style()->pixelMetric(pm, nullptr, pw); + } + else + { + return static_cast(parent)->spacing(); + } +} diff --git a/ui/flowlayout.h b/ui/flowlayout.h new file mode 100644 index 00000000..0db9e284 --- /dev/null +++ b/ui/flowlayout.h @@ -0,0 +1,50 @@ +/* +Copyright 2020-2025 Vector 35 Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#pragma once + +#include +#include +#include + +class FlowLayout : public QLayout +{ +public: + explicit FlowLayout(QWidget *parent, int margin = -1, int hSpacing = -1, int vSpacing = -1); + explicit FlowLayout(int margin = -1, int hSpacing = -1, int vSpacing = -1); + ~FlowLayout(); + + void addItem(QLayoutItem *item) override; + int horizontalSpacing() const; + int verticalSpacing() const; + Qt::Orientations expandingDirections() const override; + bool hasHeightForWidth() const override; + int heightForWidth(int) const override; + int count() const override; + QLayoutItem *itemAt(int index) const override; + QSize minimumSize() const override; + void setGeometry(const QRect &rect) override; + QSize sizeHint() const override; + QLayoutItem *takeAt(int index) override; + +private: + int doLayout(const QRect &rect, bool testOnly) const; + int smartSpacing(QStyle::PixelMetric pm) const; + + QList itemList; + int m_hSpace; + int m_vSpace; +}; From f2589046c848ae5781ec78d09cd039dbe106d2d5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 21 Oct 2025 12:49:09 +0000 Subject: [PATCH 3/3] Add documentation comments for FlowLayout and DebugControlsWidget Co-authored-by: xusheng6 <94503187+xusheng6@users.noreply.github.com> --- ui/controlswidget.h | 4 +++- ui/flowlayout.h | 3 +++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/ui/controlswidget.h b/ui/controlswidget.h index 7dfa096d..d269857a 100644 --- a/ui/controlswidget.h +++ b/ui/controlswidget.h @@ -27,7 +27,9 @@ limitations under the License. using namespace BinaryNinjaDebuggerAPI; - +// Debug controls widget that displays debugger control buttons in a flow layout. +// Buttons automatically wrap to the next row when the widget is narrow, ensuring +// all controls remain accessible in narrow sidebars. class DebugControlsWidget : public QWidget { Q_OBJECT diff --git a/ui/flowlayout.h b/ui/flowlayout.h index 0db9e284..54161790 100644 --- a/ui/flowlayout.h +++ b/ui/flowlayout.h @@ -20,6 +20,9 @@ limitations under the License. #include #include +// FlowLayout arranges widgets in a left-to-right flow, wrapping to the next row +// when there is insufficient horizontal space. This is similar to how text wraps +// in a word processor or how items flow in CSS flexbox with flex-wrap enabled. class FlowLayout : public QLayout { public: