From 9e3508134b5b8d4e93fe2c869d94063c174cab62 Mon Sep 17 00:00:00 2001
From: Jonas Kvinge <jonas@jkvinge.net>
Date: Wed, 23 Sep 2020 00:52:41 +0200
Subject: [PATCH] Add compilation to edit tag dialog

---
 src/dialogs/edittagdialog.cpp |  34 ++++++++-
 src/dialogs/edittagdialog.ui  | 139 ++++++++++++++++++++++++++++------
 src/widgets/lineedit.cpp      |  30 ++++++--
 src/widgets/lineedit.h        |  88 ++++++++++++++++-----
 4 files changed, 239 insertions(+), 52 deletions(-)

diff --git a/src/dialogs/edittagdialog.cpp b/src/dialogs/edittagdialog.cpp
index 7b4e0695b..adabcb5d6 100644
--- a/src/dialogs/edittagdialog.cpp
+++ b/src/dialogs/edittagdialog.cpp
@@ -54,6 +54,7 @@
 #include <QShortcut>
 #include <QSize>
 #include <QSpinBox>
+#include <QCheckBox>
 #include <QSplitter>
 #include <QTabWidget>
 #include <QTextEdit>
@@ -156,6 +157,9 @@ EditTagDialog::EditTagDialog(Application *app, QWidget *parent)
       else if (qobject_cast<QSpinBox*>(widget)) {
         connect(widget, SIGNAL(valueChanged(int)), SLOT(FieldValueEdited()));
       }
+      else if (qobject_cast<QCheckBox*>(widget)) {
+        connect(widget, SIGNAL(stateChanged(int)), SLOT(FieldValueEdited()));
+      }
     }
   }
 
@@ -275,6 +279,7 @@ QList<EditTagDialog::Data> EditTagDialog::LoadData(const SongList &songs) const
   }
 
   return ret;
