freetype-commit
[Top][All Lists]
Advanced

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

[freetype2-demos] gsoc-2022-chariri-2 e76572b 20/30: [ftinspect] Move ou


From: Werner Lemberg
Subject: [freetype2-demos] gsoc-2022-chariri-2 e76572b 20/30: [ftinspect] Move out `ftgrid` out from `MainGUI` to a new tabbed pane.
Date: Mon, 11 Jul 2022 07:17:40 -0400 (EDT)

branch: gsoc-2022-chariri-2
commit e76572bb17a3dce1f1e8fc5adbd68cf47d27ce88
Author: Charlie Jiang <w@chariri.moe>
Commit: Charlie Jiang <w@chariri.moe>

    [ftinspect] Move out `ftgrid` out from `MainGUI` to a new tabbed pane.
    
    For further addition of other FreeType demo programs, we will use a tabbed
    pane to organize all views, and the original `ftgrid` view will be the first
    one, named "Singular Grid View".
    
    An `AbstractTab` is added for helping the `MainGUI` to notify all sub-views
    when settings change. The `AbstractTab` is a pure-virtual class itself.
    
    Settings dedicate to singular grid view are moved to their tab, and the 
labels
    showing current glyph name/index are moved too. These overlay labels are 
made
    transparent to mouse click so it don't block in the way.
    
    * src/ftinspect/maingui.cpp, src/ftinspect/maingui.hpp: Move out all 
`ftgrid`-
      specific code, and the whole grid section is replaced with a tab view.
      Tabs are maintained using a separate `QVector` so we can easily access the
      "current tab". New singal `reloadCurrentTabFont`, `repaintCurrentTab` and
      the old `syncSettings` made use of this.
    
    * src/ftinspect/panels/singular.hpp, src/ftinspect/panels/singular.cpp: New
      file, as described above.
    
    * src/ftinspect/panels/abstracttab.hpp: New file, as described above.
    
    * src/ftinspect/engine/engine.cpp, src/ftinspect/engine/engine.hpp: Save
      current font's glyph count so no need to pass it down from `MainGUI` to 
all
      tabs when a font reload happens.
      Add a dedicate `antiAliasingEnabled` property, which is set when anti-
      aliasing is set to "None".
    
    * src/ftinspect/panels/settingpanel.cpp,
      src/ftinspect/panels/settingpanel.hpp: Move out all `ftgrid`-specific 
items.
    
    * src/ftinspect/widgets/customwidgets.cpp,
      src/ftinspect/widgets/customwidgets.hpp: Renamed `QSpinBoxx` to
      `ZoomSpinBox` because "Zoom" box is its only usage and such name is much
      more descriptive.
    
    * src/ftinspect/CMakeLists.txt, src/ftinspect/meson.build: Updated.
---
 src/ftinspect/CMakeLists.txt            |   1 +
 src/ftinspect/engine/engine.cpp         |   3 +-
 src/ftinspect/engine/engine.hpp         |   5 +
 src/ftinspect/maingui.cpp               | 374 ++-----------------------
 src/ftinspect/maingui.hpp               |  63 +----
 src/ftinspect/meson.build               |   2 +
 src/ftinspect/panels/abstracttab.hpp    |  22 ++
 src/ftinspect/panels/settingpanel.cpp   |  66 +----
 src/ftinspect/panels/settingpanel.hpp   |  12 -
 src/ftinspect/panels/singular.cpp       | 470 ++++++++++++++++++++++++++++++++
 src/ftinspect/panels/singular.hpp       | 121 ++++++++
 src/ftinspect/widgets/customwidgets.cpp |   6 +-
 src/ftinspect/widgets/customwidgets.hpp |   4 +-
 13 files changed, 661 insertions(+), 488 deletions(-)

diff --git a/src/ftinspect/CMakeLists.txt b/src/ftinspect/CMakeLists.txt
index a3ca49b..c0b1340 100644
--- a/src/ftinspect/CMakeLists.txt
+++ b/src/ftinspect/CMakeLists.txt
@@ -35,6 +35,7 @@ add_executable(ftinspect
   "models/ttsettingscomboboxmodel.cpp"
 
   "panels/settingpanel.cpp"
+  "panels/singular.cpp"
 )
 target_link_libraries(ftinspect
   Qt5::Core Qt5::Widgets
diff --git a/src/ftinspect/engine/engine.cpp b/src/ftinspect/engine/engine.cpp
index a635d2c..4d0f8c2 100644
--- a/src/ftinspect/engine/engine.cpp
+++ b/src/ftinspect/engine/engine.cpp
@@ -298,6 +298,7 @@ Engine::loadFont(int fontIndex,
       fontType_ = FontType_TrueType;
   }
 
+  curNumGlyphs_ = numGlyphs;
   return numGlyphs;
 }
 
@@ -474,7 +475,7 @@ Engine::update()
   {
     loadFlags_ |= FT_LOAD_NO_HINTING;
 
-    if (antiAliasingTarget_ | FT_LOAD_TARGET_MONO) // XXX does this hold?
+    if (!antiAliasingEnabled_) // XXX does this hold?
       loadFlags_ |= FT_LOAD_MONOCHROME;
   }
 
diff --git a/src/ftinspect/engine/engine.hpp b/src/ftinspect/engine/engine.hpp
index 1ba09a8..7a45094 100644
--- a/src/ftinspect/engine/engine.hpp
+++ b/src/ftinspect/engine/engine.hpp
@@ -81,6 +81,7 @@ public:
   int currentFontType() const { return fontType_; }
   const QString& currentFamilyName() { return curFamilyName_; }
   const QString& currentStyleName() { return curStyleName_; }
+  int currentFontNumberOfGlyphs() { return curNumGlyphs_; }
   int numberOfOpenedFonts();
   QString glyphName(int glyphIndex);
   long numberOfFaces(int fontIndex);
@@ -92,6 +93,7 @@ public:
   // getter named fontFileManager
   FontFileManager& fontFileManager() { return fontFileManager_; }
   EngineDefaultValues& engineDefaults() { return engineDefaults_; }
+  bool antiAliasingEnabled() { return antiAliasingEnabled_; }
 
   //////// Setters (direct or indirect)
 
@@ -115,6 +117,7 @@ public:
   void setShowSegments(bool showSegments) { showSegments_ = showSegments; }
   void setGamma(double gamma) { gamma_ = gamma; }
   void setAntiAliasingTarget(int target) { antiAliasingTarget_ = target; }
+  void setAntiAliasingEnabled(bool enabled) { antiAliasingEnabled_ = enabled; }
 
   // Note: These 3 functions now takes actual mode/version from FreeType,
   // instead of values from enum in MainGUI!
@@ -138,6 +141,7 @@ private:
 
   QString curFamilyName_;
   QString curStyleName_;
+  int curNumGlyphs_ = -1;
 
   FT_Library library_;
   FTC_Manager cacheManager_;
@@ -151,6 +155,7 @@ private:
 
   int fontType_;
 
+  bool antiAliasingEnabled_ = true;
   bool usingPixelSize_ = false;
   double pointSize_;
   double pixelSize_;
diff --git a/src/ftinspect/maingui.cpp b/src/ftinspect/maingui.cpp
index 5f4a6d4..54f5a36 100644
--- a/src/ftinspect/maingui.cpp
+++ b/src/ftinspect/maingui.cpp
@@ -19,7 +19,6 @@
 MainGUI::MainGUI(Engine* engine)
 : engine_(engine)
 {
-  setGraphicsDefaults();
   createLayout();
   createConnections();
   createActions();
@@ -35,8 +34,7 @@ MainGUI::MainGUI(Engine* engine)
 
 MainGUI::~MainGUI()
 {
-  delete gridItem_;
-  gridItem_ = NULL;
+  // empty
 }
 
 
@@ -232,73 +230,38 @@ MainGUI::showFont()
   auto state = settingPanel_->blockSignals(true);
   settingPanel_->checkHinting();
   settingPanel_->blockSignals(state);
-  indexSelector_->setMin(0);
-  indexSelector_->setMax(currentNumberOfGlyphs_ - 1);
-  indexSelector_->setCurrentIndex(indexSelector_->getCurrentIndex(), true);
+  reloadCurrentTabFont();
 }
 
 
 void
