rockbox/utils/regtools/qeditor/utils.cpp
Amaury Pouly 2c832968c9 qeditor: use delegate to show bit range information
Change-Id: I314365c3a2cb9d230c412f24d2a8034a12c43444
2015-09-10 15:20:38 +02:00

1224 lines
34 KiB
C++

/***************************************************************************
* __________ __ ___.
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
* \/ \/ \/ \/ \/
* $Id$
*
* Copyright (C) 2014 by Amaury Pouly
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
****************************************************************************/
#include "utils.h"
#include <QFontMetrics>
#include <QPainter>
#include <QTextDocument>
#include <QAbstractTextDocumentLayout>
#include <QHeaderView>
#include <QDebug>
#include <QElapsedTimer>
#include <QXmlStreamReader>
#include <QXmlStreamWriter>
#include <QTextBlock>
/**
* 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')));
}
void SocFieldEditor::SetRegField(const soc_reg_field_t& field)
{
setValidator(0);
delete m_validator;
m_validator = new SocFieldValidator(field);
setValidator(m_validator);
m_reg_field = field;
}
/**
* SocFieldCachedValue
*/
SocFieldCachedValue::SocFieldCachedValue(const soc_reg_field_t& field, uint value)
:m_field(field), m_value(value)
{
int idx = field.find_value(value);
if(idx != -1)
m_name = QString::fromStdString(field.value[idx].name);
}
/**
* SocFieldCachedItemDelegate
*/
QString SocFieldCachedItemDelegate::displayText(const QVariant& value, const QLocale& locale) const
{
// FIXME see QTBUG-30392
if(value.type() == QVariant::UserType && value.userType() == qMetaTypeId< SocFieldCachedValue >())
{
const SocFieldCachedValue& v = value.value< SocFieldCachedValue >();
int bitcount = v.field().last_bit - v.field().first_bit;
QString name = v.value_name();
QString strval = QString("0x%1").arg(v.value(), (bitcount + 3) / 4, 16, QChar('0'));
switch(m_mode)
{
case DisplayName:
if(name.size() > 0)
return name;
/* fallthrough */
case DisplayValueAndName:
if(name.size() > 0)
return QString("%1 (%2)").arg(strval).arg(name);
/* fallthrough */
case DisplayValue:
default:
return strval;
}
}
else if(value.type() == QVariant::UserType && value.userType() == qMetaTypeId< SocFieldBitRange >())
{
const SocFieldBitRange& br = value.value< SocFieldBitRange >();
if(br.GetFirstBit() == br.GetLastBit())
return QString("%1").arg(br.GetFirstBit());
else
return QString("%1:%2").arg(br.GetLastBit()).arg(br.GetFirstBit());
}
else
return QStyledItemDelegate::displayText(value, locale);
}
/**
* SocFieldCachedEditor
*/
SocFieldCachedEditor::SocFieldCachedEditor(QWidget *parent)
:SocFieldEditor(soc_reg_field_t(), parent)
{
}
SocFieldCachedEditor::~SocFieldCachedEditor()
{
}
SocFieldCachedValue SocFieldCachedEditor::value() const
{
return SocFieldCachedValue(m_value.field(), field());
}
void SocFieldCachedEditor::setValue(SocFieldCachedValue val)
{
m_value = val;
SetRegField(m_value.field());
setField(m_value.value());
}
/**
* SocFieldEditorCreator
*/
QWidget *SocFieldEditorCreator::createWidget(QWidget *parent) const
{
return new SocFieldEditor(m_field, parent);
}
QByteArray SocFieldEditorCreator::valuePropertyName() const
{
return QByteArray("field");
}
/**
* SocFieldCachedEditorCreator
*/
QWidget *SocFieldCachedEditorCreator::createWidget(QWidget *parent) const
{
return new SocFieldCachedEditor(parent);
}
QByteArray SocFieldCachedEditorCreator::valuePropertyName() const
{
return QByteArray("value");
}
/**
* RegFieldTableModel
*/
RegFieldTableModel::RegFieldTableModel(QObject *parent)
:QAbstractTableModel(parent)
{
m_read_only = true;
}
int RegFieldTableModel::rowCount(const QModelIndex& /* parent */) const
{
return m_reg.field.size();
}
int RegFieldTableModel::columnCount(const QModelIndex& /* parent */) const
{
return ColumnCountOffset + m_value.size();
}
QVariant RegFieldTableModel::data(const QModelIndex& index, int role) const
{
int section = index.column();
const soc_reg_field_t& field = m_reg.field[index.row()];
/* column independent code */
const RegThemeGroup *theme = 0;
switch(m_status[index.row()])
{
case Normal: theme = &m_theme.normal; break;
case Diff: theme = &m_theme.diff; break;
case Error: theme = &m_theme.error; break;
case None: default: break;
}
if(role == Qt::FontRole)
return theme ? QVariant(theme->font) : QVariant();
if(role == Qt::BackgroundRole)
return theme ? QVariant(theme->background) : QVariant();
if(role == Qt::ForegroundRole)
return theme ? QVariant(theme->foreground) : QVariant();
/* column dependent code */
if(section == BitRangeColumn)
{
if(role == Qt::DisplayRole)
return QVariant::fromValue(SocFieldBitRange(field));
else if(role == Qt::TextAlignmentRole)
return QVariant(Qt::AlignVCenter | Qt::AlignHCenter);
else
return QVariant();
}
if(section == NameColumn)
{
if(role == Qt::DisplayRole)
return QVariant(QString::fromStdString(field.name));
else
return QVariant();
}
if(section < FirstValueColumn + m_value.size())
{
int idx = section - FirstValueColumn;
if(role == Qt::DisplayRole)
{
if(!m_value[idx].isValid())
return QVariant("<error>");
return QVariant::fromValue(SocFieldCachedValue(field,
field.extract(m_value[idx].value< soc_word_t >())));
}
else if(role == Qt::EditRole)
{
if(!m_value[idx].isValid())
return QVariant();
return QVariant::fromValue(SocFieldCachedValue(field,
field.extract(m_value[idx].value< soc_word_t >())));
}
else if(role == Qt::TextAlignmentRole)
return QVariant(Qt::AlignVCenter | Qt::AlignHCenter);
else
return QVariant();
}
section -= m_value.size();
if(section == DescColumnOffset)
{
if(role == Qt::DisplayRole)
return QVariant(QString::fromStdString(field.desc));
else
return QVariant();
}
return QVariant();
}
bool RegFieldTableModel::setData(const QModelIndex& idx, const QVariant& value, int role)
{
if(role != Qt::EditRole)
return false;
int section = idx.column();
if(section < FirstValueColumn || section >= FirstValueColumn + m_value.size())
return false;
section -= FirstValueColumn;
const SocFieldCachedValue& v = value.value< SocFieldCachedValue >();
if(!m_value[section].isValid())
return false;
soc_word_t old_val = m_value[section].value< soc_word_t >();
m_value[section] = QVariant(v.field().replace(old_val, v.value()));
// update column
RecomputeTheme();
emit dataChanged(index(0, section), index(rowCount() - 1, section));
emit OnValueModified(section);
return true;
}
Qt::ItemFlags RegFieldTableModel::flags(const QModelIndex& index) const
{
Qt::ItemFlags flags = Qt::ItemIsSelectable | Qt::ItemIsEnabled;
int section = index.column();
if(section < FirstValueColumn || section >= FirstValueColumn + m_value.size())
return flags;
section -= FirstValueColumn;
if(m_value[section].isValid() && !m_read_only)
flags |= Qt::ItemIsEditable;
return flags;
}
QVariant RegFieldTableModel::headerData(int section, Qt::Orientation orientation,
int role) const
{
if(orientation == Qt::Vertical)
return QVariant();
if(role != Qt::DisplayRole)
return QVariant();
if(section == BitRangeColumn)
return QVariant("Bits");
if(section == NameColumn)
return QVariant("Name");
if(section < FirstValueColumn + m_value.size())
{
int idx = section - FirstValueColumn;
if(m_value.size() == 1)
return QVariant("Value");
else
return QVariant(QString("Value %1").arg((QChar)('A' + idx)));
}
section -= m_value.size();
if(section == DescColumnOffset)
return QVariant("Description");
return QVariant();
}
void RegFieldTableModel::SetReadOnly(bool en)
{
if(en == m_read_only)
return;
m_read_only = en;
}
void RegFieldTableModel::SetRegister(const soc_reg_t& reg)
{
/* remove all rows */
beginResetModel();
m_reg.field.clear();
endResetModel();
/* add them all */
beginInsertRows(QModelIndex(), 0, reg.field.size() - 1);
m_reg = reg;
RecomputeTheme();
endInsertRows();
}
void RegFieldTableModel::SetValues(const QVector< QVariant >& values)
{
/* remove all value columns */
beginRemoveColumns(QModelIndex(), FirstValueColumn,
FirstValueColumn + m_value.size() - 1);
m_value.clear();
endRemoveColumns();
/* add them back */
beginInsertColumns(QModelIndex(), FirstValueColumn,
FirstValueColumn + values.size() - 1);
m_value = values;
RecomputeTheme();
endInsertColumns();
}
QVariant RegFieldTableModel::GetValue(int index)
{
return m_value[index];
}
void RegFieldTableModel::SetTheme(const RegTheme& theme)
{
m_theme = theme;
RecomputeTheme();
emit dataChanged(index(0, 0), index(rowCount() - 1, columnCount() - 1));
}
void RegFieldTableModel::RecomputeTheme()
{
m_status.resize(m_reg.field.size());
for(size_t i = 0; i < m_reg.field.size(); i++)
{
m_status[i] = None;
if(!m_theme.valid || m_value.size() == 0)
continue;
m_status[i] = Normal;
const soc_reg_field_t& field = m_reg.field[i];
QVariant val;
for(int j = 0; j < m_value.size(); j++)
{
QVariant val2 = m_value[j];
if(!val2.isValid())
continue;
val2 = QVariant(field.extract(val2.value< soc_word_t >()));
if(!val.isValid())
val = val2;
else if(val != val2)
m_status[i] = Diff;
}
}
}
/**
* 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
}
/**
* GrowingTableView
*/
GrowingTableView::GrowingTableView(QWidget *parent)
:QTableView(parent)
{
}
void GrowingTableView::setModel(QAbstractItemModel *m)
{
if(model())
disconnect(model(), SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&)),
this, SLOT(DataChanged(const QModelIndex&, const QModelIndex&)));
QTableView::setModel(m);
connect(model(), SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&)),
this, SLOT(DataChanged(const QModelIndex&, const QModelIndex&)));
DataChanged(QModelIndex(), QModelIndex());
}
void GrowingTableView::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 < model()->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();
}
/**
* BackendSelector
*/
BackendSelector::BackendSelector(Backend *backend, QWidget *parent)
:QWidget(parent), m_backend(backend)
{
m_data_selector = new QComboBox(this);
m_data_selector->addItem(QIcon::fromTheme("text-x-generic"), "Nothing...", QVariant(DataSelNothing));
m_data_selector->addItem(QIcon::fromTheme("document-open"), "File...", QVariant(DataSelFile));
#ifdef HAVE_HWSTUB
m_data_selector->addItem(QIcon::fromTheme("multimedia-player"), "Device...", QVariant(DataSelDevice));
#endif
m_data_sel_edit = new QLineEdit(this);
m_data_sel_edit->setReadOnly(true);
m_nothing_text = new QLabel(this);
m_nothing_text->setTextFormat(Qt::RichText);
QHBoxLayout *data_sel_layout = new QHBoxLayout(this);
data_sel_layout->addWidget(m_data_selector);
data_sel_layout->addWidget(m_data_sel_edit, 1);
data_sel_layout->addWidget(m_nothing_text, 1);
data_sel_layout->addStretch(0);
#ifdef HAVE_HWSTUB
m_dev_selector = new QComboBox;
data_sel_layout->addWidget(m_dev_selector, 1);
#endif
m_io_backend = m_backend->CreateDummyIoBackend();
connect(m_data_selector, SIGNAL(activated(int)),
this, SLOT(OnDataSelChanged(int)));
#ifdef HAVE_HWSTUB
connect(m_dev_selector, SIGNAL(currentIndexChanged(int)),
this, SLOT(OnDevChanged(int)));
connect(&m_hwstub_helper, SIGNAL(OnDevListChanged(bool, struct libusb_device *)),
this, SLOT(OnDevListChanged2(bool, struct libusb_device *)));
#endif
OnDataSelChanged(0);
}
BackendSelector::~BackendSelector()
{
#ifdef HAVE_HWSTUB
ClearDevList();
#endif
delete m_io_backend;
}
void BackendSelector::SetNothingMessage(const QString& msg)
{
m_nothing_text->setText(msg);
}
void BackendSelector::OnDataSelChanged(int index)
{
if(index == -1)
return;
QVariant var = m_data_selector->itemData(index);
if(var == DataSelFile)
{
m_nothing_text->hide();
m_data_sel_edit->show();
#ifdef HAVE_HWSTUB
m_dev_selector->hide();
#endif
QFileDialog *fd = new QFileDialog(m_data_selector);
fd->setFilter("Textual files (*.txt);;All files (*)");
fd->setDirectory(Settings::Get()->value("regtab/loaddatadir", QDir::currentPath()).toString());
if(fd->exec())
{
QStringList filenames = fd->selectedFiles();
ChangeBackend(m_backend->CreateFileIoBackend(filenames[0]));
m_data_sel_edit->setText(filenames[0]);
}
Settings::Get()->setValue("regtab/loaddatadir", fd->directory().absolutePath());
}
#ifdef HAVE_HWSTUB
else if(var == DataSelDevice)
{
m_nothing_text->hide();
m_data_sel_edit->hide();
m_dev_selector->show();
OnDevListChanged();
}
#endif
else
{
m_data_sel_edit->hide();
m_nothing_text->show();
#ifdef HAVE_HWSTUB
m_dev_selector->hide();
#endif
ChangeBackend(m_backend->CreateDummyIoBackend());
}
}
#ifdef HAVE_HWSTUB
void BackendSelector::OnDevListChanged2(bool arrived, struct libusb_device *dev)
{
Q_UNUSED(arrived);
Q_UNUSED(dev);
OnDevListChanged();
}
void BackendSelector::OnDevListChanged()
{
ClearDevList();
QList< HWStubDevice* > list = m_hwstub_helper.GetDevList();
foreach(HWStubDevice *dev, list)
{
QString name = QString("Bus %1 Device %2: %3").arg(dev->GetBusNumber())
.arg(dev->GetDevAddress()).arg(dev->GetTargetInfo().bName);
m_dev_selector->addItem(QIcon::fromTheme("multimedia-player"), name,
QVariant::fromValue((void *)dev));
}
if(list.size() > 0)
m_dev_selector->setCurrentIndex(0);
}
void BackendSelector::OnDevChanged(int index)
{
if(index == -1)
return;
HWStubDevice *dev = reinterpret_cast< HWStubDevice* >(m_dev_selector->itemData(index).value< void* >());
delete m_io_backend;
/* NOTE: make a copy of the HWStubDevice device because the one in the list
* might get destroyed when clearing the list while the backend is still
* active: this would result in a double free when the backend is also destroyed */
m_io_backend = m_backend->CreateHWStubIoBackend(new HWStubDevice(dev));
emit OnSelect(m_io_backend);
}
void BackendSelector::ClearDevList()
{
while(m_dev_selector->count() > 0)
{
HWStubDevice *dev = reinterpret_cast< HWStubDevice* >(m_dev_selector->itemData(0).value< void* >());
delete dev;
m_dev_selector->removeItem(0);
}
}
#endif
IoBackend *BackendSelector::GetBackend()
{
return m_io_backend;
}
void BackendSelector::ChangeBackend(IoBackend *new_backend)
{
/* WARNING: delete old backend *after* calling the signal, otherwise the old backend
* might get used after delete */
emit OnSelect(new_backend);
delete m_io_backend;
m_io_backend = new_backend;
}
/**
* MessageWidget
*/
MessageWidget::MessageWidget(QWidget *parent)
:QFrame(parent)
{
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum);
m_icon = new QLabel(this);
m_icon->hide();
m_text = new QLabel(this);
m_text->setTextFormat(Qt::RichText);
m_close = new QToolButton(this);
m_close->setText("close");
m_close->setIcon(style()->standardIcon(QStyle::SP_DialogCloseButton));
m_close->setAutoRaise(true);
QHBoxLayout *layout = new QHBoxLayout(this);
layout->addWidget(m_icon, 0);
layout->addWidget(m_text, 1);
layout->addWidget(m_close, 0);
m_id = 0;
connect(m_close, SIGNAL(clicked(bool)), this, SLOT(OnClose(bool)));
hide();
}
MessageWidget::~MessageWidget()
{
}
void MessageWidget::UpdateType()
{
/* style stolen from KMessageWidget */
QColor bg, border;
switch(m_type)
{
case Positive:
bg.setRgb(140, 228, 124);
border.setRgb(56, 175, 58);
break;
case Information:
bg.setRgb(161, 178, 202);
border.setRgb(59, 79, 175);
break;
case Warning:
bg.setRgb(228, 227, 127);
border.setRgb(175, 169, 61);
break;
case Error:
bg.setRgb(233, 199, 196);
border.setRgb(175, 74, 60);
break;
default:
break;
}
setStyleSheet(QString(
"QFrame { background-color: %1;"
"border-radius: 5px;"
"border: 1px solid %2;"
"}"
"QLabel { border: none; }")
.arg(bg.name())
.arg(border.name()));
}
int MessageWidget::SetMessage(MessageType type, const QString& msg)
{
m_type = type;
m_text->setText(msg);
UpdateType();
show();
return ++m_id;
}
void MessageWidget::HideMessage(int id)
{
if(m_id == id)
OnClose(true);
}
void MessageWidget::OnClose(bool clicked)
{
Q_UNUSED(clicked);
hide();
}