Merged disassembly and graph right click

This commit is contained in:
xarkes 2017-10-10 12:17:05 +02:00
parent 93195999d8
commit a70001bf59
15 changed files with 185 additions and 195 deletions

View File

@ -972,6 +972,6 @@ void MainWindow::on_actionRefresh_contents_triggered()
void MainWindow::on_actionAsmOptions_triggered() void MainWindow::on_actionAsmOptions_triggered()
{ {
auto dialog = new AsmOptionsDialog(core, this); auto dialog = new AsmOptionsDialog(this);
dialog->show(); dialog->show();
} }

View File

@ -72,7 +72,8 @@ SOURCES += \
widgets/SectionsWidget.cpp \ widgets/SectionsWidget.cpp \
widgets/Sidebar.cpp \ widgets/Sidebar.cpp \
widgets/StringsWidget.cpp \ widgets/StringsWidget.cpp \
widgets/SymbolsWidget.cpp widgets/SymbolsWidget.cpp \
menus/DisassemblyContextMenu.cpp
HEADERS += \ HEADERS += \
cutter.h \ cutter.h \
@ -117,7 +118,8 @@ HEADERS += \
widgets/SectionsWidget.h \ widgets/SectionsWidget.h \
widgets/Sidebar.h \ widgets/Sidebar.h \
widgets/StringsWidget.h \ widgets/StringsWidget.h \
widgets/SymbolsWidget.h widgets/SymbolsWidget.h \
menus/DisassemblyContextMenu.h
FORMS += \ FORMS += \
widgets/MemoryWidget.ui \ widgets/MemoryWidget.ui \

View File