-MainGUI::syncSettings()
+MainGUI::repaintCurrentTab()
 {
-  // Spinbox value cannot become negative
-  engine_->setDPI(static_cast<unsigned int>(dpiSpinBox_->value()));
-
-  if (unitsComboBox_->currentIndex() == Units_px)
-    engine_->setSizeByPixel(sizeDoubleSpinBox_->value());
-  else
-    engine_->setSizeByPoint(sizeDoubleSpinBox_->value());
-
-  settingPanel_->syncSettings();
+  syncSettings();
+  tabs_[tabWidget_->currentIndex()]->repaint();
 }
 
 
 void
-MainGUI::clearStatusBar()
+MainGUI::reloadCurrentTabFont()
 {
-  statusBar()->clearMessage();
-  statusBar()->setStyleSheet("");
+  tabs_[tabWidget_->currentIndex()]->reloadFont();
 }
 
 
 void
-MainGUI::checkUnits()
+MainGUI::syncSettings()
 {
-  int index = unitsComboBox_->currentIndex();
-
-  if (index == Units_px)
-  {
-    dpiLabel_->setEnabled(false);
-    dpiSpinBox_->setEnabled(false);
-    sizeDoubleSpinBox_->setSingleStep(1);
-    sizeDoubleSpinBox_->setValue(qRound(sizeDoubleSpinBox_->value()));
-  }
-  else
-  {
-    dpiLabel_->setEnabled(true);
-    dpiSpinBox_->setEnabled(true);
-    sizeDoubleSpinBox_->setSingleStep(0.5);
-  }
-
-  drawGlyph();
+  settingPanel_->syncSettings();
+  tabs_[tabWidget_->currentIndex()]->syncSettings();
 }
 
 
 void
-MainGUI::setGlyphIndex(int index)
+MainGUI::clearStatusBar()
 {
-  // only adjust current glyph index if we have a valid font
-  if (currentNumberOfGlyphs_ > 0)
-  {
-    currentGlyphIndex_ = qBound(0, index, currentNumberOfGlyphs_ - 1);
-  }
-
-  QString upperHex = QString::number(currentGlyphIndex_, 16).toUpper();
-  glyphIndexLabel_->setText(
-      QString("%1 (0x%2)").arg(currentGlyphIndex_).arg(upperHex));
-  glyphNameLabel_->setText(engine_->glyphName(currentGlyphIndex_));
-
-  drawGlyph();
+  statusBar()->clearMessage();
+  statusBar()->setStyleSheet("");
 }
 
 
@@ -452,184 +415,6 @@ MainGUI::nextNamedInstance()
 }
 
 
-void
-MainGUI::zoom()
-{
-  int scale = zoomSpinBox_->value();
-
-  QTransform transform;
-  transform.scale(scale, scale);
-
-  // we want horizontal and vertical 1px lines displayed with full pixels;
-  // we thus have to shift the coordinate system accordingly, using a value
-  // that represents 0.5px (i.e., half the 1px line width) after the scaling
-  qreal shift = 0.5 / scale;
-  transform.translate(shift, shift);
-
-  glyphView_->setTransform(transform);
-  updateGrid();
-}
-
-
-void
-MainGUI::backToCenter()
-{
-  glyphView_->centerOn(0, 0);
-  if (currentGlyphBitmapItem_)
-    glyphView_->ensureVisible(currentGlyphBitmapItem_);
-  else if (currentGlyphPointsItem_)
-    glyphView_->ensureVisible(currentGlyphPointsItem_);
-
-  updateGrid();
-}
-
-
-void
-MainGUI::updateGrid()
-{
-  if (gridItem_)
-    gridItem_->updateRect();
-}
-
-
-void
-MainGUI::wheelZoom(QWheelEvent* event)
-{
-  int numSteps = event->angleDelta().y() / 120;
-  int zoomAfter = zoomSpinBox_->value() + numSteps;
-  zoomAfter = std::max(zoomSpinBox_->minimum(),
-                       std::min(zoomAfter, zoomSpinBox_->maximum()));
-  zoomSpinBox_->setValue(zoomAfter);
-  // TODO: Zoom relative to viewport left-bottom?
-}
-
-
-void
-MainGUI::wheelResize(QWheelEvent* event)
-{
-  int numSteps = event->angleDelta().y() / 120;
-  double sizeAfter = sizeDoubleSpinBox_->value() + numSteps * 0.5;
-  sizeAfter = std::max(sizeDoubleSpinBox_->minimum(),
-                       std::min(sizeAfter, sizeDoubleSpinBox_->maximum()));
-  sizeDoubleSpinBox_->setValue(sizeAfter);
-}
-
-
-void
-MainGUI::setGraphicsDefaults()
-{
-  // color tables (with suitable opacity values) for converting
-  // FreeType's pixmaps to something Qt understands
-  monoColorTable_.append(QColor(Qt::transparent).rgba());
-  monoColorTable_.append(QColor(Qt::black).rgba());
-
-  for (int i = 0xFF; i >= 0; i--)
-    grayColorTable_.append(qRgba(i, i, i, 0xFF - i));
-
-  // XXX make this user-configurable
-
-  axisPen_.setColor(Qt::black);
-  axisPen_.setWidth(0);
-  blueZonePen_.setColor(QColor(64, 64, 255, 64)); // light blue
-  blueZonePen_.setWidth(0);
-  gridPen_.setColor(Qt::lightGray);
-  gridPen_.setWidth(0);
-  offPen_.setColor(Qt::darkGreen);
-  offPen_.setWidth(3);
-  onPen_.setColor(Qt::red);
-  onPen_.setWidth(3);
-  outlinePen_.setColor(Qt::red);
-  outlinePen_.setWidth(0);
-  segmentPen_.setColor(QColor(64, 255, 128, 64)); // light green
-  segmentPen_.setWidth(0);
-}
-
-
-void
-MainGUI::drawGlyph()
-{
-  // the call to `engine->loadOutline' updates FreeType's load flags
-
-  if (!engine_)
-    return;
-
-  if (currentGlyphBitmapItem_)
-  {
-    glyphScene_->removeItem(currentGlyphBitmapItem_);
-    delete currentGlyphBitmapItem_;
-
-    currentGlyphBitmapItem_ = NULL;
-  }
-
-  if (currentGlyphOutlineItem_)
-  {
-    glyphScene_->removeItem(currentGlyphOutlineItem_);
-    delete currentGlyphOutlineItem_;
-
-    currentGlyphOutlineItem_ = NULL;
-  }
-
-  if (currentGlyphPointsItem_)
-  {
-    glyphScene_->removeItem(currentGlyphPointsItem_);
-    delete currentGlyphPointsItem_;
-
-    currentGlyphPointsItem_ = NULL;
-  }
-
-  if (currentGlyphPointNumbersItem_)
-  {
-    glyphScene_->removeItem(currentGlyphPointNumbersItem_);
-    delete currentGlyphPointNumbersItem_;
-
-    currentGlyphPointNumbersItem_ = NULL;
-  }
-
-  syncSettings();
-  FT_Outline* outline = engine_->loadOutline(currentGlyphIndex_);
-  if (outline)
-  {
-    if (settingPanel_->showBitmapChecked())
-    {
-      // XXX support LCD
-      FT_Pixel_Mode pixelMode = FT_PIXEL_MODE_GRAY;
-      if (settingPanel_->antiAliasingModeIndex()
-          == AntiAliasingComboBoxModel::AntiAliasing_None)
-        pixelMode = FT_PIXEL_MODE_MONO;
-
-      currentGlyphBitmapItem_ = new GlyphBitmap(outline,
-                                               engine_->ftLibrary(),
-                                               pixelMode,
-                                               monoColorTable_,
-                                               grayColorTable_);
-      glyphScene_->addItem(currentGlyphBitmapItem_);
-    }
-
-    if (settingPanel_->showOutLinesChecked())
-    {
-      currentGlyphOutlineItem_ = new GlyphOutline(outlinePen_, outline);
-      glyphScene_->addItem(currentGlyphOutlineItem_);
-    }
-
-    if (settingPanel_->showPointsChecked())
-    {
-      currentGlyphPointsItem_ = new GlyphPoints(onPen_, offPen_, outline);
-      glyphScene_->addItem(currentGlyphPointsItem_);
-
-      if (settingPanel_->showPointNumbersChecked())
-      {
-        currentGlyphPointNumbersItem_ = new GlyphPointNumbers(onPen_,
-                                                             offPen_,
-                                                             outline);
-        glyphScene_->addItem(currentGlyphPointNumbersItem_);
-      }
-    }
-  }
-
-  glyphScene_->update();
-}
-
-
 // XXX distances are specified in pixels,
 //     making the layout dependent on the output device resolution
 void
