From 4356666101e0e7985e65a19f86bc4a74519e93f9 Mon Sep 17 00:00:00 2001 From: Amaury Pouly Date: Mon, 7 Apr 2014 11:28:04 +0200 Subject: regtools: completely rework qeditor, improve soc desc library and tools The graphical editor can now display and editor description files. The library has been improved to provide more useful function. The XML format has been slightly changed: only one soc is allowed per file (this is was already de facto the case since was the root tag). Also introduce a DTD to validate the files. Change-Id: If70ba35b6dc0242bdb87411cf4baee9597798aac --- utils/regtools/qeditor/aux.cpp | 746 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 746 insertions(+) create mode 100644 utils/regtools/qeditor/aux.cpp (limited to 'utils/regtools/qeditor/aux.cpp') diff --git a/utils/regtools/qeditor/aux.cpp b/utils/regtools/qeditor/aux.cpp new file mode 100644 index 0000000000..0614bb57f6 --- /dev/null +++ b/utils/regtools/qeditor/aux.cpp @@ -0,0 +1,746 @@ +#include "aux.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/** + * SocBitRangeValidator + */ +SocBitRangeValidator::SocBitRangeValidator(QObject *parent) + :QValidator(parent) +{ +} + +void SocBitRangeValidator::fixup(QString& input) const +{ + input = input.trimmed(); +} + +QValidator::State SocBitRangeValidator::validate(QString& input, int& pos) const +{ + Q_UNUSED(pos); + int first, last; + State state = parse(input, last, first); + return state; +} + +QValidator::State SocBitRangeValidator::parse(const QString& input, int& last, int& first) const +{ + // the empty string is always intermediate + if(input.size() == 0) + return Intermediate; + // check if there is ':' + int pos = input.indexOf(':'); + if(pos == -1) + pos = input.size(); + // if field start with ':', the last bit is implicit and is 31 + if(pos > 0) + { + // parse last bit and check it's between 0 and 31 + bool ok = false; + last = input.left(pos).toInt(&ok); + if(!ok || last < 0 || last >= 32) + return Invalid; + } + else + last = 31; + // parse first bit + if(pos < input.size() - 1) + { + bool ok = false; + first = input.mid(pos + 1).toInt(&ok); + if(!ok || first < 0 || first > last) + return Invalid; + } + // if input ends with ':', first bit is implicit and is 0 + else if(pos == input.size() - 1) + first = 0; + // if there no ':', first=last + else + first = last; + return Acceptable; +} + +/** + * SocFieldValidator + */ + +SocFieldValidator::SocFieldValidator(QObject *parent) + :QValidator(parent) +{ + m_field.first_bit = 0; + m_field.last_bit = 31; +} + +SocFieldValidator::SocFieldValidator(const soc_reg_field_t& field, QObject *parent) + :QValidator(parent), m_field(field) +{ +} + +void SocFieldValidator::fixup(QString& input) const +{ + input = input.trimmed(); +} + +QValidator::State SocFieldValidator::validate(QString& input, int& pos) const +{ + Q_UNUSED(pos); + soc_word_t val; + State state = parse(input, val); + return state; +} + +QValidator::State SocFieldValidator::parse(const QString& input, soc_word_t& val) const +{ + // the empty string is always intermediate + if(input.size() == 0) + return Intermediate; + // first check named values + State state = Invalid; + foreach(const soc_reg_field_value_t& value, m_field.value) + { + QString name = QString::fromLocal8Bit(value.name.c_str()); + // cannot be a substring if too long or empty + if(input.size() > name.size()) + continue; + // check equal string + if(input == name) + { + state = Acceptable; + val = value.value; + break; + } + // check substring + if(name.startsWith(input)) + state = Intermediate; + } + // early return for exact match + if(state == Acceptable) + return state; + // do a few special cases for convenience + if(input.compare("0x", Qt::CaseInsensitive) == 0 || + input.compare("0b", Qt::CaseInsensitive) == 0) + return Intermediate; + // try by parsing + unsigned basis, pos; + if(input.size() >= 2 && input.startsWith("0x", Qt::CaseInsensitive)) + { + basis = 16; + pos = 2; + } + else if(input.size() >= 2 && input.startsWith("0b", Qt::CaseInsensitive)) + { + basis = 2; + pos = 2; + } + else if(input.size() >= 2 && input.startsWith("0")) + { + basis = 8; + pos = 1; + } + else + { + basis = 10; + pos = 0; + } + bool ok = false; + unsigned long v = input.mid(pos).toULong(&ok, basis); + // if not ok, return result of name parsing + if(!ok) + return state; + // if ok, check if it fits in the number of bits + unsigned nr_bits = m_field.last_bit - m_field.first_bit + 1; + unsigned long max = nr_bits == 32 ? 0xffffffff : (1 << nr_bits) - 1; + if(v <= max) + { + val = v; + return Acceptable; + } + + return state; +} + +/** + * RegLineEdit + */ +RegLineEdit::RegLineEdit(QWidget *parent) + :QWidget(parent) +{ + m_layout = new QHBoxLayout(this); + m_button = new QToolButton(this); + m_button->setCursor(Qt::ArrowCursor); + m_button->setStyleSheet("QToolButton { font-weight: bold; color: white; background: black; }"); + m_button->setPopupMode(QToolButton::InstantPopup); + m_edit = new QLineEdit(this); + m_layout->addWidget(m_button); + m_layout->addWidget(m_edit); + m_menu = new QMenu(this); + connect(m_menu->addAction("Write"), SIGNAL(triggered()), this, SLOT(OnWriteAct())); + connect(m_menu->addAction("Set"), SIGNAL(triggered()), this, SLOT(OnSetAct())); + connect(m_menu->addAction("Clear"), SIGNAL(triggered()), this, SLOT(OnClearAct())); + connect(m_menu->addAction("Toggle"), SIGNAL(triggered()), this, SLOT(OnToggleAct())); + EnableSCT(false); + SetReadOnly(false); + ShowMode(true); + SetMode(Write); +} + +void RegLineEdit::SetReadOnly(bool ro) +{ + m_edit->setReadOnly(ro); + m_readonly = ro; + ShowMode(!ro); +} + +void RegLineEdit::EnableSCT(bool en) +{ + m_has_sct = en; + if(!m_has_sct) + { + m_button->setMenu(0); + SetMode(Write); + } + else + m_button->setMenu(m_menu); +} + +RegLineEdit::~RegLineEdit() +{ +} + +QLineEdit *RegLineEdit::GetLineEdit() +{ + return m_edit; +} + +void RegLineEdit::ShowMode(bool show) +{ + if(show) + m_button->show(); + else + m_button->hide(); +} + +void RegLineEdit::OnWriteAct() +{ + SetMode(Write); +} + +void RegLineEdit::OnSetAct() +{ + SetMode(Set); +} + +void RegLineEdit::OnClearAct() +{ + SetMode(Clear); +} + +void RegLineEdit::OnToggleAct() +{ + SetMode(Toggle); +} + +void RegLineEdit::SetMode(EditMode mode) +{ + m_mode = mode; + switch(m_mode) + { + case Write: m_button->setText("WR"); break; + case Set: m_button->setText("SET"); break; + case Clear: m_button->setText("CLR"); break; + case Toggle: m_button->setText("TOG"); break; + default: break; + } +} + +RegLineEdit::EditMode RegLineEdit::GetMode() +{ + return m_mode; +} + +void RegLineEdit::setText(const QString& text) +{ + m_edit->setText(text); +} + +QString RegLineEdit::text() const +{ + return m_edit->text(); +} + +/** + * SocFieldItemDelegate + */ + +QString SocFieldItemDelegate::displayText(const QVariant& value, const QLocale& locale) const +{ + if(value.type() == QVariant::UInt) + return QString("0x%1").arg(value.toUInt(), (m_bitcount + 3) / 4, 16, QChar('0')); + else + return QStyledItemDelegate::displayText(value, locale); +} + +/** + * SocFieldEditor + */ +SocFieldEditor::SocFieldEditor(const soc_reg_field_t& field, QWidget *parent) + :QLineEdit(parent), m_reg_field(field) +{ + m_validator = new SocFieldValidator(field); + setValidator(m_validator); +} + +SocFieldEditor::~SocFieldEditor() +{ + delete m_validator; +} + +uint SocFieldEditor::field() const +{ + soc_word_t v; + /* in case validator fails to parse, return old value */ + if(m_validator->parse(text(), v) == QValidator::Acceptable) + return v; + else + return m_field; +} + +void SocFieldEditor::setField(uint field) +{ + m_field = field; + int digits = (m_reg_field.last_bit - m_reg_field.first_bit + 4) / 4; + setText(QString("0x%1").arg(field, digits, 16, QChar('0'))); +} + +/** + * SocFieldEditorCreator + */ +QWidget *SocFieldEditorCreator::createWidget(QWidget *parent) const +{ + return new SocFieldEditor(m_field, parent); +} + +QByteArray SocFieldEditorCreator::valuePropertyName() const +{ + return QByteArray("field"); +} + +/** + * RegSexyDisplay + */ +RegSexyDisplay::RegSexyDisplay(const SocRegRef& reg, QWidget *parent) + :QWidget(parent), m_reg(reg) +{ + m_size = QSize(); +} + +int RegSexyDisplay::separatorSize() const +{ + return 1; +} + +int RegSexyDisplay::marginSize() const +{ + return fontMetrics().height() / 3; +} + +int RegSexyDisplay::textSep() const +{ + return marginSize() / 2; +} + +int RegSexyDisplay::headerHeight() const +{ + return 2 * marginSize() + textSep() + 2 * fontMetrics().height(); +} + +int RegSexyDisplay::columnWidth() const +{ + return 2 * marginSize() + fontMetrics().height(); +} + +int RegSexyDisplay::maxContentHeight() const +{ + int max = 0; + QFontMetrics metrics = fontMetrics(); + for(size_t i = 0; i < m_reg.GetReg().field.size(); i++) + { + QString s = QString::fromStdString(m_reg.GetReg().field[i].name); + // add extra spaces arounds + s = " " + s + " "; + max = qMax(max, metrics.boundingRect(s).width()); + } + return 2 * marginSize() + max; +} + +int RegSexyDisplay::gapHeight() const +{ + return marginSize() / 2; +} + +QSize RegSexyDisplay::minimumSizeHint() const +{ + /* cache computation because it's expensive */ + if(m_size.isValid()) + return m_size; + /* width: display 32 columns + 33 vertical separators */ + m_size.setWidth(32 * columnWidth() + 33 * separatorSize()); + /* height: one separator + two digits + one separator + margin + separator + * + names + separator */ + m_size.setHeight(4 * separatorSize() + headerHeight() + gapHeight() + maxContentHeight()); + return m_size; +} + +QSize RegSexyDisplay::sizeHint() const +{ + return minimumSizeHint(); +} + +void RegSexyDisplay::paintEvent(QPaintEvent *event) +{ + // FIXME could be optimised with QStaticText + Q_UNUSED(event); + int txt_h = fontMetrics().height(); + int sep_sz = separatorSize(); + int w = width(); + int h = height() - 1; + int col_w = (w - 33 * sep_sz) / 32; + int hdr_h = headerHeight(); + int gap_h = gapHeight(); + int tot_w = 33 * sep_sz + 32 * col_w; + int margin = marginSize(); + int txt_sep = textSep(); + int tot_hdr_sz = 2 * sep_sz + hdr_h; + // computer xshift + int x_shift = (w - tot_w) / 2; +#define ith_col_x(i) (x_shift + (i) * (sep_sz + col_w)) + + QPainter painter(this); + QBrush back_brush = palette().base(); + QBrush line_brush = palette().dark(); + + // fill interesting zone with base + painter.fillRect(x_shift, 0, tot_w, h, back_brush); + + // draw top and bottom lines + painter.setPen(QPen(palette().dark(), sep_sz)); + painter.fillRect(x_shift, 0, tot_w, sep_sz, line_brush); + painter.fillRect(x_shift, h - sep_sz, tot_w, sep_sz, line_brush); + // draw intemediate lines + for(int i = 0; i <= 32; i++) + painter.fillRect(ith_col_x(i), 0, sep_sz, 2 * sep_sz + hdr_h, line_brush); + // draw bottom header lines + painter.fillRect(ith_col_x(0), sep_sz + hdr_h, tot_w, sep_sz, line_brush); + painter.fillRect(ith_col_x(0), tot_hdr_sz + gap_h, tot_w, sep_sz, line_brush); + // redraw some lines but wider + for(int i = 4; i < 32; i += 4) + painter.fillRect(ith_col_x(i) - sep_sz, 0, 3 * sep_sz, tot_hdr_sz, line_brush); + // draw numbers in the header + painter.setPen(palette().brush(QPalette::ButtonText).color()); + for(int i = 0; i < 32; i++) + { + QRect r(ith_col_x(i), sep_sz + margin, col_w, txt_h); + painter.drawText(r, Qt::AlignCenter, QString("%1").arg((31 - i) / 10)); + r.translate(0, txt_h + txt_sep); + painter.drawText(r, Qt::AlignCenter, QString("%1").arg((31 - i) % 10)); + } + // display content + for(size_t i = 0; i < m_reg.GetReg().field.size(); i++) + { + const soc_reg_field_t& field = m_reg.GetReg().field[i]; + QRect r(QPoint(ith_col_x(31 - field.last_bit) + sep_sz, tot_hdr_sz), + QPoint(ith_col_x(32 - field.first_bit), h - sep_sz)); + painter.fillRect(r.x() - sep_sz, r.y(), sep_sz, r.height(), line_brush); + painter.fillRect(r.right(), r.y(), sep_sz, r.height(), line_brush); + r.setY(r.y() + gap_h + sep_sz); + // draw rotated text + painter.save(); + painter.translate(r.bottomLeft()); + painter.rotate(-90); + //painter.fillRect(QRect(0, 0, r.height(), r.width()), QBrush(Qt::red)); + QRect r2(0, 0, r.height(), r.width()); + painter.drawText(r2, Qt::AlignCenter, QString::fromStdString(field.name)); + painter.restore(); + } +#undef ith_col_x +} + +/** + * GrowingTextEdit + */ +GrowingTextEdit::GrowingTextEdit(QWidget *parent) + :QTextEdit(parent) +{ + connect(this, SIGNAL(textChanged()), this, SLOT(TextChanged())); + setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); + setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); +} + +void GrowingTextEdit::TextChanged() +{ + int content_size = document()->documentLayout()->documentSize().height(); + content_size = qMax(content_size, fontMetrics().height()); + setFixedHeight(content_size + contentsMargins().top() + contentsMargins().bottom()); +} + +/** + * GrowingTableWidget + */ +GrowingTableWidget::GrowingTableWidget(QWidget *parent) + :QTableWidget(parent) +{ + connect(model(), SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&)), + this, SLOT(DataChanged(const QModelIndex&, const QModelIndex&))); +} + +void GrowingTableWidget::DataChanged(const QModelIndex& tl, const QModelIndex& br) +{ + Q_UNUSED(tl); + Q_UNUSED(br); + resizeRowsToContents(); + resizeColumnsToContents(); + int h = contentsMargins().top() + contentsMargins().bottom(); + h += horizontalHeader()->height(); + for(int i = 0; i < rowCount(); i++) + h += rowHeight(i); + setMinimumHeight(h); +} + +/** + * MyTextEditor + */ +MyTextEditor::MyTextEditor(QWidget *parent) + :QWidget(parent) +{ + QVBoxLayout *layout = new QVBoxLayout; + m_toolbar = new QToolBar(this); + m_edit = new QTextEdit(this); + layout->addWidget(m_toolbar, 0); + layout->addWidget(m_edit, 1); + setLayout(layout); + setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + + m_edit->setAcceptRichText(false); + m_edit->setAutoFormatting(QTextEdit::AutoAll); + + m_bold_button = new QToolButton(this); + m_bold_button->setIcon(QIcon::fromTheme("format-text-bold")); + m_bold_button->setText("bold"); + m_bold_button->setCheckable(true); + + m_italic_button = new QToolButton(this); + m_italic_button->setIcon(QIcon::fromTheme("format-text-italic")); + m_italic_button->setText("italic"); + m_italic_button->setCheckable(true); + + m_underline_button = new QToolButton(this); + m_underline_button->setIcon(QIcon::fromTheme("format-text-underline")); + m_underline_button->setText("underline"); + m_underline_button->setCheckable(true); + + m_toolbar->addWidget(m_bold_button); + m_toolbar->addWidget(m_italic_button); + m_toolbar->addWidget(m_underline_button); + + connect(m_bold_button, SIGNAL(toggled(bool)), this, SLOT(OnTextBold(bool))); + connect(m_italic_button, SIGNAL(toggled(bool)), this, SLOT(OnTextItalic(bool))); + connect(m_underline_button, SIGNAL(toggled(bool)), this, SLOT(OnTextUnderline(bool))); + connect(m_edit, SIGNAL(textChanged()), this, SLOT(OnInternalTextChanged())); + connect(m_edit, SIGNAL(currentCharFormatChanged(const QTextCharFormat&)), + this, SLOT(OnCharFormatChanged(const QTextCharFormat&))); + + SetGrowingMode(false); + SetReadOnly(false); +} + +void MyTextEditor::SetReadOnly(bool en) +{ + m_read_only = en; + if(en) + m_toolbar->hide(); + else + m_toolbar->hide(); + m_edit->setReadOnly(en); +} + +void MyTextEditor::SetGrowingMode(bool en) +{ + m_growing_mode = en; + if(en) + { + m_edit->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum); + m_edit->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + OnTextChanged(); + } + else + { + m_edit->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + m_edit->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); + } +} + +void MyTextEditor::OnInternalTextChanged() +{ + if(m_growing_mode) + { + int content_size = m_edit->document()->documentLayout()->documentSize().height(); + content_size = qMax(content_size, m_edit->fontMetrics().height()); + m_edit->setMinimumHeight(content_size + m_edit->contentsMargins().top() + + m_edit->contentsMargins().bottom()); + } + emit OnTextChanged(); +} + +void MyTextEditor::OnTextBold(bool checked) +{ + QTextCursor cursor = m_edit->textCursor(); + QTextCharFormat fmt = cursor.charFormat(); + fmt.setFontWeight(checked ? QFont::Bold : QFont::Normal); + cursor.setCharFormat(fmt); + m_edit->setTextCursor(cursor); +} + +void MyTextEditor::OnTextItalic(bool checked) +{ + QTextCursor cursor = m_edit->textCursor(); + QTextCharFormat fmt = cursor.charFormat(); + fmt.setFontItalic(checked); + cursor.setCharFormat(fmt); + m_edit->setTextCursor(cursor); +} + +void MyTextEditor::OnTextUnderline(bool checked) +{ + QTextCursor cursor = m_edit->textCursor(); + QTextCharFormat fmt = cursor.charFormat(); + fmt.setFontUnderline(checked); + cursor.setCharFormat(fmt); + m_edit->setTextCursor(cursor); +} + +void MyTextEditor::OnCharFormatChanged(const QTextCharFormat& fmt) +{ + /* NOTE: changing the button states programmaticaly doesn't trigger + * the toggled() signals, otherwise it would result in a loop + * between this function and OnText{Bold,Italic,Underline,...} */ + m_bold_button->setChecked(fmt.fontWeight() > QFont::Normal); + m_italic_button->setChecked(fmt.fontItalic()); + m_underline_button->setChecked(fmt.fontUnderline()); +} + +void MyTextEditor::SetTextHtml(const QString& text) +{ + m_edit->setHtml(text); +} + +QString MyTextEditor::GetTextHtml() +{ + return m_edit->toPlainText(); +} + +bool MyTextEditor::IsModified() +{ + return m_edit->document()->isModified(); +} + +/** + * MySwitchableTextEditor + */ +MySwitchableTextEditor::MySwitchableTextEditor(QWidget *parent) + :QWidget(parent) +{ + QVBoxLayout *layout = new QVBoxLayout(this); + m_edit = new MyTextEditor(this); + m_label = new QLabel(this); + m_label->setTextFormat(Qt::RichText); + m_label->setAlignment(Qt::AlignTop); + m_line = new QLineEdit(this); + + layout->addWidget(m_label); + layout->addWidget(m_edit); + layout->addWidget(m_line); + + setLayout(layout); + + m_editor_mode = false; + m_line_mode = false; + UpdateVisibility(); +} + +void MySwitchableTextEditor::SetEditorMode(bool edit) +{ + if(edit == m_editor_mode) + return; + QString text = GetTextHtml(); + m_editor_mode = edit; + UpdateVisibility(); + SetTextHtml(text); +} + +QString MySwitchableTextEditor::GetTextHtml() +{ + if(m_editor_mode) + return m_line_mode ? m_line->text() : m_edit->GetTextHtml(); + else + return m_label->text(); +} + +void MySwitchableTextEditor::SetTextHtml(const QString& text) +{ + if(m_editor_mode) + { + if(m_line_mode) + m_line->setText(text); + else + m_edit->SetTextHtml(text); + } + else + m_label->setText(text); +} + +MyTextEditor *MySwitchableTextEditor::GetEditor() +{ + return m_edit; +} + +void MySwitchableTextEditor::SetLineMode(bool en) +{ + if(m_line_mode == en) + return; + QString text = GetTextHtml(); + m_line_mode = en; + SetTextHtml(text); + UpdateVisibility(); +} + +QLineEdit *MySwitchableTextEditor::GetLineEdit() +{ + return m_line; +} + +void MySwitchableTextEditor::UpdateVisibility() +{ + m_label->setVisible(!m_editor_mode); + m_edit->setVisible(m_editor_mode && !m_line_mode); + m_line->setVisible(m_editor_mode && m_line_mode); +} + +QLabel *MySwitchableTextEditor::GetLabel() +{ + return m_label; +} + +bool MySwitchableTextEditor::IsModified() +{ + if(!m_editor_mode) + return false; + return m_line_mode ? m_line->isModified() : m_edit->IsModified(); +} -- cgit v1.2.3