+
 }
 
 void EditTagDialog::SetSongs(const SongList &s, const PlaylistItemList &items) {
@@ -325,9 +330,11 @@ void EditTagDialog::SetSongsFinished(QFuture<QList<Data>> future) {
 }
 
 void EditTagDialog::SetSongListVisibility(bool visible) {
+
   ui_->song_list->setVisible(visible);
   previous_button_->setEnabled(visible);
   next_button_->setEnabled(visible);
+
 }
 
 QVariant EditTagDialog::Data::value(const Song &song, const QString &id) {
@@ -345,6 +352,7 @@ QVariant EditTagDialog::Data::value(const Song &song, const QString &id) {
   if (id == "track") return song.track();
   if (id == "disc") return song.disc();
   if (id == "year") return song.year();
+  if (id == "compilation") return song.compilation();
   qLog(Warning) << "Unknown ID" << id;
   return QVariant();
 
@@ -365,16 +373,19 @@ void EditTagDialog::Data::set_value(const QString &id, const QVariant &value) {
   else if (id == "track") current_.set_track(value.toInt());
   else if (id == "disc") current_.set_disc(value.toInt());
   else if (id == "year") current_.set_year(value.toInt());
+  else if (id == "compilation") current_.set_compilation(value.toBool());
   else qLog(Warning) << "Unknown ID" << id;
 
 }
 
 bool EditTagDialog::DoesValueVary(const QModelIndexList &sel, const QString &id) const {
+
   QVariant value = data_[sel.first().row()].current_value(id);
   for (int i = 1; i < sel.count(); ++i) {
     if (value != data_[sel[i].row()].current_value(id)) return true;
   }
   return false;
+
 }
 
 bool EditTagDialog::IsValueModified(const QModelIndexList &sel, const QString &id) const {
@@ -396,11 +407,15 @@ void EditTagDialog::InitFieldValue(const FieldData &field, const QModelIndexList
     editor->clear_hint();
     if (varies) {
       editor->set_hint(tr(EditTagDialog::kHintText));
+      editor->set_partially();
     }
     else {
-      editor->set_text(data_[sel[0].row()].current_value(field.id_).toString());
+      editor->set_value(data_[sel[0].row()].current_value(field.id_));
     }
   }
+  else {
+    qLog(Error) << "Missing editor for" << field.editor_->objectName();
+  }
 
   UpdateModifiedField(field, sel);
 
@@ -410,8 +425,12 @@ void EditTagDialog::UpdateFieldValue(const FieldData &field, const QModelIndexLi
 
   // Get the value from the field
   QVariant value;
+
   if (ExtendedEditor *editor = dynamic_cast<ExtendedEditor*>(field.editor_)) {
-    value = editor->text();
+    value = editor->value();
+  }
+  else {
+    qLog(Error) << "Missing editor for" << field.editor_->objectName();
   }
 
   // Did we get it?
@@ -654,6 +673,7 @@ void EditTagDialog::LoadCoverFromURL() {
   QUrl cover_url = album_cover_choice_controller_->LoadCoverFromURL(song);
 
   if (!cover_url.isEmpty()) UpdateCoverOf(*song, sel, cover_url);
+
 }
 
 void EditTagDialog::SearchForCover() {
@@ -666,6 +686,7 @@ void EditTagDialog::SearchForCover() {
   QUrl cover_url = album_cover_choice_controller_->SearchForCover(song);
 
   if (!cover_url.isEmpty()) UpdateCoverOf(*song, sel, cover_url);
+
 }
 
 void EditTagDialog::UnsetCover() {
@@ -677,6 +698,7 @@ void EditTagDialog::UnsetCover() {
 
   QUrl cover_url = album_cover_choice_controller_->UnsetCover(song);
   UpdateCoverOf(*song, sel, cover_url);
+
 }
 
 void EditTagDialog::ShowCover() {
@@ -687,6 +709,7 @@ void EditTagDialog::ShowCover() {
   }
 
   album_cover_choice_controller_->ShowCover(*song);
+
 }
 
 void EditTagDialog::UpdateCoverOf(const Song &selected, const QModelIndexList &sel, const QUrl &cover_url) {
@@ -716,6 +739,7 @@ void EditTagDialog::NextSong() {
 
   int row = (ui_->song_list->currentRow() + 1) % ui_->song_list->count();
   ui_->song_list->setCurrentRow(row);
+
 }
 
 void EditTagDialog::PreviousSong() {
@@ -726,12 +750,15 @@ void EditTagDialog::PreviousSong() {
 
   int row = (ui_->song_list->currentRow() - 1 + ui_->song_list->count()) % ui_->song_list->count();
   ui_->song_list->setCurrentRow(row);
+
 }
 
 void EditTagDialog::ButtonClicked(QAbstractButton *button) {
+
   if (button == ui_->button_box->button(QDialogButtonBox::Discard)) {
     reject();
   }
+
 }
 
 void EditTagDialog::SaveData(const QList<Data> &tag_data) {
@@ -802,6 +829,7 @@ bool EditTagDialog::eventFilter(QObject *o, QEvent *e) {
     }
   }
   return false;
+
 }
 
 void EditTagDialog::showEvent(QShowEvent *e) {
@@ -815,6 +843,7 @@ void EditTagDialog::showEvent(QShowEvent *e) {
   ui_->tab_widget->setCurrentIndex(s.value("current_tab").toInt());
 
   QDialog::showEvent(e);
+
 }
 
 void EditTagDialog::hideEvent(QHideEvent *e) {
@@ -825,6 +854,7 @@ void EditTagDialog::hideEvent(QHideEvent *e) {
   s.setValue("current_tab", ui_->tab_widget->currentIndex());
 
   QDialog::hideEvent(e);
+
 }
 
 void EditTagDialog::ResetPlayCounts() {
diff --git a/src/dialogs/edittagdialog.ui b/src/dialogs/edittagdialog.ui
index 4427bad0f..a28359365 100644
--- a/src/dialogs/edittagdialog.ui
+++ b/src/dialogs/edittagdialog.ui
@@ -7,7 +7,7 @@
     <x>0</x>
     <y>0</y>
     <width>863</width>
-    <height>645</height>
+    <height>671</height>
    </rect>
   </property>
   <property name="windowTitle">
@@ -30,7 +30,7 @@
      </widget>
      <widget class="QTabWidget" name="tab_widget">
       <property name="currentIndex">
-       <number>0</number>
+       <number>1</number>
       </property>
       <widget class="QWidget" name="summary_tab">
        <attribute name="title">
@@ -150,7 +150,7 @@
            <number>18</number>
           </property>
           <item row="0" column="0">
-           <widget class="QLabel" name="length_label">
+           <widget class="QLabel" name="label_length">
             <property name="sizePolicy">
              <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
               <horstretch>0</horstretch>
@@ -194,7 +194,7 @@
            </widget>
           </item>
           <item row="0" column="2">
-           <widget class="QLabel" name="playcount_label">
+           <widget class="QLabel" name="label_playcount">
             <property name="sizePolicy">
              <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
               <horstretch>0</horstretch>
@@ -226,7 +226,7 @@
            </widget>
           </item>
           <item row="1" column="2">
-           <widget class="QLabel" name="skipcount_label">
+           <widget class="QLabel" name="label_skipcount">
             <property name="sizePolicy">
              <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
               <horstretch>0</horstretch>
@@ -252,7 +252,7 @@
            </widget>
           </item>
           <item row="1" column="0">
-           <widget class="QLabel" name="bitrate_label">
+           <widget class="QLabel" name="label_bitrate">
             <property name="sizePolicy">
              <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
               <horstretch>0</horstretch>
@@ -290,7 +290,7 @@
            </widget>
           </item>
           <item row="2" column="2">
-           <widget class="QLabel" name="lastplayed_label">
+           <widget class="QLabel" name="label_lastplayed">
             <property name="sizePolicy">
              <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
               <horstretch>0</horstretch>
@@ -316,7 +316,7 @@
            </widget>
           </item>
           <item row="2" column="0">
-           <widget class="QLabel" name="samplerate_label">
+           <widget class="QLabel" name="label_samplerate">
             <property name="sizePolicy">
              <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
               <horstretch>0</horstretch>
@@ -348,7 +348,7 @@
            </widget>
           </item>
           <item row="3" column="0">
-           <widget class="QLabel" name="bitdepth_label">
+           <widget class="QLabel" name="label_bitdepth">
             <property name="sizePolicy">
              <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
               <horstretch>0</horstretch>
@@ -380,7 +380,7 @@
            </widget>
           </item>
           <item row="4" column="0">
-           <widget class="QLabel" name="filesize_label">
+           <widget class="QLabel" name="label_filesize">
             <property name="sizePolicy">
              <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
               <horstretch>0</horstretch>
@@ -412,7 +412,7 @@
            </widget>
           </item>
           <item row="6" column="0">
-           <widget class="QLabel" name="filetype_label">
+           <widget class="QLabel" name="label_filetype">
             <property name="sizePolicy">
              <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
               <horstretch>0</horstretch>
@@ -447,7 +447,7 @@
            </widget>
           </item>
           <item row="7" column="0">
-           <widget class="QLabel" name="mtime_label">
+           <widget class="QLabel" name="label_mtime">
             <property name="sizePolicy">
              <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
               <horstretch>0</horstretch>
@@ -473,7 +473,7 @@
            </widget>
           </item>
           <item row="8" column="0">
-           <widget class="QLabel" name="ctime_label">
+           <widget class="QLabel" name="label_ctime">
             <property name="sizePolicy">
              <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
               <horstretch>0</horstretch>
@@ -499,7 +499,7 @@
            </widget>
           </item>
           <item row="5" column="0">
-           <widget class="QLabel" name="filename_label">
+           <widget class="QLabel" name="label_filename">
             <property name="text">
              <string>File name</string>
             </property>
@@ -580,6 +580,12 @@
         </item>
         <item row="7" column="0">
          <widget class="QLabel" name="genre_label">
+          <property name="minimumSize">
+           <size>
+            <width>80</width>
+            <height>0</height>
+           </size>
+          </property>
           <property name="text">
            <string>Genre</string>
           </property>
@@ -609,7 +615,13 @@
          </widget>
         </item>
         <item row="0" column="0">
-         <widget class="QLabel" name="title_label">
+         <widget class="QLabel" name="label_title">
+          <property name="minimumSize">
+           <size>
+            <width>80</width>
+            <height>0</height>
+           </size>
+          </property>
           <property name="text">
            <string>Title</string>
           </property>
@@ -619,7 +631,13 @@
          </widget>
         </item>
         <item row="6" column="0">
-         <widget class="QLabel" name="grouping_label">
+         <widget class="QLabel" name="label_grouping">
+          <property name="minimumSize">
+           <size>
+            <width>80</width>
+            <height>0</height>
+           </size>
+          </property>
           <property name="text">
            <string>Grouping</string>
           </property>
@@ -629,7 +647,13 @@
          </widget>
         </item>
         <item row="2" column="0">
-         <widget class="QLabel" name="album_label">
+         <widget class="QLabel" name="label_album">
+          <property name="minimumSize">
+           <size>
+            <width>80</width>
+            <height>0</height>
+           </size>
+          </property>
           <property name="text">
            <string>Album</string>
           </property>
@@ -669,7 +693,13 @@
          </widget>
         </item>
         <item row="3" column="0">
-         <widget class="QLabel" name="albumartist_label">
+         <widget class="QLabel" name="label_albumartist">
+          <property name="minimumSize">
+           <size>
+            <width>80</width>
+            <height>0</height>
+           </size>
+          </property>
           <property name="text">
            <string>Album artist</string>
           </property>
@@ -689,7 +719,13 @@
          </widget>
         </item>
         <item row="4" column="0">
-         <widget class="QLabel" name="composer_label">
+         <widget class="QLabel" name="label_composer">
+          <property name="minimumSize">
+           <size>
+            <width>80</width>
+            <height>0</height>
+           </size>
+          </property>
           <property name="text">
            <string>Composer</string>
           </property>
@@ -699,7 +735,13 @@
          </widget>
         </item>
         <item row="11" column="0">
-         <widget class="QLabel" name="comment_label">
+         <widget class="QLabel" name="label_comment">
+          <property name="minimumSize">
+           <size>
+            <width>80</width>
+            <height>0</height>
+           </size>
+          </property>
           <property name="text">
            <string>Comment</string>
           </property>
@@ -719,7 +761,7 @@
          </widget>
         </item>
         <item row="0" column="2">
-         <widget class="QLabel" name="track_label">
+         <widget class="QLabel" name="label_track">
           <property name="text">
            <string>Track</string>
           </property>
@@ -755,7 +797,13 @@
          </widget>
         </item>
         <item row="1" column="0">
-         <widget class="QLabel" name="artist_label">
+         <widget class="QLabel" name="label_artist">
+          <property name="minimumSize">
+           <size>
+            <width>80</width>
+            <height>0</height>
+           </size>
+          </property>
           <property name="text">
            <string>Artist</string>
           </property>
@@ -791,7 +839,7 @@
          </widget>
         </item>
         <item row="2" column="2">
-         <widget class="QLabel" name="year_label">
+         <widget class="QLabel" name="label_year">
           <property name="text">
            <string>Year</string>
           </property>
@@ -811,7 +859,13 @@
          </widget>
         </item>
         <item row="5" column="0">
-         <widget class="QLabel" name="performer_label">
+         <widget class="QLabel" name="label_performer">
+          <property name="minimumSize">
+           <size>
+            <width>80</width>
+            <height>0</height>
+           </size>
+          </property>
           <property name="text">
            <string>Performer</string>
           </property>
@@ -822,6 +876,12 @@
         </item>
         <item row="10" column="0">
          <widget class="QLabel" name="label_lyrics">
+          <property name="minimumSize">
+           <size>
+            <width>80</width>
+            <height>0</height>
+           </size>
+          </property>
           <property name="text">
            <string>Lyrics</string>
           </property>
@@ -840,6 +900,32 @@
           </property>
          </widget>
         </item>
+        <item row="8" column="1">
+         <widget class="CheckBox" name="compilation">
+          <property name="has_reset_button" stdset="0">
+           <bool>false</bool>
+          </property>
+          <property name="has_clear_button" stdset="0">
+           <bool>false</bool>
+          </property>
+         </widget>
+        </item>
+        <item row="8" column="0">
+         <widget class="QLabel" name="label_compilation">
+          <property name="minimumSize">
+           <size>
+            <width>80</width>
+            <height>0</height>
+           </size>
+          </property>
+          <property name="text">
+           <string>Compilation</string>
+          </property>
+          <property name="buddy">
+           <cstring>compilation</cstring>
+          </property>
+         </widget>
+        </item>
        </layout>
       </widget>
      </widget>
@@ -878,6 +964,11 @@
    <extends>QSpinBox</extends>
    <header>widgets/lineedit.h</header>
   </customwidget>
+  <customwidget>
+   <class>CheckBox</class>
+   <extends>QCheckBox</extends>
+   <header>widgets/lineedit.h</header>
+  </customwidget>
  </customwidgets>
  <tabstops>
   <tabstop>song_list</tabstop>
diff --git a/src/widgets/lineedit.cpp b/src/widgets/lineedit.cpp
index 6eff359c3..7aaa1061f 100644
--- a/src/widgets/lineedit.cpp
+++ b/src/widgets/lineedit.cpp
@@ -71,19 +71,21 @@ ExtendedEditor::ExtendedEditor(QWidget *widget, int extra_right_padding, bool dr
   reset_button_->setFocusPolicy(Qt::NoFocus);
   reset_button_->hide();
 
-  widget->connect(clear_button_, SIGNAL(clicked()), widget, SLOT(clear()));
   widget->connect(clear_button_, SIGNAL(clicked()), widget, SLOT(setFocus()));
+  if (qobject_cast<QLineEdit*>(widget) || qobject_cast<QPlainTextEdit*>(widget) || qobject_cast<QSpinBox*>(widget)) {
+    widget->connect(clear_button_, SIGNAL(clicked()), widget, SLOT(clear()));
+  }
 
   UpdateButtonGeometry();
 
 }
 
-void ExtendedEditor::set_hint(const QString& hint) {
+void ExtendedEditor::set_hint(const QString &hint) {
   hint_ = hint;
   widget_->update();
 }
 
-void ExtendedEditor::set_clear_button(bool visible) {
+void ExtendedEditor::set_clear_button(const bool visible) {
   has_clear_button_ = visible;
   clear_button_->setVisible(visible);
   UpdateButtonGeometry();
@@ -93,7 +95,7 @@ bool ExtendedEditor::has_reset_button() const {
   return reset_button_->isVisible();
 }
 
-void ExtendedEditor::set_reset_button(bool visible) {
+void ExtendedEditor::set_reset_button(const bool visible) {
   reset_button_->setVisible(visible);
   UpdateButtonGeometry();
 }
@@ -161,7 +163,7 @@ LineEdit::LineEdit(QWidget *parent) : QLineEdit(parent), ExtendedEditor(this) {
   connect(this, SIGNAL(textChanged(QString)), SLOT(text_changed(QString)));
 }
 
-void LineEdit::text_changed(const QString& text) {
+void LineEdit::text_changed(const QString &text) {
 
   if (text.isEmpty()) {
     // Consider empty string as LTR
@@ -222,8 +224,26 @@ void SpinBox::resizeEvent(QResizeEvent *e) {
   Resize();
 }
 
+CheckBox::CheckBox(QWidget *parent)
+  : QCheckBox(parent), ExtendedEditor(this, 14, false)
+{
+  connect(reset_button_, SIGNAL(clicked()), SIGNAL(Reset()));
+}
+
+void CheckBox::paintEvent(QPaintEvent *e) {
+  QCheckBox::paintEvent(e);
+  Paint(this);
+}
+
+void CheckBox::resizeEvent(QResizeEvent *e) {
+  QCheckBox::resizeEvent(e);
+  Resize();
+}
+
 QString SpinBox::textFromValue(int val) const {
+
   if (val <= 0 && !hint_.isEmpty())
     return "-";
   return QSpinBox::textFromValue(val);
+
 }
diff --git a/src/widgets/lineedit.h b/src/widgets/lineedit.h
index 83470c3df..f999bf7eb 100644
--- a/src/widgets/lineedit.h
+++ b/src/widgets/lineedit.h
@@ -30,6 +30,7 @@
 #include <QLineEdit>
 #include <QPlainTextEdit>
 #include <QSpinBox>
+#include <QCheckBox>
 
 class QToolButton;
 class QPaintDevice;
@@ -37,6 +38,7 @@ class QPaintEvent;
 class QResizeEvent;
 
 class LineEditInterface {
+
  public:
   explicit LineEditInterface(QWidget *widget) : widget_(widget) {}
 
@@ -44,40 +46,43 @@ class LineEditInterface {
 
   virtual ~LineEditInterface() {}
 
-  virtual void clear() { set_text(QString()); }
+  virtual void set_enabled(const bool enabled) = 0;
   virtual void set_focus() = 0;
-  virtual QString text() const = 0;
-  virtual void set_text(const QString& text) = 0;
+
+  virtual void clear() = 0;
+  virtual QVariant value() const = 0;
+  virtual void set_value(const QVariant &value) = 0;
 
   virtual QString hint() const = 0;
-  virtual void set_hint(const QString& hint) = 0;
+  virtual void set_hint(const QString &hint) = 0;
   virtual void clear_hint() = 0;
 
-  virtual void set_enabled(bool enabled) = 0;
+  virtual void set_partially() {}
 
  protected:
   QWidget *widget_;
 };
 
 class ExtendedEditor : public LineEditInterface {
+
  public:
   explicit ExtendedEditor(QWidget *widget, int extra_right_padding = 0, bool draw_hint = true);
   ~ExtendedEditor() override {}
 
-  virtual bool is_empty() const { return text().isEmpty(); }
+  virtual bool is_empty() const { return value().toString().isEmpty(); }
 
   QString hint() const override { return hint_; }
-  void set_hint(const QString& hint) override;
+  void set_hint(const QString &hint) override;
   void clear_hint() override { set_hint(QString()); }
 
   bool has_clear_button() const { return has_clear_button_; }
-  void set_clear_button(bool visible);
+  void set_clear_button(const bool visible);
 
   bool has_reset_button() const;
-  void set_reset_button(bool visible);
+  void set_reset_button(const bool visible);
 
   qreal font_point_size() const { return font_point_size_; }
-  void set_font_point_size(qreal size) { font_point_size_ = size; }
+  void set_font_point_size(const qreal size) { font_point_size_ = size; }
 
  protected:
   void Paint(QPaintDevice *device);
@@ -111,10 +116,14 @@ class LineEdit : public QLineEdit, public ExtendedEditor {
   explicit LineEdit(QWidget *parent = nullptr);
 
   // ExtendedEditor
-  void set_focus() override { QLineEdit::setFocus(); }
-  QString text() const override { return QLineEdit::text(); }
-  void set_text(const QString& text) override { QLineEdit::setText(text); }
   void set_enabled(bool enabled) override { QLineEdit::setEnabled(enabled); }
+  void set_focus() override { QLineEdit::setFocus(); }
+
+  QVariant value() const override { return QLineEdit::text(); }
+  void set_value(const QVariant &value) override { QLineEdit::setText(value.toString()); }
+
+ public slots:
+  void clear() override { QLineEdit::clear(); }
 
  protected:
   void paintEvent(QPaintEvent*) override;
@@ -125,7 +134,7 @@ class LineEdit : public QLineEdit, public ExtendedEditor {
   void set_rtl(bool rtl) { is_rtl_ = rtl; }
 
  private slots:
-  void text_changed(const QString& text);
+  void text_changed(const QString &text);
 
  signals:
   void Reset();
@@ -141,10 +150,14 @@ class TextEdit : public QPlainTextEdit, public ExtendedEditor {
   explicit TextEdit(QWidget *parent = nullptr);
 
   // ExtendedEditor
-  void set_focus() override { QPlainTextEdit::setFocus(); }
-  QString text() const override { return QPlainTextEdit::toPlainText(); }
-  void set_text(const QString& text) override { QPlainTextEdit::setPlainText(text); }
   void set_enabled(bool enabled) override { QPlainTextEdit::setEnabled(enabled); }
+  void set_focus() override { QPlainTextEdit::setFocus(); }
+
+  QVariant value() const override { return QPlainTextEdit::toPlainText(); }
+  void set_value(const QVariant &value) override { QPlainTextEdit::setPlainText(value.toString()); }
+
+ public slots:
+  void clear() override { QPlainTextEdit::clear(); }
 
  protected:
   void paintEvent(QPaintEvent*) override;
@@ -167,11 +180,44 @@ class SpinBox : public QSpinBox, public ExtendedEditor {
   QString textFromValue(int val) const override;
 
   // ExtendedEditor
-  bool is_empty() const override { return text().isEmpty() || text() == "0"; }
-  void set_focus() override { QSpinBox::setFocus(); }
-  QString text() const override { return QSpinBox::text(); }
-  void set_text(const QString& text) override { QSpinBox::setValue(text.toInt()); }
   void set_enabled(bool enabled) override { QSpinBox::setEnabled(enabled); }
+  void set_focus() override { QSpinBox::setFocus(); }
+
+  QVariant value() const override { return QSpinBox::value(); }
+  void set_value(const QVariant &value) override { QSpinBox::setValue(value.toInt()); }
+  bool is_empty() const override { return text().isEmpty() || text() == "0"; }
+
+ public slots:
+  void clear() override { QSpinBox::clear(); }
+
+ protected:
+  void paintEvent(QPaintEvent*) override;
+  void resizeEvent(QResizeEvent*) override;
+
+ signals:
+  void Reset();
+};
+
+class CheckBox : public QCheckBox, public ExtendedEditor {
+  Q_OBJECT
+  Q_PROPERTY(QString hint READ hint WRITE set_hint)
+  Q_PROPERTY(bool has_clear_button READ has_clear_button WRITE set_clear_button)
+  Q_PROPERTY(bool has_reset_button READ has_reset_button WRITE set_reset_button)
+
+ public:
+  explicit CheckBox(QWidget *parent = nullptr);
+
+  // ExtendedEditor
+  void set_enabled(bool enabled) override { QCheckBox::setEnabled(enabled); }
+  void set_focus() override { QCheckBox::setFocus(); }
+
+  bool is_empty() const override { return text().isEmpty() || text() == "0"; }
+  QVariant value() const override { return QCheckBox::isChecked(); }
+  void set_value(const QVariant &value) override { QCheckBox::setCheckState(value.toBool() ? Qt::Checked : Qt::Unchecked); }
+  void set_partially() override { QCheckBox::setCheckState(Qt::PartiallyChecked); }
+
+ public slots:
+  void clear() override { QCheckBox::setChecked(false); }
 
  protected:
   void paintEvent(QPaintEvent*) override;