A modern Music Player Daemon based on Rockbox open source high quality audio player
libadwaita
audio
rust
zig
deno
mpris
rockbox
mpd
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2014 by Amaury Pouly
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
16 *
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
19 *
20 ****************************************************************************/
21#include "utils.h"
22#include <QFontMetrics>
23#include <QPainter>
24#include <QTextDocument>
25#include <QAbstractTextDocumentLayout>
26#include <QHeaderView>
27#include <QDebug>
28#include <QElapsedTimer>
29#include <QXmlStreamReader>
30#include <QXmlStreamWriter>
31#include <QTextBlock>
32#include <QApplication>
33#include <QPushButton>
34#include <QMessageBox>
35#include <QStandardItemModel>
36
37/**
38 * SocBitRangeValidator
39 */
40SocBitRangeValidator::SocBitRangeValidator(QObject *parent)
41 :QValidator(parent)
42{
43 m_width = 32;
44}
45
46void SocBitRangeValidator::fixup(QString& input) const
47{
48 input = input.trimmed();
49}
50
51QValidator::State SocBitRangeValidator::validate(QString& input, int& pos) const
52{
53 Q_UNUSED(pos);
54 int first, last;
55 State state = parse(input, last, first);
56 return state;
57}
58
59QValidator::State SocBitRangeValidator::parse(const QString& input, int& last, int& first) const
60{
61 // the empty string is always intermediate
62 if(input.size() == 0)
63 return Intermediate;
64 // check if there is ':'
65 int pos = input.indexOf(':');
66 if(pos == -1)
67 pos = input.size();
68 // if field start with ':', the last bit is implicit and is 31
69 if(pos > 0)
70 {
71 // parse last bit and check it's between 0 and 31
72 bool ok = false;
73 last = input.left(pos).toInt(&ok);
74 if(!ok || last < 0 || last >= m_width)
75 return Invalid;
76 }
77 else
78 last = m_width - 1;
79 // parse first bit
80 if(pos < input.size() - 1)
81 {
82 bool ok = false;
83 first = input.mid(pos + 1).toInt(&ok);
84 if(!ok || first < 0 || first > last)
85 return Invalid;
86 }
87 // if input ends with ':', first bit is implicit and is 0
88 else if(pos == input.size() - 1)
89 first = 0;
90 // if there no ':', first=last
91 else
92 first = last;
93 return Acceptable;
94}
95
96void SocBitRangeValidator::setWidth(int nr_bits)
97{
98 m_width = nr_bits;
99}
100
101QString SocBitRangeValidator::generate(int last_bit, int first_bit) const
102{
103 if(last_bit == first_bit)
104 return QString("%1").arg(first_bit);
105 else
106 return QString("%1:%2").arg(last_bit).arg(first_bit);
107}
108
109/**
110 * SocFieldValidator
111 */
112
113SocFieldValidator::SocFieldValidator(QObject *parent)
114 :QValidator(parent)
115{
116 m_field.pos = 0;
117 m_field.width = 32;
118}
119
120SocFieldValidator::SocFieldValidator(const soc_desc::field_t& field, QObject *parent)
121 :QValidator(parent), m_field(field)
122{
123}
124
125void SocFieldValidator::fixup(QString& input) const
126{
127 input = input.trimmed();
128}
129
130QValidator::State SocFieldValidator::validate(QString& input, int& pos) const
131{
132 Q_UNUSED(pos);
133 soc_word_t val;
134 State state = parse(input, val);
135 return state;
136}
137
138QValidator::State SocFieldValidator::parse(const QString& input, soc_word_t& val) const
139{
140 // the empty string is always intermediate
141 if(input.size() == 0)
142 return Intermediate;
143 // first check named values
144 State state = Invalid;
145 foreach(const soc_desc::enum_t& value, m_field.enum_)
146 {
147 QString name = QString::fromLocal8Bit(value.name.c_str());
148 // cannot be a substring if too long or empty
149 if(input.size() > name.size())
150 continue;
151 // check equal string
152 if(input == name)
153 {
154 state = Acceptable;
155 val = value.value;
156 break;
157 }
158 // check substring
159 if(name.startsWith(input))
160 state = Intermediate;
161 }
162 // early return for exact match
163 if(state == Acceptable)
164 return state;
165 // do a few special cases for convenience
166 if(input.compare("0x", Qt::CaseInsensitive) == 0 ||
167 input.compare("0b", Qt::CaseInsensitive) == 0)
168 return Intermediate;
169 // try by parsing
170 unsigned basis, pos;
171 if(input.size() >= 2 && input.startsWith("0x", Qt::CaseInsensitive))
172 {
173 basis = 16;
174 pos = 2;
175 }
176 else if(input.size() >= 2 && input.startsWith("0b", Qt::CaseInsensitive))
177 {
178 basis = 2;
179 pos = 2;
180 }
181 else if(input.size() >= 2 && input.startsWith("0"))
182 {
183 basis = 8;
184 pos = 1;
185 }
186 else
187 {
188 basis = 10;
189 pos = 0;
190 }
191 bool ok = false;
192 unsigned long v = input.mid(pos).toULong(&ok, basis);
193 // if not ok, return result of name parsing
194 if(!ok)
195 return state;
196 // if ok, check if it fits in the number of bits
197 unsigned nr_bits = m_field.width;
198 unsigned long max = nr_bits == 32 ? 0xffffffff : (1 << nr_bits) - 1;
199 if(v <= max)
200 {
201 val = v;
202 return Acceptable;
203 }
204
205 return state;
206}
207
208/**
209 * RegLineEdit
210 */
211RegLineEdit::RegLineEdit(QWidget *parent)
212 :QWidget(parent)
213{
214 m_layout = new QHBoxLayout(this);
215 m_button = new QToolButton(this);
216 m_button->setCursor(Qt::ArrowCursor);
217 m_button->setStyleSheet("QToolButton { font-weight: bold; color: white; background: black; }");
218 m_button->setPopupMode(QToolButton::InstantPopup);
219 m_edit = new QLineEdit(this);
220 m_layout->addWidget(m_button);
221 m_layout->addWidget(m_edit);
222 m_menu = new QMenu(this);
223 connect(m_menu->addAction("Write"), SIGNAL(triggered()), this, SLOT(OnWriteAct()));
224 connect(m_menu->addAction("Set"), SIGNAL(triggered()), this, SLOT(OnSetAct()));
225 connect(m_menu->addAction("Clear"), SIGNAL(triggered()), this, SLOT(OnClearAct()));
226 connect(m_menu->addAction("Toggle"), SIGNAL(triggered()), this, SLOT(OnToggleAct()));
227 EnableSCT(false);
228 SetReadOnly(false);
229 ShowMode(true);
230 SetMode(Write);
231}
232
233void RegLineEdit::SetReadOnly(bool ro)
234{
235 m_edit->setReadOnly(ro);
236 m_readonly = ro;
237 ShowMode(!ro);
238}
239
240void RegLineEdit::EnableSCT(bool en)
241{
242 m_has_sct = en;
243 if(!m_has_sct)
244 {
245 m_button->setMenu(0);
246 SetMode(Write);
247 }
248 else
249 m_button->setMenu(m_menu);
250}
251
252RegLineEdit::~RegLineEdit()
253{
254}
255
256QLineEdit *RegLineEdit::GetLineEdit()
257{
258 return m_edit;
259}
260
261void RegLineEdit::ShowMode(bool show)
262{
263 if(show)
264 m_button->show();
265 else
266 m_button->hide();
267}
268
269void RegLineEdit::OnWriteAct()
270{
271 SetMode(Write);
272}
273
274void RegLineEdit::OnSetAct()
275{
276 SetMode(Set);
277}
278
279void RegLineEdit::OnClearAct()
280{
281 SetMode(Clear);
282}
283
284void RegLineEdit::OnToggleAct()
285{
286 SetMode(Toggle);
287}
288
289void RegLineEdit::SetMode(EditMode mode)
290{
291 m_mode = mode;
292 switch(m_mode)
293 {
294 case Write: m_button->setText("WR"); break;
295 case Set: m_button->setText("SET"); break;
296 case Clear: m_button->setText("CLR"); break;
297 case Toggle: m_button->setText("TOG"); break;
298 default: break;
299 }
300}
301
302RegLineEdit::EditMode RegLineEdit::GetMode()
303{
304 return m_mode;
305}
306
307void RegLineEdit::setText(const QString& text)
308{
309 m_edit->setText(text);
310}
311
312QString RegLineEdit::text() const
313{
314 return m_edit->text();
315}
316
317/**
318 * SocFieldItemDelegate
319 */
320
321QString SocFieldItemDelegate::displayText(const QVariant& value, const QLocale& locale) const
322{
323 if(value.type() == QVariant::UInt)
324 return QString("0x%1").arg(value.toUInt(), (m_bitcount + 3) / 4, 16, QChar('0'));
325 else
326 return QStyledItemDelegate::displayText(value, locale);
327}
328
329void SocFieldItemDelegate::setWidth(int bitcount)
330{
331 m_bitcount = bitcount;
332}
333
334/**
335 * SocFieldEditor
336 */
337SocFieldEditor::SocFieldEditor(const soc_desc::field_t& field, QWidget *parent)
338 :QLineEdit(parent), m_reg_field(field)
339{
340 m_validator = new SocFieldValidator(field);
341 setValidator(m_validator);
342 connect(this, SIGNAL(editingFinished()), this, SLOT(editDone()));
343 setAlignment(Qt::AlignCenter);
344 setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Minimum);
345}
346
347SocFieldEditor::~SocFieldEditor()
348{
349 delete m_validator;
350}
351
352void SocFieldEditor::editDone()
353{
354 emit editingFinished(field());
355}
356
357uint SocFieldEditor::field() const
358{
359 soc_word_t v;
360 /* in case validator fails to parse, return old value */
361 if(m_validator->parse(text(), v) == QValidator::Acceptable)
362 return v;
363 else
364 return m_field;
365}
366
367void SocFieldEditor::setField(uint field)
368{
369 m_field = field;
370 int digits = (m_reg_field.width + 3) / 4;
371 setText(QString("0x%1").arg(field, digits, 16, QChar('0')));
372}
373
374void SocFieldEditor::SetRegField(const soc_desc::field_t& field)
375{
376 setValidator(0);
377 delete m_validator;
378 m_validator = new SocFieldValidator(field);
379 setValidator(m_validator);
380 m_reg_field = field;
381}
382
383/**
384 * SocFieldCachedValue
385 */
386SocFieldCachedValue::SocFieldCachedValue(const soc_desc::field_t& field, uint value)
387 :m_field(field), m_value(value)
388{
389 int idx = field.find_value(value);
390 if(idx != -1)
391 m_name = QString::fromStdString(field.enum_[idx].name);
392}
393
394bool SocFieldCachedValue::operator<(const SocFieldCachedValue& o) const
395{
396 return m_value < o.m_value;
397}
398
399/**
400 * SocFieldBitRange
401 */
402
403bool SocFieldBitRange::operator<(const SocFieldBitRange& o) const
404{
405 if(m_first_bit < o.m_first_bit)
406 return true;
407 if(m_first_bit > o.m_first_bit)
408 return false;
409 return m_last_bit < o.m_last_bit;
410}
411
412bool SocFieldBitRange::operator!=(const SocFieldBitRange& o) const
413{
414 return m_first_bit != o.m_first_bit || m_last_bit != o.m_last_bit;
415}
416
417/**
418 * SocFieldCachedItemDelegate
419 */
420
421SocFieldCachedItemDelegate::SocFieldCachedItemDelegate(QObject *parent)
422 :QStyledItemDelegate(parent)
423{
424 m_mode = DisplayValueAndName;
425}
426
427QString SocFieldCachedItemDelegate::displayText(const QVariant& value, const QLocale& locale) const
428{
429 if(isUserType< SocFieldCachedValue >(value))
430 {
431 const SocFieldCachedValue& v = value.value< SocFieldCachedValue >();
432 int bitcount = v.field().width;
433 QString name = v.value_name();
434 QString strval = QString("0x%1").arg(v.value(), (bitcount + 3) / 4, 16, QChar('0'));
435 switch(m_mode)
436 {
437 case DisplayName:
438 if(name.size() > 0)
439 return name;
440 /* fallthrough */
441 case DisplayValueAndName:
442 if(name.size() > 0)
443 return QString("%1 (%2)").arg(strval).arg(name);
444 /* fallthrough */
445 case DisplayValue:
446 default:
447 return strval;
448 }
449 }
450 else if(value.type() == QVariant::UserType && value.userType() == qMetaTypeId< SocFieldBitRange >())
451 {
452 const SocFieldBitRange& br = value.value< SocFieldBitRange >();
453 if(br.GetFirstBit() == br.GetLastBit())
454 return QString("%1").arg(br.GetFirstBit());
455 else
456 return QString("%1:%2").arg(br.GetLastBit()).arg(br.GetFirstBit());
457 }
458 else
459 return QStyledItemDelegate::displayText(value, locale);
460}
461
462/**
463 * SocFieldCachedEditor
464 */
465SocFieldCachedEditor::SocFieldCachedEditor(QWidget *parent)
466 :SocFieldEditor(soc_desc::field_t(), parent)
467{
468}
469
470SocFieldCachedEditor::~SocFieldCachedEditor()
471{
472}
473
474SocFieldCachedValue SocFieldCachedEditor::value() const
475{
476 return SocFieldCachedValue(m_value.field(), field());
477}
478
479void SocFieldCachedEditor::setValue(SocFieldCachedValue val)
480{
481 m_value = val;
482 SetRegField(m_value.field());
483 setField(m_value.value());
484}
485
486/**
487 * SocAccessItemDelegate
488 */
489QString SocAccessItemDelegate::displayText(const QVariant& value, const QLocale& locale) const
490{
491 if(isUserType< soc_desc::access_t >(value))
492 {
493 soc_desc::access_t acc = value.value< soc_desc::access_t >();
494 switch(acc)
495 {
496 case soc_desc::UNSPECIFIED: return m_unspec_text;
497 case soc_desc::READ_ONLY: return "Read-Only";
498 case soc_desc::READ_WRITE: return "Read-Write";
499 case soc_desc::WRITE_ONLY: return "Write-Only";
500 default: return "<bug>";
501 }
502 }
503 else
504 return QStyledItemDelegate::displayText(value, locale);
505}
506
507/**
508 * SocAccessEditor
509 */
510SocAccessEditor::SocAccessEditor(const QString& unspec_text, QWidget *parent)
511 :QComboBox(parent)
512{
513 addItem(unspec_text, QVariant::fromValue(soc_desc::UNSPECIFIED));
514 addItem("Read-Only", QVariant::fromValue(soc_desc::READ_ONLY));
515 addItem("Read-Write", QVariant::fromValue(soc_desc::READ_WRITE));
516 addItem("Write-Only", QVariant::fromValue(soc_desc::WRITE_ONLY));
517}
518
519SocAccessEditor::~SocAccessEditor()
520{
521}
522
523soc_desc::access_t SocAccessEditor::access() const
524{
525 return itemData(currentIndex()).value< soc_desc::access_t >();
526}
527
528void SocAccessEditor::setAccess(soc_desc::access_t acc)
529{
530 setCurrentIndex(findData(QVariant::fromValue(acc)));
531}
532
533/**
534 * SocFieldEditorCreator
535 */
536QWidget *SocFieldEditorCreator::createWidget(QWidget *parent) const
537{
538 return new SocFieldEditor(m_field, parent);
539}
540
541QByteArray SocFieldEditorCreator::valuePropertyName() const
542{
543 return QByteArray("field");
544}
545
546void SocFieldEditorCreator::setWidth(int bitcount)
547{
548 m_field.width = bitcount;
549}
550
551/**
552 * SocAccessEditorCreator
553 */
554QWidget *SocAccessEditorCreator::createWidget(QWidget *parent) const
555{
556 return new SocAccessEditor(m_unspec_text, parent);
557}
558
559QByteArray SocAccessEditorCreator::valuePropertyName() const
560{
561 return QByteArray("access");
562}
563
564/**
565 * SocFieldCachedEditorCreator
566 */
567QWidget *SocFieldCachedEditorCreator::createWidget(QWidget *parent) const
568{
569 return new SocFieldCachedEditor(parent);
570}
571
572QByteArray SocFieldCachedEditorCreator::valuePropertyName() const
573{
574 return QByteArray("value");
575}
576
577/**
578 * RegFieldTableModel
579 */
580
581RegFieldTableModel::RegFieldTableModel(QObject *parent)
582 :QAbstractTableModel(parent)
583{
584 m_read_only = true;
585}
586
587int RegFieldTableModel::rowCount(const QModelIndex& /* parent */) const
588{
589 return m_reg.field.size();
590}
591
592int RegFieldTableModel::columnCount(const QModelIndex& /* parent */) const
593{
594 return ColumnCountOffset + m_value.size();
595}
596
597QVariant RegFieldTableModel::data(const QModelIndex& index, int role) const
598{
599 if(index.row() < 0 || (size_t)index.row() >= m_reg.field.size())
600 return QVariant();
601 int section = index.column();
602 const soc_desc::field_t& field = m_reg.field[index.row()];
603 /* column independent code */
604 const RegThemeGroup *theme = 0;
605 switch(m_status[index.row()])
606 {
607 case Normal: theme = &m_theme.normal; break;
608 case Diff: theme = &m_theme.diff; break;
609 case Error: theme = &m_theme.error; break;
610 case None: default: break;
611 }
612 if(role == Qt::FontRole)
613 return theme ? QVariant(theme->font) : QVariant();
614 if(role == Qt::BackgroundRole)
615 return theme ? QVariant(theme->background) : QVariant();
616 if(role == Qt::ForegroundRole)
617 return theme ? QVariant(theme->foreground) : QVariant();
618 /* column dependent code */
619 if(section == BitRangeColumn)
620 {
621 if(role == Qt::DisplayRole)
622 return QVariant::fromValue(SocFieldBitRange(field));
623 else if(role == Qt::TextAlignmentRole)
624 return QVariant(Qt::AlignVCenter | Qt::AlignHCenter);
625 else
626 return QVariant();
627 }
628 if(section == NameColumn)
629 {
630 if(role == Qt::DisplayRole)
631 return QVariant(QString::fromStdString(field.name));
632 else
633 return QVariant();
634 }
635 if(section < FirstValueColumn + m_value.size())
636 {
637 int idx = section - FirstValueColumn;
638 if(role == Qt::DisplayRole)
639 {
640 if(!m_value[idx].isValid())
641 return QVariant("<error>");
642 return QVariant::fromValue(SocFieldCachedValue(field,
643 field.extract(m_value[idx].value< soc_word_t >())));
644 }
645 else if(role == Qt::EditRole)
646 {
647 if(!m_value[idx].isValid())
648 return QVariant();
649 return QVariant::fromValue(SocFieldCachedValue(field,
650 field.extract(m_value[idx].value< soc_word_t >())));
651 }
652 else if(role == Qt::TextAlignmentRole)
653 return QVariant(Qt::AlignVCenter | Qt::AlignHCenter);
654 else
655 return QVariant();
656 }
657 section -= m_value.size();
658 if(section == DescColumnOffset)
659 {
660 if(role == Qt::DisplayRole)
661 return QVariant(QString::fromStdString(field.desc));
662 else
663 return QVariant();
664 }
665 return QVariant();
666}
667
668bool RegFieldTableModel::setData(const QModelIndex& idx, const QVariant& value, int role)
669{
670 if(role != Qt::EditRole)
671 return false;
672 int section = idx.column();
673 if(section == BitRangeColumn)
674 {
675 if(idx.row() < 0 || idx.row() >= rowCount())
676 return false;
677 if(value.type() != QVariant::UserType && value.userType() == qMetaTypeId< SocFieldBitRange >())
678 return false;
679 SocFieldBitRange bitrange = value.value< SocFieldBitRange >();
680 m_reg.field[idx.row()].pos = bitrange.GetFirstBit();
681 m_reg.field[idx.row()].width = bitrange.GetLastBit() - bitrange.GetFirstBit() + 1;
682 emit OnBitrangeModified(idx.row());
683 }
684 if(section < FirstValueColumn || section >= FirstValueColumn + m_value.size())
685 {
686 qDebug() << "ignore setData to column " << section;
687 return false;
688 }
689 section -= FirstValueColumn;
690 const SocFieldCachedValue& v = value.value< SocFieldCachedValue >();
691 if(!m_value[section].isValid())
692 return false;
693 soc_word_t old_val = m_value[section].value< soc_word_t >();
694 m_value[section] = QVariant(v.field().replace(old_val, v.value()));
695 // update column
696 RecomputeTheme();
697 emit dataChanged(index(0, section), index(rowCount() - 1, section));
698 emit OnValueModified(section);
699 return true;
700}
701
702Qt::ItemFlags RegFieldTableModel::flags(const QModelIndex& index) const
703{
704 Qt::ItemFlags flags = Qt::ItemIsSelectable | Qt::ItemIsEnabled;
705 int section = index.column();
706 if(section < FirstValueColumn || section >= FirstValueColumn + m_value.size())
707 {
708 /* bitrange or name */
709 if(!m_read_only)
710 flags |= Qt::ItemIsEditable;
711 return flags;
712 }
713 section -= FirstValueColumn;
714 if(m_value[section].isValid() && !m_read_only)
715 flags |= Qt::ItemIsEditable;
716 return flags;
717}
718
719QVariant RegFieldTableModel::headerData(int section, Qt::Orientation orientation,
720 int role) const
721{
722 if(orientation == Qt::Vertical)
723 return QVariant();
724 if(role != Qt::DisplayRole)
725 return QVariant();
726 if(section == BitRangeColumn)
727 return QVariant("Bits");
728 if(section == NameColumn)
729 return QVariant("Name");
730 if(section < FirstValueColumn + m_value.size())
731 {
732 int idx = section - FirstValueColumn;
733 if(m_value.size() == 1)
734 return QVariant("Value");
735 else
736 return QVariant(QString("Value %1").arg((QChar)('A' + idx)));
737 }
738 section -= m_value.size();
739 if(section == DescColumnOffset)
740 return QVariant("Description");
741 return QVariant();
742}
743
744void RegFieldTableModel::SetReadOnly(bool en)
745{
746 if(en == m_read_only)
747 return;
748 m_read_only = en;
749}
750
751void RegFieldTableModel::SetRegister(const soc_desc::register_t& reg)
752{
753 /* remove all rows */
754 beginResetModel();
755 m_reg.field.clear();
756 endResetModel();
757 /* add them all */
758 beginInsertRows(QModelIndex(), 0, reg.field.size() - 1);
759 m_reg = reg;
760 RecomputeTheme();
761 endInsertRows();
762}
763
764void RegFieldTableModel::UpdateRegister(const soc_desc::register_t& reg)
765{
766 m_reg = reg;
767 RecomputeTheme();
768 emit dataChanged(index(0, 0), index(rowCount() - 1, columnCount() - 1));
769}
770
771soc_desc::register_t RegFieldTableModel::GetRegister() const
772{
773 return m_reg;
774}
775
776void RegFieldTableModel::SetValues(const QVector< QVariant >& values)
777{
778 /* remove all value columns */
779 beginRemoveColumns(QModelIndex(), FirstValueColumn,
780 FirstValueColumn + m_value.size() - 1);
781 m_value.clear();
782 endRemoveColumns();
783 /* add them back */
784 beginInsertColumns(QModelIndex(), FirstValueColumn,
785 FirstValueColumn + values.size() - 1);
786 m_value = values;
787 RecomputeTheme();
788 endInsertColumns();
789}
790
791QVariant RegFieldTableModel::GetValue(int index)
792{
793 return m_value[index];
794}
795
796void RegFieldTableModel::SetTheme(const RegTheme& theme)
797{
798 m_theme = theme;
799 RecomputeTheme();
800 emit dataChanged(index(0, 0), index(rowCount() - 1, columnCount() - 1));
801}
802
803void RegFieldTableModel::RecomputeTheme()
804{
805 m_status.resize(m_reg.field.size());
806 for(size_t i = 0; i < m_reg.field.size(); i++)
807 {
808 m_status[i] = None;
809 if(!m_theme.valid || m_value.size() == 0)
810 continue;
811 m_status[i] = Normal;
812 const soc_desc::field_t& field = m_reg.field[i];
813 QVariant val;
814 for(int j = 0; j < m_value.size(); j++)
815 {
816 QVariant val2 = m_value[j];
817 if(!val2.isValid())
818 continue;
819 val2 = QVariant(field.extract(val2.value< soc_word_t >()));
820 if(!val.isValid())
821 val = val2;
822 else if(val != val2)
823 m_status[i] = Diff;
824 }
825 }
826}
827
828/**
829 * RegFieldProxyModel
830 */
831
832bool RegFieldProxyModel::lessThan(const QModelIndex& left,
833 const QModelIndex& right) const
834{
835 QVariant ldata = sourceModel()->data(left);
836 QVariant rdata = sourceModel()->data(right);
837 if(isUserType< SocFieldBitRange >(ldata) &&
838 isUserType< SocFieldBitRange >(rdata))
839 {
840 return ldata.value< SocFieldBitRange >() <
841 rdata.value< SocFieldBitRange >();
842 }
843 else if(isUserType< SocFieldCachedValue >(ldata) &&
844 isUserType< SocFieldCachedValue >(rdata))
845 {
846 return ldata.value< SocFieldCachedValue >() <
847 rdata.value< SocFieldCachedValue >();
848 }
849 else
850 return QSortFilterProxyModel::lessThan(left, right);
851}
852
853/**
854 * YRegDisplayItemEditor
855 */
856YRegDisplayItemEditor::YRegDisplayItemEditor(QWidget *parent, YRegDisplay *display,
857 YRegDisplayItemDelegate *delegate, QModelIndex bitrange_index,
858 QModelIndex name_index)
859 :QWidget(parent), m_display_delegate(delegate),
860 m_display(display), m_state(Idle)
861{
862 m_col_width = m_display->BitrangeRect(SocFieldBitRange(0, 0)).width();
863 m_resize_margin = m_col_width / 4;
864 setEditorData(bitrange_index, name_index);
865 setMouseTracking(true);
866 setAutoFillBackground(true);
867 setFocusPolicy(Qt::StrongFocus); // QItemDelegate says it's important
868}
869
870void YRegDisplayItemEditor::setEditorData(QModelIndex bitrange_index, QModelIndex name_index)
871{
872 if(m_state != Idle)
873 {
874 m_state = Idle;
875 QApplication::restoreOverrideCursor();
876 }
877 m_bitrange_index = bitrange_index;
878 m_name_index = name_index;
879 m_bitrange = bitrange_index.data().value< SocFieldBitRange >();
880}
881
882void YRegDisplayItemEditor::getEditorData(QVariant& name, QVariant& bitrange)
883{
884 name = QVariant(); /* don't touch the name */
885 bitrange = QVariant::fromValue(m_bitrange);
886}
887
888YRegDisplayItemEditor::~YRegDisplayItemEditor()
889{
890 /* make sure to restore cursor if modified */
891 if(m_state != Idle)
892 {
893 m_state = Idle;
894 QApplication::restoreOverrideCursor();
895 }
896}
897
898YRegDisplayItemEditor::Zone YRegDisplayItemEditor::GetZone(const QPoint& pt)
899{
900 if(!rect().contains(pt))
901 return NoZone;
902 if(pt.x() >= 0 && pt.x() <= m_resize_margin)
903 return ResizeLeftZone;
904 if(pt.x() >= width() - m_resize_margin && pt.x() <= width())
905 return ResizeRightZone;
906 return MoveZone;
907}
908
909void YRegDisplayItemEditor::mouseMoveEvent(QMouseEvent *event)
910{
911 Zone zone = GetZone(event->pos());
912 bool in_resize_zone = (zone == ResizeLeftZone || zone == ResizeRightZone);
913 /* resizing/moving has priority */
914 if(m_state == ResizingLeft || m_state == ResizingRight || m_state == Moving)
915 {
916 SocFieldBitRange new_bitrange = m_bitrange;
917 if(m_state == Moving)
918 {
919 /* Compute new bitrange: we know the offset of the mouse relative to the
920 * left of the register: use that offset to compute the new position of
921 * the MSB bit. To make it more natural, add half of a column of margin
922 * so that the register does not move until half of a bit column displacement
923 * was made */
924 int bit = m_display->bitColumnAt(mapTo(m_display,
925 event->pos() - QPoint(m_move_offset - m_col_width / 2, 0)));
926 new_bitrange.SetLastBit(bit);
927 int w = m_bitrange.GetLastBit() - m_bitrange.GetFirstBit();
928 /* make sure range is valid */
929 if(bit - w < 0)
930 return;
931 new_bitrange.SetFirstBit(bit - w);
932 }
933 else
934 {
935 /* Compute new bitrange. To make it more natural, add quarter of a column of margin
936 * so that the register does not resize until quarter of a bit column displacement
937 * was made */
938 int bit = m_display->bitColumnAt(mapTo(m_display, event->pos()
939 + QPoint(m_col_width / 4, 0)));
940 if(m_state == ResizingLeft)
941 new_bitrange.SetLastBit(bit);
942 else
943 new_bitrange.SetFirstBit(bit);
944 /* make sure range is valid */
945 if(new_bitrange.GetLastBit() < new_bitrange.GetFirstBit())
946 return;
947 }
948 /* make sure range does not overlap with other fields */
949 /* TODO */
950 /* update current bitrange (display only) and resize widget */
951 if(m_bitrange != new_bitrange)
952 {
953 m_bitrange = new_bitrange;
954 /* resize widget */
955 QRect rect = m_display->BitrangeRect(m_bitrange);
956 rect.moveTopLeft(parentWidget()->mapFromGlobal(m_display->mapToGlobal(rect.topLeft())));
957 setGeometry(rect);
958 }
959 }
960 /* any zone -> resize zone */
961 else if(in_resize_zone)
962 {
963 /* don't do unnecessary changes */
964 if(m_state != InResizeZone)
965 {
966 /* restore old cursor if needed */
967 if(m_state != Idle)
968 QApplication::restoreOverrideCursor();
969 m_state = InResizeZone;
970 QApplication::setOverrideCursor(QCursor(Qt::SizeHorCursor));
971 }
972 }
973 /* any zone -> move zone */
974 else if(zone == MoveZone)
975 {
976 /* don't do unnecessary changes */
977 if(m_state != InMoveZone)
978 {
979 /* restore old cursor if needed */
980 if(m_state != Idle)
981 QApplication::restoreOverrideCursor();
982 m_state = InMoveZone;
983 QApplication::setOverrideCursor(QCursor(Qt::SizeAllCursor));
984 }
985 }
986 /* any zone -> no zone */
987 else if(zone == NoZone)
988 {
989 if(m_state != Idle)
990 {
991 m_state = Idle;
992 QApplication::restoreOverrideCursor();
993 }
994 }
995}
996
997void YRegDisplayItemEditor::leaveEvent(QEvent *event)
998{
999 Q_UNUSED(event);
1000 if(m_state == InResizeZone)
1001 {
1002 m_state = Idle;
1003 QApplication::restoreOverrideCursor();
1004 }
1005}
1006
1007void YRegDisplayItemEditor::mousePressEvent(QMouseEvent *event)
1008{
1009 /* just in case the mouseMove event was not done */
1010 mouseMoveEvent(event);
1011 /* we need to track mouse outside of widget but Qt already grabs the mouse
1012 * for us on mouse press in widget */
1013 if(m_state == InResizeZone)
1014 {
1015 if(GetZone(event->pos()) == ResizeLeftZone)
1016 m_state = ResizingLeft;
1017 else
1018 m_state = ResizingRight;
1019 }
1020 else if(m_state == InMoveZone)
1021 {
1022 m_state = Moving;
1023 /* store offset from the left, to keep relative position of the register
1024 * with respect to the mouse */
1025 m_move_offset = event->pos().x();
1026 }
1027}
1028
1029void YRegDisplayItemEditor::mouseReleaseEvent(QMouseEvent *event)
1030{
1031 if(m_state == ResizingLeft || m_state == ResizingRight || m_state == Moving)
1032 {
1033 QApplication::restoreOverrideCursor();
1034 m_state = Idle;
1035 /* update cursor */
1036 mouseMoveEvent(event);
1037 }
1038}
1039
1040void YRegDisplayItemEditor::paintEvent(QPaintEvent *event)
1041{
1042 Q_UNUSED(event);
1043 QPainter painter(this);
1044 /* reuse delegate code to paint */
1045 QStyleOptionViewItem opt = m_display->viewOptions();
1046 opt.state |= QStyle::State_HasFocus | QStyle::State_Selected | QStyle::State_Active;
1047 opt.displayAlignment = Qt::AlignHCenter | Qt::AlignVCenter;
1048 opt.rect = rect();
1049 opt.showDecorationSelected = true;
1050 m_display_delegate->initStyleOption(&opt, m_name_index);
1051 m_display_delegate->MyPaint(&painter, opt);
1052}
1053
1054/**
1055 * YRegDisplayItemDelegate
1056 */
1057
1058YRegDisplayItemDelegate::YRegDisplayItemDelegate(QObject *parent)
1059 :QStyledItemDelegate(parent)
1060{
1061}
1062
1063void YRegDisplayItemDelegate::MyPaint(QPainter *painter, const QStyleOptionViewItem& option) const
1064{
1065 QStyleOptionViewItem opt = option;
1066 painter->save();
1067 // draw everything rotated, requires careful manipulation of the
1068 // rects involved
1069 painter->translate(opt.rect.bottomLeft());
1070 painter->rotate(-90);
1071 opt.rect = QRect(0, 0, opt.rect.height(), opt.rect.width());
1072 QStyle *style = opt.widget ? opt.widget->style() : QApplication::style();
1073 style->drawControl(QStyle::CE_ItemViewItem, &opt, painter, opt.widget);
1074 painter->restore();
1075}
1076
1077void YRegDisplayItemDelegate::paint(QPainter *painter,
1078 const QStyleOptionViewItem& option, const QModelIndex& index) const
1079{
1080 QStyleOptionViewItem opt = option;
1081 // default alignment is centered unless specified
1082 opt.displayAlignment = Qt::AlignHCenter | Qt::AlignVCenter;
1083 initStyleOption(&opt, index);
1084
1085 MyPaint(painter, opt);
1086
1087}
1088
1089QSize YRegDisplayItemDelegate::sizeHint(const QStyleOptionViewItem& option,
1090 const QModelIndex& index) const
1091{
1092 /* useless in our case, the view ignores this */
1093 Q_UNUSED(option);
1094 Q_UNUSED(index);
1095 return QSize();
1096}
1097
1098QWidget *YRegDisplayItemDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem& option,
1099 const QModelIndex& index) const
1100{
1101 Q_UNUSED(option);
1102 Q_UNUSED(index);
1103 YRegDisplay *display = dynamic_cast< YRegDisplay* >(parent->parent());
1104 Q_ASSERT(display != nullptr);
1105 /* column 0 is name, column 1 is range */
1106 return new YRegDisplayItemEditor(parent, display, const_cast< YRegDisplayItemDelegate* >(this),
1107 index.sibling(index.row(), 0), index.sibling(index.row(), 1));
1108}
1109
1110void YRegDisplayItemDelegate::setEditorData(QWidget *editor, const QModelIndex& index) const
1111{
1112 dynamic_cast< YRegDisplayItemEditor* >(editor)->setEditorData(
1113 index.sibling(index.row(), 0), index.sibling(index.row(), 1));
1114}
1115
1116void YRegDisplayItemDelegate::setModelData(QWidget *editor, QAbstractItemModel *model,
1117 const QModelIndex& index) const
1118{
1119 QVariant name, bitrange;
1120 dynamic_cast< YRegDisplayItemEditor* >(editor)->getEditorData(name, bitrange);
1121 if(name.isValid())
1122 model->setData(index.sibling(index.row(), 1), name);
1123 if(bitrange.isValid())
1124 model->setData(index.sibling(index.row(), 0), bitrange);
1125}
1126
1127/**
1128 * YRegDisplay
1129 */
1130
1131YRegDisplay::YRegDisplay(QWidget *parent)
1132 :QAbstractItemView(parent)
1133{
1134 m_is_dirty = true;
1135 m_range_col = 0;
1136 m_data_col = 1;
1137 m_nr_bits = 32;
1138 // the frame around the register is ugly, disable it
1139 setFrameShape(QFrame::NoFrame);
1140 setSelectionMode(SingleSelection);
1141 setEditTriggers(QAbstractItemView::DoubleClicked | QAbstractItemView::SelectedClicked);
1142 setItemDelegate(new YRegDisplayItemDelegate(this));
1143}
1144
1145void YRegDisplay::setWidth(int nr_bits)
1146{
1147 m_nr_bits = nr_bits;
1148 m_is_dirty = true;
1149 recomputeGeometry();
1150 updateGeometries();
1151}
1152
1153int YRegDisplay::bitColumnAt(const QPoint& point, bool closest) const
1154{
1155 int wx = point.x() + horizontalScrollBar()->value();
1156 for(int bit = 0; bit < m_nr_bits; bit++)
1157 {
1158 int off = columnOffset(bitToColumn(bit));
1159 int w = columnWidth(bitToColumn(bit));
1160 if(wx >= off && wx < off + w)
1161 return bit;
1162 if(wx >= off + w && closest)
1163 return bit;
1164 }
1165 return closest ? m_nr_bits - 1 : -1;
1166}
1167
1168QModelIndex YRegDisplay::indexAt(const QPoint& point) const
1169{
1170 if(!model())
1171 return QModelIndex();
1172 int wx = point.x() + horizontalScrollBar()->value();
1173 int wy = point.y() + verticalScrollBar()->value();
1174
1175 for(int i = 0; i < model()->rowCount(); i++)
1176 {
1177 QModelIndex index = model()->index(i, m_data_col, rootIndex());
1178 QRect r = itemRect(index);
1179 if(!r.isValid())
1180 continue;
1181 if(r.contains(wx, wy))
1182 return index;
1183 }
1184 return QModelIndex();
1185}
1186
1187void YRegDisplay::scrollTo(const QModelIndex& index, ScrollHint hint)
1188{
1189 Q_UNUSED(index);
1190 Q_UNUSED(hint);
1191}
1192
1193QRect YRegDisplay::visualRect(const QModelIndex &index) const
1194{
1195 QRect rect = itemRect(index);
1196 if(rect.isValid())
1197 return QRect(rect.left() - horizontalScrollBar()->value(),
1198 rect.top() - verticalScrollBar()->value(),
1199 rect.width(), rect.height());
1200 else
1201 return rect;
1202}
1203
1204bool YRegDisplay::isIndexHidden(const QModelIndex& index) const
1205{
1206 Q_UNUSED(index);
1207 return false;
1208}
1209
1210QModelIndex YRegDisplay::moveCursor(CursorAction cursorAction, Qt::KeyboardModifiers modifiers)
1211{
1212 Q_UNUSED(cursorAction);
1213 Q_UNUSED(modifiers);
1214 return QModelIndex();
1215}
1216
1217void YRegDisplay::setSelection(const QRect& r, QItemSelectionModel::SelectionFlags flags)
1218{
1219 if(!model())
1220 return;
1221 QRect rect = r.translated(horizontalScrollBar()->value(),
1222 verticalScrollBar()->value()).normalized();
1223
1224 QItemSelection sel;
1225 for(int i = 0; i < model()->rowCount(); i++)
1226 {
1227 QModelIndex index = model()->index(i, m_data_col, rootIndex());
1228 QRect r = itemRect(index);
1229 if(!r.isValid())
1230 continue;
1231 if(r.intersects(rect))
1232 sel.select(index, index);
1233 }
1234 selectionModel()->select(sel, flags);
1235}
1236
1237int YRegDisplay::verticalOffset() const
1238{
1239 return verticalScrollBar()->value();
1240}
1241
1242int YRegDisplay::horizontalOffset() const
1243{
1244 return horizontalScrollBar()->value();
1245}
1246
1247void YRegDisplay::scrollContentsBy(int dx, int dy)
1248{
1249 viewport()->scroll(dx, dy);
1250}
1251
1252void YRegDisplay::setModel(QAbstractItemModel *model)
1253{
1254 QAbstractItemView::setModel(model);
1255 m_is_dirty = true;
1256}
1257
1258void YRegDisplay::dataChanged(const QModelIndex &topLeft,
1259 const QModelIndex &bottomRight)
1260{
1261 m_is_dirty = true;
1262 QAbstractItemView::dataChanged(topLeft, bottomRight);
1263}
1264
1265void YRegDisplay::rowsInserted(const QModelIndex &parent, int start, int end)
1266{
1267 m_is_dirty = true;
1268 QAbstractItemView::rowsInserted(parent, start, end);
1269}
1270
1271void YRegDisplay::rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end)
1272{
1273 m_is_dirty = true;
1274 QAbstractItemView::rowsAboutToBeRemoved(parent, start, end);
1275}
1276
1277int YRegDisplay::separatorSize() const
1278{
1279 return 1;
1280}
1281
1282int YRegDisplay::marginSize() const
1283{
1284 return viewOptions().fontMetrics.height() / 3;
1285}
1286
1287int YRegDisplay::headerTextSep() const
1288{
1289 return marginSize() / 2;
1290}
1291
1292int YRegDisplay::headerHeight() const
1293{
1294 return 2 * marginSize() + headerTextSep() + 2 * viewOptions().fontMetrics.height();
1295}
1296
1297int YRegDisplay::minColumnWidth() const
1298{
1299 return 2 * marginSize() + viewOptions().fontMetrics.height();
1300}
1301
1302int YRegDisplay::maxColumnWidth() const
1303{
1304 return 2 * minColumnWidth();
1305}
1306
1307int YRegDisplay::columnWidth(int col) const
1308{
1309 int avail = width() - (m_nr_bits + 1) * separatorSize();
1310 int small_w = qMin(avail / m_nr_bits, maxColumnWidth());
1311 int nr_big = avail - small_w * m_nr_bits;
1312 if(col < nr_big)
1313 return small_w + 1;
1314 else
1315 return small_w;
1316}
1317
1318int YRegDisplay::columnOffset(int col) const
1319{
1320 int off = separatorSize();
1321 for(int i = 0; i < col; i++)
1322 off += columnWidth(i) + separatorSize();
1323 int all_w = off;
1324 for(int i = col; i < m_nr_bits; i++)
1325 all_w += columnWidth(i) + separatorSize();
1326 return off + (width() - all_w) / 2;
1327}
1328
1329int YRegDisplay::maxContentHeight() const
1330{
1331 int max = 0;
1332 QFontMetrics metrics = viewOptions().fontMetrics;
1333 if(model())
1334 {
1335 for(int i = 0; i < model()->rowCount(); i++)
1336 {
1337 QModelIndex index = model()->index(i, m_data_col, rootIndex());
1338 QString s = model()->data(index).toString();
1339 max = qMax(max, metrics.boundingRect(s).width());
1340 }
1341 }
1342 return 2 * marginSize() + max;
1343}
1344
1345int YRegDisplay::gapHeight() const
1346{
1347 return marginSize() / 2;
1348}
1349
1350int YRegDisplay::bitToColumn(int bit) const
1351{
1352 return m_nr_bits - 1 - bit;
1353}
1354
1355QRegion YRegDisplay::visualRegionForSelection(const QItemSelection& selection) const
1356{
1357 QRegion region;
1358 foreach(const QItemSelectionRange &range, selection)
1359 {
1360 for(int row = range.top(); row <= range.bottom(); ++row)
1361 {
1362 for(int column = range.left(); column < range.right(); ++column)
1363 {
1364 QModelIndex index = model()->index(row, column, rootIndex());
1365 region += visualRect(index);
1366 }
1367 }
1368 }
1369 return region;
1370}
1371
1372QRect YRegDisplay::itemRect(const QModelIndex& index) const
1373{
1374 if(!index.isValid())
1375 return QRect();
1376 QVariant vrange = model()->data(model()->index(index.row(), m_range_col, rootIndex()));
1377 if(!vrange.canConvert< SocFieldBitRange >())
1378 return QRect();
1379 SocFieldBitRange range = vrange.value< SocFieldBitRange >();
1380 return itemRect(range, index.column());
1381}
1382
1383QRect YRegDisplay::BitrangeRect(const SocFieldBitRange& range) const
1384{
1385 return itemRect(range, m_data_col);
1386}
1387
1388QRect YRegDisplay::itemRect(const SocFieldBitRange& range, int col) const
1389{
1390 int top, bot;
1391 if(col == m_range_col)
1392 {
1393 top = separatorSize();
1394 bot = separatorSize() + headerHeight() - 1;
1395 }
1396 else if(col == m_data_col)
1397 {
1398 top = headerHeight() + 3 * separatorSize() + gapHeight();
1399 bot = height() - separatorSize() - 1;
1400 }
1401 else
1402 return QRect();
1403 int first_col = bitToColumn(range.GetFirstBit());
1404 return QRect(QPoint(columnOffset(bitToColumn(range.GetLastBit())), top),
1405 QPoint(columnOffset(first_col) + columnWidth(first_col) - 1, bot));
1406}
1407
1408void YRegDisplay::paintEvent(QPaintEvent *event)
1409{
1410 Q_UNUSED(event);
1411 QPainter painter(viewport());
1412 painter.setRenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing);
1413 painter.translate(-horizontalScrollBar()->value(), -verticalScrollBar()->value());
1414 QStyleOptionViewItem option = viewOptions();
1415 int txt_h = option.fontMetrics.height();
1416 int grid_hint = style()->styleHint(QStyle::SH_Table_GridLineColor, &option, this);
1417 QBrush grid_brush(static_cast< QRgb >(grid_hint));
1418 QBrush back_brush = option.palette.base();
1419 int sep_sz = separatorSize();
1420 QItemSelectionModel *selections = selectionModel();
1421
1422 // paint header
1423 for(int bit = 0; bit < m_nr_bits; bit++)
1424 {
1425 QRect r = itemRect(SocFieldBitRange(bit, bit), m_range_col);
1426 // paint background
1427 painter.fillRect(r, back_brush);
1428 // paint top digit
1429 r.setTop(r.top() + marginSize());
1430 r.setBottom(r.top() + txt_h);
1431 style()->drawItemText(&painter, r, Qt::AlignHCenter, option.palette,
1432 true, QString("%1").arg(bit / 10), foregroundRole());
1433 // paint bottom digit
1434 r.setTop(r.bottom() + headerTextSep());
1435 r.setBottom(r.top() + txt_h);
1436 style()->drawItemText(&painter, r, Qt::AlignHCenter, option.palette,
1437 true, QString("%1").arg(bit % 10), foregroundRole());
1438 }
1439 // paint header grid
1440 for(int bit = 1; bit < m_nr_bits; bit++)
1441 {
1442 QRect r = itemRect(SocFieldBitRange(bit, bit), m_range_col);
1443 r.setCoords(r.right() + 1, r.top(), r.right() + sep_sz, r.bottom());
1444 if((bit % 4) == 0)
1445 r.setCoords(r.left() - sep_sz, r.top(), r.right() + sep_sz, r.bottom());
1446 painter.fillRect(r, grid_brush);
1447 }
1448 QRect hdr_r = itemRect(SocFieldBitRange(0, m_nr_bits - 1), m_range_col);
1449 painter.fillRect(QRect(hdr_r.left(), hdr_r.top() - sep_sz, hdr_r.width(), sep_sz), grid_brush);
1450 painter.fillRect(QRect(hdr_r.left(), hdr_r.bottom() + 1, hdr_r.width(), sep_sz), grid_brush);
1451 // paint header gap
1452 QRect gap_r(hdr_r.left(), hdr_r.bottom() + sep_sz + 1, hdr_r.width(), gapHeight());
1453 painter.fillRect(gap_r, back_brush);
1454 // paint header bottom line
1455 painter.fillRect(QRect(gap_r.left(), gap_r.bottom() + 1, gap_r.width(), sep_sz), grid_brush);
1456 // paint background
1457 QRect data_r = itemRect(SocFieldBitRange(0, m_nr_bits - 1), m_data_col);
1458 //painter.fillRect(data_r, back_brush);
1459 // paint data bottom line
1460 painter.fillRect(QRect(data_r.left(), data_r.bottom() + 1, data_r.width(), sep_sz), grid_brush);
1461 // paint left/right lines
1462 painter.fillRect(QRect(hdr_r.left() - sep_sz, hdr_r.top() - sep_sz, sep_sz, height()), grid_brush);
1463 painter.fillRect(QRect(hdr_r.right() + 1, hdr_r.top() - sep_sz, sep_sz, height()), grid_brush);
1464
1465 // paint model
1466 if(!model())
1467 return;
1468
1469 for(int i = 0; i < model()->rowCount(); i++)
1470 {
1471 QModelIndex index = model()->index(i, m_data_col, rootIndex());
1472 QRect r = itemRect(index);
1473 if(!r.isValid())
1474 continue;
1475 QString name = index.data().toString();
1476 // paint background
1477 QStyleOptionViewItem opt = viewOptions();
1478 opt.rect = r;
1479 //opt.showDecorationSelected = true;
1480 style()->drawPrimitive(QStyle::PE_PanelItemViewRow, &opt, &painter, this);
1481 if(selections->isSelected(index))
1482 opt.state |= QStyle::State_Selected;
1483 if(currentIndex() == index)
1484 opt.state |= QStyle::State_HasFocus;
1485 if(m_hover == index)
1486 opt.state |= QStyle::State_MouseOver;
1487 itemDelegate(index)->paint(&painter, opt, index);
1488 // paint left/right lines
1489 painter.fillRect(QRect(r.left() - sep_sz, r.top(), sep_sz, r.height()), grid_brush);
1490 painter.fillRect(QRect(r.right() + 1, r.top(), sep_sz, r.height()), grid_brush);
1491 }
1492}
1493
1494void YRegDisplay::recomputeGeometry()
1495{
1496 if(!m_is_dirty)
1497 return;
1498 /* height: header + gap + sep + content + sep */
1499 m_minimum_height = 0;
1500 m_minimum_height += headerHeight() + gapHeight();
1501 m_minimum_height += 2 * separatorSize() + maxContentHeight();
1502 /* width: sep + (col + sep) * n */
1503 m_minimum_width = separatorSize() * (m_nr_bits + 1) + minColumnWidth() * m_nr_bits;
1504 m_is_dirty = false;
1505 viewport()->update();
1506}
1507
1508void YRegDisplay::resizeEvent(QResizeEvent*)
1509{
1510 m_is_dirty = true;
1511 recomputeGeometry();
1512 updateGeometries();
1513}
1514
1515void YRegDisplay::updateGeometries()
1516{
1517 horizontalScrollBar()->setSingleStep(1);
1518 horizontalScrollBar()->setPageStep(viewport()->width());
1519 horizontalScrollBar()->setRange(0, qMax(0, m_minimum_width - viewport()->width()));
1520 verticalScrollBar()->setSingleStep(1);
1521 verticalScrollBar()->setPageStep(viewport()->height());
1522 verticalScrollBar()->setRange(0, qMax(0, m_minimum_height - viewport()->height()));
1523}
1524
1525bool YRegDisplay::viewportEvent(QEvent *event)
1526{
1527 /* FIXME Apparently QAbstractItemView tracks the hovered index but keeps it
1528 * in its private part which is not accessible, which makes it useless...
1529 * This code reimplements it */
1530 switch (event->type())
1531 {
1532 case QEvent::HoverEnter:
1533 m_hover = indexAt(static_cast<QHoverEvent*>(event)->pos());
1534 update(m_hover);
1535 break;
1536 case QEvent::HoverLeave:
1537 update(m_hover); // update old
1538 m_hover = QModelIndex();
1539 break;
1540 case QEvent::HoverMove:
1541 {
1542 QModelIndex old = m_hover;
1543 m_hover = indexAt(static_cast<QHoverEvent*>(event)->pos());
1544 if(m_hover != old)
1545 viewport()->update(visualRect(old)|visualRect(m_hover));
1546 break;
1547 }
1548 default:
1549 break;
1550 }
1551 return QAbstractItemView::viewportEvent(event);
1552}
1553
1554/**
1555 * GrowingTableView
1556 */
1557GrowingTableView::GrowingTableView(QWidget *parent)
1558 :QTableView(parent)
1559{
1560}
1561
1562void GrowingTableView::setModel(QAbstractItemModel *m)
1563{
1564 if(model())
1565 disconnect(model(), SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&)),
1566 this, SLOT(DataChanged(const QModelIndex&, const QModelIndex&)));
1567 QTableView::setModel(m);
1568 connect(model(), SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&)),
1569 this, SLOT(DataChanged(const QModelIndex&, const QModelIndex&)));
1570 DataChanged(QModelIndex(), QModelIndex());
1571}
1572
1573void GrowingTableView::DataChanged(const QModelIndex& tl, const QModelIndex& br)
1574{
1575 Q_UNUSED(tl);
1576 Q_UNUSED(br);
1577 resizeColumnsToContents();
1578 int h = contentsMargins().top() + contentsMargins().bottom();
1579 h += horizontalHeader()->height();
1580 for(int i = 0; i < model()->rowCount(); i++)
1581 h += rowHeight(i);
1582 setMinimumHeight(h);
1583}
1584
1585/**
1586 * MyTextEditor
1587 */
1588MyTextEditor::MyTextEditor(QWidget *parent)
1589 :QWidget(parent)
1590{
1591 QVBoxLayout *layout = new QVBoxLayout;
1592 m_toolbar = new QToolBar(this);
1593 m_edit = new QTextEdit(this);
1594#if QT_VERSION >= QT_VERSION_CHECK(5, 2, 0)
1595 /* Qt 5.2 have a hardcoded sizeHint for QAbstractScrollArea which makes it
1596 * hard to have a good behaviour for the text editor. Fortunately 5.2 introduces
1597 * a new option to adjust this to the content. */
1598 m_edit->setSizeAdjustPolicy(QAbstractScrollArea::AdjustToContents);
1599#endif
1600 layout->addWidget(m_toolbar, 0);
1601 layout->addWidget(m_edit, 1);
1602 setLayout(layout);
1603 setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
1604
1605 m_edit->setAcceptRichText(false);
1606 m_edit->setAutoFormatting(QTextEdit::AutoAll);
1607
1608 m_bold_button = new QToolButton(this);
1609 m_bold_button->setIcon(YIconManager::Get()->GetIcon(YIconManager::FormatTextBold));
1610 m_bold_button->setText("bold");
1611 m_bold_button->setCheckable(true);
1612
1613 m_italic_button = new QToolButton(this);
1614 m_italic_button->setIcon(YIconManager::Get()->GetIcon(YIconManager::FormatTextItalic));
1615 m_italic_button->setText("italic");
1616 m_italic_button->setCheckable(true);
1617
1618 m_underline_button = new QToolButton(this);
1619 m_underline_button->setIcon(YIconManager::Get()->GetIcon(YIconManager::FormatTextUnderline));
1620 m_underline_button->setText("underline");
1621 m_underline_button->setCheckable(true);
1622
1623 m_toolbar->addWidget(m_bold_button);
1624 m_toolbar->addWidget(m_italic_button);
1625 m_toolbar->addWidget(m_underline_button);
1626
1627 connect(m_bold_button, SIGNAL(toggled(bool)), this, SLOT(OnTextBold(bool)));
1628 connect(m_italic_button, SIGNAL(toggled(bool)), this, SLOT(OnTextItalic(bool)));
1629 connect(m_underline_button, SIGNAL(toggled(bool)), this, SLOT(OnTextUnderline(bool)));
1630 connect(m_edit, SIGNAL(textChanged()), this, SLOT(OnInternalTextChanged()));
1631 connect(m_edit, SIGNAL(currentCharFormatChanged(const QTextCharFormat&)),
1632 this, SLOT(OnCharFormatChanged(const QTextCharFormat&)));
1633
1634 m_edit->installEventFilter(this);
1635
1636 SetGrowingMode(false);
1637 SetReadOnly(false);
1638 m_toolbar->hide();
1639}
1640
1641void MyTextEditor::SetReadOnly(bool en)
1642{
1643 m_read_only = en;
1644 if(en)
1645 m_toolbar->hide();
1646 else
1647 m_toolbar->show();
1648 m_edit->setReadOnly(en);
1649}
1650
1651void MyTextEditor::SetGrowingMode(bool en)
1652{
1653 m_growing_mode = en;
1654 if(en)
1655 {
1656 m_edit->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum);
1657 m_edit->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
1658 OnInternalTextChanged();
1659 }
1660 else
1661 {
1662 m_edit->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
1663 m_edit->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
1664 }
1665}
1666
1667void MyTextEditor::OnInternalTextChanged()
1668{
1669 if(m_growing_mode)
1670 {
1671 int content_size = m_edit->document()->documentLayout()->documentSize().height();
1672 content_size = qMax(content_size, m_edit->fontMetrics().height());
1673 m_edit->setMinimumHeight(content_size + m_edit->contentsMargins().top() +
1674 m_edit->contentsMargins().bottom());
1675 }
1676 emit OnTextChanged();
1677 emit OnTextChanged(GetTextHtml());
1678}
1679
1680void MyTextEditor::OnTextBold(bool checked)
1681{
1682 QTextCursor cursor = m_edit->textCursor();
1683 QTextCharFormat fmt = cursor.charFormat();
1684 fmt.setFontWeight(checked ? QFont::Bold : QFont::Normal);
1685 cursor.setCharFormat(fmt);
1686 m_edit->setTextCursor(cursor);
1687}
1688
1689void MyTextEditor::OnTextItalic(bool checked)
1690{
1691 QTextCursor cursor = m_edit->textCursor();
1692 QTextCharFormat fmt = cursor.charFormat();
1693 fmt.setFontItalic(checked);
1694 cursor.setCharFormat(fmt);
1695 m_edit->setTextCursor(cursor);
1696}
1697
1698void MyTextEditor::OnTextUnderline(bool checked)
1699{
1700 QTextCursor cursor = m_edit->textCursor();
1701 QTextCharFormat fmt = cursor.charFormat();
1702 fmt.setFontUnderline(checked);
1703 cursor.setCharFormat(fmt);
1704 m_edit->setTextCursor(cursor);
1705}
1706
1707void MyTextEditor::OnCharFormatChanged(const QTextCharFormat& fmt)
1708{
1709 /* NOTE: changing the button states programmaticaly doesn't trigger
1710 * the toggled() signals, otherwise it would result in a loop
1711 * between this function and OnText{Bold,Italic,Underline,...} */
1712 m_bold_button->setChecked(fmt.fontWeight() > QFont::Normal);
1713 m_italic_button->setChecked(fmt.fontItalic());
1714 m_underline_button->setChecked(fmt.fontUnderline());
1715}
1716
1717void MyTextEditor::SetTextHtml(const QString& text)
1718{
1719 m_edit->setHtml(text);
1720}
1721
1722QString MyTextEditor::GetTextHtml()
1723{
1724 return m_edit->toPlainText();
1725}
1726
1727bool MyTextEditor::IsModified()
1728{
1729 return m_edit->document()->isModified();
1730}
1731
1732bool MyTextEditor::eventFilter(QObject *object, QEvent *event)
1733{
1734 if(object != m_edit)
1735 return false;
1736 if(m_read_only)
1737 return false;
1738 if(event->type() == QEvent::FocusIn)
1739 m_toolbar->show();
1740 else if(event->type() == QEvent::FocusOut)
1741 m_toolbar->hide();
1742 return false;
1743}
1744
1745/**
1746 * BackendSelector
1747 */
1748BackendSelector::BackendSelector(Backend *backend, QWidget *parent)
1749 :QWidget(parent), m_backend(backend)
1750{
1751 m_data_selector = new QComboBox(this);
1752 m_data_selector->addItem(YIconManager::Get()->GetIcon(YIconManager::TextGeneric),
1753 "Nothing...", QVariant(DataSelNothing));
1754 m_data_selector->addItem(YIconManager::Get()->GetIcon(YIconManager::DocumentOpen),
1755 "File...", QVariant(DataSelFile));
1756#ifdef HAVE_HWSTUB
1757 m_data_selector->addItem(YIconManager::Get()->GetIcon(YIconManager::MultimediaPlayer),
1758 "USB Device...", QVariant(DataSelDevice));
1759#endif
1760 m_data_sel_edit = new QLineEdit(this);
1761 m_data_sel_edit->setReadOnly(true);
1762 m_nothing_text = new QLabel(this);
1763 m_nothing_text->setTextFormat(Qt::RichText);
1764 QHBoxLayout *data_sel_layout = new QHBoxLayout(this);
1765 data_sel_layout->addWidget(m_data_selector);
1766 data_sel_layout->addWidget(m_data_sel_edit, 1);
1767 data_sel_layout->addWidget(m_nothing_text, 1);
1768 data_sel_layout->addStretch(0);
1769#ifdef HAVE_HWSTUB
1770 m_dev_selector = new QComboBox;
1771 m_ctx_model = new HWStubContextModel;
1772 m_ctx_model->EnableDummy(true, "Please select a device...");
1773 m_dev_selector->setModel(m_ctx_model); /* m_dev_selector will delete m_ctx_model */
1774 m_ctx_selector = new QComboBox;
1775 m_ctx_manager = HWStubManager::Get();
1776 m_ctx_selector->setModel(m_ctx_manager);
1777 m_ctx_manage_button = new QPushButton();
1778 m_ctx_manage_button->setIcon(YIconManager::Get()->GetIcon(YIconManager::Preferences));
1779 m_ctx_manage_button->setToolTip("Manage contexts");
1780 data_sel_layout->addWidget(m_dev_selector, 1);
1781 data_sel_layout->addWidget(m_ctx_selector);
1782 data_sel_layout->addWidget(m_ctx_manage_button);
1783#endif
1784
1785 m_io_backend = m_backend->CreateDummyIoBackend();
1786
1787 connect(m_data_selector, SIGNAL(activated(int)),
1788 this, SLOT(OnDataSelChanged(int)));
1789#ifdef HAVE_HWSTUB
1790 connect(m_ctx_selector, SIGNAL(currentIndexChanged(int)), this,
1791 SLOT(OnContextSelChanged(int)));
1792 connect(m_dev_selector, SIGNAL(currentIndexChanged(int)), this,
1793 SLOT(OnDeviceSelChanged(int)));
1794 connect(m_dev_selector, SIGNAL(activated(int)), this,
1795 SLOT(OnDeviceSelActivated(int)));
1796#endif
1797
1798#ifdef HAVE_HWSTUB
1799 OnContextSelChanged(0);
1800#endif
1801 OnDataSelChanged(0);
1802}
1803
1804BackendSelector::~BackendSelector()
1805{
1806 /* avoid m_ctx_selector from deleting HWStubManager */
1807#ifdef HAVE_HWSTUB
1808 m_ctx_selector->setModel(new QStandardItemModel());
1809#endif
1810 delete m_io_backend;
1811}
1812
1813void BackendSelector::SetNothingMessage(const QString& msg)
1814{
1815 m_nothing_text->setText(msg);
1816}
1817
1818void BackendSelector::OnDataSelChanged(int index)
1819{
1820 if(index == -1)
1821 return;
1822 QVariant var = m_data_selector->itemData(index);
1823 if(var == DataSelFile)
1824 {
1825 m_nothing_text->hide();
1826 m_data_sel_edit->show();
1827#ifdef HAVE_HWSTUB
1828 m_dev_selector->hide();
1829 m_ctx_selector->hide();
1830 m_ctx_manage_button->hide();
1831#endif
1832 QFileDialog *fd = new QFileDialog(m_data_selector);
1833 QStringList filters;
1834 filters << "Textual files (*.txt)";
1835 filters << "All files (*)";
1836 fd->setNameFilters(filters);
1837 fd->setDirectory(Settings::Get()->value("regtab/loaddatadir", QDir::currentPath()).toString());
1838 if(fd->exec())
1839 {
1840 QStringList filenames = fd->selectedFiles();
1841 ChangeBackend(m_backend->CreateFileIoBackend(filenames[0]));
1842 m_data_sel_edit->setText(filenames[0]);
1843 }
1844 Settings::Get()->setValue("regtab/loaddatadir", fd->directory().absolutePath());
1845 }
1846#ifdef HAVE_HWSTUB
1847 else if(var == DataSelDevice)
1848 {
1849 m_nothing_text->hide();
1850 m_data_sel_edit->hide();
1851 m_dev_selector->show();
1852 m_ctx_selector->show();
1853 m_ctx_manage_button->show();
1854 /* explicitely change the backend now */
1855 OnDeviceSelActivated(m_dev_selector->currentIndex());
1856 }
1857#endif
1858 else
1859 {
1860 m_data_sel_edit->hide();
1861 m_nothing_text->show();
1862#ifdef HAVE_HWSTUB
1863 m_dev_selector->hide();
1864 m_ctx_selector->hide();
1865 m_ctx_manage_button->hide();
1866#endif
1867
1868 ChangeBackend(m_backend->CreateDummyIoBackend());
1869 }
1870}
1871
1872IoBackend *BackendSelector::GetBackend()
1873{
1874 return m_io_backend;
1875}
1876
1877void BackendSelector::ChangeBackend(IoBackend *new_backend)
1878{
1879 /* WARNING: delete old backend *after* calling the signal, otherwise the old backend
1880 * might get used after delete */
1881 emit OnSelect(new_backend);
1882 delete m_io_backend;
1883 m_io_backend = new_backend;
1884}
1885
1886#ifdef HAVE_HWSTUB
1887void BackendSelector::OnContextSelChanged(int index)
1888{
1889 m_ctx_model->SetContext(m_ctx_manager->GetContext(index));
1890 m_dev_selector->setCurrentIndex(0);
1891}
1892
1893void BackendSelector::OnDeviceSelChanged(int index)
1894{
1895 /* if current selection is -1, because device was removed or a new context
1896 * was selected, select entry 0, which is dummy. Not that this will not
1897 * call activate(), we don't want to change the current backend if the user
1898 * is using another type of backend. */
1899 if(index == -1)
1900 m_dev_selector->setCurrentIndex(0);
1901}
1902
1903void BackendSelector::OnDeviceSelActivated(int index)
1904{
1905 auto dev = new HWStubDevice(m_ctx_model->GetDevice(index));
1906 if(!dev->IsValid())
1907 {
1908 delete dev;
1909 ChangeBackend(m_backend->CreateDummyIoBackend());
1910 }
1911 else
1912 ChangeBackend(new HWStubIoBackend(dev));
1913}
1914#endif
1915
1916/**
1917 * YTabWidget
1918 */
1919YTabWidget::YTabWidget(QTabBar *bar, QWidget *parent)
1920 :QTabWidget(parent), m_other_button(0)
1921{
1922 if(bar != 0)
1923 setTabBar(bar);
1924 m_tab_open_button = new QToolButton(this);
1925 m_tab_open_button->setIcon(YIconManager::Get()->GetIcon(YIconManager::ListAdd));
1926 m_tab_open_button->setAutoRaise(true);
1927 m_tab_open_button->setPopupMode(QToolButton::InstantPopup);
1928 /* the arrow with an icon only is pretty ugly and QToolButton has no way
1929 * to remove the arrow programmaticaly, so use the CSS to do that */
1930 m_tab_open_button->setStyleSheet("QToolButton::menu-indicator { image: none; }");
1931 setCornerWidget(m_tab_open_button, Qt::TopLeftCorner);
1932 setTabOpenable(false);
1933 connect(m_tab_open_button, SIGNAL(clicked(bool)), this, SLOT(OnOpenButton(bool)));
1934 /* there is a quirk in the default QStyle: if the tab bar is empty, it
1935 * returns the minimum size of the corner widget, which is 0 for tool buttons */
1936 m_tab_open_button->setMinimumSize(m_tab_open_button->sizeHint());
1937 setMinimumSize(m_tab_open_button->sizeHint());
1938}
1939
1940void YTabWidget::setTabOpenable(bool openable)
1941{
1942 m_tab_openable = openable;
1943 m_tab_open_button->setVisible(openable);
1944}
1945
1946void YTabWidget::OnOpenButton(bool checked)
1947{
1948 Q_UNUSED(checked);
1949 emit tabOpenRequested();
1950}
1951
1952void YTabWidget::setTabOpenMenu(QMenu *menu)
1953{
1954 m_tab_open_button->setMenu(menu);
1955}
1956
1957void YTabWidget::setOtherMenu(QMenu *menu)
1958{
1959 if(menu == nullptr)
1960 {
1961 if(m_other_button)
1962 delete m_other_button;
1963 m_other_button = nullptr;
1964 }
1965 else
1966 {
1967 if(m_other_button == nullptr)
1968 {
1969 m_other_button = new QToolButton(this);
1970 m_other_button->setText("Menu");
1971 m_other_button->setAutoRaise(true);
1972 m_other_button->setPopupMode(QToolButton::InstantPopup);
1973 setCornerWidget(m_other_button, Qt::TopRightCorner);
1974 }
1975 m_other_button->setMenu(menu);
1976 }
1977}
1978
1979/**
1980 * MessageWidget
1981 */
1982MessageWidget::MessageWidget(QWidget *parent)
1983 :QFrame(parent)
1984{
1985 setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum);
1986
1987 m_icon = new QLabel(this);
1988 m_icon->hide();
1989 m_text = new QLabel(this);
1990 m_text->setTextFormat(Qt::RichText);
1991 m_text->setWordWrap(true);
1992 m_close = new QToolButton(this);
1993 m_close->setText("close");
1994 m_close->setIcon(style()->standardIcon(QStyle::QStyle::SP_DialogCloseButton));
1995 m_close->setAutoRaise(true);
1996
1997 QHBoxLayout *layout = new QHBoxLayout(this);
1998 layout->addWidget(m_icon, 0);
1999 layout->addWidget(m_text, 1);
2000 layout->addWidget(m_close, 0);
2001
2002 m_id = 0;
2003
2004 connect(m_close, SIGNAL(clicked(bool)), this, SLOT(OnClose(bool)));
2005
2006 hide();
2007}
2008
2009MessageWidget::~MessageWidget()
2010{
2011}
2012
2013void MessageWidget::UpdateType()
2014{
2015 /* style stolen from KMessageWidget */
2016 QColor bg, border;
2017 switch(m_type)
2018 {
2019 case Positive:
2020 bg.setRgb(140, 228, 124);
2021 border.setRgb(56, 175, 58);
2022 break;
2023 case Information:
2024 bg.setRgb(161, 178, 202);
2025 border.setRgb(59, 79, 175);
2026 break;
2027 case Warning:
2028 bg.setRgb(228, 227, 127);
2029 border.setRgb(175, 169, 61);
2030 break;
2031 case Error:
2032 bg.setRgb(233, 199, 196);
2033 border.setRgb(175, 74, 60);
2034 break;
2035 default:
2036 break;
2037 }
2038 setStyleSheet(QString(
2039 "QFrame { background-color: %1;"
2040 "border-radius: 5px;"
2041 "border: 1px solid %2;"
2042 "}"
2043 "QLabel { border: none; }")
2044 .arg(bg.name())
2045 .arg(border.name()));
2046}
2047
2048int MessageWidget::SetMessage(MessageType type, const QString& msg)
2049{
2050 m_type = type;
2051 m_text->setText(msg);
2052 UpdateType();
2053 show();
2054 return ++m_id;
2055}
2056
2057void MessageWidget::HideMessage(int id)
2058{
2059 if(m_id == id)
2060 OnClose(true);
2061}
2062
2063void MessageWidget::OnClose(bool clicked)
2064{
2065 Q_UNUSED(clicked);
2066 hide();
2067}
2068
2069/*
2070 * YIconManager
2071 */
2072YIconManager *YIconManager::m_singleton = nullptr;
2073
2074YIconManager::YIconManager()
2075{
2076 m_icon_name[ListAdd] = "list-add";
2077 m_icon_fallback[ListAdd] = QStyle::SP_CustomBase; /* drawn by RenderListAdd */
2078 m_icon_name[ListRemove] = "list-remove";
2079 m_icon_fallback[ListRemove] = QStyle::SP_CustomBase; /* drawn by RenderListAdd */
2080 m_icon_name[DocumentNew] = "document-new";
2081 m_icon_fallback[DocumentNew] = QStyle::SP_FileDialogNewFolder;
2082 m_icon_name[DocumentEdit] = "document-edit";
2083 m_icon_fallback[DocumentEdit] = QStyle::SP_FileDialogContentsView;
2084 m_icon_name[DocumentOpen] = "document-open";
2085 m_icon_fallback[DocumentOpen] = QStyle::SP_DialogOpenButton;
2086 m_icon_name[DocumentSave] = "document-save";
2087 m_icon_fallback[DocumentSave] = QStyle::SP_DialogSaveButton;
2088 m_icon_name[DocumentSaveAs] = "document-save-as";
2089 m_icon_fallback[DocumentSaveAs] = QStyle::SP_DialogSaveButton;
2090 m_icon_name[Preferences] = "preferences-system";
2091 m_icon_fallback[Preferences] = QStyle::SP_FileDialogInfoView;
2092 m_icon_name[FolderNew] = "folder-new";
2093 m_icon_fallback[FolderNew] = QStyle::SP_FileDialogNewFolder;
2094 m_icon_name[Computer] = "computer";
2095 m_icon_fallback[Computer] = QStyle::SP_ComputerIcon;
2096 m_icon_name[Cpu] = "cpu";
2097 m_icon_fallback[Cpu] = QStyle::SP_DriveHDIcon;
2098 m_icon_name[DialogError] = "dialog-error";
2099 m_icon_fallback[DialogError] = QStyle::SP_MessageBoxCritical;
2100 m_icon_name[ViewRefresh] = "view-refresh";
2101 m_icon_fallback[ViewRefresh] = QStyle::SP_BrowserReload;
2102 m_icon_name[SytemRun] = "system-run";
2103 m_icon_fallback[SytemRun] = QStyle::SP_MediaPlay;
2104 m_icon_name[ApplicationExit] = "application-exit";
2105 m_icon_fallback[ApplicationExit] = QStyle::SP_TitleBarCloseButton;
2106 m_icon_name[HelpAbout] = "help-about";
2107 m_icon_fallback[HelpAbout] = QStyle::SP_MessageBoxInformation;
2108 m_icon_name[FormatTextBold] = "format-text-bold";
2109 m_icon_fallback[FormatTextBold] = QStyle::SP_CustomBase; /* drawn by RenderLetter */
2110 m_icon_name[FormatTextItalic] = "format-text-italic";
2111 m_icon_fallback[FormatTextItalic] = QStyle::SP_CustomBase; /* drawn by RenderLetter */
2112 m_icon_name[FormatTextUnderline] = "format-text-underline";
2113 m_icon_fallback[FormatTextUnderline] = QStyle::SP_CustomBase; /* drawn by RenderLetter */
2114 m_icon_name[TextGeneric] = "text-x-generic";
2115 m_icon_fallback[TextGeneric] = QStyle::SP_FileDialogDetailedView;
2116 m_icon_name[MultimediaPlayer] = "multimedia-player";
2117 m_icon_fallback[MultimediaPlayer] = QStyle::SP_ComputerIcon;
2118}
2119
2120YIconManager::~YIconManager()
2121{
2122}
2123
2124YIconManager *YIconManager::Get()
2125{
2126 if(m_singleton == nullptr)
2127 m_singleton = new YIconManager();
2128 return m_singleton;
2129}
2130
2131QIcon YIconManager::GetIcon(IconType type)
2132{
2133 if(type < 0 || type >= MaxIcon)
2134 return QIcon();
2135 /* cache icons */
2136 if(m_icon[type].isNull())
2137 m_icon[type] = QIcon::fromTheme(m_icon_name[type], GetFallbackIcon(type));
2138 return m_icon[type];
2139}
2140
2141namespace
2142{
2143 QIcon RenderListAdd()
2144 {
2145 QPixmap pix(64, 64);
2146 pix.fill(Qt::transparent);
2147 QPainter paint(&pix);
2148 paint.fillRect(30, 12, 4, 40, QColor(255, 0, 0));
2149 paint.fillRect(12, 30, 40, 4, QColor(255, 0, 0));
2150 return QIcon(pix);
2151 }
2152
2153 QIcon RenderListRemove()
2154 {
2155 QPixmap pix(64, 64);
2156 pix.fill(Qt::transparent);
2157 QPainter paint(&pix);
2158 paint.setPen(QColor(255, 0, 0));
2159 paint.drawLine(12, 12, 52, 52);
2160 paint.drawLine(12, 52, 52, 16);
2161 return QIcon(pix);
2162 }
2163
2164 QIcon RenderUnknown()
2165 {
2166 QPixmap pix(64, 64);
2167 pix.fill();
2168 QPainter paint(&pix);
2169 paint.fillRect(0, 0, 64, 64, QColor(255, 0, 0));
2170 return QIcon(pix);
2171 }
2172
2173 QIcon RenderText(const QString& str, bool bold, bool italic, bool underline)
2174 {
2175 QPixmap pix(64, 64);
2176 pix.fill();
2177 QPainter paint(&pix);
2178 QFont font = QApplication::font("QButton");
2179 font.setBold(bold);
2180 font.setItalic(italic);
2181 font.setUnderline(underline);
2182 font.setPixelSize(64);
2183 paint.setFont(font);
2184 paint.drawText(0, 0, 64, 64, Qt::AlignCenter, str);
2185 return QIcon(pix);
2186 }
2187}
2188
2189QIcon YIconManager::GetFallbackIcon(IconType type)
2190{
2191 if(m_icon_fallback[type] != QStyle::SP_CustomBase)
2192 return QApplication::style()->standardIcon(m_icon_fallback[type]);
2193 switch(type)
2194 {
2195 case ListAdd: return RenderListAdd(); break;
2196 case ListRemove: return RenderListRemove(); break;
2197 case FormatTextBold: return RenderText("B", true, false, false);
2198 case FormatTextItalic: return RenderText("I", false, true, false);
2199 case FormatTextUnderline: return RenderText("U", false, false, true);
2200 default: return RenderUnknown(); break;
2201 }
2202}
2203
2204/**
2205 * Misc
2206 */
2207
2208QGroupBox *Misc::EncloseInBox(const QString& name, QWidget *widget)
2209{
2210 QVBoxLayout *layout = new QVBoxLayout;
2211 layout->addWidget(widget);
2212 return Misc::EncloseInBox(name, layout);
2213}
2214
2215QGroupBox *Misc::EncloseInBox(const QString& name, QLayout *layout)
2216{
2217 QGroupBox *group = new QGroupBox(name);
2218 group->setLayout(layout);
2219 return group;
2220}