freetype-commit
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[freetype2-demos] gsoc-2022-chariri-final 665ae2ef 2/2: [ftinspect] Rewr


From: Werner Lemberg
Subject: [freetype2-demos] gsoc-2022-chariri-final 665ae2ef 2/2: [ftinspect] Rewrite `MainGUI`.
Date: Sat, 3 Sep 2022 10:27:56 -0400 (EDT)

branch: gsoc-2022-chariri-final
commit 665ae2ef12dea1d331e6bee5b193417b5566b42b
Author: Charlie Jiang <w@chariri.moe>
Commit: Charlie Jiang <w@chariri.moe>

    [ftinspect] Rewrite `MainGUI`.
    
    Note: This commit compiles, but the main view is removed, so you will get a
    blank right panel. The singular view will be added back with major changes
    in the next commit.
    
    This commit mainly introduces below changes:
    
    1. The original `MainGUI` contains almost all GUI elements. Most of them are
       taken out to modular components. The current `MainGUI` serves only as
       a skeleton provides coordinate between components, greatly shortened.
       It also provides some auxiliary code such as calling to file chooser.
    2. The left panel is moved to `SettingPanel` class. The current
       `settingpanel.[ch]pp` are directly modified from the latest code, so they
       contains some options not implemented in the `Engine`.
       Structural code that is only added accompanying later change is removed,
       such as the comparator mode. Such code will be added back with the
       related feature. However, code for unimplemented options are simply
       commented out.
    3. The main part is transformed into a tabbed view. The original code is
       removed. Refactored, it will be added back in the next commit.
    4. The navigation buttons (Next/Prev Font/Face/NI) are changed into the new
       triplet-selector, which is a major UI improvement.
    
    * src/ftinspect/maingui.cpp, src/ftinspect/maingui.hpp: As described.
    
    * src/ftinspect/panels/abstracttab.hpp: Add `AbstractTab` which is an
      interface for all tabs listening for font reloading and repainting.
    
    * src/ftinspect/panels/settingpanel.cpp,
      src/ftinspect/panels/settingpanel.hpp:
      As described, this is the left panel. This requires intensive reviewing
      since many bugs had rooted here.
    
    * src/ftinspect/widgets/tripletselector.cpp,
      src/ftinspect/widgets/tripletselector.hpp:
      As described, this is the triplet (Font/Subface/NI) selector.
      This component is also responsible for repopulating triplet information
      and keep up with the font file change (i.e. the
      `FontFileManager::currentFileChanged` event is captured here).
    
    * src/ftinspect/engine/engine.cpp, src/ftinspect/engine/engine.hpp:
      Add `fontValid` and `namedInstanceName` since `TripletSelector` requires
      them. However, they'll subject to change later.
    
    * src/ftinspect/ftinspect.cpp: Remove call to `MainGUI::setDefaults`.
    
    * src/ftinspect/CMakeLists.txt, src/ftinspect/meson.build: Updated.
---
 src/ftinspect/CMakeLists.txt              |   3 +
 src/ftinspect/engine/engine.cpp           |  52 +++
 src/ftinspect/engine/engine.hpp           |   2 +
 src/ftinspect/ftinspect.cpp               |   1 -
 src/ftinspect/maingui.cpp                 | 291 +++++++++++++
 src/ftinspect/maingui.hpp                 | 103 +++++
 src/ftinspect/meson.build                 |   5 +
 src/ftinspect/panels/abstracttab.hpp      |  20 +
 src/ftinspect/panels/settingpanel.cpp     | 670 ++++++++++++++++++++++++++++++
 src/ftinspect/panels/settingpanel.hpp     | 131 ++++++
 src/ftinspect/widgets/tripletselector.cpp | 490 ++++++++++++++++++++++
 src/ftinspect/widgets/tripletselector.hpp |  67 +++
 12 files changed, 1834 insertions(+), 1 deletion(-)