@@ -660,70 +445,15 @@ MainGUI::createLayout()
   leftWidget_->setSizePolicy(leftWidgetPolicy);
 
   // right side
-  glyphIndexLabel_ = new QLabel(this);
-  glyphNameLabel_ = new QLabel(this);
   fontNameLabel_ = new QLabel(this);
 
-  glyphScene_ = new QGraphicsScene(this);
-
-  currentGlyphBitmapItem_ = NULL;
-  currentGlyphOutlineItem_ = NULL;
-  currentGlyphPointsItem_ = NULL;
-  currentGlyphPointNumbersItem_ = NULL;
-
-  glyphView_ = new QGraphicsViewx(this);
-  glyphView_->setRenderHint(QPainter::Antialiasing, true);
-  glyphView_->setDragMode(QGraphicsView::ScrollHandDrag);
-  glyphView_->setOptimizationFlags(QGraphicsView::DontSavePainterState);
-  glyphView_->setViewportUpdateMode(QGraphicsView::SmartViewportUpdate);
-  glyphView_->setTransformationAnchor(QGraphicsView::AnchorUnderMouse);
-  glyphView_->setScene(glyphScene_);
-  
-  gridItem_ = new Grid(glyphView_, gridPen_, axisPen_);
-  glyphScene_->addItem(gridItem_);
-
-  // Don't use QGraphicsTextItem: We want this hint to be anchored at the
-  // top-left corner.
-  mouseUsageHint_ = new QLabel(tr("Scroll: Grid Up/Down\n"
-    "Alt + Scroll: Grid Left/Right\n"
-    "Ctrl + Scroll: Adjust Zoom (Relative to cursor)\n"
-    "Shift + Scroll: Adjust Font Size"), glyphView_);
-  mouseUsageHint_->setMargin(10);
-  auto hintFont = font();
-  hintFont.setPixelSize(24);
-  mouseUsageHint_->setFont(hintFont);
-
-  sizeLabel_ = new QLabel(tr("Size "), this);
-  sizeLabel_->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
-  sizeDoubleSpinBox_ = new QDoubleSpinBox;
-  sizeDoubleSpinBox_->setAlignment(Qt::AlignRight);
-  sizeDoubleSpinBox_->setDecimals(1);
-  sizeDoubleSpinBox_->setRange(1, 500);
-  sizeLabel_->setBuddy(sizeDoubleSpinBox_);
-
-  indexSelector_ = new GlyphIndexSelector(this);
-  indexSelector_->setSingleMode(true);
-
-  unitsComboBox_ = new QComboBox(this);
-  unitsComboBox_->insertItem(Units_px, "px");
-  unitsComboBox_->insertItem(Units_pt, "pt");
-
-  dpiLabel_ = new QLabel(tr("DPI "), this);
-  dpiLabel_->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
-  dpiSpinBox_ = new QSpinBox(this);
-  dpiSpinBox_->setAlignment(Qt::AlignRight);
-  dpiSpinBox_->setRange(10, 600);
-  dpiLabel_->setBuddy(dpiSpinBox_);
-
-  zoomLabel_ = new QLabel(tr("Zoom Factor"), this);
-  zoomLabel_->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
-  zoomSpinBox_ = new QSpinBoxx(this);
-  zoomSpinBox_->setAlignment(Qt::AlignRight);
-  zoomSpinBox_->setRange(1, 1000 - 1000 % 64);
-  zoomSpinBox_->setKeyboardTracking(false);
-  zoomLabel_->setBuddy(zoomSpinBox_);
-
-  centerGridButton_ = new QPushButton("Go Back to Grid Center", this);
+  singularTab_ = new SingularTab(this, engine_);
+
+  tabWidget_ = new QTabWidget(this);
+
+  // Note those two list must be in sync
+  tabs_.append(singularTab_);
+  tabWidget_->addTab(singularTab_, tr("Singular Grid View"));
 
   previousFontButton_ = new QPushButton(tr("Previous Font"), this);
   nextFontButton_ = new QPushButton(tr("Next Font"), this);
@@ -733,26 +463,6 @@ MainGUI::createLayout()
     = new QPushButton(tr("Previous Named Instance"), this);
   nextNamedInstanceButton_ = new QPushButton(tr("Next Named Instance"), this);
 
-  infoRightLayout = new QGridLayout;
-  infoRightLayout->addWidget(glyphIndexLabel_, 0, 0);
-  infoRightLayout->addWidget(glyphNameLabel_, 0, 1);
-  infoRightLayout->addWidget(fontNameLabel_, 0, 2);
-
-  sizeLayout_ = new QHBoxLayout;
-  sizeLayout_->addStretch(2);
-  sizeLayout_->addWidget(sizeLabel_);
-  sizeLayout_->addWidget(sizeDoubleSpinBox_);
-  sizeLayout_->addWidget(unitsComboBox_);
-  sizeLayout_->addStretch(1);
-  sizeLayout_->addWidget(dpiLabel_);
-  sizeLayout_->addWidget(dpiSpinBox_);
-  sizeLayout_->addStretch(1);
-  sizeLayout_->addWidget(zoomLabel_);
-  sizeLayout_->addWidget(zoomSpinBox_);
-  sizeLayout_->addStretch(1);
-  sizeLayout_->addWidget(centerGridButton_);
-  sizeLayout_->addStretch(2);
-
   fontLayout = new QGridLayout;
   fontLayout->setColumnStretch(0, 2);
   fontLayout->addWidget(nextFontButton_, 0, 1);
@@ -766,12 +476,8 @@ MainGUI::createLayout()
   fontLayout->setColumnStretch(6, 2);
 
   rightLayout_ = new QVBoxLayout;
-  rightLayout_->addLayout(infoRightLayout);
-  rightLayout_->addWidget(glyphView_);
-  rightLayout_->addWidget(indexSelector_);
-  rightLayout_->addSpacing(10); // XXX px
-  rightLayout_->addLayout(sizeLayout_);
-  rightLayout_->addSpacing(10); // XXX px
+  rightLayout_->addWidget(fontNameLabel_);
+  rightLayout_->addWidget(tabWidget_);
   rightLayout_->addLayout(fontLayout);
 
   // for symmetry with the left side use a widget also
