/*************************************************************************** * __________ __ ___. * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include /** * SocBitRangeValidator */ SocBitRangeValidator::SocBitRangeValidator(QObject *parent) :QValidator(parent) { m_width = 32; } 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 >= m_width) return Invalid; } else last = m_width - 1; // 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; } void SocBitRangeValidator::setWidth(int nr_bits) { m_width = nr_bits; } QString SocBitRangeValidator::generate(int last_bit, int first_bit) const { if(last_bit == first_bit) return QString("%1").arg(first_bit); else return QString("%1:%2").arg(last_bit).arg(first_bit); } /** * SocFieldValidator */ SocFieldValidator::SocFieldValidator(QObject *parent) :QValidator(parent) { m_field.pos = 0; m_field.width = 32; } SocFieldValidator::SocFieldValidator(const soc_desc::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_desc::enum_t& value, m_field.enum_) { 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.width; 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); } void SocFieldItemDelegate::setWidth(int bitcount) { m_bitcount = bitcount; } /** * SocFieldEditor */ SocFieldEditor::SocFieldEditor(const soc_desc::field_t& field, QWidget *parent) :QLineEdit(parent), m_reg_field(field) { m_validator = new SocFieldValidator(field); setValidator(m_validator); connect(this, SIGNAL(editingFinished()), this, SLOT(editDone())); setAlignment(Qt::AlignCenter); setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Minimum); } SocFieldEditor::~SocFieldEditor() { delete m_validator; } void SocFieldEditor::editDone() { emit editingFinished(field()); } 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.width + 3) / 4; setText(QString("0x%1").arg(field, digits, 16, QChar('0'))); } void SocFieldEditor::SetRegField(const soc_desc::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_desc::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.enum_[idx].name); } bool SocFieldCachedValue::operator<(const SocFieldCachedValue& o) const { return m_value < o.m_value; } /** * SocFieldBitRange */ bool SocFieldBitRange::operator<(const SocFieldBitRange& o) const { if(m_first_bit < o.m_first_bit) return true; if(m_first_bit > o.m_first_bit) return false; return m_last_bit < o.m_last_bit; } bool SocFieldBitRange::operator!=(const SocFieldBitRange& o) const { return m_first_bit != o.m_first_bit || m_last_bit != o.m_last_bit; } /** * SocFieldCachedItemDelegate */ SocFieldCachedItemDelegate::SocFieldCachedItemDelegate(QObject *parent) :QStyledItemDelegate(parent) { m_mode = DisplayValueAndName; } QString SocFieldCachedItemDelegate::displayText(const QVariant& value, const QLocale& locale) const { if(isUserType< SocFieldCachedValue >(value)) { const SocFieldCachedValue& v = value.value< SocFieldCachedValue >(); int bitcount = v.field().width; 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_desc::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()); } /** * SocAccessItemDelegate */ QString SocAccessItemDelegate::displayText(const QVariant& value, const QLocale& locale) const { if(isUserType< soc_desc::access_t >(value)) { soc_desc::access_t acc = value.value< soc_desc::access_t >(); switch(acc) { case soc_desc::UNSPECIFIED: return m_unspec_text; case soc_desc::READ_ONLY: return "Read-Only"; case soc_desc::READ_WRITE: return "Read-Write"; case soc_desc::WRITE_ONLY: return "Write-Only"; default: return ""; } } else return QStyledItemDelegate::displayText(value, locale); } /** * SocAccessEditor */ SocAccessEditor::SocAccessEditor(const QString& unspec_text, QWidget *parent) :QComboBox(parent) { addItem(unspec_text, QVariant::fromValue(soc_desc::UNSPECIFIED)); addItem("Read-Only", QVariant::fromValue(soc_desc::READ_ONLY)); addItem("Read-Write", QVariant::fromValue(soc_desc::READ_WRITE)); addItem("Write-Only", QVariant::fromValue(soc_desc::WRITE_ONLY)); } SocAccessEditor::~SocAccessEditor() { } soc_desc::access_t SocAccessEditor::access() const { return itemData(currentIndex()).value< soc_desc::access_t >(); } void SocAccessEditor::setAccess(soc_desc::access_t acc) { setCurrentIndex(findData(QVariant::fromValue(acc))); } /** * SocFieldEditorCreator */ QWidget *SocFieldEditorCreator::createWidget(QWidget *parent) const { return new SocFieldEditor(m_field, parent); } QByteArray SocFieldEditorCreator::valuePropertyName() const { return QByteArray("field"); } void SocFieldEditorCreator::setWidth(int bitcount) { m_field.width = bitcount; } /** * SocAccessEditorCreator */ QWidget *SocAccessEditorCreator::createWidget(QWidget *parent) const { return new SocAccessEditor(m_unspec_text, parent); } QByteArray SocAccessEditorCreator::valuePropertyName() const { return QByteArray("access"); } /** * 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 { if(index.row() < 0 || (size_t)index.row() >= m_reg.field.size()) return QVariant(); int section = index.column(); const soc_desc::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(""); 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 == BitRangeColumn) { if(idx.row() < 0 || idx.row() >= rowCount()) return false; if(value.type() != QVariant::UserType && value.userType() == qMetaTypeId< SocFieldBitRange >()) return false; SocFieldBitRange bitrange = value.value< SocFieldBitRange >(); m_reg.field[idx.row()].pos = bitrange.GetFirstBit(); m_reg.field[idx.row()].width = bitrange.GetLastBit() - bitrange.GetFirstBit() + 1; emit OnBitrangeModified(idx.row()); } if(section < FirstValueColumn || section >= FirstValueColumn + m_value.size()) { qDebug() << "ignore setData to column " << section; 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()) { /* bitrange or name */ if(!m_read_only) flags |= Qt::ItemIsEditable; 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_desc::register_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::UpdateRegister(const soc_desc::register_t& reg) { m_reg = reg; RecomputeTheme(); emit dataChanged(index(0, 0), index(rowCount() - 1, columnCount() - 1)); } soc_desc::register_t RegFieldTableModel::GetRegister() const { return m_reg; } 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_desc::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; } } } /** * RegFieldProxyModel */ bool RegFieldProxyModel::lessThan(const QModelIndex& left, const QModelIndex& right) const { QVariant ldata = sourceModel()->data(left); QVariant rdata = sourceModel()->data(right); if(isUserType< SocFieldBitRange >(ldata) && isUserType< SocFieldBitRange >(rdata)) { return ldata.value< SocFieldBitRange >() < rdata.value< SocFieldBitRange >(); } else if(isUserType< SocFieldCachedValue >(ldata) && isUserType< SocFieldCachedValue >(rdata)) { return ldata.value< SocFieldCachedValue >() < rdata.value< SocFieldCachedValue >(); } else return QSortFilterProxyModel::lessThan(left, right); } /** * YRegDisplayItemEditor */ YRegDisplayItemEditor::YRegDisplayItemEditor(QWidget *parent, YRegDisplay *display, YRegDisplayItemDelegate *delegate, QModelIndex bitrange_index, QModelIndex name_index) :QWidget(parent), m_display_delegate(delegate), m_display(display), m_state(Idle) { m_col_width = m_display->BitrangeRect(SocFieldBitRange(0, 0)).width(); m_resize_margin = m_col_width / 4; setEditorData(bitrange_index, name_index); setMouseTracking(true); setAutoFillBackground(true); setFocusPolicy(Qt::StrongFocus); // QItemDelegate says it's important } void YRegDisplayItemEditor::setEditorData(QModelIndex bitrange_index, QModelIndex name_index) { if(m_state != Idle) { m_state = Idle; QApplication::restoreOverrideCursor(); } m_bitrange_index = bitrange_index; m_name_index = name_index; m_bitrange = bitrange_index.data().value< SocFieldBitRange >(); } void YRegDisplayItemEditor::getEditorData(QVariant& name, QVariant& bitrange) { name = QVariant(); /* don't touch the name */ bitrange = QVariant::fromValue(m_bitrange); } YRegDisplayItemEditor::~YRegDisplayItemEditor() { /* make sure to restore cursor if modified */ if(m_state != Idle) { m_state = Idle; QApplication::restoreOverrideCursor(); } } YRegDisplayItemEditor::Zone YRegDisplayItemEditor::GetZone(const QPoint& pt) { if(!rect().contains(pt)) return NoZone; if(pt.x() >= 0 && pt.x() <= m_resize_margin) return ResizeLeftZone; if(pt.x() >= width() - m_resize_margin && pt.x() <= width()) return ResizeRightZone; return MoveZone; } void YRegDisplayItemEditor::mouseMoveEvent(QMouseEvent *event) { Zone zone = GetZone(event->pos()); bool in_resize_zone = (zone == ResizeLeftZone || zone == ResizeRightZone); /* resizing/moving has priority */ if(m_state == ResizingLeft || m_state == ResizingRight || m_state == Moving) { SocFieldBitRange new_bitrange = m_bitrange; if(m_state == Moving) { /* Compute new bitrange: we know the offset of the mouse relative to the * left of the register: use that offset to compute the new position of * the MSB bit. To make it more natural, add half of a column of margin * so that the register does not move until half of a bit column displacement * was made */ int bit = m_display->bitColumnAt(mapTo(m_display, event->pos() - QPoint(m_move_offset - m_col_width / 2, 0))); new_bitrange.SetLastBit(bit); int w = m_bitrange.GetLastBit() - m_bitrange.GetFirstBit(); /* make sure range is valid */ if(bit - w < 0) return; new_bitrange.SetFirstBit(bit - w); } else { /* Compute new bitrange. To make it more natural, add quarter of a column of margin * so that the register does not resize until quarter of a bit column displacement * was made */ int bit = m_display->bitColumnAt(mapTo(m_display, event->pos() + QPoint(m_col_width / 4, 0))); if(m_state == ResizingLeft) new_bitrange.SetLastBit(bit); else new_bitrange.SetFirstBit(bit); /* make sure range is valid */ if(new_bitrange.GetLastBit() < new_bitrange.GetFirstBit()) return; } /* make sure range does not overlap with other fields */ /* TODO */ /* update current bitrange (display only) and resize widget */ if(m_bitrange != new_bitrange) { m_bitrange = new_bitrange; /* resize widget */ QRect rect = m_display->BitrangeRect(m_bitrange); rect.moveTopLeft(parentWidget()->mapFromGlobal(m_display->mapToGlobal(rect.topLeft()))); setGeometry(rect); } } /* any zone -> resize zone */ else if(in_resize_zone) { /* don't do unnecessary changes */ if(m_state != InResizeZone) { /* restore old cursor if needed */ if(m_state != Idle) QApplication::restoreOverrideCursor(); m_state = InResizeZone; QApplication::setOverrideCursor(QCursor(Qt::SizeHorCursor)); } } /* any zone -> move zone */ else if(zone == MoveZone) { /* don't do unnecessary changes */ if(m_state != InMoveZone) { /* restore old cursor if needed */ if(m_state != Idle) QApplication::restoreOverrideCursor(); m_state = InMoveZone; QApplication::setOverrideCursor(QCursor(Qt::SizeAllCursor)); } } /* any zone -> no zone */ else if(zone == NoZone) { if(m_state != Idle) { m_state = Idle; QApplication::restoreOverrideCursor(); } } } void YRegDisplayItemEditor::leaveEvent(QEvent *event) { Q_UNUSED(event); if(m_state == InResizeZone) { m_state = Idle; QApplication::restoreOverrideCursor(); } } void YRegDisplayItemEditor::mousePressEvent(QMouseEvent *event) { /* just in case the mouseMove event was not done */ mouseMoveEvent(event); /* we need to track mouse outside of widget but Qt already grabs the mouse * for us on mouse press in widget */ if(m_state == InResizeZone) { if(GetZone(event->pos()) == ResizeLeftZone) m_state = ResizingLeft; else m_state = ResizingRight; } else if(m_state == InMoveZone) { m_state = Moving; /* store offset from the left, to keep relative position of the register * with respect to the mouse */ m_move_offset = event->pos().x(); } } void YRegDisplayItemEditor::mouseReleaseEvent(QMouseEvent *event) { if(m_state == ResizingLeft || m_state == ResizingRight || m_state == Moving) { QApplication::restoreOverrideCursor(); m_state = Idle; /* update cursor */ mouseMoveEvent(event); } } void YRegDisplayItemEditor::paintEvent(QPaintEvent *event) { Q_UNUSED(event); QPainter painter(this); /* reuse delegate code to paint */ QStyleOptionViewItem opt = m_display->viewOptions(); opt.state |= QStyle::State_HasFocus | QStyle::State_Selected | QStyle::State_Active; opt.displayAlignment = Qt::AlignHCenter | Qt::AlignVCenter; opt.rect = rect(); opt.showDecorationSelected = true; m_display_delegate->initStyleOption(&opt, m_name_index); m_display_delegate->MyPaint(&painter, opt); } /** * YRegDisplayItemDelegate */ YRegDisplayItemDelegate::YRegDisplayItemDelegate(QObject *parent) :QStyledItemDelegate(parent) { } void YRegDisplayItemDelegate::MyPaint(QPainter *painter, const QStyleOptionViewItem& option) const { QStyleOptionViewItem opt = option; painter->save(); // draw everything rotated, requires careful manipulation of the // rects involved painter->translate(opt.rect.bottomLeft()); painter->rotate(-90); opt.rect = QRect(0, 0, opt.rect.height(), opt.rect.width()); QStyle *style = opt.widget ? opt.widget->style() : QApplication::style(); style->drawControl(QStyle::CE_ItemViewItem, &opt, painter, opt.widget); painter->restore(); } void YRegDisplayItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem& option, const QModelIndex& index) const { QStyleOptionViewItem opt = option; // default alignment is centered unless specified opt.displayAlignment = Qt::AlignHCenter | Qt::AlignVCenter; initStyleOption(&opt, index); MyPaint(painter, opt); } QSize YRegDisplayItemDelegate::sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const { /* useless in our case, the view ignores this */ Q_UNUSED(option); Q_UNUSED(index); return QSize(); } QWidget *YRegDisplayItemDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem& option, const QModelIndex& index) const { Q_UNUSED(option); Q_UNUSED(index); YRegDisplay *display = dynamic_cast< YRegDisplay* >(parent->parent()); Q_ASSERT(display != nullptr); /* column 0 is name, column 1 is range */ return new YRegDisplayItemEditor(parent, display, const_cast< YRegDisplayItemDelegate* >(this), index.sibling(index.row(), 0), index.sibling(index.row(), 1)); } void YRegDisplayItemDelegate::setEditorData(QWidget *editor, const QModelIndex& index) const { dynamic_cast< YRegDisplayItemEditor* >(editor)->setEditorData( index.sibling(index.row(), 0), index.sibling(index.row(), 1)); } void YRegDisplayItemDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex& index) const { QVariant name, bitrange; dynamic_cast< YRegDisplayItemEditor* >(editor)->getEditorData(name, bitrange); if(name.isValid()) model->setData(index.sibling(index.row(), 1), name); if(bitrange.isValid()) model->setData(index.sibling(index.row(), 0), bitrange); } /** * YRegDisplay */ YRegDisplay::YRegDisplay(QWidget *parent) :QAbstractItemView(parent) { m_is_dirty = true; m_range_col = 0; m_data_col = 1; m_nr_bits = 32; // the frame around the register is ugly, disable it setFrameShape(QFrame::NoFrame); setSelectionMode(SingleSelection); setEditTriggers(QAbstractItemView::DoubleClicked | QAbstractItemView::SelectedClicked); setItemDelegate(new YRegDisplayItemDelegate(this)); } void YRegDisplay::setWidth(int nr_bits) { m_nr_bits = nr_bits; m_is_dirty = true; recomputeGeometry(); updateGeometries(); } int YRegDisplay::bitColumnAt(const QPoint& point, bool closest) const { int wx = point.x() + horizontalScrollBar()->value(); for(int bit = 0; bit < m_nr_bits; bit++) { int off = columnOffset(bitToColumn(bit)); int w = columnWidth(bitToColumn(bit)); if(wx >= off && wx < off + w) return bit; if(wx >= off + w && closest) return bit; } return closest ? m_nr_bits - 1 : -1; } QModelIndex YRegDisplay::indexAt(const QPoint& point) const { if(!model()) return QModelIndex(); int wx = point.x() + horizontalScrollBar()->value(); int wy = point.y() + verticalScrollBar()->value(); for(int i = 0; i < model()->rowCount(); i++) { QModelIndex index = model()->index(i, m_data_col, rootIndex()); QRect r = itemRect(index); if(!r.isValid()) continue; if(r.contains(wx, wy)) return index; } return QModelIndex(); } void YRegDisplay::scrollTo(const QModelIndex& index, ScrollHint hint) { Q_UNUSED(index); Q_UNUSED(hint); } QRect YRegDisplay::visualRect(const QModelIndex &index) const { QRect rect = itemRect(index); if(rect.isValid()) return QRect(rect.left() - horizontalScrollBar()->value(), rect.top() - verticalScrollBar()->value(), rect.width(), rect.height()); else return rect; } bool YRegDisplay::isIndexHidden(const QModelIndex& index) const { Q_UNUSED(index); return false; } QModelIndex YRegDisplay::moveCursor(CursorAction cursorAction, Qt::KeyboardModifiers modifiers) { Q_UNUSED(cursorAction); Q_UNUSED(modifiers); return QModelIndex(); } void YRegDisplay::setSelection(const QRect& r, QItemSelectionModel::SelectionFlags flags) { if(!model()) return; QRect rect = r.translated(horizontalScrollBar()->value(), verticalScrollBar()->value()).normalized(); QItemSelection sel; for(int i = 0; i < model()->rowCount(); i++) { QModelIndex index = model()->index(i, m_data_col, rootIndex()); QRect r = itemRect(index); if(!r.isValid()) continue; if(r.intersects(rect)) sel.select(index, index); } selectionModel()->select(sel, flags); } int YRegDisplay::verticalOffset() const { return verticalScrollBar()->value(); } int YRegDisplay::horizontalOffset() const { return horizontalScrollBar()->value(); } void YRegDisplay::scrollContentsBy(int dx, int dy) { viewport()->scroll(dx, dy); } void YRegDisplay::setModel(QAbstractItemModel *model) { QAbstractItemView::setModel(model); m_is_dirty = true; } void YRegDisplay::dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight) { m_is_dirty = true; QAbstractItemView::dataChanged(topLeft, bottomRight); } void YRegDisplay::rowsInserted(const QModelIndex &parent, int start, int end) { m_is_dirty = true; QAbstractItemView::rowsInserted(parent, start, end); } void YRegDisplay::rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end) { m_is_dirty = true; QAbstractItemView::rowsAboutToBeRemoved(parent, start, end); } int YRegDisplay::separatorSize() const { return 1; } int YRegDisplay::marginSize() const { return viewOptions().fontMetrics.height() / 3; } int YRegDisplay::headerTextSep() const { return marginSize() / 2; } int YRegDisplay::headerHeight() const { return 2 * marginSize() + headerTextSep() + 2 * viewOptions().fontMetrics.height(); } int YRegDisplay::minColumnWidth() const { return 2 * marginSize() + viewOptions().fontMetrics.height(); } int YRegDisplay::maxColumnWidth() const { return 2 * minColumnWidth(); } int YRegDisplay::columnWidth(int col) const { int avail = width() - (m_nr_bits + 1) * separatorSize(); int small_w = qMin(avail / m_nr_bits, maxColumnWidth()); int nr_big = avail - small_w * m_nr_bits; if(col < nr_big) return small_w + 1; else return small_w; } int YRegDisplay::columnOffset(int col) const { int off = separatorSize(); for(int i = 0; i < col; i++) off += columnWidth(i) + separatorSize(); int all_w = off; for(int i = col; i < m_nr_bits; i++) all_w += columnWidth(i) + separatorSize(); return off + (width() - all_w) / 2; } int YRegDisplay::maxContentHeight() const { int max = 0; QFontMetrics metrics = viewOptions().fontMetrics; if(model()) { for(int i = 0; i < model()->rowCount(); i++) { QModelIndex index = model()->index(i, m_data_col, rootIndex()); QString s = model()->data(index).toString(); max = qMax(max, metrics.boundingRect(s).width()); } } return 2 * marginSize() + max; } int YRegDisplay::gapHeight() const { return marginSize() / 2; } int YRegDisplay::bitToColumn(int bit) const { return m_nr_bits - 1 - bit; } QRegion YRegDisplay::visualRegionForSelection(const QItemSelection& selection) const { QRegion region; foreach(const QItemSelectionRange &range, selection) { for(int row = range.top(); row <= range.bottom(); ++row) { for(int column = range.left(); column < range.right(); ++column) { QModelIndex index = model()->index(row, column, rootIndex()); region += visualRect(index); } } } return region; } QRect YRegDisplay::itemRect(const QModelIndex& index) const { if(!index.isValid()) return QRect(); QVariant vrange = model()->data(model()->index(index.row(), m_range_col, rootIndex())); if(!vrange.canConvert< SocFieldBitRange >()) return QRect(); SocFieldBitRange range = vrange.value< SocFieldBitRange >(); return itemRect(range, index.column()); } QRect YRegDisplay::BitrangeRect(const SocFieldBitRange& range) const { return itemRect(range, m_data_col); } QRect YRegDisplay::itemRect(const SocFieldBitRange& range, int col) const { int top, bot; if(col == m_range_col) { top = separatorSize(); bot = separatorSize() + headerHeight() - 1; } else if(col == m_data_col) { top = headerHeight() + 3 * separatorSize() + gapHeight(); bot = height() - separatorSize() - 1; } else return QRect(); int first_col = bitToColumn(range.GetFirstBit()); return QRect(QPoint(columnOffset(bitToColumn(range.GetLastBit())), top), QPoint(columnOffset(first_col) + columnWidth(first_col) - 1, bot)); } void YRegDisplay::paintEvent(QPaintEvent *event) { Q_UNUSED(event); QPainter painter(viewport()); painter.setRenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing); painter.translate(-horizontalScrollBar()->value(), -verticalScrollBar()->value()); QStyleOptionViewItem option = viewOptions(); int txt_h = option.fontMetrics.height(); int grid_hint = style()->styleHint(QStyle::SH_Table_GridLineColor, &option, this); QBrush grid_brush(static_cast< QRgb >(grid_hint)); QBrush back_brush = option.palette.base(); int sep_sz = separatorSize(); QItemSelectionModel *selections = selectionModel(); // paint header for(int bit = 0; bit < m_nr_bits; bit++) { QRect r = itemRect(SocFieldBitRange(bit, bit), m_range_col); // paint background painter.fillRect(r, back_brush); // paint top digit r.setTop(r.top() + marginSize()); r.setBottom(r.top() + txt_h); style()->drawItemText(&painter, r, Qt::AlignHCenter, option.palette, true, QString("%1").arg(bit / 10), foregroundRole()); // paint bottom digit r.setTop(r.bottom() + headerTextSep()); r.setBottom(r.top() + txt_h); style()->drawItemText(&painter, r, Qt::AlignHCenter, option.palette, true, QString("%1").arg(bit % 10), foregroundRole()); } // paint header grid for(int bit = 1; bit < m_nr_bits; bit++) { QRect r = itemRect(SocFieldBitRange(bit, bit), m_range_col); r.setCoords(r.right() + 1, r.top(), r.right() + sep_sz, r.bottom()); if((bit % 4) == 0) r.setCoords(r.left() - sep_sz, r.top(), r.right() + sep_sz, r.bottom()); painter.fillRect(r, grid_brush); } QRect hdr_r = itemRect(SocFieldBitRange(0, m_nr_bits - 1), m_range_col); painter.fillRect(QRect(hdr_r.left(), hdr_r.top() - sep_sz, hdr_r.width(), sep_sz), grid_brush); painter.fillRect(QRect(hdr_r.left(), hdr_r.bottom() + 1, hdr_r.width(), sep_sz), grid_brush); // paint header gap QRect gap_r(hdr_r.left(), hdr_r.bottom() + sep_sz + 1, hdr_r.width(), gapHeight()); painter.fillRect(gap_r, back_brush); // paint header bottom line painter.fillRect(QRect(gap_r.left(), gap_r.bottom() + 1, gap_r.width(), sep_sz), grid_brush); // paint background QRect data_r = itemRect(SocFieldBitRange(0, m_nr_bits - 1), m_data_col); //painter.fillRect(data_r, back_brush); // paint data bottom line painter.fillRect(QRect(data_r.left(), data_r.bottom() + 1, data_r.width(), sep_sz), grid_brush); // paint left/right lines painter.fillRect(QRect(hdr_r.left() - sep_sz, hdr_r.top() - sep_sz, sep_sz, height()), grid_brush); painter.fillRect(QRect(hdr_r.right() + 1, hdr_r.top() - sep_sz, sep_sz, height()), grid_brush); // paint model if(!model()) return; for(int i = 0; i < model()->rowCount(); i++) { QModelIndex index = model()->index(i, m_data_col, rootIndex()); QRect r = itemRect(index); if(!r.isValid()) continue; QString name = index.data().toString(); // paint background QStyleOptionViewItem opt = viewOptions(); opt.rect = r; //opt.showDecorationSelected = true; style()->drawPrimitive(QStyle::PE_PanelItemViewRow, &opt, &painter, this); if(selections->isSelected(index)) opt.state |= QStyle::State_Selected; if(currentIndex() == index) opt.state |= QStyle::State_HasFocus; if(m_hover == index) opt.state |= QStyle::State_MouseOver; itemDelegate(index)->paint(&painter, opt, index); // paint left/right lines painter.fillRect(QRect(r.left() - sep_sz, r.top(), sep_sz, r.height()), grid_brush); painter.fillRect(QRect(r.right() + 1, r.top(), sep_sz, r.height()), grid_brush); } } void YRegDisplay::recomputeGeometry() { if(!m_is_dirty) return; /* height: header + gap + sep + content + sep */ m_minimum_height = 0; m_minimum_height += headerHeight() + gapHeight(); m_minimum_height += 2 * separatorSize() + maxContentHeight(); /* width: sep + (col + sep) * n */ m_minimum_width = separatorSize() * (m_nr_bits + 1) + minColumnWidth() * m_nr_bits; m_is_dirty = false; viewport()->update(); } void YRegDisplay::resizeEvent(QResizeEvent*) { m_is_dirty = true; recomputeGeometry(); updateGeometries(); } void YRegDisplay::updateGeometries() { horizontalScrollBar()->setSingleStep(1); horizontalScrollBar()->setPageStep(viewport()->width()); horizontalScrollBar()->setRange(0, qMax(0, m_minimum_width - viewport()->width())); verticalScrollBar()->setSingleStep(1); verticalScrollBar()->setPageStep(viewport()->height()); verticalScrollBar()->setRange(0, qMax(0, m_minimum_height - viewport()->height())); } bool YRegDisplay::viewportEvent(QEvent *event) { /* FIXME Apparently QAbstractItemView tracks the hovered index but keeps it * in its private part which is not accessible, which makes it useless... * This code reimplements it */ switch (event->type()) { case QEvent::HoverEnter: m_hover = indexAt(static_cast(event)->pos()); update(m_hover); break; case QEvent::HoverLeave: update(m_hover); // update old m_hover = QModelIndex(); break; case QEvent::HoverMove: { QModelIndex old = m_hover; m_hover = indexAt(static_cast(event)->pos()); if(m_hover != old) viewport()->update(visualRect(old)|visualRect(m_hover)); break; } default: break; } return QAbstractItemView::viewportEvent(event); } /** * 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); 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); #if QT_VERSION >= QT_VERSION_CHECK(5, 2, 0) /* Qt 5.2 have a hardcoded sizeHint for QAbstractScrollArea which makes it * hard to have a good behaviour for the text editor. Fortunately 5.2 introduces * a new option to adjust this to the content. */ m_edit->setSizeAdjustPolicy(QAbstractScrollArea::AdjustToContents); #endif 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(YIconManager::Get()->GetIcon(YIconManager::FormatTextBold)); m_bold_button->setText("bold"); m_bold_button->setCheckable(true); m_italic_button = new QToolButton(this); m_italic_button->setIcon(YIconManager::Get()->GetIcon(YIconManager::FormatTextItalic)); m_italic_button->setText("italic"); m_italic_button->setCheckable(true); m_underline_button = new QToolButton(this); m_underline_button->setIcon(YIconManager::Get()->GetIcon(YIconManager::FormatTextUnderline)); 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&))); m_edit->installEventFilter(this); SetGrowingMode(false); SetReadOnly(false); m_toolbar->hide(); } void MyTextEditor::SetReadOnly(bool en) { m_read_only = en; if(en) m_toolbar->hide(); else m_toolbar->show(); 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); OnInternalTextChanged(); } 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(); emit OnTextChanged(GetTextHtml()); } 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(); } bool MyTextEditor::eventFilter(QObject *object, QEvent *event) { if(object != m_edit) return false; if(m_read_only) return false; if(event->type() == QEvent::FocusIn) m_toolbar->show(); else if(event->type() == QEvent::FocusOut) m_toolbar->hide(); return false; } /** * BackendSelector */ BackendSelector::BackendSelector(Backend *backend, QWidget *parent) :QWidget(parent), m_backend(backend) { m_data_selector = new QComboBox(this); m_data_selector->addItem(YIconManager::Get()->GetIcon(YIconManager::TextGeneric), "Nothing...", QVariant(DataSelNothing)); m_data_selector->addItem(YIconManager::Get()->GetIcon(YIconManager::DocumentOpen), "File...", QVariant(DataSelFile)); #ifdef HAVE_HWSTUB m_data_selector->addItem(YIconManager::Get()->GetIcon(YIconManager::MultimediaPlayer), "USB 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; m_ctx_model = new HWStubContextModel; m_ctx_model->EnableDummy(true, "Please select a device..."); m_dev_selector->setModel(m_ctx_model); /* m_dev_selector will delete m_ctx_model */ m_ctx_selector = new QComboBox; m_ctx_manager = HWStubManager::Get(); m_ctx_selector->setModel(m_ctx_manager); m_ctx_manage_button = new QPushButton(); m_ctx_manage_button->setIcon(YIconManager::Get()->GetIcon(YIconManager::Preferences)); m_ctx_manage_button->setToolTip("Manage contexts"); data_sel_layout->addWidget(m_dev_selector, 1); data_sel_layout->addWidget(m_ctx_selector); data_sel_layout->addWidget(m_ctx_manage_button); #endif m_io_backend = m_backend->CreateDummyIoBackend(); connect(m_data_selector, SIGNAL(activated(int)), this, SLOT(OnDataSelChanged(int))); #ifdef HAVE_HWSTUB connect(m_ctx_selector, SIGNAL(currentIndexChanged(int)), this, SLOT(OnContextSelChanged(int))); connect(m_dev_selector, SIGNAL(currentIndexChanged(int)), this, SLOT(OnDeviceSelChanged(int))); connect(m_dev_selector, SIGNAL(activated(int)), this, SLOT(OnDeviceSelActivated(int))); #endif #ifdef HAVE_HWSTUB OnContextSelChanged(0); #endif OnDataSelChanged(0); } BackendSelector::~BackendSelector() { /* avoid m_ctx_selector from deleting HWStubManager */ #ifdef HAVE_HWSTUB m_ctx_selector->setModel(new QStandardItemModel()); #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(); m_ctx_selector->hide(); m_ctx_manage_button->hide(); #endif QFileDialog *fd = new QFileDialog(m_data_selector); QStringList filters; filters << "Textual files (*.txt)"; filters << "All files (*)"; fd->setNameFilters(filters); 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(); m_ctx_selector->show(); m_ctx_manage_button->show(); /* explicitely change the backend now */ OnDeviceSelActivated(m_dev_selector->currentIndex()); } #endif else { m_data_sel_edit->hide(); m_nothing_text->show(); #ifdef HAVE_HWSTUB m_dev_selector->hide(); m_ctx_selector->hide(); m_ctx_manage_button->hide(); #endif ChangeBackend(m_backend->CreateDummyIoBackend()); } } 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; } #ifdef HAVE_HWSTUB void BackendSelector::OnContextSelChanged(int index) { m_ctx_model->SetContext(m_ctx_manager->GetContext(index)); m_dev_selector->setCurrentIndex(0); } void BackendSelector::OnDeviceSelChanged(int index) { /* if current selection is -1, because device was removed or a new context * was selected, select entry 0, which is dummy. Not that this will not * call activate(), we don't want to change the current backend if the user * is using another type of backend. */ if(index == -1) m_dev_selector->setCurrentIndex(0); } void BackendSelector::OnDeviceSelActivated(int index) { auto dev = new HWStubDevice(m_ctx_model->GetDevice(index)); if(!dev->IsValid()) { delete dev; ChangeBackend(m_backend->CreateDummyIoBackend()); } else ChangeBackend(new HWStubIoBackend(dev)); } #endif /** * YTabWidget */ YTabWidget::YTabWidget(QTabBar *bar, QWidget *parent) :QTabWidget(parent), m_other_button(0) { if(bar != 0) setTabBar(bar); m_tab_open_button = new QToolButton(this); m_tab_open_button->setIcon(YIconManager::Get()->GetIcon(YIconManager::ListAdd)); m_tab_open_button->setAutoRaise(true); m_tab_open_button->setPopupMode(QToolButton::InstantPopup); /* the arrow with an icon only is pretty ugly and QToolButton has no way * to remove the arrow programmaticaly, so use the CSS to do that */ m_tab_open_button->setStyleSheet("QToolButton::menu-indicator { image: none; }"); setCornerWidget(m_tab_open_button, Qt::TopLeftCorner); setTabOpenable(false); connect(m_tab_open_button, SIGNAL(clicked(bool)), this, SLOT(OnOpenButton(bool))); /* there is a quirk in the default QStyle: if the tab bar is empty, it * returns the minimum size of the corner widget, which is 0 for tool buttons */ m_tab_open_button->setMinimumSize(m_tab_open_button->sizeHint()); setMinimumSize(m_tab_open_button->sizeHint()); } void YTabWidget::setTabOpenable(bool openable) { m_tab_openable = openable; m_tab_open_button->setVisible(openable); } void YTabWidget::OnOpenButton(bool checked) { Q_UNUSED(checked); emit tabOpenRequested(); } void YTabWidget::setTabOpenMenu(QMenu *menu) { m_tab_open_button->setMenu(menu); } void YTabWidget::setOtherMenu(QMenu *menu) { if(menu == nullptr) { if(m_other_button) delete m_other_button; m_other_button = nullptr; } else { if(m_other_button == nullptr) { m_other_button = new QToolButton(this); m_other_button->setText("Menu"); m_other_button->setAutoRaise(true); m_other_button->setPopupMode(QToolButton::InstantPopup); setCornerWidget(m_other_button, Qt::TopRightCorner); } m_other_button->setMenu(menu); } } /** * 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_text->setWordWrap(true); m_close = new QToolButton(this); m_close->setText("close"); m_close->setIcon(style()->standardIcon(QStyle::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(); } /* * YIconManager */ YIconManager *YIconManager::m_singleton = nullptr; YIconManager::YIconManager() { m_icon_name[ListAdd] = "list-add"; m_icon_fallback[ListAdd] = QStyle::SP_CustomBase; /* drawn by RenderListAdd */ m_icon_name[ListRemove] = "list-remove"; m_icon_fallback[ListRemove] = QStyle::SP_CustomBase; /* drawn by RenderListAdd */ m_icon_name[DocumentNew] = "document-new"; m_icon_fallback[DocumentNew] = QStyle::SP_FileDialogNewFolder; m_icon_name[DocumentEdit] = "document-edit"; m_icon_fallback[DocumentEdit] = QStyle::SP_FileDialogContentsView; m_icon_name[DocumentOpen] = "document-open"; m_icon_fallback[DocumentOpen] = QStyle::SP_DialogOpenButton; m_icon_name[DocumentSave] = "document-save"; m_icon_fallback[DocumentSave] = QStyle::SP_DialogSaveButton; m_icon_name[DocumentSaveAs] = "document-save-as"; m_icon_fallback[DocumentSaveAs] = QStyle::SP_DialogSaveButton; m_icon_name[Preferences] = "preferences-system"; m_icon_fallback[Preferences] = QStyle::SP_FileDialogInfoView; m_icon_name[FolderNew] = "folder-new"; m_icon_fallback[FolderNew] = QStyle::SP_FileDialogNewFolder; m_icon_name[Computer] = "computer"; m_icon_fallback[Computer] = QStyle::SP_ComputerIcon; m_icon_name[Cpu] = "cpu"; m_icon_fallback[Cpu] = QStyle::SP_DriveHDIcon; m_icon_name[DialogError] = "dialog-error"; m_icon_fallback[DialogError] = QStyle::SP_MessageBoxCritical; m_icon_name[ViewRefresh] = "view-refresh"; m_icon_fallback[ViewRefresh] = QStyle::SP_BrowserReload; m_icon_name[SytemRun] = "system-run"; m_icon_fallback[SytemRun] = QStyle::SP_MediaPlay; m_icon_name[ApplicationExit] = "application-exit"; m_icon_fallback[ApplicationExit] = QStyle::SP_TitleBarCloseButton; m_icon_name[HelpAbout] = "help-about"; m_icon_fallback[HelpAbout] = QStyle::SP_MessageBoxInformation; m_icon_name[FormatTextBold] = "format-text-bold"; m_icon_fallback[FormatTextBold] = QStyle::SP_CustomBase; /* drawn by RenderLetter */ m_icon_name[FormatTextItalic] = "format-text-italic"; m_icon_fallback[FormatTextItalic] = QStyle::SP_CustomBase; /* drawn by RenderLetter */ m_icon_name[FormatTextUnderline] = "format-text-underline"; m_icon_fallback[FormatTextUnderline] = QStyle::SP_CustomBase; /* drawn by RenderLetter */ m_icon_name[TextGeneric] = "text-x-generic"; m_icon_fallback[TextGeneric] = QStyle::SP_FileDialogDetailedView; m_icon_name[MultimediaPlayer] = "multimedia-player"; m_icon_fallback[MultimediaPlayer] = QStyle::SP_ComputerIcon; } YIconManager::~YIconManager() { } YIconManager *YIconManager::Get() { if(m_singleton == nullptr) m_singleton = new YIconManager(); return m_singleton; } QIcon YIconManager::GetIcon(IconType type) { if(type < 0 || type >= MaxIcon) return QIcon(); /* cache icons */ if(m_icon[type].isNull()) m_icon[type] = QIcon::fromTheme(m_icon_name[type], GetFallbackIcon(type)); return m_icon[type]; } namespace { QIcon RenderListAdd() { QPixmap pix(64, 64); pix.fill(Qt::transparent); QPainter paint(&pix); paint.fillRect(30, 12, 4, 40, QColor(255, 0, 0)); paint.fillRect(12, 30, 40, 4, QColor(255, 0, 0)); return QIcon(pix); } QIcon RenderListRemove() { QPixmap pix(64, 64); pix.fill(Qt::transparent); QPainter paint(&pix); paint.setPen(QColor(255, 0, 0)); paint.drawLine(12, 12, 52, 52); paint.drawLine(12, 52, 52, 16); return QIcon(pix); } QIcon RenderUnknown() { QPixmap pix(64, 64); pix.fill(); QPainter paint(&pix); paint.fillRect(0, 0, 64, 64, QColor(255, 0, 0)); return QIcon(pix); } QIcon RenderText(const QString& str, bool bold, bool italic, bool underline) { QPixmap pix(64, 64); pix.fill(); QPainter paint(&pix); QFont font = QApplication::font("QButton"); font.setBold(bold); font.setItalic(italic); font.setUnderline(underline); font.setPixelSize(64); paint.setFont(font); paint.drawText(0, 0, 64, 64, Qt::AlignCenter, str); return QIcon(pix); } } QIcon YIconManager::GetFallbackIcon(IconType type) { if(m_icon_fallback[type] != QStyle::SP_CustomBase) return QApplication::style()->standardIcon(m_icon_fallback[type]); switch(type) { case ListAdd: return RenderListAdd(); break; case ListRemove: return RenderListRemove(); break; case FormatTextBold: return RenderText("B", true, false, false); case FormatTextItalic: return RenderText("I", false, true, false); case FormatTextUnderline: return RenderText("U", false, false, true); default: return RenderUnknown(); break; } } /** * Misc */ QGroupBox *Misc::EncloseInBox(const QString& name, QWidget *widget) { QVBoxLayout *layout = new QVBoxLayout; layout->addWidget(widget); return Misc::EncloseInBox(name, layout); } QGroupBox *Misc::EncloseInBox(const QString& name, QLayout *layout) { QGroupBox *group = new QGroupBox(name); group->setLayout(layout); return group; }