diff options
author | Yuri Kunde Schlesner <yuriks@yuriks.net> | 2015-09-07 21:54:45 +0200 |
---|---|---|
committer | Yuri Kunde Schlesner <yuriks@yuriks.net> | 2015-09-07 21:54:45 +0200 |
commit | 87a2fd8b3fdc0931cce95300cd85c5ee3ea0382c (patch) | |
tree | 7380029505ac8c21ab77aecb9ce3b9145b80705a | |
parent | Merge pull request #1117 from yuriks/fix-glad-build (diff) | |
parent | Shader Debugger: Allow editing of input vertex data (diff) | |
download | yuzu-87a2fd8b3fdc0931cce95300cd85c5ee3ea0382c.tar yuzu-87a2fd8b3fdc0931cce95300cd85c5ee3ea0382c.tar.gz yuzu-87a2fd8b3fdc0931cce95300cd85c5ee3ea0382c.tar.bz2 yuzu-87a2fd8b3fdc0931cce95300cd85c5ee3ea0382c.tar.lz yuzu-87a2fd8b3fdc0931cce95300cd85c5ee3ea0382c.tar.xz yuzu-87a2fd8b3fdc0931cce95300cd85c5ee3ea0382c.tar.zst yuzu-87a2fd8b3fdc0931cce95300cd85c5ee3ea0382c.zip |
-rw-r--r-- | src/citra_qt/debugger/graphics_cmdlists.cpp | 2 | ||||
-rw-r--r-- | src/citra_qt/debugger/graphics_vertex_shader.cpp | 301 | ||||
-rw-r--r-- | src/citra_qt/debugger/graphics_vertex_shader.h | 11 |
3 files changed, 187 insertions, 127 deletions
diff --git a/src/citra_qt/debugger/graphics_cmdlists.cpp b/src/citra_qt/debugger/graphics_cmdlists.cpp index 025434687..fa32d24b5 100644 --- a/src/citra_qt/debugger/graphics_cmdlists.cpp +++ b/src/citra_qt/debugger/graphics_cmdlists.cpp @@ -359,7 +359,7 @@ void GPUCommandListWidget::CopyAllToClipboard() { QClipboard* clipboard = QApplication::clipboard(); QString text; - QAbstractItemModel* model = static_cast<QAbstractListModel*>(list_widget->model()); + QAbstractItemModel* model = static_cast<QAbstractItemModel*>(list_widget->model()); for (int row = 0; row < model->rowCount({}); ++row) { for (int col = 0; col < model->columnCount({}); ++col) { diff --git a/src/citra_qt/debugger/graphics_vertex_shader.cpp b/src/citra_qt/debugger/graphics_vertex_shader.cpp index 1d9a00e89..d48b3294b 100644 --- a/src/citra_qt/debugger/graphics_vertex_shader.cpp +++ b/src/citra_qt/debugger/graphics_vertex_shader.cpp @@ -7,6 +7,7 @@ #include <QBoxLayout> #include <QFileDialog> +#include <QFormLayout> #include <QGroupBox> #include <QLabel> #include <QLineEdit> @@ -26,18 +27,10 @@ using nihstro::Instruction; using nihstro::SourceRegister; using nihstro::SwizzlePattern; -GraphicsVertexShaderModel::GraphicsVertexShaderModel(GraphicsVertexShaderWidget* parent): QAbstractItemModel(parent), par(parent) { +GraphicsVertexShaderModel::GraphicsVertexShaderModel(GraphicsVertexShaderWidget* parent): QAbstractTableModel(parent), par(parent) { } -QModelIndex GraphicsVertexShaderModel::index(int row, int column, const QModelIndex& parent) const { - return createIndex(row, column); -} - -QModelIndex GraphicsVertexShaderModel::parent(const QModelIndex& child) const { - return QModelIndex(); -} - int GraphicsVertexShaderModel::columnCount(const QModelIndex& parent) const { return 3; } @@ -65,6 +58,28 @@ QVariant GraphicsVertexShaderModel::headerData(int section, Qt::Orientation orie return QVariant(); } +static std::string SelectorToString(u32 selector) { + std::string ret; + for (int i = 0; i < 4; ++i) { + int component = (selector >> ((3 - i) * 2)) & 3; + ret += "xyzw"[component]; + } + return ret; +} + +// e.g. "-c92[a0.x].xyzw" +static void print_input(std::ostringstream& output, const SourceRegister& input, + bool negate, const std::string& swizzle_mask, bool align = true, + const std::string& address_register_name = std::string()) { + if (align) + output << std::setw(4) << std::right; + output << ((negate ? "-" : "") + input.GetName()); + + if (!address_register_name.empty()) + output << '[' << address_register_name << ']'; + output << '.' << swizzle_mask; +}; + QVariant GraphicsVertexShaderModel::data(const QModelIndex& index, int role) const { switch (role) { case Qt::DisplayRole: @@ -81,102 +96,120 @@ QVariant GraphicsVertexShaderModel::data(const QModelIndex& index, int role) con case 2: { - std::stringstream output; - output.flags(std::ios::hex); - - Instruction instr = par->info.code[index.row()]; - const SwizzlePattern& swizzle = par->info.swizzle_info[instr.common.operand_desc_id].pattern; - - // longest known instruction name: "setemit " - output << std::setw(8) << std::left << instr.opcode.Value().GetInfo().name; - - // e.g. "-c92.xyzw" - static auto print_input = [](std::stringstream& output, const SourceRegister& input, - bool negate, const std::string& swizzle_mask) { - output << std::setw(4) << std::right << (negate ? "-" : "") + input.GetName(); - output << "." << swizzle_mask; + std::ostringstream output; + output.flags(std::ios::uppercase); + + // To make the code aligning columns of assembly easier to keep track of, this function + // keeps track of the start of the start of the previous column, allowing alignment + // based on desired field widths. + int current_column = 0; + auto AlignToColumn = [&](int col_width) { + // Prints spaces to the output to pad previous column to size and advances the + // column marker. + current_column += col_width; + int to_add = std::max(1, current_column - (int)output.tellp()); + for (int i = 0; i < to_add; ++i) { + output << ' '; + } }; - // e.g. "-c92[a0.x].xyzw" - static auto print_input_indexed = [](std::stringstream& output, const SourceRegister& input, - bool negate, const std::string& swizzle_mask, - const std::string& address_register_name) { - std::string relative_address; - if (!address_register_name.empty()) - relative_address = "[" + address_register_name + "]"; + const Instruction instr = par->info.code[index.row()]; + const OpCode opcode = instr.opcode; + const OpCode::Info opcode_info = opcode.GetInfo(); + const u32 operand_desc_id = opcode_info.type == OpCode::Type::MultiplyAdd ? + instr.mad.operand_desc_id.Value() : instr.common.operand_desc_id.Value(); + const SwizzlePattern swizzle = par->info.swizzle_info[operand_desc_id].pattern; - output << std::setw(10) << std::right << (negate ? "-" : "") + input.GetName() + relative_address; - output << "." << swizzle_mask; - }; + // longest known instruction name: "setemit " + int kOpcodeColumnWidth = 8; + // "rXX.xyzw " + int kOutputColumnWidth = 10; + // "-rXX.xyzw ", no attempt is made to align indexed inputs + int kInputOperandColumnWidth = 11; - // Use print_input or print_input_indexed depending on whether relative addressing is used or not. - static auto print_input_indexed_compact = [](std::stringstream& output, const SourceRegister& input, - bool negate, const std::string& swizzle_mask, - const std::string& address_register_name) { - if (address_register_name.empty()) - print_input(output, input, negate, swizzle_mask); - else - print_input_indexed(output, input, negate, swizzle_mask, address_register_name); - }; + output << opcode_info.name; - switch (instr.opcode.Value().GetInfo().type) { + switch (opcode_info.type) { case OpCode::Type::Trivial: // Nothing to do here break; case OpCode::Type::Arithmetic: + case OpCode::Type::MultiplyAdd: { // Use custom code for special instructions - switch (instr.opcode.Value().EffectiveOpCode()) { + switch (opcode.EffectiveOpCode()) { case OpCode::Id::CMP: { + AlignToColumn(kOpcodeColumnWidth); + // NOTE: CMP always writes both cc components, so we do not consider the dest mask here. - output << std::setw(4) << std::right << "cc."; - output << "xy "; + output << " cc.xy"; + AlignToColumn(kOutputColumnWidth); SourceRegister src1 = instr.common.GetSrc1(false); SourceRegister src2 = instr.common.GetSrc2(false); - print_input_indexed_compact(output, src1, swizzle.negate_src1, swizzle.SelectorToString(false).substr(0,1), instr.common.AddressRegisterName()); - output << " " << instr.common.compare_op.ToString(instr.common.compare_op.x) << " "; - print_input(output, src2, swizzle.negate_src2, swizzle.SelectorToString(true).substr(0,1)); + output << ' '; + print_input(output, src1, swizzle.negate_src1, swizzle.SelectorToString(false).substr(0,1), false, instr.common.AddressRegisterName()); + output << ' ' << instr.common.compare_op.ToString(instr.common.compare_op.x) << ' '; + print_input(output, src2, swizzle.negate_src2, swizzle.SelectorToString(true).substr(0,1), false); output << ", "; - print_input_indexed_compact(output, src1, swizzle.negate_src1, swizzle.SelectorToString(false).substr(1,1), instr.common.AddressRegisterName()); - output << " " << instr.common.compare_op.ToString(instr.common.compare_op.y) << " "; - print_input(output, src2, swizzle.negate_src2, swizzle.SelectorToString(true).substr(1,1)); + print_input(output, src1, swizzle.negate_src1, swizzle.SelectorToString(false).substr(1,1), false, instr.common.AddressRegisterName()); + output << ' ' << instr.common.compare_op.ToString(instr.common.compare_op.y) << ' '; + print_input(output, src2, swizzle.negate_src2, swizzle.SelectorToString(true).substr(1,1), false); break; } + case OpCode::Id::MAD: + case OpCode::Id::MADI: + { + AlignToColumn(kOpcodeColumnWidth); + + bool src_is_inverted = 0 != (opcode_info.subtype & OpCode::Info::SrcInversed); + SourceRegister src1 = instr.mad.GetSrc1(src_is_inverted); + SourceRegister src2 = instr.mad.GetSrc2(src_is_inverted); + SourceRegister src3 = instr.mad.GetSrc3(src_is_inverted); + + output << std::setw(3) << std::right << instr.mad.dest.Value().GetName() << '.' << swizzle.DestMaskToString(); + AlignToColumn(kOutputColumnWidth); + print_input(output, src1, swizzle.negate_src1, SelectorToString(swizzle.src1_selector)); + AlignToColumn(kInputOperandColumnWidth); + print_input(output, src2, swizzle.negate_src2, SelectorToString(swizzle.src2_selector)); + AlignToColumn(kInputOperandColumnWidth); + print_input(output, src3, swizzle.negate_src3, SelectorToString(swizzle.src3_selector)); + AlignToColumn(kInputOperandColumnWidth); + break; + } + default: { - bool src_is_inverted = 0 != (instr.opcode.Value().GetInfo().subtype & OpCode::Info::SrcInversed); + AlignToColumn(kOpcodeColumnWidth); + + bool src_is_inverted = 0 != (opcode_info.subtype & OpCode::Info::SrcInversed); - if (instr.opcode.Value().GetInfo().subtype & OpCode::Info::Dest) { + if (opcode_info.subtype & OpCode::Info::Dest) { // e.g. "r12.xy__" - output << std::setw(4) << std::right << instr.common.dest.Value().GetName() + "."; - output << swizzle.DestMaskToString(); - } else if (instr.opcode.Value().GetInfo().subtype == OpCode::Info::MOVA) { - output << std::setw(4) << std::right << "a0."; - output << swizzle.DestMaskToString(); - } else { - output << " "; + output << std::setw(3) << std::right << instr.common.dest.Value().GetName() << '.' << swizzle.DestMaskToString(); + } else if (opcode_info.subtype == OpCode::Info::MOVA) { + output << " a0." << swizzle.DestMaskToString(); } - output << " "; + AlignToColumn(kOutputColumnWidth); - if (instr.opcode.Value().GetInfo().subtype & OpCode::Info::Src1) { + if (opcode_info.subtype & OpCode::Info::Src1) { SourceRegister src1 = instr.common.GetSrc1(src_is_inverted); - print_input_indexed(output, src1, swizzle.negate_src1, swizzle.SelectorToString(false), instr.common.AddressRegisterName()); - } else { - output << " "; + print_input(output, src1, swizzle.negate_src1, swizzle.SelectorToString(false), true, instr.common.AddressRegisterName()); + AlignToColumn(kInputOperandColumnWidth); } // TODO: In some cases, the Address Register is used as an index for SRC2 instead of SRC1 - if (instr.opcode.Value().GetInfo().subtype & OpCode::Info::Src2) { + if (opcode_info.subtype & OpCode::Info::Src2) { SourceRegister src2 = instr.common.GetSrc2(src_is_inverted); print_input(output, src2, swizzle.negate_src2, swizzle.SelectorToString(true)); + AlignToColumn(kInputOperandColumnWidth); } break; } @@ -186,46 +219,55 @@ QVariant GraphicsVertexShaderModel::data(const QModelIndex& index, int role) con } case OpCode::Type::Conditional: + case OpCode::Type::UniformFlowControl: { - switch (instr.opcode.Value().EffectiveOpCode()) { + output << ' '; + + switch (opcode.EffectiveOpCode()) { case OpCode::Id::LOOP: output << "(unknown instruction format)"; break; default: - output << "if "; - - if (instr.opcode.Value().GetInfo().subtype & OpCode::Info::HasCondition) { - const char* ops[] = { - " || ", " && ", "", "" - }; - if (instr.flow_control.op != instr.flow_control.JustY) - output << ((!instr.flow_control.refx) ? "!" : " ") << "cc.x"; - - output << ops[instr.flow_control.op]; - - if (instr.flow_control.op != instr.flow_control.JustX) - output << ((!instr.flow_control.refy) ? "!" : " ") << "cc.y"; - - output << " "; - } else if (instr.opcode.Value().GetInfo().subtype & OpCode::Info::HasUniformIndex) { - output << "b" << instr.flow_control.bool_uniform_id << " "; + if (opcode_info.subtype & OpCode::Info::HasCondition) { + output << '('; + + if (instr.flow_control.op != instr.flow_control.JustY) { + if (instr.flow_control.refx) output << '!'; + output << "cc.x"; + } + + if (instr.flow_control.op == instr.flow_control.Or) { + output << " || "; + } else if (instr.flow_control.op == instr.flow_control.And) { + output << " && "; + } + + if (instr.flow_control.op != instr.flow_control.JustX) { + if (instr.flow_control.refy) output << '!'; + output << "cc.y"; + } + + output << ") "; + } else if (opcode_info.subtype & OpCode::Info::HasUniformIndex) { + output << 'b' << instr.flow_control.bool_uniform_id << ' '; } u32 target_addr = instr.flow_control.dest_offset; u32 target_addr_else = instr.flow_control.dest_offset; - if (instr.opcode.Value().GetInfo().subtype & OpCode::Info::HasAlternative) { - output << "else jump to 0x" << std::setw(4) << std::right << std::setfill('0') << 4 * instr.flow_control.dest_offset << " "; - } else if (instr.opcode.Value().GetInfo().subtype & OpCode::Info::HasExplicitDest) { - output << "jump to 0x" << std::setw(4) << std::right << std::setfill('0') << 4 * instr.flow_control.dest_offset << " "; + if (opcode_info.subtype & OpCode::Info::HasAlternative) { + output << "else jump to 0x" << std::setw(4) << std::right << std::setfill('0') << std::hex << (4 * instr.flow_control.dest_offset); + } else if (opcode_info.subtype & OpCode::Info::HasExplicitDest) { + output << "jump to 0x" << std::setw(4) << std::right << std::setfill('0') << std::hex << (4 * instr.flow_control.dest_offset); } else { // TODO: Handle other cases + output << "(unknown destination)"; } - if (instr.opcode.Value().GetInfo().subtype & OpCode::Info::HasFinishPoint) { - output << "(return on " << std::setw(4) << std::right << std::setfill('0') - << 4 * instr.flow_control.dest_offset + 4 * instr.flow_control.num_instructions << ")"; + if (opcode_info.subtype & OpCode::Info::HasFinishPoint) { + output << " (return on 0x" << std::setw(4) << std::right << std::setfill('0') << std::hex + << (4 * instr.flow_control.dest_offset + 4 * instr.flow_control.num_instructions) << ')'; } break; @@ -234,7 +276,7 @@ QVariant GraphicsVertexShaderModel::data(const QModelIndex& index, int role) con } default: - output << "(unknown instruction format)"; + output << " (unknown instruction format)"; break; } @@ -250,12 +292,23 @@ QVariant GraphicsVertexShaderModel::data(const QModelIndex& index, int role) con return GetMonospaceFont(); case Qt::BackgroundRole: - // Highlight instructions which have no debug data associated to them + { + // Highlight current instruction + int current_record_index = par->cycle_index->value(); + if (current_record_index < par->debug_data.records.size()) { + const auto& current_record = par->debug_data.records[current_record_index]; + if (index.row() == current_record.instruction_offset) { + return QColor(255, 255, 63); + } + } + + // Use a grey background for instructions which have no debug data associated to them for (const auto& record : par->debug_data.records) if (index.row() == record.instruction_offset) return QVariant(); - return QBrush(QColor(255, 255, 127)); + return QBrush(QColor(192, 192, 192)); + } // TODO: Draw arrows for each "reachable" instruction to visualize control flow @@ -288,6 +341,13 @@ GraphicsVertexShaderWidget::GraphicsVertexShaderWidget(std::shared_ptr< Pica::De : BreakPointObserverDock(debug_context, "Pica Vertex Shader", parent) { setObjectName("PicaVertexShader"); + // Clear input vertex data so that it contains valid float values in case a debug shader + // execution happens before the first Vertex Loaded breakpoint. + // TODO: This makes a crash in the interpreter much less likely, but not impossible. The + // interpreter should guard against out-of-bounds accesses to ensure crashes in it aren't + // possible. + std::memset(&input_vertex, 0, sizeof(input_vertex)); + auto input_data_mapper = new QSignalMapper(this); // TODO: Support inputting data in hexadecimal raw format @@ -312,9 +372,6 @@ GraphicsVertexShaderWidget::GraphicsVertexShaderWidget(std::shared_ptr< Pica::De cycle_index = new QSpinBox; - connect(this, SIGNAL(SelectCommand(const QModelIndex&, QItemSelectionModel::SelectionFlags)), - binary_list->selectionModel(), SLOT(select(const QModelIndex&, QItemSelectionModel::SelectionFlags))); - connect(dump_shader, SIGNAL(clicked()), this, SLOT(DumpShader())); connect(cycle_index, SIGNAL(valueChanged(int)), this, SLOT(OnCycleIndexChanged(int))); @@ -339,6 +396,9 @@ GraphicsVertexShaderWidget::GraphicsVertexShaderWidget(std::shared_ptr< Pica::De // Create an HBoxLayout to store the widgets used to specify a particular attribute // and store it in a QWidget to allow for easy hiding and unhiding. auto row_layout = new QHBoxLayout; + // Remove unecessary padding between rows + row_layout->setContentsMargins(0, 0, 0, 0); + row_layout->addWidget(new QLabel(tr("Attribute %1").arg(i, 2))); for (unsigned comp = 0; comp < 4; ++comp) row_layout->addWidget(input_data[4 * i + comp]); @@ -358,20 +418,25 @@ GraphicsVertexShaderWidget::GraphicsVertexShaderWidget(std::shared_ptr< Pica::De input_data_group->setLayout(sub_layout); main_layout->addWidget(input_data_group); } - { - auto sub_layout = new QHBoxLayout; - sub_layout->addWidget(binary_list); - main_layout->addLayout(sub_layout); - } + + // Make program listing expand to fill available space in the dialog + binary_list->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::MinimumExpanding); + main_layout->addWidget(binary_list); + main_layout->addWidget(dump_shader); { - auto sub_layout = new QHBoxLayout; - sub_layout->addWidget(new QLabel(tr("Cycle Index:"))); - sub_layout->addWidget(cycle_index); + auto sub_layout = new QFormLayout; + sub_layout->addRow(tr("Cycle Index:"), cycle_index); + main_layout->addLayout(sub_layout); } + + // Set a minimum height so that the size of this label doesn't cause the rest of the bottom + // part of the UI to keep jumping up and down when cycling through instructions. + instruction_description->setMinimumHeight(instruction_description->fontMetrics().lineSpacing() * 6); + instruction_description->setAlignment(Qt::AlignLeft | Qt::AlignTop); main_layout->addWidget(instruction_description); - main_layout->addStretch(); + main_widget->setLayout(main_layout); setWidget(main_widget); @@ -418,6 +483,7 @@ void GraphicsVertexShaderWidget::Reload(bool replace_vertex_data, void* vertex_d auto& shader_config = Pica::g_state.regs.vs; for (auto instr : shader_setup.program_code) info.code.push_back({instr}); + int num_attributes = Pica::g_state.regs.vertex_attributes.GetNumTotalAttributes(); for (auto pattern : shader_setup.swizzle_data) info.swizzle_info.push_back({pattern}); @@ -426,19 +492,18 @@ void GraphicsVertexShaderWidget::Reload(bool replace_vertex_data, void* vertex_d info.labels.insert({ entry_point, "main" }); // Generate debug information - debug_data = Pica::Shader::ProduceDebugInfo(input_vertex, 1, shader_config, shader_setup); + debug_data = Pica::Shader::ProduceDebugInfo(input_vertex, num_attributes, shader_config, shader_setup); // Reload widget state - - // Only show input attributes which are used as input to the shader - for (unsigned int attr = 0; attr < 16; ++attr) { - input_data_container[attr]->setVisible(false); - } - for (unsigned int attr = 0; attr < Pica::g_state.regs.vertex_attributes.GetNumTotalAttributes(); ++attr) { + for (unsigned int attr = 0; attr < num_attributes; ++attr) { unsigned source_attr = shader_config.input_register_map.GetRegisterForAttribute(attr); input_data_mapping[source_attr]->setText(QString("-> v%1").arg(attr)); input_data_container[source_attr]->setVisible(true); } + // Only show input attributes which are used as input to the shader + for (unsigned int attr = num_attributes; attr < 16; ++attr) { + input_data_container[attr]->setVisible(false); + } // Initialize debug info text for current cycle count cycle_index->setMaximum(debug_data.records.size() - 1); @@ -453,6 +518,8 @@ void GraphicsVertexShaderWidget::OnResumed() { void GraphicsVertexShaderWidget::OnInputAttributeChanged(int index) { float value = input_data[index]->text().toFloat(); + input_vertex.attr[index / 4][index % 4] = Pica::float24::FromFloat32(value); + // Re-execute shader with updated value Reload(); } @@ -492,8 +559,8 @@ void GraphicsVertexShaderWidget::OnCycleIndexChanged(int index) { instruction_description->setText(text); - // Scroll to current instruction - const QModelIndex& instr_index = model->index(record.instruction_offset, 0); - emit SelectCommand(instr_index, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows); + // Emit model update notification and scroll to current instruction + QModelIndex instr_index = model->index(record.instruction_offset, 0); + emit model->dataChanged(instr_index, model->index(record.instruction_offset, model->columnCount())); binary_list->scrollTo(instr_index, QAbstractItemView::EnsureVisible); } diff --git a/src/citra_qt/debugger/graphics_vertex_shader.h b/src/citra_qt/debugger/graphics_vertex_shader.h index 1b3f1f7ec..0bf1652fc 100644 --- a/src/citra_qt/debugger/graphics_vertex_shader.h +++ b/src/citra_qt/debugger/graphics_vertex_shader.h @@ -4,7 +4,7 @@ #pragma once -#include <QAbstractListModel> +#include <QAbstractTableModel> #include "graphics_breakpoint_observer.h" @@ -17,14 +17,12 @@ class QSpinBox; class GraphicsVertexShaderWidget; -class GraphicsVertexShaderModel : public QAbstractItemModel { +class GraphicsVertexShaderModel : public QAbstractTableModel { Q_OBJECT public: GraphicsVertexShaderModel(GraphicsVertexShaderWidget* parent); - QModelIndex index(int row, int column, const QModelIndex& parent = QModelIndex()) const override; - QModelIndex parent(const QModelIndex& child) const override; int columnCount(const QModelIndex& parent = QModelIndex()) const override; int rowCount(const QModelIndex& parent = QModelIndex()) const override; QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; @@ -62,11 +60,6 @@ private slots: */ void Reload(bool replace_vertex_data = false, void* vertex_data = nullptr); - -signals: - // Call this to change the current command selection in the disassembly view - void SelectCommand(const QModelIndex&, QItemSelectionModel::SelectionFlags); - private: QLabel* instruction_description; QTreeView* binary_list; |