@@ -796,29 +502,7 @@ MainGUI::createConnections()
   connect(settingPanel_, &SettingPanel::fontReloadNeeded,
           this, &MainGUI::showFont);
   connect(settingPanel_, &SettingPanel::repaintNeeded,
-          this, &MainGUI::drawGlyph);
-  connect(indexSelector_, &GlyphIndexSelector::currentIndexChanged, 
-          this, &MainGUI::setGlyphIndex);
-  connect(sizeDoubleSpinBox_, 
QOverload<double>::of(&QDoubleSpinBox::valueChanged),
-          this, &MainGUI::drawGlyph);
-  connect(unitsComboBox_, QOverload<int>::of(&QComboBox::currentIndexChanged),
-          this, &MainGUI::checkUnits);
-  connect(dpiSpinBox_, QOverload<int>::of(&QSpinBox::valueChanged),
-          this, &MainGUI::drawGlyph);
-
-  connect(zoomSpinBox_, QOverload<int>::of(&QSpinBox::valueChanged),
-          this, &MainGUI::zoom);
-  connect(glyphView_, &QGraphicsViewx::shiftWheelEvent, 
-          this, &MainGUI::wheelResize);
-  connect(glyphView_, &QGraphicsViewx::ctrlWheelEvent, 
-          this, &MainGUI::wheelZoom);
-  connect(glyphView_->horizontalScrollBar(), &QScrollBar::valueChanged,
-          this, &MainGUI::updateGrid);
-  connect(glyphView_->verticalScrollBar(), &QScrollBar::valueChanged, 
-          this, &MainGUI::updateGrid);
-
-  connect(centerGridButton_, &QPushButton::clicked,
-          this, &MainGUI::backToCenter);
+          this, &MainGUI::repaintCurrentTab);
 
   connect(previousFontButton_, &QPushButton::clicked,
           this, &MainGUI::previousFont);
@@ -886,7 +570,6 @@ void
 MainGUI::setupDragDrop()
 {
   setAcceptDrops(true);
-  glyphView_->setAcceptDrops(false);
 }
 
 
@@ -897,19 +580,12 @@ MainGUI::setDefaults()
   currentFontIndex_ = 0;
   currentFaceIndex_ = 0;
   currentNamedInstanceIndex_ = 0;
-  currentGlyphIndex_ = 0;
-
-  sizeDoubleSpinBox_->setValue(20);
-  dpiSpinBox_->setValue(96);
-  zoomSpinBox_->setValue(20);
 
-  // todo run check for settingpanel
-  checkUnits();
+  for (auto tab : tabs_)
+    tab->setDefaults();
   checkCurrentFontIndex();
   checkCurrentFaceIndex();
   checkCurrentNamedInstanceIndex();
-  indexSelector_->setCurrentIndex(indexSelector_->getCurrentIndex(), true);
-  zoom();
 }
 
 
diff --git a/src/ftinspect/maingui.hpp b/src/ftinspect/maingui.hpp
index 6172fd7..0e1126a 100644
--- a/src/ftinspect/maingui.hpp
+++ b/src/ftinspect/maingui.hpp
@@ -6,15 +6,11 @@
 #pragma once
 
 #include "engine/engine.hpp"
-#include "rendering/glyphbitmap.hpp"
-#include "rendering/glyphoutline.hpp"
-#include "rendering/glyphpointnumbers.hpp"
-#include "rendering/glyphpoints.hpp"
-#include "rendering/grid.hpp"
 #include "widgets/customwidgets.hpp"
 #include "widgets/glyphindexselector.hpp"
 #include "models/ttsettingscomboboxmodel.hpp"
 #include "panels/settingpanel.hpp"
+#include "panels/singular.hpp"
 
 #include <QAction>
 #include <QCheckBox>
@@ -72,14 +68,13 @@ protected:
 private slots:
   void about();
   void aboutQt();
-  void setGlyphIndex(int);
   void checkCurrentFaceIndex();
   void checkCurrentFontIndex();
   void checkCurrentNamedInstanceIndex();
-  void checkUnits();
   void closeFont();
   void showFont();
-  void drawGlyph();
+  void repaintCurrentTab();
+  void reloadCurrentTabFont();
   void loadFonts();
   void nextFace();
   void nextFont();
@@ -88,11 +83,6 @@ private slots:
   void previousFont();
   void previousNamedInstance();
   void watchCurrentFont();
-  void zoom();
-  void backToCenter();
-  void updateGrid();
-  void wheelZoom(QWheelEvent* event);
-  void wheelResize(QWheelEvent* event);
 
 private:
   Engine* engine_;
@@ -106,86 +96,46 @@ private:
   int currentNamedInstanceIndex_;
 
   int currentNumberOfGlyphs_;
-  int currentGlyphIndex_;
 
   // layout related stuff
-  GlyphOutline *currentGlyphOutlineItem_;
-  GlyphPoints *currentGlyphPointsItem_;
-  GlyphPointNumbers *currentGlyphPointNumbersItem_;
-  GlyphBitmap *currentGlyphBitmapItem_;
-  Grid *gridItem_ = NULL;
-  QLabel* mouseUsageHint_;
-
   QAction *aboutAct_;
   QAction *aboutQtAct_;
   QAction *closeFontAct_;
   QAction *exitAct_;
   QAction *loadFontsAct_;
 
-  GlyphIndexSelector* indexSelector_;
-  QComboBox *unitsComboBox_;
-
-  QDoubleSpinBox *sizeDoubleSpinBox_;
-
-  QGraphicsScene *glyphScene_;
-  QGraphicsViewx *glyphView_;
-
   QGridLayout *fontLayout;
-  QGridLayout *infoRightLayout;
 
   QHBoxLayout *ftinspectLayout_;
   QHBoxLayout *infoLeftLayout_;
-  QHBoxLayout *sizeLayout_;
 
-  QLabel *dpiLabel_;
   QLabel *fontFilenameLabel_;
   QLabel *fontNameLabel_;
-  QLabel *glyphIndexLabel_;
-  QLabel *glyphNameLabel_;
-  QLabel *sizeLabel_;
-  QLabel *zoomLabel_;
 
   QLocale *locale_;
 
   QMenu *menuFile_;
   QMenu *menuHelp_;
 
-  QPen axisPen_;
-  QPen blueZonePen_;
-  QPen gridPen_;
-  QPen offPen_;
-  QPen onPen_;
-  QPen outlinePen_;
-  QPen segmentPen_;
-
-  QPushButton *centerGridButton_;
   QPushButton *nextFaceButton_;
   QPushButton *nextFontButton_;
   QPushButton *nextNamedInstanceButton_;
   QPushButton *previousFaceButton_;
   QPushButton *previousFontButton_;
   QPushButton *previousNamedInstanceButton_;
-
-  QSpinBox *dpiSpinBox_;
-  QSpinBoxx *zoomSpinBox_;
   
   QVBoxLayout *leftLayout_;
   QVBoxLayout *rightLayout_;
 
-  QVector<QRgb> grayColorTable_;
-  QVector<QRgb> monoColorTable_;
-
   QWidget *ftinspectWidget_;
   QWidget *leftWidget_;
   QWidget *rightWidget_;
 
   SettingPanel* settingPanel_;
 
-  enum Units
-  {
-    Units_px,
-    Units_pt
-  };
+  QTabWidget* tabWidget_;
+  QVector<AbstractTab*> tabs_;
+  SingularTab* singularTab_;
 
   void openFonts(QStringList const& fileNames);
 
@@ -197,7 +147,6 @@ private:
   void createLayout();
   void createMenus();
   void createStatusBar();
-  void setGraphicsDefaults();
   void setupDragDrop();
 
   void readSettings();
diff --git a/src/ftinspect/meson.build b/src/ftinspect/meson.build
index a3c71d0..de0e7e9 100644
--- a/src/ftinspect/meson.build
+++ b/src/ftinspect/meson.build
@@ -34,6 +34,7 @@ if qt5_dep.found()
     'models/ttsettingscomboboxmodel.cpp',
 
     'panels/settingpanel.cpp',
+    'panels/singular.cpp',
 
     'ftinspect.cpp',
     'maingui.cpp',
@@ -47,6 +48,7 @@ if qt5_dep.found()
       'widgets/glyphindexselector.hpp',
       'models/ttsettingscomboboxmodel.hpp',
       'panels/settingpanel.hpp',
+      'panels/singular.hpp',
       'maingui.hpp',
     ],
     dependencies: qt5_dep)