@ -5,10 +5,10 @@
#include "utils/Helpers.h" #include "utils/Helpers.h"
AsmOptionsDialog::AsmOptionsDialog(CutterCore *core, QWidget *parent) AsmOptionsDialog::AsmOptionsDialog(QWidget *parent)
: QDialog(parent), : QDialog(parent),
core(core), ui(new Ui::AsmOptionsDialog),
ui(new Ui::AsmOptionsDialog) core(CutterCore::getInstance())
{ {
ui->setupUi(this); ui->setupUi(this);

View File

@ -18,16 +18,14 @@ class AsmOptionsDialog : public QDialog
Q_OBJECT Q_OBJECT
public: public:
explicit AsmOptionsDialog(CutterCore *core, QWidget *parent = nullptr); explicit AsmOptionsDialog(QWidget *parent = nullptr);
~AsmOptionsDialog(); ~AsmOptionsDialog();
private: private:
CutterCore *core; CutterCore *core;
std::unique_ptr<Ui::AsmOptionsDialog> ui; std::unique_ptr<Ui::AsmOptionsDialog> ui;
void updateFromVars(); void updateFromVars();
void saveAsDefault(); void saveAsDefault();
void resetToDefault(); void resetToDefault();

View File

@ -1,11 +1,11 @@
#include "FlagDialog.h" #include "FlagDialog.h"
#include "ui_FlagDialog.h" #include "ui_FlagDialog.h"
FlagDialog::FlagDialog(CutterCore *core, RVA offset, QWidget *parent) : FlagDialog::FlagDialog(RVA offset, QWidget *parent) :
QDialog(parent), QDialog(parent),
ui(new Ui::FlagDialog), ui(new Ui::FlagDialog),
core(core), offset(offset),
offset(offset) core(CutterCore::getInstance())
{ {
ui->setupUi(this); ui->setupUi(this);
setWindowFlags(windowFlags() & (~Qt::WindowContextHelpButtonHint)); setWindowFlags(windowFlags() & (~Qt::WindowContextHelpButtonHint));

View File

@ -15,7 +15,7 @@ class FlagDialog : public QDialog
Q_OBJECT Q_OBJECT
public: public:
explicit FlagDialog(CutterCore *core, RVA offset, QWidget *parent = 0); explicit FlagDialog(RVA offset, QWidget *parent = 0);
~FlagDialog(); ~FlagDialog();
private slots: private slots:
@ -24,9 +24,8 @@ private slots:
private: private:
std::unique_ptr<Ui::FlagDialog> ui; std::unique_ptr<Ui::FlagDialog> ui;
CutterCore *core;
RVA offset; RVA offset;
CutterCore *core;
}; };
#endif // FLAGDIALOG_H #endif // FLAGDIALOG_H

View File

@ -5,12 +5,11 @@
#include <QJsonArray> #include <QJsonArray>
XrefsDialog::XrefsDialog(MainWindow *main, QWidget *parent) : XrefsDialog::XrefsDialog(QWidget *parent) :
QDialog(parent), QDialog(parent),
addr(0), addr(0),
func_name(QString::null), func_name(QString::null),
ui(new Ui::XrefsDialog), ui(new Ui::XrefsDialog),
main(main),
core(CutterCore::getInstance()) core(CutterCore::getInstance())
{ {
ui->setupUi(this); ui->setupUi(this);

View File

@ -19,7 +19,7 @@ class XrefsDialog : public QDialog
Q_OBJECT Q_OBJECT
public: public:
explicit XrefsDialog(MainWindow *main, QWidget *parent = 0); explicit XrefsDialog(QWidget *parent = 0);
~XrefsDialog(); ~XrefsDialog();
void fillRefsForAddress(RVA addr, QString name, bool whole_function); void fillRefsForAddress(RVA addr, QString name, bool whole_function);
@ -42,7 +42,6 @@ private:
QString func_name; QString func_name;
std::unique_ptr<Ui::XrefsDialog> ui; std::unique_ptr<Ui::XrefsDialog> ui;
MainWindow *main;
CutterCore *core; CutterCore *core;
Highlighter *highlighter; Highlighter *highlighter;

View File

@ -0,0 +1,125 @@
#include "DisassemblyContextMenu.h"
#include "dialogs/AsmOptionsDialog.h"
#include "dialogs/CommentsDialog.h"
#include "dialogs/FlagDialog.h"
#include "dialogs/RenameDialog.h"
#include "dialogs/XrefsDialog.h"
#include <QtCore>
DisassemblyContextMenu::DisassemblyContextMenu(RVA offset, QWidget *parent) :
QMenu(parent),
offset(offset)
{
actionAddComment.setText("Add comment");
this->addAction(&actionAddComment);
actionAddFlag.setText("Add flag");
this->addAction(&actionAddFlag);
actionRename.setText("Rename");
this->addAction(&actionRename);
actionXRefs.setText("Show xrefs");
this->addAction(&actionXRefs);
this->addSeparator();
actionDisplayOptions.setText("Show options");
this->addAction(&actionDisplayOptions);
connect(&actionAddComment, SIGNAL(triggered(bool)), this, SLOT(on_actionAddComment_triggered()));
connect(&actionAddFlag, SIGNAL(triggered(bool)), this, SLOT(on_actionAddFlag_triggered()));
connect(&actionRename, SIGNAL(triggered(bool)), this, SLOT(on_actionRename_triggered()));
connect(&actionXRefs, SIGNAL(triggered(bool)), this, SLOT(on_actionXRefs_triggered()));
connect(&actionDisplayOptions, SIGNAL(triggered()), this, SLOT(on_actionDisplayOptions_triggered()));
/*
* // Set Disas popup menu
QMenu *menu = ui->disasTextEdit_2->createStandardContextMenu();
QTextCursor cur = ui->disasTextEdit_2->textCursor();
// Move cursor to mouse position to get proper function data
cur.setPosition(ui->disasTextEdit_2->cursorForPosition(pt).position(), QTextCursor::MoveAnchor);
ui->disasTextEdit_2->setTextCursor(cur);
if (cur.hasSelection())
{
menu->addSeparator();
menu->addAction(ui->actionSend_to_Notepad);
ui->disasTextEdit_2->setContextMenuPolicy(Qt::DefaultContextMenu);
}
else
{
// Add menu actions
menu->clear();
menu->addAction(ui->actionDisasAdd_comment);
menu->addAction(ui->actionAddFlag);
menu->addAction(ui->actionFunctionsRename);
menu->addAction(ui->actionFunctionsUndefine);
menu->addSeparator();
menu->addAction(ui->actionXRefs);
menu->addSeparator();
menu->addAction(ui->actionDisasCopy_All);
menu->addAction(ui->actionDisasCopy_Bytes);
menu->addAction(ui->actionDisasCopy_Disasm);
menu->addSeparator();
menu->addAction(ui->actionDisplayOptions);
ui->disasTextEdit_2->setContextMenuPolicy(Qt::CustomContextMenu);
}
menu->exec(ui->disasTextEdit_2->mapToGlobal(pt));
delete menu;
ui->disasTextEdit_2->setContextMenuPolicy(Qt::CustomContextMenu);
*/
}
DisassemblyContextMenu::~DisassemblyContextMenu()
{
}
void DisassemblyContextMenu::on_actionAddComment_triggered()
{
RAnalFunction *fcn = CutterCore::getInstance()->functionAt(offset);
CommentsDialog *c = new CommentsDialog(this);
if (c->exec())
{
QString comment = c->getComment();
CutterCore::getInstance()->setComment(offset, comment);
if (fcn)
{
CutterCore::getInstance()->seek(fcn->addr);
}
}
}
void DisassemblyContextMenu::on_actionAddFlag_triggered()
{
FlagDialog *dialog = new FlagDialog(offset, this->parentWidget());
dialog->exec();
}
void DisassemblyContextMenu::on_actionRename_triggered()
{
// Get function for clicked offset
RAnalFunction *fcn = CutterCore::getInstance()->functionAt(offset);
RenameDialog *dialog = new RenameDialog(this);
// Get function based on click position
dialog->setFunctionName(fcn->name);
if (dialog->exec())
{
// Get new function name
QString new_name = dialog->getFunctionName();
// Rename function in r2 core
CutterCore::getInstance()->renameFunction(fcn->name, new_name);
// Seek to new renamed function
CutterCore::getInstance()->seek(fcn->addr);
}
}
void DisassemblyContextMenu::on_actionXRefs_triggered()
{
XrefsDialog *dialog = new XrefsDialog(this);
dialog->fillRefsForAddress(offset, RAddressString(offset), false);
dialog->exec();
}
void DisassemblyContextMenu::on_actionDisplayOptions_triggered()
{
AsmOptionsDialog *dialog = new AsmOptionsDialog(this->parentWidget());
dialog->show();
}

View File

@ -0,0 +1,28 @@
#ifndef DISASSEMBLYCONTEXTMENU_H
#define DISASSEMBLYCONTEXTMENU_H
#include "cutter.h"
#include <QMenu>
class DisassemblyContextMenu : public QMenu {
Q_OBJECT
public:
DisassemblyContextMenu(RVA offset, QWidget *parent = nullptr);
~DisassemblyContextMenu();
private:
RVA offset;
QAction actionAddComment;
QAction actionAddFlag;
QAction actionRename;
QAction actionXRefs;
QAction actionDisplayOptions;
private slots:
void on_actionAddComment_triggered();
void on_actionAddFlag_triggered();
void on_actionRename_triggered();
void on_actionXRefs_triggered();
void on_actionDisplayOptions_triggered();
};
#endif // DISASSEMBLYCONTEXTMENU_H

View File

@ -1,5 +1,6 @@
/* x64dbg DisassemblerGraphView */ /* x64dbg DisassemblerGraphView */
#include "DisassemblerGraphView.h" #include "DisassemblerGraphView.h"
#include "menus/DisassemblyContextMenu.h"
#include <vector> #include <vector>
#include <QPainter> #include <QPainter>
#include <QScrollBar> #include <QScrollBar>
@ -543,7 +544,6 @@ duint DisassemblerGraphView::getInstrForMouseEvent(QMouseEvent* event)
bool DisassemblerGraphView::getTokenForMouseEvent(QMouseEvent* event, Token & tokenOut) bool DisassemblerGraphView::getTokenForMouseEvent(QMouseEvent* event, Token & tokenOut)
{ {
Q_UNUSED(event); Q_UNUSED(event);
Q_UNUSED(tokenOut);
/* TODO /* TODO
//Convert coordinates to system used in blocks //Convert coordinates to system used in blocks
int xofs = this->horizontalScrollBar()->value(); int xofs = this->horizontalScrollBar()->value();
@ -662,12 +662,11 @@ void DisassemblerGraphView::mousePressEvent(QMouseEvent* event)
this->viewport()->update(); this->viewport()->update();
/*if(event->button() == Qt::RightButton) if(event->button() == Qt::RightButton)
{ {
QMenu wMenu(this); DisassemblyContextMenu cMenu(instr, this);
mMenuBuilder->build(&wMenu); cMenu.exec(event->globalPos()); //execute context menu
wMenu.exec(event->globalPos()); //execute context menu }
}*/
} }
else if(event->button() == Qt::LeftButton) else if(event->button() == Qt::LeftButton)
{ {
@ -735,6 +734,7 @@ void DisassemblerGraphView::mouseDoubleClickEvent(QMouseEvent* event)
{ {
duint instr = this->getInstrForMouseEvent(event); duint instr = this->getInstrForMouseEvent(event);
//DbgCmdExec(QString("graph dis.branchdest(%1), silent").arg(ToPtrString(instr)).toUtf8().constData()); //DbgCmdExec(QString("graph dis.branchdest(%1), silent").arg(ToPtrString(instr)).toUtf8().constData());
CutterCore::getInstance()->seek(instr);
} }
} }
@ -1372,7 +1372,7 @@ void DisassemblerGraphView::renderFunction(Function & func)
//Adjust scroll bars for new size //Adjust scroll bars for new size
auto areaSize = this->viewport()->size(); auto areaSize = this->viewport()->size();
this->adjustSize(areaSize.width(), areaSize.height()); this->adjustSize(areaSize.width(), areaSize.height());
puts("Adjust scroll bars for new size"); //puts("Adjust scroll bars for new size");
if(this->desired_pos) if(this->desired_pos)
{ {

View File

@ -503,7 +503,7 @@ void FunctionsWidget::on_action_References_triggered()
// Get selected item in functions tree view // Get selected item in functions tree view
QTreeView *treeView = getCurrentTreeView(); QTreeView *treeView = getCurrentTreeView();
FunctionDescription function = treeView->selectionModel()->currentIndex().data(FunctionModel::FunctionDescriptionRole).value<FunctionDescription>(); FunctionDescription function = treeView->selectionModel()->currentIndex().data(FunctionModel::FunctionDescriptionRole).value<FunctionDescription>();
XrefsDialog *x = new XrefsDialog(this->main, this); XrefsDialog *x = new XrefsDialog(this);
x->fillRefsForAddress(function.offset, function.name, true); x->fillRefsForAddress(function.offset, function.name, true);
x->exec(); x->exec();
} }

View File

@ -5,10 +5,7 @@
#include "MainWindow.h" #include "MainWindow.h"
#include "utils/Helpers.h" #include "utils/Helpers.h"
#include "dialogs/XrefsDialog.h" #include "dialogs/XrefsDialog.h"
#include "dialogs/RenameDialog.h" #include "menus/DisassemblyContextMenu.h"
#include "dialogs/CommentsDialog.h"
#include "dialogs/AsmOptionsDialog.h"
#include "dialogs/FlagDialog.h"
#include <QTemporaryFile> #include <QTemporaryFile>
#include <QFontDialog> #include <QFontDialog>
@ -983,50 +980,10 @@ void MemoryWidget::showHexASCIIContextMenu(const QPoint &pt)
delete menu; delete menu;
} }
void MemoryWidget::on_actionDisplayOptions_triggered()
{
auto dialog = new AsmOptionsDialog(core, this);
dialog->show();
}
void MemoryWidget::showDisasContextMenu(const QPoint &pt) void MemoryWidget::showDisasContextMenu(const QPoint &pt)
{ {
// Set Disas popup menu DisassemblyContextMenu menu(this->readCurrentDisassemblyOffset(), ui->disasTextEdit_2);
QMenu *menu = ui->disasTextEdit_2->createStandardContextMenu(); menu.exec(ui->disasTextEdit_2->mapToGlobal(pt));
QTextCursor cur = ui->disasTextEdit_2->textCursor();
// Move cursor to mouse position to get proper function data
cur.setPosition(ui->disasTextEdit_2->cursorForPosition(pt).position(), QTextCursor::MoveAnchor);
ui->disasTextEdit_2->setTextCursor(cur);
if (cur.hasSelection())
{
menu->addSeparator();
menu->addAction(ui->actionSend_to_Notepad);
ui->disasTextEdit_2->setContextMenuPolicy(Qt::DefaultContextMenu);
}
else
{
// Add menu actions
menu->clear();
menu->addAction(ui->actionDisasAdd_comment);
menu->addAction(ui->actionAddFlag);
menu->addAction(ui->actionFunctionsRename);
menu->addAction(ui->actionFunctionsUndefine);
menu->addSeparator();
menu->addAction(ui->actionXRefs);
menu->addSeparator();
menu->addAction(ui->actionDisasCopy_All);
menu->addAction(ui->actionDisasCopy_Bytes);
menu->addAction(ui->actionDisasCopy_Disasm);
menu->addSeparator();
menu->addAction(ui->actionDisplayOptions);
ui->disasTextEdit_2->setContextMenuPolicy(Qt::CustomContextMenu);
}
menu->exec(ui->disasTextEdit_2->mapToGlobal(pt));
delete menu;
ui->disasTextEdit_2->setContextMenuPolicy(Qt::CustomContextMenu);
} }
void MemoryWidget::on_showInfoButton_2_clicked() void MemoryWidget::on_showInfoButton_2_clicked()
@ -1067,12 +1024,10 @@ void MemoryWidget::showXrefsDialog()
QString ele = lastline.split(" ", QString::SkipEmptyParts)[0]; QString ele = lastline.split(" ", QString::SkipEmptyParts)[0];
if (ele.contains("0x")) if (ele.contains("0x"))
{ {
/*TODO FIXME RVA addr = ele.toLongLong(0, 16);
* RVA addr = ele.toLongLong(0, 16); XrefsDialog *x = new XrefsDialog(this);
XrefsDialog *x = new XrefsDialog(this->main, this);
x->fillRefsForAddress(addr, RAddressString(addr), false); x->fillRefsForAddress(addr, RAddressString(addr), false);
x->exec(); x->exec();
*/
} }
} }
@ -1199,77 +1154,6 @@ void MemoryWidget::on_actionSend_to_Notepad_triggered()
// this->main->sendToNotepad(text); // this->main->sendToNotepad(text);
} }
void MemoryWidget::on_actionDisasAdd_comment_triggered()
{
RVA offset = readCurrentDisassemblyOffset();
// Get function for clicked offset
RAnalFunction *fcn = this->core->functionAt(offset);
CommentsDialog *c = new CommentsDialog(this);
if (c->exec())
{
// Get new function name
QString comment = c->getComment();
//this->main->add_debug_output("Comment: " + comment + " at: " + ele);
// Rename function in r2 core
this->core->setComment(offset, comment);
// Seek to new renamed function
if (fcn)
{
core->seek(fcn->addr);
}
// TODO: Refresh functions tree widget
}
// this->main->refreshComments();
}
void MemoryWidget::on_actionAddFlag_triggered()
{
RVA offset = readCurrentDisassemblyOffset();
FlagDialog *dialog = new FlagDialog(core, offset, this);
if (dialog->exec())
{
//QString comment = dialog->getFlagName();
// Rename function in r2 core
//this->core->setComment(offset, comment);
}
// FIXME?
// this->main->refreshComments();
}
void MemoryWidget::on_actionFunctionsRename_triggered()
{
// Get current offset
QTextCursor tc = this->disasTextEdit->textCursor();
tc.select(QTextCursor::LineUnderCursor);
QString lastline = tc.selectedText();
QString ele = lastline.split(" ", QString::SkipEmptyParts)[0];
if (ele.contains("0x"))
{
// Get function for clicked offset
RAnalFunction *fcn = this->core->functionAt(ele.toLongLong(0, 16));
RenameDialog *r = new RenameDialog(this);
// Get function based on click position
r->setFunctionName(fcn->name);
if (r->exec())
{
// Get new function name
QString new_name = r->getFunctionName();
// Rename function in r2 core
this->core->renameFunction(fcn->name, new_name);
// Seek to new renamed function
this->core->seek(fcn->addr);
}
}
// FIXME?
// this->main->refreshFunctions();
}
void MemoryWidget::on_action8columns_triggered() void MemoryWidget::on_action8columns_triggered()
{ {
this->core->setConfig("hex.cols", 8); this->core->setConfig("hex.cols", 8);
@ -1777,11 +1661,6 @@ void MemoryWidget::setScrollMode()
qhelpers::setVerticalScrollMode(ui->xrefToTreeWidget_2); qhelpers::setVerticalScrollMode(ui->xrefToTreeWidget_2);
} }
void MemoryWidget::on_actionXRefs_triggered()
{
showXrefsDialog();
}
void MemoryWidget::on_copyMD5_clicked() void MemoryWidget::on_copyMD5_clicked()
{ {

View File

@ -131,7 +131,6 @@ private slots:
void on_actionHideDisasm_side_panel_triggered(); void on_actionHideDisasm_side_panel_triggered();
void on_actionHideHexdump_side_panel_triggered(); void on_actionHideHexdump_side_panel_triggered();
void on_actionHideGraph_side_panel_triggered(); void on_actionHideGraph_side_panel_triggered();
void on_actionDisplayOptions_triggered();
void on_disasButton_clicked(); void on_disasButton_clicked();
void on_graphButton_clicked(); void on_graphButton_clicked();
@ -140,9 +139,6 @@ private slots:
void showHexdumpContextMenu(const QPoint &pt); void showHexdumpContextMenu(const QPoint &pt);
void showHexASCIIContextMenu(const QPoint &pt); void showHexASCIIContextMenu(const QPoint &pt);
void on_actionSend_to_Notepad_triggered(); void on_actionSend_to_Notepad_triggered();
void on_actionDisasAdd_comment_triggered();
void on_actionAddFlag_triggered();
void on_actionFunctionsRename_triggered();
void on_hexHexText_2_selectionChanged(); void on_hexHexText_2_selectionChanged();
void on_hexArchComboBox_2_currentTextChanged(const QString &arg1); void on_hexArchComboBox_2_currentTextChanged(const QString &arg1);
@ -178,7 +174,6 @@ private slots:
void on_previewToolButton_clicked(); void on_previewToolButton_clicked();
void on_decoToolButton_clicked(); void on_decoToolButton_clicked();
void on_previewToolButton_2_clicked(); void on_previewToolButton_2_clicked();
void on_actionXRefs_triggered();
void on_copyMD5_clicked(); void on_copyMD5_clicked();
void on_copySHA1_clicked(); void on_copySHA1_clicked();
void on_simpleGrapgToolButton_clicked(); void on_simpleGrapgToolButton_clicked();

View File

@ -1220,7 +1220,7 @@ p, li { white-space: pre-wrap; }
</item> </item>
</layout> </layout>
</widget> </widget>
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QTabWidget" name="memSideTabWidget_2"> <widget class="QTabWidget" name="memSideTabWidget_2">
@ -2739,7 +2739,7 @@ QToolTip {
</item> </item>
</layout> </layout>
</widget> </widget>
</widget> </widget>
</item> </item>
</layout> </layout>
</widget> </widget>
@ -2783,22 +2783,6 @@ QToolTip {
<string>Send to notepad</string> <string>Send to notepad</string>
</property> </property>
</action> </action>
<action name="actionDisasAdd_comment">
<property name="text">
<string>Add comment</string>
</property>
<property name="toolTip">
<string>Add comment</string>
</property>
</action>
<action name="actionFunctionsRename">
<property name="text">
<string>Rename</string>
</property>
<property name="toolTip">
<string>Rename</string>
</property>
</action>
<action name="actionFunctionsUndefine"> <action name="actionFunctionsUndefine">
<property name="text"> <property name="text">
<string>Undefine</string> <string>Undefine</string>
@ -2823,11 +2807,6 @@ QToolTip {
<string>Copy bytes</string> <string>Copy bytes</string>
</property> </property>
</action> </action>
<action name="actionDisplayOptions">
<property name="text">
<string>Display Options</string>
</property>
</action>
<action name="actionDisasCopy_Disasm"> <action name="actionDisasCopy_Disasm">
<property name="text"> <property name="text">
<string>Copy disasm</string> <string>Copy disasm</string>
@ -2948,19 +2927,6 @@ QToolTip {
<string>Insert String</string> <string>Insert String</string>
</property> </property>
</action> </action>
<action name="actionXRefs">
<property name="text">
<string>XRefs</string>
</property>
<property name="toolTip">
<string>XRefs</string>
</property>
</action>
<action name="actionAddFlag">
<property name="text">
<string>Add flag</string>
</property>
</action>
</widget> </widget>
<customwidgets> <customwidgets>
<customwidget> <customwidget>
@ -2974,8 +2940,8 @@ QToolTip {
</resources> </resources>
<connections/> <connections/>
<buttongroups> <buttongroups>
<buttongroup name="buttonGroup"/>
<buttongroup name="buttonGroup_2"/> <buttongroup name="buttonGroup_2"/>
<buttongroup name="buttonGroup_3"/> <buttongroup name="buttonGroup_3"/>
<buttongroup name="buttonGroup"/>
</buttongroups> </buttongroups>
</ui> </ui>