diff --git a/src/ftinspect/CMakeLists.txt b/src/ftinspect/CMakeLists.txt
index badc20a4..418dbedc 100644
--- a/src/ftinspect/CMakeLists.txt
+++ b/src/ftinspect/CMakeLists.txt
@@ -30,8 +30,11 @@ add_executable(ftinspect
   "glyphcomponents/grid.cpp"
 
   "widgets/customwidgets.cpp"
+  "widgets/tripletselector.cpp"
 
   "models/customcomboboxmodels.cpp"
+
+  "panels/settingpanel.cpp"
 )
 target_link_libraries(ftinspect
   Qt5::Core Qt5::Widgets
diff --git a/src/ftinspect/engine/engine.cpp b/src/ftinspect/engine/engine.cpp
index 1e6fa168..fc4d3d67 100644
--- a/src/ftinspect/engine/engine.cpp
+++ b/src/ftinspect/engine/engine.cpp
@@ -237,6 +237,50 @@ Engine::numberOfNamedInstances(int fontIndex,
 }
 
 
+QString
+Engine::namedInstanceName(int fontIndex, long faceIndex, int index)
+{
+  if (fontIndex < 0)
+    return {};
+
+  FT_Face face;
+  QString name;
+
+  // search triplet (fontIndex, faceIndex, index)
+  FTC_FaceID ftcFaceID = reinterpret_cast<FTC_FaceID>
+                           (faceIDMap_.value(FaceID(fontIndex,
+                                                    faceIndex,
+                                                    index)));
+  if (ftcFaceID)
+  {
+    // found
+    if (!FTC_Manager_LookupFace(cacheManager_, ftcFaceID, &face))
+      name = QString("%1 %2")
+               .arg(face->family_name)
+               .arg(face->style_name);
+  }
+  else
+  {
+    // not found; try to load triplet (fontIndex, faceIndex, index)
+    ftcFaceID = reinterpret_cast<FTC_FaceID>(faceCounter_);
+    faceIDMap_.insert(FaceID(fontIndex, faceIndex, index),
+                      faceCounter_++);
+
+    if (!FTC_Manager_LookupFace(cacheManager_, ftcFaceID, &face))
+      name = QString("%1 %2")
+               .arg(face->family_name)
+               .arg(face->style_name);
+    else
+    {
+      faceIDMap_.remove(FaceID(fontIndex, faceIndex, 0));
+      faceCounter_--;
+    }
+  }
+
+  return name;
+}
+
+
 int
 Engine::loadFont(int fontIndex,
                  long faceIndex,
@@ -394,6 +438,14 @@ Engine::numberOfOpenedFonts()
 }
 
 
+bool
+Engine::fontValid()
+{
+  // TODO: use fallback font
+  return ftSize_ != NULL;
+}
+
+
 void
 Engine::openFonts(QStringList fontFileNames)
 {
diff --git a/src/ftinspect/engine/engine.hpp b/src/ftinspect/engine/engine.hpp
index 664c4b98..351cf5b9 100644
--- a/src/ftinspect/engine/engine.hpp
+++ b/src/ftinspect/engine/engine.hpp
@@ -85,6 +85,7 @@ public:
   int numberOfOpenedFonts();
 
   // (for current fonts)
+  bool fontValid();
   int currentFontType() const { return fontType_; }
   const QString& currentFamilyName() { return curFamilyName_; }
   const QString& currentStyleName() { return curStyleName_; }
@@ -92,6 +93,7 @@ public:
   long numberOfFaces(int fontIndex);
   int numberOfNamedInstances(int fontIndex,
                              long faceIndex);
+  QString namedInstanceName(int fontIndex, long faceIndex, int index);
 
   //////// Setters (direct or indirect)
 
diff --git a/src/ftinspect/ftinspect.cpp b/src/ftinspect/ftinspect.cpp
index 91da7809..4060f9c9 100644
--- a/src/ftinspect/ftinspect.cpp
+++ b/src/ftinspect/ftinspect.cpp
@@ -23,7 +23,6 @@ main(int argc,
 
   Engine engine;
   MainGUI gui(&engine);
-  gui.setDefaults();
 
   gui.show();
 
diff --git a/src/ftinspect/maingui.cpp b/src/ftinspect/maingui.cpp
new file mode 100644
index 00000000..30fca199
--- /dev/null
+++ b/src/ftinspect/maingui.cpp
@@ -0,0 +1,291 @@
+// maingui.cpp
+
+// Copyright (C) 2016-2022 by Werner Lemberg.
+
+
+#include "maingui.hpp"
+
+#include <QApplication>
+#include <QFileDialog>
+#include <QMessageBox>
+#include <QSettings>
+#include <QScrollBar>
+#include <QStatusBar>
+
+
+MainGUI::MainGUI(Engine* engine)
+: engine_(engine)
+{
+  createLayout();
+  createConnections();
+  createActions();
+  createMenus();
+
+  readSettings();
+  setUnifiedTitleAndToolBarOnMac(true);
+
+  show();
+}
+
+
+MainGUI::~MainGUI()
+{
+  // empty
+}
+
+
+// overloading
+
+void
+MainGUI::closeEvent(QCloseEvent* event)
+{
+  writeSettings();
+  event->accept();
+}
+
+
+void
+MainGUI::keyPressEvent(QKeyEvent* event)
+{
+  // Delegate key events to tabs
+  if (!tabWidget_->currentWidget()->eventFilter(this, event))
+    QMainWindow::keyPressEvent(event);
+}
+
+
+void
+MainGUI::about()
+{
+  QMessageBox::about(
+    this,
+    tr("About ftinspect"),
+    tr("<p>This is <b>ftinspect</b> version %1<br>"
+       " Copyright %2 2016-2022<br>"
+       " by Werner Lemberg <tt>&lt;wl@gnu.org&gt;</tt></p>"
+       ""
+       "<p><b>ftinspect</b> shows how a font gets rendered"
+       " by FreeType, allowing control over virtually"
+       " all rendering parameters.</p>"
+       ""
+       "<p>License:"
+       " <a 
href='https://gitlab.freedesktop.org/freetype/freetype/-/blob/master/docs/FTL.TXT'>FreeType"
+       " License (FTL)</a> or"
+       " <a 
href='https://gitlab.freedesktop.org/freetype/freetype/-/blob/master/docs/GPLv2.TXT'>GNU"
+       " GPLv2</a></p>")
+       .arg(QApplication::applicationVersion())
+       .arg(QChar(0xA9)));
+}
+
+
+void
+MainGUI::aboutQt()
+{
+  QApplication::aboutQt();
+}
+
+
+void
+MainGUI::loadFonts()
+{
+  QStringList files = QFileDialog::getOpenFileNames(
+                        this,
+                        tr("Load one or more fonts"),
+                        QDir::homePath(),
+                        "",
+                        NULL,
+                        QFileDialog::ReadOnly);
+  openFonts(files);
+}
+
+
+void
+MainGUI::openFonts(QStringList const& fileNames)
+{
+  engine_->openFonts(fileNames);
+  tripletSelector_->repopulateFonts();
+}
+
+
+void
+MainGUI::onTripletChanged()
+{
+  auto state = settingPanel_->blockSignals(true);
+  settingPanel_->onFontChanged();
+  settingPanel_->blockSignals(state);
+  reloadCurrentTabFont();
+}
+
+
+void
+MainGUI::switchTab()
+{
+  reloadCurrentTabFont();
+  lastTab_ = tabWidget_->currentWidget();
+}
+
+
+void
+MainGUI::repaintCurrentTab()
+{
+  applySettings();
+  tabs_[tabWidget_->currentIndex()]->repaintGlyph();
+}
+
+
+void
+MainGUI::reloadCurrentTabFont()
+{
+  engine_->resetCache();
+  applySettings();
+  if (tabWidget_->currentIndex() > 0 
+      && static_cast<size_t>(tabWidget_->currentIndex()) < tabs_.size())
+    tabs_[tabWidget_->currentIndex()]->reloadFont();
+}
+
+
+void
+MainGUI::applySettings()
+{
+  settingPanel_->applySettings();
+  settingPanel_->applyDelayedSettings();
+}
+
+
+// XXX distances are specified in pixels,
+//     making the layout dependent on the output device resolution
+void
+MainGUI::createLayout()
+{
+  // left side
+  settingPanel_ = new SettingPanel(this, engine_);
+
+  leftLayout_ = new QVBoxLayout; // The only point is to set a margin->remove?
+  leftLayout_->addWidget(settingPanel_);
+  leftLayout_->setContentsMargins(32, 32, 0, 16);
+
+  // we don't want to expand the left side horizontally;
+  // to change the policy we have to use a widget wrapper
+  leftWidget_ = new QWidget(this);
+  leftWidget_->setLayout(leftLayout_);
+  leftWidget_->setMaximumWidth(400);
+
+  // right side
+  // TODO: create tabs here
+
+  tabWidget_ = new QTabWidget(this);
+  tabWidget_->setObjectName("mainTab"); // for stylesheet
+
+  // Note those two list must be in sync
+  // TODO: add tabs and tooltips here
+  
+  tripletSelector_ = new TripletSelector(this, engine_);
+
+  rightLayout_ = new QVBoxLayout;
+  //rightLayout_->addWidget(fontNameLabel_);
+  rightLayout_->addWidget(tabWidget_); // same for `leftLayout_`: Remove?
+  rightLayout_->setContentsMargins(16, 32, 32, 16);
+
+  // for symmetry with the left side use a widget also
+  rightWidget_ = new QWidget(this);
+  rightWidget_->setLayout(rightLayout_);
+
+  // the whole thing
+  mainPartLayout_ = new QHBoxLayout;
+  mainPartLayout_->addWidget(leftWidget_);
+  mainPartLayout_->addWidget(rightWidget_);
+
+  ftinspectLayout_ = new QVBoxLayout;
+  ftinspectLayout_->setSpacing(0);
+  ftinspectLayout_->addLayout(mainPartLayout_);
+  ftinspectLayout_->addWidget(tripletSelector_);
+  ftinspectLayout_->setContentsMargins(0, 0, 0, 0);
+
+  ftinspectWidget_ = new QWidget(this);
+  ftinspectWidget_->setLayout(ftinspectLayout_);
+
+  ftinspectLayout_->setSizeConstraint(QLayout::SetNoConstraint);
+  layout()->setSizeConstraint(QLayout::SetNoConstraint);
+  resize(ftinspectWidget_->minimumSizeHint().width(),
+         700 * logicalDpiY() / 96);
+
+  statusBar()->hide(); // remove the extra space
+  setCentralWidget(ftinspectWidget_);
+  setWindowTitle("ftinspect");
+}
+
+
+void
+MainGUI::createConnections()
+{
+  connect(settingPanel_, &SettingPanel::fontReloadNeeded,
+          this, &MainGUI::reloadCurrentTabFont);
+  connect(settingPanel_, &SettingPanel::repaintNeeded,
+          this, &MainGUI::repaintCurrentTab);
+
+  connect(tabWidget_, &QTabWidget::currentChanged,
+          this, &MainGUI::switchTab);
+
+  connect(tripletSelector_, &TripletSelector::tripletChanged,
+          this, &MainGUI::onTripletChanged);
+}
+
+
+void
+MainGUI::createActions()
+{
+  loadFontsAct_ = new QAction(tr("&Load Fonts"), this);
+  loadFontsAct_->setShortcuts(QKeySequence::Open);
+  connect(loadFontsAct_, &QAction::triggered, this, &MainGUI::loadFonts);
+
+  closeFontAct_ = new QAction(tr("&Close Font"), this);
+  closeFontAct_->setShortcuts(QKeySequence::Close);
+  connect(closeFontAct_, &QAction::triggered,
+          tripletSelector_, &TripletSelector::closeCurrentFont);
+
+  exitAct_ = new QAction(tr("E&xit"), this);
+  exitAct_->setShortcuts(QKeySequence::Quit);
+  connect(exitAct_, &QAction::triggered, this, &MainGUI::close);
+
+  aboutAct_ = new QAction(tr("&About"), this);
+  connect(aboutAct_, &QAction::triggered, this, &MainGUI::about);
+
+  aboutQtAct_ = new QAction(tr("About &Qt"), this);
+  connect(aboutQtAct_, &QAction::triggered, this, &MainGUI::aboutQt);
+}
+
+
+void
+MainGUI::createMenus()
+{
+  menuFile_ = menuBar()->addMenu(tr("&File"));
+  menuFile_->addAction(loadFontsAct_);
+  menuFile_->addAction(closeFontAct_);
+  menuFile_->addAction(exitAct_);
+
+  menuHelp_ = menuBar()->addMenu(tr("&Help"));
+  menuHelp_->addAction(aboutAct_);
+  menuHelp_->addAction(aboutQtAct_);
+}
+
+
+void
+MainGUI::readSettings()
+{
+  QSettings settings;
+//  QPoint pos = settings.value("pos", QPoint(200, 200)).toPoint();
+//  QSize size = settings.value("size", QSize(400, 400)).toSize();
+//  resize(size);
+//  move(pos);
+}
+
+
+void
+MainGUI::writeSettings()
+{
+  QSettings settings;
+//  settings.setValue("pos", pos());
+//  settings.setValue("size", size());
+}
+
+
+// end of maingui.cpp
diff --git a/src/ftinspect/maingui.hpp b/src/ftinspect/maingui.hpp
new file mode 100644
index 00000000..a5a53977
--- /dev/null
+++ b/src/ftinspect/maingui.hpp
@@ -0,0 +1,103 @@
+// maingui.hpp
+
+// Copyright (C) 2016-2022 by Werner Lemberg.
+
+
+#pragma once
+
+#include "engine/engine.hpp"
+#include "widgets/tripletselector.hpp"
+#include "panels/settingpanel.hpp"
+#include "panels/abstracttab.hpp"
+
+#include <vector>
+#include <QAction>
+#include <QCloseEvent>
+#include <QGridLayout>
+#include <QDockWidget>
+#include <QBoxLayout>
+#include <QLabel>
+#include <QMainWindow>
+#include <QMenu>
+#include <QMenuBar>
+#include <QTabWidget>
+
+
+class MainGUI
+: public QMainWindow
+{
+  Q_OBJECT
+
+public:
+  MainGUI(Engine* engine);
+  ~MainGUI() override;
+
+  friend class Engine;
+  friend FT_Error faceRequester(FTC_FaceID,
+                                FT_Library,
+                                FT_Pointer,
+                                FT_Face*);
+
+protected:
+  void closeEvent(QCloseEvent*) override;
+  void keyPressEvent(QKeyEvent* event) override;
+
+private slots:
+  void about();
+  void aboutQt();
+  void repaintCurrentTab();
+  void reloadCurrentTabFont();
+  void loadFonts();
+  void onTripletChanged();
+  void switchTab();
+
+private:
+  Engine* engine_;
+  
+  int currentNumberOfGlyphs_;
+
+  // layout related stuff
+  QAction *aboutAct_;
+  QAction *aboutQtAct_;
+  QAction *closeFontAct_;
+  QAction *exitAct_;
+  QAction *loadFontsAct_;
+
+  QVBoxLayout *ftinspectLayout_;
+  QHBoxLayout *mainPartLayout_;
+
+  QLocale *locale_;
+
+  QMenu *menuFile_;
+  QMenu *menuHelp_;
+  
+  TripletSelector* tripletSelector_;
+  
+  QVBoxLayout *leftLayout_;
+  QVBoxLayout *rightLayout_;
+
+  QWidget *ftinspectWidget_;
+  QWidget *leftWidget_;
+  QWidget *rightWidget_;
+
+  SettingPanel* settingPanel_;
+
+  QTabWidget* tabWidget_;
+  std::vector<AbstractTab*> tabs_;
+  QWidget* lastTab_ = NULL;
+
+  void openFonts(QStringList const& fileNames);
+
+  void applySettings();
+
+  void createActions();
+  void createConnections();
+  void createLayout();
+  void createMenus();
+
+  void readSettings();
+  void writeSettings();
+};
+
+
+// end of maingui.hpp
diff --git a/src/ftinspect/meson.build b/src/ftinspect/meson.build
index 07ec87ed..5ea21973 100644
--- a/src/ftinspect/meson.build
+++ b/src/ftinspect/meson.build
@@ -31,9 +31,12 @@ if qt5_dep.found()
     'glyphcomponents/grid.cpp',
 
     'widgets/customwidgets.cpp',
+    'widgets/tripletselector.cpp',
 
     'models/customcomboboxmodels.cpp',
 
+    'panels/settingpanel.cpp',
+
     'ftinspect.cpp',
     'maingui.cpp',
     'uihelper.cpp',
@@ -43,8 +46,10 @@ if qt5_dep.found()
     moc_headers: [
       'engine/fontfilemanager.hpp',
       'widgets/customwidgets.hpp',
+      'widgets/tripletselector.hpp',
       'maingui.hpp',
       'models/customcomboboxmodels.hpp',
+      'panels/settingpanel.hpp',
     ],
     dependencies: qt5_dep)
 
diff --git a/src/ftinspect/panels/abstracttab.hpp 
b/src/ftinspect/panels/abstracttab.hpp
new file mode 100644
index 00000000..bd27f8da
--- /dev/null
+++ b/src/ftinspect/panels/abstracttab.hpp
@@ -0,0 +1,20 @@
+// abstracttab.hpp
+
+// Copyright (C) 2022 by Charlie Jiang.
+
+#pragma once
+
+// This is an pure abstract interface for a ftinspect "tab".
+// The interface itself does not inherit from `QWidget`, but should be used as
+// the second base class.
+class AbstractTab
+{
+public:
+  virtual ~AbstractTab() = default; // must be `virtual` for `dynamic_cast`
+  
+  virtual void repaintGlyph() = 0;
+  virtual void reloadFont() = 0;
+};
+
+
+// end of abstracttab.hpp
diff --git a/src/ftinspect/panels/settingpanel.cpp 
b/src/ftinspect/panels/settingpanel.cpp
new file mode 100644
index 00000000..cf5871c8
--- /dev/null
+++ b/src/ftinspect/panels/settingpanel.cpp
@@ -0,0 +1,670 @@
+// settingpanel.cpp
+
+// Copyright (C) 2022 by Charlie Jiang.
+
+#include "settingpanel.hpp"
+
+#include "../uihelper.hpp"
+
+#include <QColorDialog>
+
+// for `FT_DEBUG_AUTOFIT`
+#include <freetype/config/ftoption.h>
+
+SettingPanel::SettingPanel(QWidget* parent,
+                           Engine* engine)
+: QWidget(parent),
+  engine_(engine)
+{
+#ifdef FT_DEBUG_AUTOFIT
+  debugMode_ = !comparatorMode_;
+#else
+  debugMode_ = false;
+#endif
+  createLayout();
+  setDefaults();
+  createConnections();
+  checkAllSettings();
+}
+
+
+int
+SettingPanel::antiAliasingModeIndex()
+{
+  return antiAliasingComboBox_->currentIndex();
+}
+
+
+bool
+SettingPanel::kerningEnabled()
+{
+  return kerningCheckBox_->isChecked();
+}
+
+
+bool
+SettingPanel::lsbRsbDeltaEnabled()
+{
+  return lsbRsbDeltaCheckBox_->isChecked();
+}
+
+
+void
+SettingPanel::populatePalettes()
+{
+  // TODO: Impl
+}
+
+
+void
+SettingPanel::checkAllSettings()
+{
+  onFontChanged();
+  checkAntiAliasing();
+}
+
+
+void
+SettingPanel::checkHinting()
+{
+  if (hintingCheckBox_->isChecked())
+  {
+    // TODO: tricky: disable auto-hinting
+    checkAutoHinting(); // this will emit repaint
+  }
+  else
+  {
+    hintingModeLabel_->setEnabled(false);
+    hintingModeComboBox_->setEnabled(false);
+
+    autoHintingCheckBox_->setEnabled(false);
+    if (debugMode_)
+    {
+      horizontalHintingCheckBox_->setEnabled(false);
+      verticalHintingCheckBox_->setEnabled(false);
+      blueZoneHintingCheckBox_->setEnabled(false);
+      segmentDrawingCheckBox_->setEnabled(false);
+    }
+
+    stemDarkeningCheckBox_->setEnabled(false);
+    antiAliasingComboBoxModel_->setLightAntiAliasingEnabled(false);
+    auto aaMode = antiAliasingComboBox_->currentIndex();
+    if (aaMode == AntiAliasingComboBoxModel::AntiAliasing_Light
+        || aaMode == AntiAliasingComboBoxModel::AntiAliasing_Light_SubPixel)
+      antiAliasingComboBox_->setCurrentIndex(
+        AntiAliasingComboBoxModel::AntiAliasing_Normal);
+    
+    emit repaintNeeded();
+  }
+}
+
+
+void
+SettingPanel::checkHintingMode()
+{
+  //if (!comparatorMode_)
+  //  applyDelayedSettings();
+
+  emit fontReloadNeeded();
+}
+
+
+void
+SettingPanel::checkAutoHinting()
+{
+  if (autoHintingCheckBox_->isChecked())
+  {
+    hintingModeLabel_->setEnabled(false);
+    hintingModeComboBox_->setEnabled(false);
+
+    if (debugMode_)
+    {
+      horizontalHintingCheckBox_->setEnabled(true);
+      verticalHintingCheckBox_->setEnabled(true);
+      blueZoneHintingCheckBox_->setEnabled(true);
+      segmentDrawingCheckBox_->setEnabled(true);
+    }
+
+    antiAliasingComboBoxModel_->setLightAntiAliasingEnabled(true);
+    auto aaMode = antiAliasingComboBox_->currentIndex();
+    stemDarkeningCheckBox_->setEnabled(
+      aaMode == AntiAliasingComboBoxModel::AntiAliasing_Light
+      || aaMode == AntiAliasingComboBoxModel::AntiAliasing_Light_SubPixel);
+  }
+  else
+  {
+    if (engine_->currentFontType() == Engine::FontType_CFF
+        || engine_->currentFontType() == Engine::FontType_TrueType)
+    {
+      hintingModeLabel_->setEnabled(true);
+      hintingModeComboBox_->setEnabled(true);
+    }
+
+    if (debugMode_)
+    {
+      horizontalHintingCheckBox_->setEnabled(false);
+      verticalHintingCheckBox_->setEnabled(false);
+      blueZoneHintingCheckBox_->setEnabled(false);
+      segmentDrawingCheckBox_->setEnabled(false);
+    }
+
+    antiAliasingComboBoxModel_->setLightAntiAliasingEnabled(false);
+    stemDarkeningCheckBox_->setEnabled(false);
+
+    auto aaMode = antiAliasingComboBox_->currentIndex();
+    if (aaMode == AntiAliasingComboBoxModel::AntiAliasing_Light
+        || aaMode == AntiAliasingComboBoxModel::AntiAliasing_Light_SubPixel)
+      antiAliasingComboBox_->setCurrentIndex(
+          AntiAliasingComboBoxModel::AntiAliasing_Normal);
+  }
+  emit repaintNeeded();
+}
+
+
+void
+SettingPanel::checkAntiAliasing()
+{
+  int index = antiAliasingComboBox_->currentIndex();
+  auto isMono = index == AntiAliasingComboBoxModel::AntiAliasing_None;
+  auto isLight
+    = index == AntiAliasingComboBoxModel::AntiAliasing_Light
+      || index == AntiAliasingComboBoxModel::AntiAliasing_Light_SubPixel;
+  auto disableLCD
+    = index == AntiAliasingComboBoxModel::AntiAliasing_None
+      || index == AntiAliasingComboBoxModel::AntiAliasing::AntiAliasing_Normal
+      || isLight;
+
+  lcdFilterLabel_->setEnabled(!disableLCD);
+  lcdFilterComboBox_->setEnabled(!disableLCD);
+  stemDarkeningCheckBox_->setEnabled(isLight);
+  gammaSlider_->setEnabled(!isMono);
+
+  emit repaintNeeded();
+}
+
+
+void
+SettingPanel::checkPalette()
+{
+  paletteComboBox_->setEnabled(colorLayerCheckBox_->isChecked());
+  emit repaintNeeded();
+}
+
+
+void
+SettingPanel::checkStemDarkening()
+{
+  //if (!comparatorMode_)
+  //  applyDelayedSettings();
+
+  emit fontReloadNeeded();
+}
+
+
+void
+SettingPanel::openBackgroundPicker()
+{
+  auto result = QColorDialog::getColor(backgroundColor_, 
+                                       this,
+                                       tr("Background Color"));
+  if (result.isValid())
+  {
+    backgroundColor_ = result;
+    resetColorBlocks();
+    emit repaintNeeded();
+  }
+}
+
+
+void
+SettingPanel::openForegroundPicker()
+{
+  auto result = QColorDialog::getColor(foregroundColor_, 
+                                       this,
+                                       tr("Foreground Color"),
+                                       QColorDialog::ShowAlphaChannel);
+  if (result.isValid())
+  {
+    foregroundColor_ = result;
+    resetColorBlocks();
+    emit repaintNeeded();
+  }
+}
+
+
+void
+SettingPanel::updateGamma()
+{
+  gammaValueLabel_->setText(QString::number(gammaSlider_->value() / 10.0,
+                           'f',
+                           1));
+  emit repaintNeeded();
+}
+
+
+void
+SettingPanel::resetColorBlocks()
+{
+  foregroundBlock_->setStyleSheet(
+    QString("QWidget {background-color: rgba(%1, %2, %3, %4);}")
+      .arg(foregroundColor_.red())
+      .arg(foregroundColor_.green())
+      .arg(foregroundColor_.blue())
+      .arg(foregroundColor_.alpha()));
+  backgroundBlock_->setStyleSheet(
+    QString("QWidget {background-color: rgba(%1, %2, %3, %4);}")
+      .arg(backgroundColor_.red())
+      .arg(backgroundColor_.green())
+      .arg(backgroundColor_.blue())
+      .arg(backgroundColor_.alpha()));
+}
+
+
+void
+SettingPanel::onFontChanged()
+{
+  auto blockState = blockSignals(signalsBlocked());
+  
+  if (engine_->currentFontType() == Engine::FontType_CFF)
+  {
+    hintingModeComboBoxModel_->setCurrentEngineType(
+      HintingModeComboBoxModel::HintingEngineType_CFF, false);
+    hintingModeComboBox_->setCurrentIndex(currentCFFHintingMode_);
+  }
+  else if (engine_->currentFontType() == Engine::FontType_TrueType)
+  {
+    // TODO: tricky
+    hintingModeComboBoxModel_->setCurrentEngineType(
+      HintingModeComboBoxModel::HintingEngineType_TrueType, false);
+    hintingModeComboBox_->setCurrentIndex(currentTTInterpreterVersion_);
+  }
+  else
+  {
+    hintingModeLabel_->setEnabled(false);
+    hintingModeComboBox_->setEnabled(false);
+  }
+
+  checkHinting();
+
+  //engine_->reloadFont();
+  //auto hasColor = engine_->currentFontHasColorLayers();
+  //colorLayerCheckBox_->setEnabled(hasColor);
+  //if (!hasColor)
+  //  colorLayerCheckBox_->setChecked(false);
+  populatePalettes();
+  //mmgxPanel_->reloadFont();
+  blockSignals(blockState);
+
+  // Place this after `blockSignals` to let the signals emitted normally
+  //auto bmapOnly = engine_->currentFontBitmapOnly();
+  //embeddedBitmapCheckBox_->setEnabled(
+  //  !bmapOnly && engine_->currentFontHasEmbeddedBitmap());
+  //if (bmapOnly)
+  //  embeddedBitmapCheckBox_->setChecked(true);
+}
+
+
+void
+SettingPanel::applySettings()
+{
+  engine_->setLcdFilter(
+    static_cast<FT_LcdFilter>(lcdFilterComboboxModel_->indexToValue(
+      lcdFilterComboBox_->currentIndex())));
+
+  auto aaSettings = antiAliasingComboBoxModel_->indexToValue(
+    antiAliasingComboBox_->currentIndex());
+  engine_->setAntiAliasingTarget(aaSettings.loadFlag);
+  //engine_->setRenderMode(aaSettings.renderMode);
+
+  //engine_->setAntiAliasingEnabled(antiAliasingComboBox_->currentIndex()
+  //  != AntiAliasingComboBoxModel::AntiAliasing_None);
+  engine_->setHinting(hintingCheckBox_->isChecked());
+  engine_->setAutoHinting(autoHintingCheckBox_->isChecked());
+
+  if (debugMode_)
+  {
+    engine_->setHorizontalHinting(horizontalHintingCheckBox_->isChecked());
+    engine_->setVerticalHinting(verticalHintingCheckBox_->isChecked());
+    engine_->setBlueZoneHinting(blueZoneHintingCheckBox_->isChecked());
+    engine_->setShowSegments(segmentDrawingCheckBox_->isChecked());
+  }
+
+  engine_->setGamma(gammaSlider_->value() / 10.0);
+
+  //engine_->setEmbeddedBitmap(embeddedBitmapCheckBox_->isChecked());
+  //engine_->setPaletteIndex(paletteComboBox_->currentIndex());
+
+  //engine_->setUseColorLayer(colorLayerCheckBox_->isChecked());
+  //engine_->setLCDUsesBGR(aaSettings.isBGR);
+  //engine_->setLCDSubPixelPositioning(
+  //  antiAliasingComboBox_->currentIndex()
+  //    == AntiAliasingComboBoxModel::AntiAliasing_Light_SubPixel);
+
+  //engine_->renderingEngine()->setForeground(foregroundColor_.rgba());
+  //engine_->renderingEngine()->setBackground(backgroundColor_.rgba());
+  //mmgxPanel_->applySettings();
+}
+
+
+void
+SettingPanel::applyDelayedSettings()
+{
+  // This must not be combined into `applySettings`:
+  // those engine manipulations will reset the whole cache!!
+  // Therefore must only be called when the selection of the combo box actually
+  // changes a.k.a. QComboBox::activate.
+
+  int index = hintingModeComboBox_->currentIndex();
+
+  if (engine_->currentFontType() == Engine::FontType_CFF)
+  {
+    engine_->setCFFHintingMode(
+      hintingModeComboBoxModel_->indexToCFFMode(index));
+    if (index >= 0)
+      currentCFFHintingMode_ = index;
+  }
+  else if (engine_->currentFontType() == Engine::FontType_TrueType)
+  {
+    engine_->setTTInterpreterVersion(
+      hintingModeComboBoxModel_->indexToTTInterpreterVersion(index));
+    if (index >= 0)
+      currentTTInterpreterVersion_ = index;
+  }
+
+  //engine_->setStemDarkening(stemDarkeningCheckBox_->isChecked());
+}
+
+
+void
+SettingPanel::createLayout()
+{
+  hintingCheckBox_ = new QCheckBox(tr("Hinting"), this);
+
+  hintingModeLabel_ = new QLabel(tr("Hinting Mode"), this);
+  hintingModeLabel_->setAlignment(Qt::AlignRight);
+
+  hintingModeComboBoxModel_ = new HintingModeComboBoxModel(this);
+  hintingModeComboBox_ = new QComboBox(this);
+  hintingModeComboBox_->setModel(hintingModeComboBoxModel_);
+  hintingModeLabel_->setBuddy(hintingModeComboBox_);
+
+  autoHintingCheckBox_ = new QCheckBox(tr("Auto-Hinting"), this);
+  stemDarkeningCheckBox_ = new QCheckBox(tr("Stem Darkening"), this);
+
+  if (debugMode_)
+  {
+    horizontalHintingCheckBox_ = new QCheckBox(tr("Horizontal Hinting"), this);
+    verticalHintingCheckBox_ = new QCheckBox(tr("Vertical Hinting"), this);
+    blueZoneHintingCheckBox_ = new QCheckBox(tr("Blue-Zone Hinting"), this);
+    segmentDrawingCheckBox_ = new QCheckBox(tr("Segment Drawing"), this);
+  }
+  
+  embeddedBitmapCheckBox_ = new QCheckBox(tr("Enable Embedded Bitmap"), this);
+  colorLayerCheckBox_ = new QCheckBox(tr("Enable Color Layer"), this);
+
+  antiAliasingLabel_ = new QLabel(tr("Anti-Aliasing"), this);
+  antiAliasingLabel_->setAlignment(Qt::AlignRight);
+
+  antiAliasingComboBoxModel_ = new AntiAliasingComboBoxModel(this);
+  antiAliasingComboBox_ = new QComboBox(this);
+  antiAliasingComboBox_->setModel(antiAliasingComboBoxModel_);
+  antiAliasingLabel_->setBuddy(antiAliasingComboBox_);
+
+  lcdFilterLabel_ = new QLabel(tr("LCD Filter"), this);
+  lcdFilterLabel_->setAlignment(Qt::AlignRight);
+
+  lcdFilterComboboxModel_ = new LCDFilterComboBoxModel(this);
+  lcdFilterComboBox_ = new QComboBox(this);
+  lcdFilterComboBox_->setModel(lcdFilterComboboxModel_);
+  lcdFilterLabel_->setBuddy(lcdFilterComboBox_);
+
+  paletteLabel_ = new QLabel(tr("Palette: "), this);
+
+  paletteComboBox_ = new QComboBox(this);
+  paletteLabel_->setBuddy(paletteComboBox_);
+
+  gammaLabel_ = new QLabel(tr("Gamma"), this);
+  gammaLabel_->setAlignment(Qt::AlignRight);
+  gammaSlider_ = new QSlider(Qt::Horizontal, this);
+  gammaSlider_->setRange(3, 30); // in 1/10th
+  gammaSlider_->setTickPosition(QSlider::TicksBelow);
+  gammaSlider_->setTickInterval(5);
+  gammaSlider_->setPageStep(1);
+  gammaSlider_->setSingleStep(1);
+  gammaLabel_->setBuddy(gammaSlider_);
+  gammaValueLabel_ = new QLabel(this);
+
+  // TODO: MM/GX
+  mmgxPanel_ = new QWidget(this);
+
+  backgroundButton_ = new QPushButton(tr("Background"), this);
+  foregroundButton_ = new QPushButton(tr("Foreground"), this);
+
+  backgroundBlock_ = new QFrame(this);
+  backgroundBlock_->setFrameStyle(QFrame::Box);
+  backgroundBlock_->setLineWidth(1);
+  backgroundBlock_->setFixedWidth(18);
+
+  foregroundBlock_ = new QFrame(this);
+  foregroundBlock_->setFrameStyle(QFrame::Box);
+  foregroundBlock_->setLineWidth(1);
+  foregroundBlock_->setFixedWidth(18);
+
+  generalTab_ = new QWidget(this);
+
+  generalTab_->setSizePolicy(QSizePolicy::MinimumExpanding,
+                             QSizePolicy::MinimumExpanding);
+
+  tab_ = new QTabWidget(this);
+  tab_->setSizePolicy(QSizePolicy::MinimumExpanding,
+                      QSizePolicy::MinimumExpanding);
+
+  // Tooltips
+  hintingCheckBox_->setToolTip(tr("Enable hinting a.k.a. grid-fitting."));
+  hintingModeComboBox_->setToolTip(
+    tr("Modes not available for current font type will be disabled. No "
+       "effect when auto-hinting is enabled"));
+  autoHintingCheckBox_->setToolTip(tr("Enable FreeType Auto-Hinter."));
+  if (debugMode_)
+  {
+    horizontalHintingCheckBox_->setToolTip(tr("(auto-hinter debug option)"));
+    verticalHintingCheckBox_  ->setToolTip(tr("(auto-hinter debug option)"));
+    blueZoneHintingCheckBox_  ->setToolTip(tr("(auto-hinter debug option)"));
+    segmentDrawingCheckBox_   ->setToolTip(tr("(auto-hinter debug option)"));
+  }
+  antiAliasingComboBox_->setToolTip(tr("Select anti-aliasing mode."));
+  lcdFilterComboBox_->setToolTip(
+    tr("Select LCD filter (only valid when LCD AA is enabled)."));
+  embeddedBitmapCheckBox_->setToolTip(tr(
+    "Enable embedded bitmap strikes (force enabled for bitmap-only fonts)."));
+  stemDarkeningCheckBox_->setToolTip(
+    tr("Enable stem darkening (only valid for auto-hinter with gamma "
+       "correction enabled and with Light AA modes)."));
+  gammaSlider_->setToolTip("Gamma correction value.");
+  colorLayerCheckBox_->setToolTip(tr("Enable color layer rendering."));
+  paletteComboBox_->setToolTip(tr("Select color layer palette (only valid when 
"
+                                  "any palette exists in the font)."));
+  backgroundButton_->setToolTip(tr("Set canvas background color."));
+  foregroundButton_->setToolTip(tr("Set text color."));
+
+  // Layouting
+  if (debugMode_)
+  {
+    debugLayout_ = new QVBoxLayout;
+    debugLayout_->setContentsMargins(20, 0, 0, 0);
+    debugLayout_->addWidget(horizontalHintingCheckBox_);
+    debugLayout_->addWidget(verticalHintingCheckBox_);
+    debugLayout_->addWidget(blueZoneHintingCheckBox_);
+    debugLayout_->addWidget(segmentDrawingCheckBox_);
+  }
+
+  gammaLayout_ = new QHBoxLayout;
+  gammaLayout_->addWidget(gammaLabel_);
+  gammaLayout_->addWidget(gammaSlider_);
+  gammaLayout_->addWidget(gammaValueLabel_);
+
+  colorPickerLayout_ = new QHBoxLayout;
+  colorPickerLayout_->addWidget(backgroundBlock_);
+  colorPickerLayout_->addWidget(backgroundButton_, 1);
+  colorPickerLayout_->addWidget(foregroundButton_, 1);
+  colorPickerLayout_->addWidget(foregroundBlock_);
+
+  createLayoutNormal();
+  // TODO: Comparator mode.
+
+  mainLayout_ = new QVBoxLayout;
+  mainLayout_->addWidget(tab_);
+  setLayout(mainLayout_);
+  mainLayout_->setContentsMargins(0, 0, 0, 0);
+  setContentsMargins(0, 0, 0, 0);
+}
+
+
+void
+SettingPanel::createLayoutNormal()
+{
+  generalTabLayout_ = new QGridLayout;
+
+  gridLayout2ColAddWidget(generalTabLayout_, hintingCheckBox_);
+  gridLayout2ColAddWidget(generalTabLayout_, 
+                          hintingModeLabel_, hintingModeComboBox_);
+  gridLayout2ColAddWidget(generalTabLayout_, autoHintingCheckBox_);
+
+  if (debugMode_)
+    gridLayout2ColAddLayout(generalTabLayout_, debugLayout_);
+  
+  gridLayout2ColAddItem(generalTabLayout_,
+                        new QSpacerItem(0, 20, QSizePolicy::Minimum,
+                                        QSizePolicy::MinimumExpanding));
+
+  gridLayout2ColAddWidget(generalTabLayout_, 
+                          antiAliasingLabel_, antiAliasingComboBox_);
+  gridLayout2ColAddWidget(generalTabLayout_, 
+                          lcdFilterLabel_, lcdFilterComboBox_);
+  
+  gridLayout2ColAddItem(generalTabLayout_,
+                        new QSpacerItem(0, 20, QSizePolicy::Minimum,
+                                        QSizePolicy::MinimumExpanding));
+
+  gridLayout2ColAddLayout(generalTabLayout_, colorPickerLayout_);
+  gridLayout2ColAddLayout(generalTabLayout_, gammaLayout_);
+  gridLayout2ColAddWidget(generalTabLayout_, stemDarkeningCheckBox_);
+  gridLayout2ColAddWidget(generalTabLayout_, embeddedBitmapCheckBox_);
+  gridLayout2ColAddWidget(generalTabLayout_, colorLayerCheckBox_);
+  gridLayout2ColAddWidget(generalTabLayout_, 
+                          paletteLabel_, paletteComboBox_);
+  
+  gridLayout2ColAddItem(generalTabLayout_,
+                        new QSpacerItem(0, 20, QSizePolicy::Minimum,
+                                        QSizePolicy::MinimumExpanding));
+
+  generalTabLayout_->setColumnStretch(1, 1);
+  generalTab_->setLayout(generalTabLayout_);
+
+  tab_->addTab(generalTab_, tr("General"));
+  tab_->addTab(mmgxPanel_, tr("MM/GX"));
+
+  tab_->setTabToolTip(0, tr("General settings."));
+  tab_->setTabToolTip(1, tr("MM/GX axis parameters."));
+}
+
+
+void
+SettingPanel::createConnections()
+{
+  // use `qOverload` here to prevent ambiguity.
+  connect(hintingModeComboBox_, 
+          QOverload<int>::of(&QComboBox::currentIndexChanged),
+          this, &SettingPanel::checkHintingMode);
+  connect(antiAliasingComboBox_,
+          QOverload<int>::of(&QComboBox::currentIndexChanged),
+          this, &SettingPanel::checkAntiAliasing);
+  connect(lcdFilterComboBox_, 
+          QOverload<int>::of(&QComboBox::currentIndexChanged),
+          this, &SettingPanel::repaintNeeded);
+  connect(paletteComboBox_,
+          QOverload<int>::of(&QComboBox::currentIndexChanged), 
+          this, &SettingPanel::repaintNeeded);
+
+  connect(gammaSlider_, &QSlider::valueChanged,
+          this, &SettingPanel::updateGamma);
+  
+  connect(hintingCheckBox_, &QCheckBox::clicked,
+          this, &SettingPanel::checkHinting);
+
+  if (debugMode_)
+  {
+    connect(horizontalHintingCheckBox_, &QCheckBox::clicked,
+            this, &SettingPanel::repaintNeeded);
+    connect(verticalHintingCheckBox_, &QCheckBox::clicked,
+            this, &SettingPanel::repaintNeeded);
+    connect(blueZoneHintingCheckBox_, &QCheckBox::clicked,
+            this, &SettingPanel::repaintNeeded);
+    connect(segmentDrawingCheckBox_, &QCheckBox::clicked,
+            this, &SettingPanel::repaintNeeded);
+  }
+
+  connect(autoHintingCheckBox_, &QCheckBox::clicked,
+          this, &SettingPanel::checkAutoHinting);
+  connect(embeddedBitmapCheckBox_, &QCheckBox::clicked,
+          this, &SettingPanel::fontReloadNeeded);
+  connect(stemDarkeningCheckBox_, &QCheckBox::clicked,
+          this, &SettingPanel::checkStemDarkening);
+  connect(colorLayerCheckBox_, &QCheckBox::clicked,
+          this, &SettingPanel::checkPalette);
+
+  connect(backgroundButton_, &QPushButton::clicked,
+          this, &SettingPanel::openBackgroundPicker);
+  connect(foregroundButton_, &QPushButton::clicked,
+          this, &SettingPanel::openForegroundPicker);
+}
+
+
+void
+SettingPanel::setDefaults()
+{
+  Engine::EngineDefaultValues& defaults = engine_->engineDefaults();
+
+  hintingModeComboBoxModel_->setSupportedModes(
+    { defaults.ttInterpreterVersionDefault,
+      defaults.ttInterpreterVersionOther,
+      defaults.ttInterpreterVersionOther1 },
+    { defaults.cffHintingEngineDefault, 
+      defaults.cffHintingEngineOther });
+
+  currentCFFHintingMode_
+    = hintingModeComboBoxModel_->cffModeToIndex(
+    defaults.cffHintingEngineDefault);
+  currentTTInterpreterVersion_
+    = hintingModeComboBoxModel_->ttInterpreterVersionToIndex(
+        defaults.ttInterpreterVersionDefault);
+
+  hintingCheckBox_->setChecked(true);
+
+  antiAliasingComboBox_->setCurrentIndex(
+    AntiAliasingComboBoxModel::AntiAliasing_Normal);
+  lcdFilterComboBox_->setCurrentIndex(
+    LCDFilterComboBoxModel::LCDFilter_Light);
+
+  if (debugMode_)
+  {
+    horizontalHintingCheckBox_->setChecked(true);
+    verticalHintingCheckBox_->setChecked(true);
+    blueZoneHintingCheckBox_->setChecked(true);
+    embeddedBitmapCheckBox_->setChecked(false);
+  }
+  
+  colorLayerCheckBox_->setChecked(true);
+  paletteComboBox_->setEnabled(false);
+
+  // These need to be set even in Comperator mode.
+  backgroundColor_ = Qt::white;
+  foregroundColor_ = Qt::black;
+  resetColorBlocks();
+
+  gammaSlider_->setValue(18); // 1.8
+  updateGamma();
+}
+
+
+// end of settingpanel.cpp
diff --git a/src/ftinspect/panels/settingpanel.hpp 
b/src/ftinspect/panels/settingpanel.hpp
new file mode 100644
index 00000000..7f1d5e32
--- /dev/null
+++ b/src/ftinspect/panels/settingpanel.hpp
@@ -0,0 +1,131 @@
+// settingpanel.hpp
+
+// Copyright (C) 2022 by Charlie Jiang.
+
+#pragma once
+
+#include "../engine/engine.hpp"
+#include "../models/customcomboboxmodels.hpp"
+
+#include <QWidget>
+#include <QTabWidget>
+#include <QLabel>
+#include <QComboBox>
+#include <QCheckBox>
+#include <QGridLayout>
+#include <QBoxLayout>
+#include <QPushButton>
+
+class SettingPanel
+: public QWidget
+{
+  Q_OBJECT
+public:
+  SettingPanel(QWidget* parent, Engine* engine);
+  ~SettingPanel() override = default;
+
+  void onFontChanged();
+  void applySettings();
+  /*
+   * When in comparator mode, this is needed to sync the hinting modes when
+   * reloading the font.
+   */
+  void applyDelayedSettings();
+
+  //////// Getters/Setters
+
+  int antiAliasingModeIndex();
+  bool kerningEnabled();
+  bool lsbRsbDeltaEnabled();
+
+signals:
+  void fontReloadNeeded();
+  void repaintNeeded();
+
+private:
+  Engine* engine_;
+
+  int currentCFFHintingMode_;
+  int currentTTInterpreterVersion_;
+  
+  bool debugMode_ = false;
+
+  QTabWidget* tab_;
+
+  QWidget* generalTab_;
+  QWidget* hintingRenderingTab_;
+  //SettingPanelMMGX* mmgxPanel_;
+  QWidget* mmgxPanel_;
+
+  QLabel* gammaLabel_;
+  QLabel* gammaValueLabel_;
+  QLabel* antiAliasingLabel_;
+  QLabel* hintingModeLabel_;
+  QLabel* lcdFilterLabel_;
+  QLabel* paletteLabel_;
+
+  QCheckBox* hintingCheckBox_;
+  QCheckBox* horizontalHintingCheckBox_;
+  QCheckBox* verticalHintingCheckBox_;
+  QCheckBox* blueZoneHintingCheckBox_;
+  QCheckBox* segmentDrawingCheckBox_;
+  QCheckBox* autoHintingCheckBox_;
+  QCheckBox* stemDarkeningCheckBox_;
+  QCheckBox* embeddedBitmapCheckBox_;
+  QCheckBox* colorLayerCheckBox_;
+  QCheckBox* kerningCheckBox_;
+  QCheckBox* lsbRsbDeltaCheckBox_;
+
+  AntiAliasingComboBoxModel* antiAliasingComboBoxModel_;
+  HintingModeComboBoxModel* hintingModeComboBoxModel_;
+  LCDFilterComboBoxModel* lcdFilterComboboxModel_;
+
+  QComboBox* hintingModeComboBox_;
+  QComboBox* antiAliasingComboBox_;
+  QComboBox* lcdFilterComboBox_;
+  QComboBox* paletteComboBox_;
+
+  QSlider* gammaSlider_;
+
+  QPushButton* backgroundButton_;
+  QPushButton* foregroundButton_;
+  QFrame* backgroundBlock_;
+  QFrame* foregroundBlock_;
+
+  QVBoxLayout* mainLayout_;
+  QGridLayout* generalTabLayout_;
+  QGridLayout* hintingRenderingTabLayout_;
+  QVBoxLayout* debugLayout_;
+  QHBoxLayout* gammaLayout_;
+  QHBoxLayout* colorPickerLayout_;
+
+  QColor backgroundColor_;
+  QColor foregroundColor_;
+
+  //////// Initializing funcs
+
+  void createConnections();
+  void createLayout();
+  void createLayoutNormal();
+  void setDefaults();
+
+  //////// Other funcs
+
+  void populatePalettes();
+
+  void checkAllSettings();
+  void checkHinting();
+  void checkHintingMode();
+  void checkAutoHinting();
+  void checkAntiAliasing();
+  void checkPalette();
+  void checkStemDarkening();
+
+  void openBackgroundPicker();
+  void openForegroundPicker();
+  void updateGamma();
+  void resetColorBlocks();
+};
+
+
+// end of settingpanel.hpp
diff --git a/src/ftinspect/widgets/tripletselector.cpp 
b/src/ftinspect/widgets/tripletselector.cpp
new file mode 100644
index 00000000..d0f3f416
--- /dev/null
+++ b/src/ftinspect/widgets/tripletselector.cpp
@@ -0,0 +1,490 @@
+// tripletselector.cpp
+
+// Copyright (C) 2022 by Charlie Jiang.
+
+#include "tripletselector.hpp"
+
+#include "../engine/engine.hpp"
+
+#include <functional>
+
+TripletSelector::TripletSelector(QWidget* parent,
+                                 Engine* engine)
+: QWidget(parent),
+  engine_(engine)
+{
+  createLayout();
+  createConnections();
+  checkButtons();
+}
+
+
+TripletSelector::~TripletSelector()
+{
+}
+
+
+void
+TripletSelector::repopulateFonts()
+{
+  auto oldSize = fontComboBox_->count();
+  auto oldIndex = fontComboBox_->currentIndex();
+
+  {
+    QSignalBlocker blk(fontComboBox_);
+    QSignalBlocker blk2(faceComboBox_);
+    QSignalBlocker blk3(niComboBox_);
+    fontComboBox_->clear();
+    
+    auto& ffm = engine_->fontFileManager();
+    auto newSize = ffm.size();
+    for (int i = 0; i < newSize; i++)
+    {
+      auto& info = ffm[i];
+      auto name = info.filePath();
+      auto displayName = info.fileName();
+      if (info.isSymbolicLink())
+        displayName += " [symlink]";
+
+      fontComboBox_->addItem(displayName, name);
+    }
+
+    if (newSize > oldSize)
+    {
+      // if we have new fonts, set the current index to the first new one
+      fontComboBox_->setCurrentIndex(oldSize);
+    }
+    else if (newSize < oldSize)
+    {
+      if (oldIndex >= newSize)
+        oldIndex = newSize - 1;
+      if (oldIndex < 0)
+        oldIndex = -1;
+      fontComboBox_->setCurrentIndex(oldIndex);
+    }
+
+    // Note no signal will be emitted from any combobox until this block ends
+  }
+
+  // This will check buttons & reload the triplet
+  repopulateFaces();
+}
+
+
+void
+TripletSelector::repopulateFaces(bool fontSwitched)
+{
+  // Avoid unnecessary recreating, to reduce interruption of user oper
+  auto needToRecreate = fontSwitched;
+  auto oldSize = faceComboBox_->count();
+
+  auto fontIndex = fontComboBox_->currentIndex();
+  auto newSize = engine_->numberOfFaces(fontIndex);
+
+  if (fontIndex < 0 || newSize < 0)
+  {
+    // Clear and go
+    faceComboBox_->clear();
+    // This will check buttons & reload the triplet
+    repopulateNamedInstances(fontSwitched);
+    return;
+  }
+
+  if (newSize != oldSize)
+    needToRecreate = true;
+
+  std::vector<QString> newFaces;
+  newFaces.reserve(newSize);
+  for (long i = 0; i < newSize; i++)
+  {
+    newFaces.emplace_back(engine_->namedInstanceName(fontIndex, i, 0));
+    if (!needToRecreate && newFaces[i] != faceComboBox_->itemData(i))
+      needToRecreate = true;
+  }
+
+  if (!needToRecreate)
+  {
+    // no need to refersh the combobox
+    // This will check buttons & reload the triplet
+    repopulateNamedInstances(fontSwitched);
+    return;
+  }
+
+  {
+    QSignalBlocker blk2(faceComboBox_);
+    QSignalBlocker blk3(niComboBox_);
+    faceComboBox_->clear();
+
+    for (long i = 0; i < newSize; i++)
+    {
+      auto& name = newFaces[i];
+      auto displayName = QString("%1: %2").arg(i).arg(name);
+      faceComboBox_->addItem(displayName, name);
+    }
+
+    faceComboBox_->setCurrentIndex(0);
+    // Note no signal will be emitted from any combobox until this block ends
+  }
+
+  // This will check buttons & reload the triplet
+  repopulateNamedInstances(true);
+}
+
+
+void
+TripletSelector::repopulateNamedInstances(bool fontSwitched)
+{
+  // Avoid unnecessary recreating, to reduce interruption of user oper
+  // Similar to `repopulateFaces`
+  auto needToRecreate = fontSwitched;
+  auto oldSize = niComboBox_->count();
+
+  auto fontIndex = fontComboBox_->currentIndex();
+  auto faceIndex = faceComboBox_->currentIndex();
+  auto newSize = engine_->numberOfNamedInstances(fontIndex, faceIndex);
+
+  if (fontIndex < 0 || faceIndex < 0 || newSize < 0)
+  {
+    // Clear and go, don't forget checking buttons and loading triplet
+    niComboBox_->clear();
+    checkButtons();
+    loadTriplet();
+    return;
+  }
+
+  if (newSize != oldSize)
+    needToRecreate = true;
+
+  std::vector<QString> newFaces;
+  newFaces.reserve(newSize);
+  for (long i = 0; i < newSize; i++)
+  {
+    newFaces.emplace_back(engine_->namedInstanceName(fontIndex, faceIndex, i));
+    if (!needToRecreate && newFaces[i] != niComboBox_->itemData(i))
+      needToRecreate = true;
+  }
+
+  niComboBox_->setEnabled(newSize > 1);
+
+  if (!needToRecreate)
+  {
+    // no need to refersh the combobox
+    checkButtons();
+    loadTriplet();
+    return;
+  }
+
+  {
+    QSignalBlocker blk3(niComboBox_);
+    niComboBox_->clear();
+
+    for (long i = 0; i < newSize; i++)
+    {
+      auto& name = newFaces[i];
+      auto displayName = QString("%1: %2").arg(i).arg(name);
+      if (i == 0)
+        displayName = "* " + displayName;
+      niComboBox_->addItem(displayName, name);
+    }
+
+    niComboBox_->setCurrentIndex(0);
+    // Note no signal will be emitted from any combobox until this block ends
+  }
+
+  checkButtons();
+  loadTriplet();
+}
+
+
+void
+TripletSelector::closeCurrentFont()
+{
+  auto idx = fontComboBox_->currentIndex();
+  if (idx < 0)
+    return;
+  engine_->fontFileManager().remove(idx);
+
+  // show next font after deletion, i.e., retain index if possible
+  int num = engine_->numberOfOpenedFonts();
+  if (num)
+  {
+    if (idx >= num)
+      idx = num - 1;
+  }
+  else
+    idx = -1;
+
+  {
+    // Shut up when repopulating
+    QSignalBlocker blockerThis(this);
+    QSignalBlocker blockerComboBox(fontComboBox_);
+    repopulateFonts();
+  }
+
+  if (idx != -1)
+    faceComboBox_->setCurrentIndex(idx);
+  updateFont();
+}
+
+
+void
+TripletSelector::updateFont()
+{
+  auto idx = fontComboBox_->currentIndex();
+  auto num = engine_->numberOfOpenedFonts();
+  if (idx < 0)
+  {
+    faceComboBox_->clear();
+    niComboBox_->clear();
+
+    checkButtons();
+    loadTriplet();
+    return;
+  }
+
+  if (num <= 0 || idx >= num)
+  {
+    // out of sync: this shouldn't happen
+    repopulateFonts();
+    return;
+  }
+
+  // This will check buttons & reload the triplet
+  repopulateFaces();
+}
+
+
+void
+TripletSelector::updateFace()
+{
+  auto idx = faceComboBox_->currentIndex();
+  auto num = engine_->numberOfFaces(fontComboBox_->currentIndex());
+  
+  if (idx >= num)
+  {
+    // out of sync
+    repopulateFaces();
+    return;
+  }
+
+  // This will check buttons & reload the triplet
+  repopulateNamedInstances();
+}
+
+
+void
+TripletSelector::updateNI()
+{
+  auto idx = niComboBox_->currentIndex();
+  auto num = engine_->numberOfNamedInstances(fontComboBox_->currentIndex(),
+                                             faceComboBox_->currentIndex());
+  
+  if (idx >= num)
+  {
+    // out of sync
+    repopulateNamedInstances();
+    return;
+  }
+
+  checkButtons();
+  loadTriplet();
+}
+
+
+void
+TripletSelector::checkButtons()
+{
+  fontUpButton_->setEnabled(fontComboBox_->currentIndex() > 0);
+  fontDownButton_->setEnabled(fontComboBox_->currentIndex()
+                              < fontComboBox_->count() - 1);
+  closeFontButton_->setEnabled(faceComboBox_->currentIndex() >= 0);
+
+  faceUpButton_->setEnabled(faceComboBox_->currentIndex() > 0);
+  faceDownButton_->setEnabled(faceComboBox_->currentIndex()
+                              < faceComboBox_->count() - 1);
+
+  niUpButton_->setEnabled(niComboBox_->currentIndex() > 0);
+  niDownButton_->setEnabled(niComboBox_->currentIndex()
+                            < niComboBox_->count() - 1);
+}
+
+
+void
+TripletSelector::watchCurrentFont()
+{
+  repopulateFaces(false);
+}
+
+
+void
+TripletSelector::createLayout()
+{
+  fontComboBox_ = new QComboBox(this);
+  faceComboBox_ = new QComboBox(this);
+  niComboBox_   = new QComboBox(this);
+
+  fontComboBox_->setPlaceholderText(tr("No font open"));
+  faceComboBox_->setPlaceholderText(tr("No face available"));
+  niComboBox_->setPlaceholderText(tr("No named instance available"));
+  
+  closeFontButton_ = new QToolButton(this);
+  fontUpButton_    = new QToolButton(this);
+  faceUpButton_    = new QToolButton(this);
+  niUpButton_      = new QToolButton(this);
+  fontDownButton_  = new QToolButton(this);
+  faceDownButton_  = new QToolButton(this);
+  niDownButton_    = new QToolButton(this);
+
+  closeFontButton_->setText(tr("Close"));
+  fontUpButton_   ->setText(tr("\xE2\x86\x91"));
+  faceUpButton_   ->setText(tr("\xE2\x86\x91"));
+  niUpButton_     ->setText(tr("\xE2\x86\x91"));
+  fontDownButton_ ->setText(tr("\xE2\x86\x93"));
+  faceDownButton_ ->setText(tr("\xE2\x86\x93"));
+  niDownButton_   ->setText(tr("\xE2\x86\x93"));
+  
+  fontComboBox_   ->setSizePolicy(QSizePolicy::Minimum, 
QSizePolicy::Expanding);
+  faceComboBox_   ->setSizePolicy(QSizePolicy::Minimum, 
QSizePolicy::Expanding);
+  niComboBox_     ->setSizePolicy(QSizePolicy::Minimum, 
QSizePolicy::Expanding);
+  closeFontButton_->setSizePolicy(QSizePolicy::Maximum, 
QSizePolicy::Expanding);
+  fontUpButton_   ->setFixedSize(30, 30);
+  faceUpButton_   ->setFixedSize(30, 30);
+  niUpButton_     ->setFixedSize(30, 30);
+  fontDownButton_ ->setFixedSize(30, 30);
+  faceDownButton_ ->setFixedSize(30, 30);
+  niDownButton_   ->setFixedSize(30, 30);
+
+  // Tooltips
+  fontComboBox_->setToolTip(tr("Current font"));
+  faceComboBox_->setToolTip(tr("Current subfont (face)"));
+  niComboBox_->setToolTip(
+    tr("Current named instance (only available for variable fonts)"));
+  closeFontButton_->setToolTip(tr("Close current font"));
+  fontUpButton_   ->setToolTip(tr("Previous font"));
+  faceUpButton_   ->setToolTip(tr("Previous subfont (face)"));
+  niUpButton_     ->setToolTip(tr("Previous named instance"));
+  fontDownButton_ ->setToolTip(tr("Next font"));
+  faceDownButton_ ->setToolTip(tr("Next subfont (face)"));
+  niDownButton_   ->setToolTip(tr("Next named instance"));
+
+  // Layouting
+  layout_ = new QHBoxLayout;
+  layout_->setSpacing(0);
+  layout_->setContentsMargins(0, 0, 0, 0);
+
+  layout_->addWidget(fontComboBox_);
+  layout_->addWidget(fontUpButton_);
+  layout_->addWidget(fontDownButton_);
+  layout_->addWidget(closeFontButton_);
+  layout_->addWidget(faceComboBox_);
+  layout_->addWidget(faceUpButton_);
+  layout_->addWidget(faceDownButton_);
+  layout_->addWidget(niComboBox_);
+  layout_->addWidget(niUpButton_);
+  layout_->addWidget(niDownButton_);
+
+  setFixedHeight(30);
+  setLayout(layout_);
+  layout_->setContentsMargins(0, 0, 0, 0);
+}
+
+
+void
+TripletSelector::createConnections()
+{
+  connect(fontComboBox_, QOverload<int>::of(&QComboBox::currentIndexChanged),
+          this, &TripletSelector::updateFont);
+  connect(faceComboBox_, QOverload<int>::of(&QComboBox::currentIndexChanged),
+          this, &TripletSelector::updateFace);
+  connect(niComboBox_, QOverload<int>::of(&QComboBox::currentIndexChanged),
+          this, &TripletSelector::updateNI);
+
+  connect(closeFontButton_, &QToolButton::clicked, 
+          this, &TripletSelector::closeCurrentFont);
+  connect(fontUpButton_   , &QToolButton::clicked, 
+          this, 
+          std::bind(&TripletSelector::previousComboBoxItem, fontComboBox_));
+  connect(faceUpButton_   , &QToolButton::clicked, 
+          this, 
+          std::bind(&TripletSelector::previousComboBoxItem, faceComboBox_));
+  connect(niUpButton_     , &QToolButton::clicked, 
+          this, 
+          std::bind(&TripletSelector::previousComboBoxItem, niComboBox_));
+  connect(fontDownButton_ , &QToolButton::clicked, 
+          this, 
+          std::bind(&TripletSelector::nextComboBoxItem, fontComboBox_));
+  connect(faceDownButton_ , &QToolButton::clicked, 
+          this,
+          std::bind(&TripletSelector::nextComboBoxItem, faceComboBox_));
+  connect(niDownButton_   , &QToolButton::clicked, 
+          this, 
+          std::bind(&TripletSelector::nextComboBoxItem, niComboBox_));
+
+  connect(&engine_->fontFileManager(), &FontFileManager::currentFileChanged,
+          this, &TripletSelector::watchCurrentFont);
+}
+
+
+void
+TripletSelector::loadTriplet()
+{
+  // we do lazy computation of FT_Face objects
+
+  // TODO really?
+  auto fontIndex = fontComboBox_->currentIndex();
+  auto faceIndex = faceComboBox_->currentIndex();
+  auto instanceIndex = niComboBox_->currentIndex();
+
+  if (fontIndex >= 0 && fontIndex < engine_->numberOfOpenedFonts())
+  {
+    QFileInfo& fileInfo = engine_->fontFileManager()[fontIndex];
+    engine_->fontFileManager().updateWatching(fontIndex);
+
+    if (!fileInfo.exists())
+    {
+      // On Unix-like systems, the symlink's target gets opened; this
+      // implies that deletion of a symlink doesn't make `engine->loadFont'
+      // fail since it operates on a file handle pointing to the target.
+      // For this reason, we remove the font to enforce a reload.
+      engine_->removeFont(fontIndex, false);
+    }
+  }
+
+  engine_->loadFont(fontIndex, faceIndex, instanceIndex);
+  
+  // TODO: This may messes up with bitmap-only fonts.
+  if (!engine_->fontValid())
+  {
+    // there might be various reasons why the current
+    // (file, face, instance) triplet is invalid or missing;
+    // we thus start our timer to periodically test
+    // whether the font starts working
+    if (faceIndex >= 0 && faceIndex < engine_->numberOfOpenedFonts())
+      engine_->fontFileManager().timerStart();
+  }
+
+  emit tripletChanged();
+}
+
+
+void
+TripletSelector::nextComboBoxItem(QComboBox* c)
+{
+  if (c->currentIndex() < 0 || c->currentIndex() >= c->count() - 1)
+    return;
+  // No need to handle further steps, the event handler will take care of these
+  c->setCurrentIndex(c->currentIndex() + 1);
+}
+
+
+void
+TripletSelector::previousComboBoxItem(QComboBox* c)
+{
+  if (c->currentIndex() <= 0)
+    return;
+  // No need to handle further steps, the event handler will take care of these
+  c->setCurrentIndex(c->currentIndex() - 1);
+}
+
+
+// end of tripletselector.cpp
diff --git a/src/ftinspect/widgets/tripletselector.hpp 
b/src/ftinspect/widgets/tripletselector.hpp
new file mode 100644
index 00000000..693f1ad2
--- /dev/null
+++ b/src/ftinspect/widgets/tripletselector.hpp
@@ -0,0 +1,67 @@
+// QPushButton.hpp
+
+// Copyright (C) 2022 by Charlie Jiang.
+
+#pragma once
+
+#include <vector>
+#include <QWidget>
+#include <QComboBox>
+#include <QPushButton>
+#include <QToolButton>
+#include <QBoxLayout>
+
+class Engine;
+class TripletSelector
+: public QWidget
+{
+  Q_OBJECT
+
+public:
+  TripletSelector(QWidget* parent,
+                  Engine* engine);
+  ~TripletSelector() override;
+
+  void repopulateFonts();
+  void closeCurrentFont();
+
+signals:
+  void tripletChanged();
+
+private:
+  Engine* engine_;
+
+  QComboBox* fontComboBox_;
+  QComboBox* faceComboBox_;
+  QComboBox* niComboBox_;
+
+  QToolButton* closeFontButton_;
+
+  QToolButton* fontUpButton_;
+  QToolButton* fontDownButton_;
+  QToolButton* faceUpButton_;
+  QToolButton* faceDownButton_;
+  QToolButton* niUpButton_;
+  QToolButton* niDownButton_;
+
+  QHBoxLayout* layout_;
+
+  void checkButtons();
+  void watchCurrentFont();
+
+  void createLayout();
+  void createConnections();
+
+  void repopulateFaces(bool fontSwitched = true);
+  void repopulateNamedInstances(bool fontSwitched = true);
+  void updateFont();
+  void updateFace();
+  void updateNI();
+  void loadTriplet();
+
+  static void nextComboBoxItem(QComboBox* c);
+  static void previousComboBoxItem(QComboBox* c);
+};
+
+
+// end of QPushButton.hpp



reply via email to

[Prev in Thread] Current Thread [Next in Thread]