diff --git a/src/ftinspect/panels/abstracttab.hpp 
b/src/ftinspect/panels/abstracttab.hpp
new file mode 100644
index 0000000..270e907
--- /dev/null
+++ b/src/ftinspect/panels/abstracttab.hpp
@@ -0,0 +1,22 @@
+// 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 syncSettings() = 0;
+  virtual void setDefaults() = 0;
+  virtual void repaint() = 0;
+  virtual void reloadFont() = 0;
+};
+
+
+// end of abstracttab.hpp
diff --git a/src/ftinspect/panels/settingpanel.cpp 
b/src/ftinspect/panels/settingpanel.cpp
index c459481..5781865 100644
--- a/src/ftinspect/panels/settingpanel.cpp
+++ b/src/ftinspect/panels/settingpanel.cpp
@@ -22,34 +22,6 @@ SettingPanel::antiAliasingModeIndex()
 }
 
 
-bool
-SettingPanel::showBitmapChecked()
-{
-  return showBitmapCheckBox_->isChecked();
-}
-
-
-bool
-SettingPanel::showOutLinesChecked()
-{
-  return showOutlinesCheckBox_->isChecked();
-}
-
-
-bool
-SettingPanel::showPointNumbersChecked()
-{
-  return showPointNumbersCheckBox_->isChecked();
-}
-
-
-bool
-SettingPanel::showPointsChecked()
-{
-  return showPointsCheckBox_->isChecked();
-}
-
-
 void
 SettingPanel::checkAllSettings()
 {
@@ -57,7 +29,6 @@ SettingPanel::checkAllSettings()
   checkAutoHinting();
   checkAntiAliasing();
   checkLCDFilter();
-  checkShowPoints();
 }
 
 
@@ -196,17 +167,6 @@ SettingPanel::checkAntiAliasing()
 }
 
 
-void
-SettingPanel::checkShowPoints()
-{
-  if (showPointsCheckBox_->isChecked())
-    showPointNumbersCheckBox_->setEnabled(true);
-  else
-    showPointNumbersCheckBox_->setEnabled(false);
-  emit repaintNeeded();
-}
-
-
 void
 SettingPanel::checkLCDFilter()
 {
@@ -222,6 +182,8 @@ SettingPanel::syncSettings()
       lcdFilterComboBox_->currentIndex())));
   engine_->setAntiAliasingTarget(antiAliasingComboBoxModel_->indexToValue(
     antiAliasingComboBox_->currentIndex()));
+  engine_->setAntiAliasingEnabled(antiAliasingComboBox_->currentIndex()
+    != AntiAliasingComboBoxModel::AntiAliasing_None);
   engine_->setHinting(hintingCheckBox_->isChecked());
   engine_->setAutoHinting(autoHintingCheckBox_->isChecked());
   engine_->setHorizontalHinting(horizontalHintingCheckBox_->isChecked());
@@ -264,14 +226,6 @@ SettingPanel::createConnections()
 
   connect(autoHintingCheckBox_, &QCheckBox::clicked,
           this, &SettingPanel::checkAutoHinting);
-  connect(showBitmapCheckBox_, &QCheckBox::clicked,
-          this, &SettingPanel::repaintNeeded);
-  connect(showPointsCheckBox_, &QCheckBox::clicked, 
-          this, &SettingPanel::repaintNeeded);
-  connect(showPointNumbersCheckBox_, &QCheckBox::clicked,
-          this, &SettingPanel::repaintNeeded);
-  connect(showOutlinesCheckBox_, &QCheckBox::clicked,
-          this, &SettingPanel::repaintNeeded);
 }
 
 
@@ -336,11 +290,6 @@ SettingPanel::createLayout()
   gammaSlider_->setTickInterval(5);
   gammaLabel_->setBuddy(gammaSlider_);
 
-  showBitmapCheckBox_ = new QCheckBox(tr("Show Bitmap"), this);
-  showPointsCheckBox_ = new QCheckBox(tr("Show Points"), this);
-  showPointNumbersCheckBox_ = new QCheckBox(tr("Show Point Numbers"), this);
-  showOutlinesCheckBox_ = new QCheckBox(tr("Show Outlines"), this);
-
   hintingModeLayout_ = new QHBoxLayout;
   hintingModeLayout_->addWidget(hintingModeLabel_);
   hintingModeLayout_->addWidget(hintingModeComboBox_);
@@ -373,10 +322,6 @@ SettingPanel::createLayout()
   gammaLayout_->addWidget(gammaLabel_);
   gammaLayout_->addWidget(gammaSlider_);
 
-  pointNumbersLayout_ = new QHBoxLayout;
-  pointNumbersLayout_->addSpacing(20); // XXX px
-  pointNumbersLayout_->addWidget(showPointNumbersCheckBox_);
-
   generalTabLayout_ = new QVBoxLayout;
   generalTabLayout_->addWidget(hintingCheckBox_);
   generalTabLayout_->addLayout(hintingModeLayout_);
@@ -394,10 +339,6 @@ SettingPanel::createLayout()
   generalTabLayout_->addLayout(gammaLayout_);
   generalTabLayout_->addSpacing(20); // XXX px
   generalTabLayout_->addStretch(1);
-  generalTabLayout_->addWidget(showBitmapCheckBox_);
-  generalTabLayout_->addWidget(showPointsCheckBox_);
-  generalTabLayout_->addLayout(pointNumbersLayout_);
-  generalTabLayout_->addWidget(showOutlinesCheckBox_);
 
   generalTab_ = new QWidget(this);
   generalTab_->setLayout(generalTabLayout_);
@@ -444,9 +385,6 @@ SettingPanel::setDefaults()
   verticalHintingCheckBox_->setChecked(true);
   blueZoneHintingCheckBox_->setChecked(true);
 
-  showBitmapCheckBox_->setChecked(true);
-  showOutlinesCheckBox_->setChecked(true);
-
   gammaSlider_->setValue(18); // 1.8
 }
 
diff --git a/src/ftinspect/panels/settingpanel.hpp 
b/src/ftinspect/panels/settingpanel.hpp
index ba133c7..562af3f 100644
--- a/src/ftinspect/panels/settingpanel.hpp
+++ b/src/ftinspect/panels/settingpanel.hpp
@@ -28,12 +28,6 @@ public:
 
   int antiAliasingModeIndex();
 
-  // TODO This would eventually go to separate panel for ftglyph (Singular 
View)
-  bool showBitmapChecked();
-  bool showOutLinesChecked();
-  bool showPointNumbersChecked();
-  bool showPointsChecked();
-
 signals:
   void fontReloadNeeded();
   void repaintNeeded();
@@ -46,7 +40,6 @@ public slots:
   void checkHintingMode();
   void checkAutoHinting();
   void checkAntiAliasing();
-  void checkShowPoints();
   void checkLCDFilter();
 
 private:
@@ -71,10 +64,6 @@ private:
   QCheckBox* blueZoneHintingCheckBox_;
   QCheckBox* segmentDrawingCheckBox_;
   QCheckBox* autoHintingCheckBox_;
-  QCheckBox* showBitmapCheckBox_;
-  QCheckBox* showOutlinesCheckBox_;
-  QCheckBox* showPointNumbersCheckBox_;
-  QCheckBox* showPointsCheckBox_;
 
   AntiAliasingComboBoxModel* antiAliasingComboBoxModel_;
   HintingModeComboBoxModel* hintingModeComboBoxModel_;
@@ -95,7 +84,6 @@ private:
   QHBoxLayout* antiAliasingLayout_;
   QHBoxLayout* lcdFilterLayout_;
   QHBoxLayout* gammaLayout_;
-  QHBoxLayout* pointNumbersLayout_;
 
   QVBoxLayout* generalTabLayout_;
 
diff --git a/src/ftinspect/panels/singular.cpp 
b/src/ftinspect/panels/singular.cpp
new file mode 100644
index 0000000..6d95f75
--- /dev/null
+++ b/src/ftinspect/panels/singular.cpp
@@ -0,0 +1,470 @@
+// singular.cpp
+
+// Copyright (C) 2022 by Charlie Jiang.
+
+#include "singular.hpp"
+
+#include <QSizePolicy>
+#include <QWheelEvent>
+
+
+SingularTab::SingularTab(QWidget* parent, Engine* engine)
+: QWidget(parent), engine_(engine)
+{
+  setGraphicsDefaults();
+  createLayout();
+  createConnections();
+
+  currentGlyphIndex_ = 0;
+  checkShowPoints();
+  checkUnits();
+}
+
+
+SingularTab::~SingularTab()
+{
+  delete gridItem_;
+  gridItem_ = NULL;
+}
+
+
+void
+SingularTab::setGlyphIndex(int index)
+{
+  // only adjust current glyph index if we have a valid font
+  if (currentGlyphCount_ > 0)
+  {
+    currentGlyphIndex_ = qBound(0, index, currentGlyphCount_ - 1);
+  }
+
+  QString upperHex = QString::number(currentGlyphIndex_, 16).toUpper();
+  glyphIndexLabel_->setText(
+      QString("%1 (0x%2)").arg(currentGlyphIndex_).arg(upperHex));
+  glyphNameLabel_->setText(engine_->glyphName(currentGlyphIndex_));
+
+  drawGlyph();
+}
+
+
+void
+SingularTab::drawGlyph()
+{
+  // the call to `engine->loadOutline' updates FreeType's load flags
+
+  if (!engine_)
+    return;
+
+  if (currentGlyphBitmapItem_)
+  {
+    glyphScene_->removeItem(currentGlyphBitmapItem_);
+    delete currentGlyphBitmapItem_;
+
+    currentGlyphBitmapItem_ = NULL;
+  }
+
+  if (currentGlyphOutlineItem_)
+  {
+    glyphScene_->removeItem(currentGlyphOutlineItem_);
+    delete currentGlyphOutlineItem_;
+
+    currentGlyphOutlineItem_ = NULL;
+  }
+
+  if (currentGlyphPointsItem_)
+  {
+    glyphScene_->removeItem(currentGlyphPointsItem_);
+    delete currentGlyphPointsItem_;
+
+    currentGlyphPointsItem_ = NULL;
+  }
+
+  if (currentGlyphPointNumbersItem_)
+  {
+    glyphScene_->removeItem(currentGlyphPointNumbersItem_);
+    delete currentGlyphPointNumbersItem_;
+
+    currentGlyphPointNumbersItem_ = NULL;
+  }
+
+  syncSettings();
+  FT_Outline* outline = engine_->loadOutline(currentGlyphIndex_);
+  if (outline)
+  {
+    if (showBitmapCheckBox_->isChecked())
+    {
+      // XXX support LCD
+      FT_Pixel_Mode pixelMode = FT_PIXEL_MODE_GRAY;
+      if (!engine_->antiAliasingEnabled())
+        pixelMode = FT_PIXEL_MODE_MONO;
+
+      currentGlyphBitmapItem_ = new GlyphBitmap(outline,
+                                               engine_->ftLibrary(),
+                                               pixelMode,
+                                               monoColorTable_,
+                                               grayColorTable_);
+      glyphScene_->addItem(currentGlyphBitmapItem_);
+    }
+
+    if (showOutlinesCheckBox_->isChecked())
+    {
+      currentGlyphOutlineItem_ = new GlyphOutline(outlinePen_, outline);
+      glyphScene_->addItem(currentGlyphOutlineItem_);
+    }
+
+    if (showPointsCheckBox_->isChecked())
+    {
+      currentGlyphPointsItem_ = new GlyphPoints(onPen_, offPen_, outline);
+      glyphScene_->addItem(currentGlyphPointsItem_);
+
+      if (showPointNumbersCheckBox_->isChecked())
+      {
+        currentGlyphPointNumbersItem_ = new GlyphPointNumbers(onPen_,
+                                                             offPen_,
+                                                             outline);
+        glyphScene_->addItem(currentGlyphPointNumbersItem_);
+      }
+    }
+  }
+
+  glyphScene_->update();
+}
+
+
+void
+SingularTab::checkUnits()
+{
+  int index = unitsComboBox_->currentIndex();
+
+  if (index == Units_px)
+  {
+    dpiLabel_->setEnabled(false);
+    dpiSpinBox_->setEnabled(false);
+    sizeDoubleSpinBox_->setSingleStep(1);
+    sizeDoubleSpinBox_->setValue(qRound(sizeDoubleSpinBox_->value()));
+  }
+  else
+  {
+    dpiLabel_->setEnabled(true);
+    dpiSpinBox_->setEnabled(true);
+    sizeDoubleSpinBox_->setSingleStep(0.5);
+  }
+
+  drawGlyph();
+}
+
+
+void
+SingularTab::checkShowPoints()
+{
+  if (showPointsCheckBox_->isChecked())
+    showPointNumbersCheckBox_->setEnabled(true);
+  else
+    showPointNumbersCheckBox_->setEnabled(false);
+  drawGlyph();
+}
+
+
+void
+SingularTab::zoom()
+{
+  int scale = zoomSpinBox_->value();
+
+  QTransform transform;
+  transform.scale(scale, scale);
+
+  // we want horizontal and vertical 1px lines displayed with full pixels;
+  // we thus have to shift the coordinate system accordingly, using a value
+  // that represents 0.5px (i.e., half the 1px line width) after the scaling
+  qreal shift = 0.5 / scale;
+  transform.translate(shift, shift);
+
+  glyphView_->setTransform(transform);
+  updateGrid();
+}
+
+
+void
+SingularTab::backToCenter()
+{
+  glyphView_->centerOn(0, 0);
+  if (currentGlyphBitmapItem_)
+    glyphView_->ensureVisible(currentGlyphBitmapItem_);
+  else if (currentGlyphPointsItem_)
+    glyphView_->ensureVisible(currentGlyphPointsItem_);
+
+  updateGrid();
+}
+
+
+void
+SingularTab::updateGrid()
+{
+  if (gridItem_)
+    gridItem_->updateRect();
+}
+
+
+void
+SingularTab::wheelZoom(QWheelEvent* event)
+{
+  int numSteps = event->angleDelta().y() / 120;
+  int zoomAfter = zoomSpinBox_->value() + numSteps;
+  zoomAfter = std::max(zoomSpinBox_->minimum(),
+                       std::min(zoomAfter, zoomSpinBox_->maximum()));
+  zoomSpinBox_->setValue(zoomAfter);
+  // TODO: Zoom relative to viewport left-bottom?
+}
+
+
+void
+SingularTab::wheelResize(QWheelEvent* event)
+{
+  int numSteps = event->angleDelta().y() / 120;
+  double sizeAfter = sizeDoubleSpinBox_->value() + numSteps * 0.5;
+  sizeAfter = std::max(sizeDoubleSpinBox_->minimum(),
+                       std::min(sizeAfter, sizeDoubleSpinBox_->maximum()));
+  sizeDoubleSpinBox_->setValue(sizeAfter);
+}
+
+
+void
+SingularTab::createLayout()
+{
+  glyphScene_ = new QGraphicsScene(this);
+
+  currentGlyphBitmapItem_ = NULL;
+  currentGlyphOutlineItem_ = NULL;
+  currentGlyphPointsItem_ = NULL;
+  currentGlyphPointNumbersItem_ = NULL;
+
+  glyphView_ = new QGraphicsViewx(this);
+  glyphView_->setRenderHint(QPainter::Antialiasing, true);
+  glyphView_->setAcceptDrops(false);
+  glyphView_->setDragMode(QGraphicsView::ScrollHandDrag);
+  glyphView_->setOptimizationFlags(QGraphicsView::DontSavePainterState);
+  glyphView_->setViewportUpdateMode(QGraphicsView::SmartViewportUpdate);
+  glyphView_->setTransformationAnchor(QGraphicsView::AnchorUnderMouse);
+  glyphView_->setScene(glyphScene_);
+
+  gridItem_ = new Grid(glyphView_, gridPen_, axisPen_);
+  glyphScene_->addItem(gridItem_);
+
+  // Don't use QGraphicsTextItem: We want this hint to be anchored at the
+  // top-left corner.
+  mouseUsageHint_ = new QLabel(tr(
+                      "Scroll: Grid Up/Down\n"
+                      "Alt + Scroll: Grid Left/Right\n"
+                      "Ctrl + Scroll: Adjust Zoom (Relative to cursor)\n"
+                      "Shift + Scroll: Adjust Font Size"),
+                      glyphView_);
+  auto hintFont = font();
+  hintFont.setPixelSize(24);
+  mouseUsageHint_->setFont(hintFont);
+  mouseUsageHint_->setAttribute(Qt::WA_TransparentForMouseEvents, true);
+
+  glyphIndexLabel_ = new QLabel(glyphView_);
+  glyphNameLabel_ = new QLabel(glyphView_);
+  glyphIndexLabel_->setFont(hintFont);
+  glyphNameLabel_->setFont(hintFont);
+  glyphIndexLabel_->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
+  glyphNameLabel_->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
+  glyphIndexLabel_->setAttribute(Qt::WA_TransparentForMouseEvents, true);
+  glyphNameLabel_->setAttribute(Qt::WA_TransparentForMouseEvents, true);
+
+  sizeLabel_ = new QLabel(tr("Size "), this);
+  sizeLabel_->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
+  sizeDoubleSpinBox_ = new QDoubleSpinBox;
+  sizeDoubleSpinBox_->setAlignment(Qt::AlignRight);
+  sizeDoubleSpinBox_->setDecimals(1);
+  sizeDoubleSpinBox_->setRange(1, 500);
+  sizeLabel_->setBuddy(sizeDoubleSpinBox_);
+
+  indexSelector_ = new GlyphIndexSelector(this);
+  indexSelector_->setSingleMode(true);
+
+  unitsComboBox_ = new QComboBox(this);
+  unitsComboBox_->insertItem(Units_px, "px");
+  unitsComboBox_->insertItem(Units_pt, "pt");
+
+  dpiLabel_ = new QLabel(tr("DPI "), this);
+  dpiLabel_->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
+  dpiSpinBox_ = new QSpinBox(this);
+  dpiSpinBox_->setAlignment(Qt::AlignRight);
+  dpiSpinBox_->setRange(10, 600);
+  dpiLabel_->setBuddy(dpiSpinBox_);
+
+  zoomLabel_ = new QLabel(tr("Zoom Factor"), this);
+  zoomLabel_->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
+  zoomSpinBox_ = new ZoomSpinBox(this);
+  zoomSpinBox_->setAlignment(Qt::AlignRight);
+  zoomSpinBox_->setRange(1, 1000 - 1000 % 64);
+  zoomSpinBox_->setKeyboardTracking(false);
+  zoomLabel_->setBuddy(zoomSpinBox_);
+
+  centerGridButton_ = new QPushButton("Go Back to Grid Center", this);
+
+  showBitmapCheckBox_ = new QCheckBox(tr("Show Bitmap"), this);
+  showPointsCheckBox_ = new QCheckBox(tr("Show Points"), this);
+  showPointNumbersCheckBox_ = new QCheckBox(tr("Show Point Numbers"), this);
+  showOutlinesCheckBox_ = new QCheckBox(tr("Show Outlines"), this);
+
+  sizeLayout_ = new QHBoxLayout;
+  sizeLayout_->addStretch(2);
+  sizeLayout_->addWidget(sizeLabel_);
+  sizeLayout_->addWidget(sizeDoubleSpinBox_);
+  sizeLayout_->addWidget(unitsComboBox_);
+  sizeLayout_->addStretch(1);
+  sizeLayout_->addWidget(dpiLabel_);
+  sizeLayout_->addWidget(dpiSpinBox_);
+  sizeLayout_->addStretch(1);
+  sizeLayout_->addWidget(zoomLabel_);
+  sizeLayout_->addWidget(zoomSpinBox_);
+  sizeLayout_->addStretch(1);
+  sizeLayout_->addWidget(centerGridButton_);
+  sizeLayout_->addStretch(2);
+
+  checkBoxesLayout_ = new QHBoxLayout;
+  checkBoxesLayout_->setSpacing(10);
+  checkBoxesLayout_->addWidget(showBitmapCheckBox_);
+  checkBoxesLayout_->addWidget(showPointsCheckBox_);
+  checkBoxesLayout_->addWidget(showPointNumbersCheckBox_);
+  checkBoxesLayout_->addWidget(showOutlinesCheckBox_);
+
+  glyphOverlayIndexLayout_ = new QHBoxLayout;
+  glyphOverlayIndexLayout_->addWidget(glyphIndexLabel_);
+  glyphOverlayIndexLayout_->addWidget(glyphNameLabel_);
+  glyphOverlayLayout_ = new QGridLayout;
+  glyphOverlayLayout_->addWidget(mouseUsageHint_, 0, 0,
+                                 Qt::AlignTop | Qt::AlignLeft);
+  glyphOverlayLayout_->addLayout(glyphOverlayIndexLayout_, 0, 1,
+                                 Qt::AlignTop | Qt::AlignRight);
+  glyphView_->setLayout(glyphOverlayLayout_);
+
+  mainLayout_ = new QVBoxLayout;
+  mainLayout_->addWidget(glyphView_);
+  mainLayout_->addWidget(indexSelector_);
+  mainLayout_->addSpacing(10); // XXX px
+  mainLayout_->addLayout(sizeLayout_);
+  mainLayout_->addLayout(checkBoxesLayout_);
+  mainLayout_->addSpacing(10); // XXX px
+
+  setLayout(mainLayout_);
+}
+
+
+void
+SingularTab::createConnections()
+{
+  connect(indexSelector_, &GlyphIndexSelector::currentIndexChanged, 
+          this, &SingularTab::setGlyphIndex);
+  connect(sizeDoubleSpinBox_, 
QOverload<double>::of(&QDoubleSpinBox::valueChanged),
+          this, &SingularTab::drawGlyph);
+  connect(unitsComboBox_, QOverload<int>::of(&QComboBox::currentIndexChanged),
+          this, &SingularTab::checkUnits);
+  connect(dpiSpinBox_, QOverload<int>::of(&QSpinBox::valueChanged),
+          this, &SingularTab::drawGlyph);
+
+  connect(zoomSpinBox_, QOverload<int>::of(&QSpinBox::valueChanged),
+          this, &SingularTab::zoom);
+  connect(glyphView_, &QGraphicsViewx::shiftWheelEvent, 
+          this, &SingularTab::wheelResize);
+  connect(glyphView_, &QGraphicsViewx::ctrlWheelEvent, 
+          this, &SingularTab::wheelZoom);
+  connect(glyphView_->horizontalScrollBar(), &QScrollBar::valueChanged,
+          this, &SingularTab::updateGrid);
+  connect(glyphView_->verticalScrollBar(), &QScrollBar::valueChanged, 
+          this, &SingularTab::updateGrid);
+
+  connect(centerGridButton_, &QPushButton::clicked,
+          this, &SingularTab::backToCenter);
+
+  connect(showBitmapCheckBox_, &QCheckBox::clicked,
+          this, &SingularTab::drawGlyph);
+  connect(showPointsCheckBox_, &QCheckBox::clicked, 
+          this, &SingularTab::checkShowPoints);
+  connect(showPointNumbersCheckBox_, &QCheckBox::clicked,
+          this, &SingularTab::drawGlyph);
+  connect(showOutlinesCheckBox_, &QCheckBox::clicked,
+          this, &SingularTab::drawGlyph);
+}
+
+
+void
+SingularTab::setGraphicsDefaults()
+{
+  // color tables (with suitable opacity values) for converting
+  // FreeType's pixmaps to something Qt understands
+  monoColorTable_.append(QColor(Qt::transparent).rgba());
+  monoColorTable_.append(QColor(Qt::black).rgba());
+
+  for (int i = 0xFF; i >= 0; i--)
+    grayColorTable_.append(qRgba(i, i, i, 0xFF - i));
+
+  // XXX make this user-configurable
+
+  axisPen_.setColor(Qt::black);
+  axisPen_.setWidth(0);
+  blueZonePen_.setColor(QColor(64, 64, 255, 64)); // light blue
+  blueZonePen_.setWidth(0);
+  gridPen_.setColor(Qt::lightGray);
+  gridPen_.setWidth(0);
+  offPen_.setColor(Qt::darkGreen);
+  offPen_.setWidth(3);
+  onPen_.setColor(Qt::red);
+  onPen_.setWidth(3);
+  outlinePen_.setColor(Qt::red);
+  outlinePen_.setWidth(0);
+  segmentPen_.setColor(QColor(64, 255, 128, 64)); // light green
+  segmentPen_.setWidth(0);
+}
+
+
+void
+SingularTab::repaint()
+{
+  drawGlyph();
+}
+
+
+void
+SingularTab::reloadFont()
+{
+  currentGlyphCount_ = engine_->currentFontNumberOfGlyphs();
+  indexSelector_->setMin(0);
+  indexSelector_->setMax(currentGlyphCount_);
+  indexSelector_->setCurrentIndex(indexSelector_->getCurrentIndex(), true);
+  drawGlyph();
+}
+
+
+void
+SingularTab::syncSettings()
+{
+  // Spinbox value cannot become negative
+  engine_->setDPI(static_cast<unsigned int>(dpiSpinBox_->value()));
+
+  if (unitsComboBox_->currentIndex() == Units_px)
+    engine_->setSizeByPixel(sizeDoubleSpinBox_->value());
+  else
+    engine_->setSizeByPoint(sizeDoubleSpinBox_->value());
+}
+
+
+void
+SingularTab::setDefaults()
+{
+  currentGlyphIndex_ = 0;
+
+  sizeDoubleSpinBox_->setValue(20);
+  dpiSpinBox_->setValue(96);
+  zoomSpinBox_->setValue(20);
+  showBitmapCheckBox_->setChecked(true);
+  showOutlinesCheckBox_->setChecked(true);
+
+  checkUnits();
+  indexSelector_->setCurrentIndex(indexSelector_->getCurrentIndex(), true);
+  zoom();
+}
+
+
+// end of singular.cpp
diff --git a/src/ftinspect/panels/singular.hpp 
b/src/ftinspect/panels/singular.hpp
new file mode 100644
index 0000000..e1b1c80
--- /dev/null
+++ b/src/ftinspect/panels/singular.hpp
@@ -0,0 +1,121 @@
+// singular.hpp
+
+// Copyright (C) 2022 by Charlie Jiang.
+
+#pragma once
+
+#include "abstracttab.hpp"
+#include "../widgets/customwidgets.hpp"
+#include "../widgets/glyphindexselector.hpp"
+#include "../rendering/glyphbitmap.hpp"
+#include "../rendering/glyphoutline.hpp"
+#include "../rendering/glyphpointnumbers.hpp"
+#include "../rendering/glyphpoints.hpp"
+#include "../rendering/grid.hpp"
+#include "../engine/engine.hpp"
+#include "../models/ttsettingscomboboxmodel.hpp"
+
+#include <QWidget>
+#include <QPushButton>
+#include <QSpinBox>
+#include <QGraphicsScene>
+#include <QGraphicsView>
+#include <QScrollBar>
+#include <QLabel>
+#include <QComboBox>
+#include <QPen>
+#include <QCheckBox>
+#include <QVector>
+#include <QGridLayout>
+#include <QBoxLayout>
+
+class SingularTab
+: public QWidget, public AbstractTab
+{
+  Q_OBJECT
+public:
+  SingularTab(QWidget* parent, Engine* engine);
+  virtual ~SingularTab();
+
+  void repaint();
+  void reloadFont();
+  void syncSettings();
+  void setDefaults();
+
+private slots:
+  void setGlyphIndex(int);
+  void drawGlyph();
+
+  void checkUnits();
+  void checkShowPoints();
+
+  void zoom();
+  void backToCenter();
+  void wheelZoom(QWheelEvent* event);
+  void wheelResize(QWheelEvent* event);
+
+private:
+  int currentGlyphIndex_;
+  int currentGlyphCount_;
+
+  Engine* engine_;
+
+  QGraphicsScene* glyphScene_;
+  QGraphicsViewx* glyphView_;
+
+  GlyphOutline* currentGlyphOutlineItem_;
+  GlyphPoints* currentGlyphPointsItem_;
+  GlyphPointNumbers* currentGlyphPointNumbersItem_;
+  GlyphBitmap* currentGlyphBitmapItem_;
+  Grid* gridItem_ = NULL;
+  QLabel* mouseUsageHint_;
+
+  GlyphIndexSelector* indexSelector_;
+  QLabel* dpiLabel_;
+  QLabel* sizeLabel_;
+  QLabel* zoomLabel_;
+  QSpinBox* dpiSpinBox_;
+  ZoomSpinBox* zoomSpinBox_;
+  QComboBox* unitsComboBox_;
+  QDoubleSpinBox* sizeDoubleSpinBox_;
+  QPushButton* centerGridButton_;
+
+  QLabel* glyphIndexLabel_;
+  QLabel* glyphNameLabel_;
+
+  QCheckBox* showBitmapCheckBox_;
+  QCheckBox* showOutlinesCheckBox_;
+  QCheckBox* showPointNumbersCheckBox_;
+  QCheckBox* showPointsCheckBox_;
+
+  QVBoxLayout* mainLayout_;
+  QHBoxLayout* checkBoxesLayout_;
+  QHBoxLayout* sizeLayout_;
+  QGridLayout* glyphOverlayLayout_;
+  QHBoxLayout* glyphOverlayIndexLayout_;
+
+  QPen axisPen_;
+  QPen blueZonePen_;
+  QPen gridPen_;
+  QPen offPen_;
+  QPen onPen_;
+  QPen outlinePen_;
+  QPen segmentPen_;
+
+  QVector<QRgb> grayColorTable_;
+  QVector<QRgb> monoColorTable_;
+
+  enum Units
+  {
+    Units_px,
+    Units_pt
+  };
+
+  void createLayout();
+  void createConnections();
+  void setGraphicsDefaults();
+  
+  void updateGrid();
+};
+
+// end of singular.hpp
diff --git a/src/ftinspect/widgets/customwidgets.cpp 
b/src/ftinspect/widgets/customwidgets.cpp
index 893fe0d..85d0f32 100644
--- a/src/ftinspect/widgets/customwidgets.cpp
+++ b/src/ftinspect/widgets/customwidgets.cpp
@@ -71,7 +71,7 @@ QGraphicsViewx::resizeEvent(QResizeEvent* event)
 // so that we can do that symmetrically
 
 int
-QSpinBoxx::valueFromText(const QString& text) const
+ZoomSpinBox::valueFromText(const QString& text) const
 {
   int val = QSpinBox::valueFromText(text);
 
@@ -92,14 +92,14 @@ QSpinBoxx::valueFromText(const QString& text) const
 }
 
 
-QSpinBoxx::QSpinBoxx(QWidget* parent)
+ZoomSpinBox::ZoomSpinBox(QWidget* parent)
 : QSpinBox(parent)
 {
 }
 
 
 void
-QSpinBoxx::stepBy(int steps)
+ZoomSpinBox::stepBy(int steps)
 {
   int val = value();
 
diff --git a/src/ftinspect/widgets/customwidgets.hpp 
b/src/ftinspect/widgets/customwidgets.hpp
index 4c51f63..87296b8 100644
--- a/src/ftinspect/widgets/customwidgets.hpp
+++ b/src/ftinspect/widgets/customwidgets.hpp
@@ -43,13 +43,13 @@ private:
 
 
 // we want to have our own `stepBy' function for the zoom spin box
-class QSpinBoxx
+class ZoomSpinBox
 : public QSpinBox
 {
   Q_OBJECT
 
 public:
-  QSpinBoxx(QWidget* parent);
+  ZoomSpinBox(QWidget* parent);
   void stepBy(int val);
   int valueFromText(const QString& text) const;
 };



reply via email to

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