From 1a889dc612c036763d80815a00d4b9a8fc73ba78 Mon Sep 17 00:00:00 2001
From: Adrien Tétar
Date: Thu, 29 Oct 2015 21:09:19 +0100
Subject: meta: shufflings, loadMostRecentFile, metricsWindow: canvas key
shortcuts
---
Lib/defconQt/fontView.py | 203 ++++++----
Lib/defconQt/glyphView.py | 4 +-
Lib/defconQt/metricsWindow.py | 879 ++++++++++++++++++++++++++++++++++++++++++
Lib/defconQt/spaceCenter.py | 826 ---------------------------------------
4 files changed, 1003 insertions(+), 909 deletions(-)
create mode 100644 Lib/defconQt/metricsWindow.py
delete mode 100644 Lib/defconQt/spaceCenter.py
(limited to 'Lib')
diff --git a/Lib/defconQt/fontView.py b/Lib/defconQt/fontView.py
index b0cc7e9..523719f 100644
--- a/Lib/defconQt/fontView.py
+++ b/Lib/defconQt/fontView.py
@@ -1,3 +1,4 @@
+from defconQt import __version__
from defconQt.featureTextEditor import MainEditWindow
from defconQt.fontInfo import TabDialog
from defconQt.glyphCollectionView import GlyphCollectionWidget
@@ -8,7 +9,7 @@ from defconQt.scriptingWindow import MainScriptingWindow
from defconQt.objects.defcon import GlyphSet, TFont, TGlyph
from defconQt.util import platformSpecific
from defcon import Color, Component
-from defconQt.spaceCenter import MainSpaceWindow, comboBoxItems
+from defconQt.metricsWindow import MainMetricsWindow, comboBoxItems
from PyQt5.QtCore import (
pyqtSignal, QEvent, QMimeData, QRegularExpression, QSettings, Qt)
from PyQt5.QtGui import (
@@ -16,13 +17,14 @@ from PyQt5.QtGui import (
QRegularExpressionValidator, QTextCursor)
from PyQt5.QtWidgets import (
QAbstractItemView, QAction, QApplication, QCheckBox, QComboBox, QDialog,
- QDialogButtonBox, QErrorMessage, QFileDialog, QGridLayout, QGroupBox,
- QLabel, QLineEdit, QListWidget, QListWidgetItem, QMainWindow, QMenu,
- QMessageBox, QPlainTextEdit, QPushButton, QRadioButton, QSlider, QSplitter,
- QTabWidget, QTextEdit, QToolTip, QVBoxLayout, QWidget)
+ QDialogButtonBox, QFileDialog, QGridLayout, QGroupBox, QLabel, QLineEdit,
+ QListWidget, QListWidgetItem, QMainWindow, QMenu, QMessageBox,
+ QPlainTextEdit, QPushButton, QRadioButton, QSlider, QSplitter, QTabWidget,
+ QTextEdit, QToolTip, QVBoxLayout, QWidget)
from collections import OrderedDict
import os
import pickle
+import platform
import traceback
cannedDesign = [
@@ -114,11 +116,11 @@ class Application(QApplication):
MAX_RECENT_FILES = 6
-class InspectorWindow(QWidget):
+class InfoPanel(QWidget):
def __init__(self):
- super(InspectorWindow, self).__init__(flags=Qt.Tool)
- self.setWindowTitle("Inspector")
+ super().__init__(flags=Qt.Tool)
+ self.setWindowTitle("Info Panel")
self._blocked = False
self._glyph = None
@@ -248,7 +250,8 @@ class InspectorWindow(QWidget):
self.setLayout(mainLayout)
def showEvent(self, event):
- super(InspectorWindow, self).showEvent(event)
+ super().showEvent(event)
+ # TODO: does not put window on secondary screen if applicable
screenRect = QApplication.desktop().screenGeometry()
widgetRect = self.frameGeometry()
x = screenRect.width() - (widgetRect.width() + 20)
@@ -381,11 +384,11 @@ class InspectorWindow(QWidget):
self._blocked = False
-class AddGlyphDialog(QDialog):
+class AddGlyphsDialog(QDialog):
# TODO: implement Frederik's Glyph Construction Builder
def __init__(self, currentGlyphs=None, parent=None):
- super(AddGlyphDialog, self).__init__(parent)
+ super(AddGlyphsDialog, self).__init__(parent)
self.setWindowModality(Qt.WindowModal)
self.setWindowTitle("Add glyphs…")
self.currentGlyphs = currentGlyphs
@@ -398,8 +401,8 @@ class AddGlyphDialog(QDialog):
for name, glyphNames in glyphSets.items():
self.importCharDrop.addItem(name, glyphNames)
self.importCharDrop.currentIndexChanged[int].connect(self.importGlyphs)
- self.addGlyphEdit = QPlainTextEdit(self)
- self.addGlyphEdit.setFocus(True)
+ self.addGlyphsEdit = QPlainTextEdit(self)
+ self.addGlyphsEdit.setFocus(True)
self.addUnicodeBox = QCheckBox("Add Unicode", self)
self.addUnicodeBox.setChecked(True)
@@ -415,7 +418,7 @@ class AddGlyphDialog(QDialog):
l = 0
layout.addWidget(self.importCharDrop, l, 3, 1, 2)
l += 1
- layout.addWidget(self.addGlyphEdit, l, 0, 1, 5)
+ layout.addWidget(self.addGlyphsEdit, l, 0, 1, 5)
l += 1
layout.addWidget(self.addUnicodeBox, l, 0)
layout.addWidget(self.addAsTemplateBox, l, 1)
@@ -435,7 +438,7 @@ class AddGlyphDialog(QDialog):
sortFont=dialog.sortFontBox.isChecked(),
)
newGlyphNames = []
- for name in dialog.addGlyphEdit.toPlainText().split():
+ for name in dialog.addGlyphsEdit.toPlainText().split():
if name not in dialog.currentGlyphNames:
newGlyphNames.append(name)
return (newGlyphNames, params, result)
@@ -444,7 +447,7 @@ class AddGlyphDialog(QDialog):
if index == 0:
return
glyphNames = self.importCharDrop.currentData()
- editorNames = self.addGlyphEdit.toPlainText().split()
+ editorNames = self.addGlyphsEdit.toPlainText().split()
currentNames = set(self.currentGlyphNames) ^ set(editorNames)
changed = False
for name in glyphNames:
@@ -452,12 +455,12 @@ class AddGlyphDialog(QDialog):
changed = True
editorNames.append(name)
if changed:
- self.addGlyphEdit.setPlainText(" ".join(editorNames))
- cursor = self.addGlyphEdit.textCursor()
+ self.addGlyphsEdit.setPlainText(" ".join(editorNames))
+ cursor = self.addGlyphsEdit.textCursor()
cursor.movePosition(QTextCursor.End, QTextCursor.MoveAnchor)
- self.addGlyphEdit.setTextCursor(cursor)
+ self.addGlyphsEdit.setTextCursor(cursor)
self.importCharDrop.setCurrentIndex(0)
- self.addGlyphEdit.setFocus(True)
+ self.addGlyphsEdit.setFocus(True)
class SortDialog(QDialog):
@@ -467,7 +470,7 @@ class SortDialog(QDialog):
self.setWindowModality(Qt.WindowModal)
self.setWindowTitle("Sort…")
- self.smartSortBox = QRadioButton("Smart sort", self)
+ self.smartSortBox = QRadioButton("Optimized sort", self)
self.smartSortBox.setToolTip("A combination of simple, complex and "
"custom sorts that give optimized "
"ordering results.")
@@ -624,10 +627,16 @@ class MainWindow(QMainWindow):
self.collectionWidget = GlyphCollectionWidget(self)
self._font = None
self._sortDescriptor = None
- if font is None:
- self.newFile(True)
- else:
+ settings = QSettings()
+ loadRecentFile = settings.value("core/loadRecentFile", False, bool)
+ if font is None and loadRecentFile:
+ recentFiles = settings.value("core/recentFiles", [], type=str)
+ if len(recentFiles):
+ self.openFile(recentFiles[0], True)
+ elif font is not None:
self.font = font
+ if self._font is None:
+ self.newFile(True)
self.collectionWidget.glyphSelectedCallback = self._selectionChanged
self.collectionWidget.doubleClickCallback = self._glyphOpened
# TODO: should default be True or False?
@@ -640,7 +649,7 @@ class MainWindow(QMainWindow):
fileMenu.addAction("&New…", self.newFile, QKeySequence.New)
fileMenu.addAction("&Open…", self.openFile, QKeySequence.Open)
# recent files
- self.recentFilesMenu = QMenu("Open &recent…", self)
+ self.recentFilesMenu = QMenu("Open &Recent…", self)
for i in range(MAX_RECENT_FILES):
action = QAction(self.recentFilesMenu)
action.setVisible(False)
@@ -650,14 +659,14 @@ class MainWindow(QMainWindow):
fileMenu.addMenu(self.recentFilesMenu)
fileMenu.addSeparator()
fileMenu.addAction("&Save", self.saveFile, QKeySequence.Save)
- fileMenu.addAction("Save &as…", self.saveFileAs, QKeySequence.SaveAs)
+ fileMenu.addAction("Save &As…", self.saveFileAs, QKeySequence.SaveAs)
fileMenu.addAction("Export…", self.export)
- fileMenu.addAction("Reload from disk", self.reload)
+ fileMenu.addAction("Reload From Disk", self.reload)
fileMenu.addAction("E&xit", self.close, QKeySequence.Quit)
menuBar.addMenu(fileMenu)
editMenu = QMenu("&Edit", self)
- markColorMenu = QMenu("Flag color", self)
+ markColorMenu = QMenu("Flag Color", self)
pixmap = QPixmap(24, 24)
none = markColorMenu.addAction("None", self.markColor)
none.setData(None)
@@ -675,7 +684,7 @@ class MainWindow(QMainWindow):
green.setData(QColor(Qt.green))
editMenu.addMenu(markColorMenu)
editMenu.addAction("Copy", self.copy, QKeySequence.Copy)
- editMenu.addAction("Copy as component",
+ editMenu.addAction("Copy As Component",
self.copyAsComponent, "Ctrl+Alt+C")
editMenu.addAction("Paste", self.paste, QKeySequence.Paste)
editMenu.addSeparator()
@@ -685,23 +694,23 @@ class MainWindow(QMainWindow):
fontMenu = QMenu("&Font", self)
# TODO: work out sensible shortcuts and make sure they're
# cross-platform ready - consider extracting them into separate file?
- fontMenu.addAction("&Add glyph", self.addGlyph, "Ctrl+G")
- fontMenu.addAction("Font &info", self.fontInfo, "Ctrl+Alt+I")
- fontMenu.addAction("Font &features", self.fontFeatures, "Ctrl+Alt+F")
+ fontMenu.addAction("&Add Glyphs…", self.addGlyphs, "Ctrl+G")
+ fontMenu.addAction("Font &Info", self.fontInfo, "Ctrl+Alt+I")
+ fontMenu.addAction("Font &Features", self.fontFeatures, "Ctrl+Alt+F")
fontMenu.addSeparator()
- fontMenu.addAction("Sort…", self.sortGlyphs)
+ fontMenu.addAction("&Sort…", self.sortGlyphs)
menuBar.addMenu(fontMenu)
pythonMenu = QMenu("&Python", self)
- pythonMenu.addAction("Scripting &window", self.scripting, "Ctrl+Alt+R")
+ pythonMenu.addAction("Scripting &Window", self.scripting, "Ctrl+Alt+R")
menuBar.addMenu(pythonMenu)
windowMenu = QMenu("&Windows", self)
- action = windowMenu.addAction("&Inspector", self.inspector, "Ctrl+I")
+ action = windowMenu.addAction("&Info Panel", self.infoPanel, "Ctrl+I")
# XXX: we're getting duplicate shortcut when we spawn a new window...
action.setShortcutContext(Qt.ApplicationShortcut)
- windowMenu.addAction("&Space center", self.spaceCenter, "Ctrl+Alt+S")
- windowMenu.addAction("&Groups window", self.fontGroups, "Ctrl+Alt+G")
+ windowMenu.addAction("&Metrics Window", self.metrics, "Ctrl+Alt+S")
+ windowMenu.addAction("&Groups Window", self.groups, "Ctrl+Alt+G")
menuBar.addMenu(windowMenu)
helpMenu = QMenu("&Help", self)
@@ -753,7 +762,7 @@ class MainWindow(QMainWindow):
else:
self.font = font
- def openFile(self, path=None):
+ def openFile(self, path=None, stickToSelf=False):
if not path:
path, ok = QFileDialog.getOpenFileName(
self, "Open File", '',
@@ -765,7 +774,7 @@ class MainWindow(QMainWindow):
if ".plist" in path:
path = os.path.dirname(path)
for window in QApplication.topLevelWidgets():
- if (isinstance(window, MainWindow)
+ if (isinstance(window, MainWindow) and window._font is not None
and window._font.path == path):
window.raise_()
return
@@ -774,8 +783,11 @@ class MainWindow(QMainWindow):
except:
print(traceback.format_exc())
return
- window = MainWindow(font)
- window.show()
+ if not stickToSelf:
+ window = MainWindow(font)
+ window.show()
+ else:
+ self.font = font
def openRecentFont(self):
fontPath = self.sender().toolTip()
@@ -822,12 +834,12 @@ class MainWindow(QMainWindow):
try:
from ufo2fdk import haveFDK, OTFCompiler
except Exception as e:
- errorMessage = QErrorMessage(self)
- errorMessage.showMessage(e)
+ title = e.__class__.__name__
+ QMessageBox.critical(self, title, str(e))
return
if not haveFDK():
- errorMessage = QErrorMessage(self)
- errorMessage.showMessage("The Adobe FDK could not be found.")
+ QMessageBox.critical(self, "Missing dependency", "The Adobe FDK "
+ "could not be found.")
return
path, ok = QFileDialog.getSaveFileName(self, "Save File", None,
@@ -1095,32 +1107,31 @@ class MainWindow(QMainWindow):
else:
self.fontFeaturesWindow.raise_()
- def spaceCenter(self):
+ def metrics(self):
# TODO: see up here
- # TODO: show selection in a space center, rewind selection if we raise
- # window (rf)
- if not (hasattr(self, 'spaceCenterWindow')
- and self.spaceCenterWindow.isVisible()):
- self.spaceCenterWindow = MainSpaceWindow(self.font, parent=self)
- self.spaceCenterWindow.show()
+ if not (hasattr(self, 'metricsWindow')
+ and self.metricsWindow.isVisible()):
+ self.metricsWindow = MainMetricsWindow(self.font, parent=self)
+ self.metricsWindow.show()
else:
- self.spaceCenterWindow.raise_()
+ self.metricsWindow.raise_()
+ # TODO: decouple
selection = self.collectionWidget.selection
if selection:
glyphs = []
for item in sorted(selection):
glyph = self.collectionWidget.glyphs[item]
glyphs.append(glyph)
- self.spaceCenterWindow.setGlyphs(glyphs)
+ self.metricsWindow.setGlyphs(glyphs)
- def fontGroups(self):
+ def groups(self):
# TODO: see up here
- if not (hasattr(self, 'fontGroupsWindow')
- and self.fontGroupsWindow.isVisible()):
- self.fontGroupsWindow = GroupsWindow(self.font, self)
- self.fontGroupsWindow.show()
+ if not (hasattr(self, 'groupsWindow')
+ and self.groupsWindow.isVisible()):
+ self.groupsWindow = GroupsWindow(self.font, self)
+ self.groupsWindow.show()
else:
- self.fontGroupsWindow.raise_()
+ self.groupsWindow.raise_()
def scripting(self):
app = QApplication.instance()
@@ -1132,18 +1143,18 @@ class MainWindow(QMainWindow):
else:
app.scriptingWindow.show()
- def inspector(self):
+ def infoPanel(self):
app = QApplication.instance()
- if not hasattr(app, 'inspectorWindow'):
- app.inspectorWindow = InspectorWindow()
- app.inspectorWindow.show()
- elif app.inspectorWindow.isVisible():
+ if not hasattr(app, 'infoPanelWindow'):
+ app.infoPanelWindow = InfoPanel()
+ app.infoPanelWindow.show()
+ elif app.infoPanelWindow.isVisible():
# TODO: do this only if the widget is user-visible, otherwise the
# key press feels as if it did nothing
# toggle
- app.inspectorWindow.close()
+ app.infoPanelWindow.close()
else:
- app.inspectorWindow.show()
+ app.infoPanelWindow.show()
def sortGlyphs(self):
sortDescriptor, ok = SortDialog.getDescriptor(self,
@@ -1151,9 +1162,9 @@ class MainWindow(QMainWindow):
if ok:
self.sortDescriptor = sortDescriptor
- def addGlyph(self):
+ def addGlyphs(self):
glyphs = self.collectionWidget.glyphs
- newGlyphNames, params, ok = AddGlyphDialog.getNewGlyphNames(
+ newGlyphNames, params, ok = AddGlyphsDialog.getNewGlyphNames(
self, glyphs)
if ok:
sortFont = params.pop("sortFont")
@@ -1171,12 +1182,19 @@ class MainWindow(QMainWindow):
def about(self):
name = QApplication.applicationName()
- QMessageBox.about(
- self, "About {}".format(name),
- "
About {}
"
- "I am a new UFO-centric font editor and I aim to bring "
- "the robofab ecosystem to all main operating systems, "
- "in a fast and dependency-free package.
".format(name))
+ domain = QApplication.organizationDomain()
+ text = "About {}
" \
+ "I am a new UFO-centric font editor and I aim to bring " \
+ "the robofab ecosystem to all main operating systems, " \
+ "in a fast and dependency-free package.
" \
+ "Version {} – Python {}.".format(
+ name, __version__, platform.python_version())
+ if domain:
+ text += "
See {d} for more " \
+ "information.
".format(d=domain)
+ else:
+ text += ""
+ QMessageBox.about(self, "About {}".format(name), text)
class SettingsDialog(QDialog):
@@ -1188,7 +1206,8 @@ class SettingsDialog(QDialog):
self.tabWidget = QTabWidget(self)
self.tabWidget.addTab(GlyphSetTab(self), "Glyph sets")
- self.tabWidget.addTab(SpaceCenterTab(self), "Space center")
+ self.tabWidget.addTab(MetricsWindowTab(self), "Metrics Window")
+ self.tabWidget.addTab(MiscTab(self), "Misc")
buttonBox = QDialogButtonBox(
QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
@@ -1232,7 +1251,7 @@ def readGlyphSets(settings=None):
class GlyphSetTab(QWidget):
def __init__(self, parent=None):
- super(GlyphSetTab, self).__init__(parent)
+ super().__init__(parent)
settings = QSettings()
self.defaultGlyphSetBox = QCheckBox("Default glyph set:", self)
@@ -1366,16 +1385,16 @@ class GlyphSetTab(QWidget):
settings.setValue("settings/defaultGlyphSet", defaultGlyphSet)
-class SpaceCenterTab(QTabWidget):
+class MetricsWindowTab(QTabWidget):
def __init__(self, parent=None):
- super(SpaceCenterTab, self).__init__(parent)
+ super().__init__(parent)
settings = QSettings()
self.inputTextLabel = QLabel("Default text:", self)
self.inputTextList = QListWidget(self)
self.inputTextList.setDragDropMode(QAbstractItemView.InternalMove)
- entries = settings.value("spaceCenter/comboBoxItems", comboBoxItems,
+ entries = settings.value("metricsWindow/comboBoxItems", comboBoxItems,
str)
for entry in entries:
item = QListWidgetItem(entry, self.inputTextList)
@@ -1414,4 +1433,26 @@ class SpaceCenterTab(QTabWidget):
item = self.inputTextList.item(i)
entries.append(item.text())
settings = QSettings()
- settings.setValue("spaceCenter/comboBoxItems", entries)
+ settings.setValue("metricsWindow/comboBoxItems", entries)
+
+
+class MiscTab(QTabWidget):
+
+ def __init__(self, parent=None):
+ super().__init__(parent)
+
+ settings = QSettings()
+ loadRecentFile = settings.value("core/loadRecentFile", False, bool)
+ self.loadRecentFileBox = QCheckBox("Load most recent file on start",
+ self)
+ self.loadRecentFileBox.setChecked(loadRecentFile)
+
+ layout = QVBoxLayout(self)
+ l = 0
+ layout.addWidget(self.loadRecentFileBox, l)
+ self.setLayout(layout)
+
+ def writeValues(self):
+ settings = QSettings()
+ loadRecentFile = self.loadRecentFileBox.isChecked()
+ settings.setValue("core/loadRecentFile", loadRecentFile)
diff --git a/Lib/defconQt/glyphView.py b/Lib/defconQt/glyphView.py
index 87232c3..c9b483a 100644
--- a/Lib/defconQt/glyphView.py
+++ b/Lib/defconQt/glyphView.py
@@ -181,7 +181,7 @@ class AddLayerDialog(QDialog):
layout.addWidget(layerNameLabel, l, 0)
layout.addWidget(self.layerNameEdit, l, 1)
l += 1
- layout.addWidget(buttonBox, l, 2)
+ layout.addWidget(buttonBox, l, 0, 1, 2)
self.setLayout(layout)
@classmethod
@@ -302,7 +302,7 @@ class MainGfxWindow(QMainWindow):
menuBar.addMenu(fileMenu)
glyphMenu = QMenu("&Glyph", self)
- glyphMenu.addAction("&Jump", self.changeGlyph, "J")
+ glyphMenu.addAction("&Go to…", self.changeGlyph, "G")
menuBar.addMenu(glyphMenu)
self._displaySettings = DisplayStyleSettings(
diff --git a/Lib/defconQt/metricsWindow.py b/Lib/defconQt/metricsWindow.py
new file mode 100644
index 0000000..27689ab
--- /dev/null
+++ b/Lib/defconQt/metricsWindow.py
@@ -0,0 +1,879 @@
+from defconQt import icons_db # noqa
+from defconQt.glyphCollectionView import arrowKeys, cellSelectionColor
+from defconQt.glyphView import MainGfxWindow
+from defconQt.objects.defcon import TGlyph
+from getpass import getuser
+from PyQt5.QtCore import QEvent, QSettings, QSize, Qt
+from PyQt5.QtGui import (
+ QBrush, QColor, QIcon, QIntValidator, QKeySequence, QPainter, QPalette,
+ QPen)
+from PyQt5.QtWidgets import (
+ QAbstractItemView, QActionGroup, QApplication, QComboBox, QLineEdit, QMenu,
+ QPushButton, QScrollArea, QStyledItemDelegate, QTableWidget,
+ QTableWidgetItem, QVBoxLayout, QSizePolicy, QToolBar, QWidget)
+import re
+
+comboBoxItems = [
+ "abcdefghijklmnopqrstuvwxyz",
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ",
+ "0123456789",
+ "nn/? nono/? oo",
+ "HH/? HOHO/? OO",
+]
+
+defaultPointSize = 150
+glyphSelectionColor = QColor(cellSelectionColor)
+glyphSelectionColor.setAlphaF(.09)
+
+escapeRep = {
+ "//": "/slash ",
+ "\\n": "\u2029",
+}
+escapeRep = dict((re.escape(k), v) for k, v in escapeRep.items())
+escapeRe = re.compile("|".join(escapeRep.keys()))
+
+
+class MainMetricsWindow(QWidget):
+
+ def __init__(self, font, string=None, pointSize=defaultPointSize,
+ parent=None):
+ super().__init__(parent, Qt.Window)
+
+ if string is None:
+ try:
+ string = getuser()
+ except:
+ string = "World"
+ string = "Hello %s" % string
+ # TODO: drop self.font and self.glyphs, store in the widgets only
+ self.font = font
+ self.glyphs = []
+ self.toolbar = FontToolBar(pointSize, self)
+ self.canvas = GlyphsCanvas(font, pointSize, self)
+ self.table = SpaceTable(self)
+ self.toolbar.comboBox.currentIndexChanged[
+ str].connect(self.canvas.setPointSize)
+ self.canvas.doubleClickCallback = self._glyphOpened
+ self.canvas.pointSizeChangedCallback = self.toolbar.setPointSize
+ self.canvas.selectionChangedCallback = self.table.setCurrentGlyph
+ self.table.selectionChangedCallback = self.canvas.setSelected
+
+ self.toolbar.textField.editTextChanged.connect(self._textChanged)
+ self.toolbar.textField.setEditText(string)
+ app = QApplication.instance()
+ app.currentGlyphChanged.connect(self._textChanged)
+
+ layout = QVBoxLayout(self)
+ layout.addWidget(self.toolbar)
+ layout.addWidget(self.canvas.scrollArea())
+ layout.addWidget(self.table)
+ layout.setContentsMargins(0, 0, 0, 0)
+ layout.setSpacing(0)
+ self.setLayout(layout)
+ self.resize(600, 500)
+
+ self.font.info.addObserver(self, "_fontInfoChanged", "Info.Changed")
+
+ self.setWindowTitle("Metrics Window – %s %s" % (
+ self.font.info.familyName, self.font.info.styleName))
+
+ def setupFileMenu(self):
+ fileMenu = QMenu("&File", self)
+ fileMenu.addAction("&Save...", self.save, QKeySequence.Save)
+ fileMenu.addAction("E&xit", self.close, QKeySequence.Quit)
+ self.menuBar().addMenu(fileMenu)
+
+ def close(self):
+ self.font.info.removeObserver(self, "Info.Changed")
+ self._unsubscribeFromGlyphs()
+ super().close()
+
+ def _fontInfoChanged(self, notification):
+ self.canvas.fetchFontMetrics()
+ self.canvas.update()
+
+ def _glyphChanged(self, notification):
+ if not self.canvas._editing:
+ self.canvas.update()
+ if not self.table._editing:
+ self.table.updateCells(self.canvas._editing)
+
+ def _glyphOpened(self, glyph):
+ glyphViewWindow = MainGfxWindow(glyph, self.parent())
+ glyphViewWindow.show()
+
+ def _textChanged(self):
+ def fetchGlyphs(glyphNames, leftGlyphs=[], rightGlyphs=[]):
+ ret = []
+ for name in glyphNames:
+ if name == "\u2029":
+ glyph = TGlyph()
+ glyph.unicode = 2029
+ ret.append(glyph)
+ elif name in self.font:
+ ret.extend(leftGlyphs)
+ ret.append(self.font[name])
+ ret.extend(rightGlyphs)
+ return ret
+
+ # unsubscribe from the old glyphs
+ self._unsubscribeFromGlyphs()
+ # subscribe to the new glyphs
+ left = self.textToGlyphNames(self.toolbar.leftTextField.text())
+ newText = self.textToGlyphNames(self.toolbar.textField.currentText())
+ right = self.textToGlyphNames(self.toolbar.rightTextField.text())
+ leftGlyphs = fetchGlyphs(left)
+ rightGlyphs = fetchGlyphs(right)
+ finalGlyphs = fetchGlyphs(newText, leftGlyphs, rightGlyphs)
+ self._subscribeToGlyphs(finalGlyphs)
+ # set the records into the view
+ self.canvas.setGlyphs(self.glyphs)
+ self.table.setGlyphs(self.glyphs)
+
+ # Tal Leming. Edited.
+ def textToGlyphNames(self, text):
+ def catchCompile():
+ if compileStack[0] == "?":
+ glyph = app.currentGlyph()
+ if glyph is not None:
+ glyphNames.append(glyph.name)
+ elif compileStack:
+ glyphNames.append("".join(compileStack))
+
+ app = QApplication.instance()
+ # escape //, \n
+ text = escapeRe.sub(lambda m: escapeRep[re.escape(m.group(0))], text)
+ #
+ glyphNames = []
+ compileStack = None
+ for c in text:
+ # start a glyph name compile.
+ if c == "/":
+ # finishing a previous compile.
+ if compileStack is not None:
+ # only add the compile if something has been added to the
+ # stack.
+ if compileStack:
+ glyphNames.append("".join(compileStack))
+ # reset the stack.
+ compileStack = []
+ # adding to or ending a glyph name compile.
+ elif compileStack is not None:
+ # space. conclude the glyph name compile.
+ if c == " ":
+ # only add the compile if something has been added to the
+ # stack.
+ catchCompile()
+ compileStack = None
+ # add the character to the stack.
+ else:
+ compileStack.append(c)
+ # adding a character that needs to be converted to a glyph name.
+ else:
+ uni = ord(c)
+ if uni == 0x2029:
+ glyphName = c
+ else:
+ glyphName = self.font.unicodeData.glyphNameForUnicode(uni)
+ glyphNames.append(glyphName)
+ # catch remaining compile.
+ if compileStack is not None and compileStack:
+ catchCompile()
+ return glyphNames
+
+ def _subscribeToGlyphs(self, glyphs):
+ self.glyphs = glyphs
+
+ handledGlyphs = set()
+ for glyph in self.glyphs:
+ if glyph in handledGlyphs:
+ continue
+ handledGlyphs.add(glyph)
+ glyph.addObserver(self, "_glyphChanged", "Glyph.Changed")
+
+ def _unsubscribeFromGlyphs(self):
+ handledGlyphs = set()
+ for glyph in self.glyphs:
+ if glyph in handledGlyphs:
+ continue
+ handledGlyphs.add(glyph)
+ glyph.removeObserver(self, "Glyph.Changed")
+ # self.glyphs = None
+
+ def setGlyphs(self, glyphs):
+ # unsubscribe from the old glyphs
+ self._unsubscribeFromGlyphs()
+ # subscribe to the new glyphs
+ self._subscribeToGlyphs(glyphs)
+ glyphNames = []
+ for glyph in glyphs:
+ if glyph.unicode:
+ glyphNames.append(chr(glyph.unicode))
+ else:
+ glyphNames.append("".join(("/", glyph.name, " ")))
+ self.toolbar.textField.setEditText("".join(glyphNames))
+ # set the records into the view
+ self.canvas.setGlyphs(self.glyphs)
+ self.table.setGlyphs(self.glyphs)
+
+pointSizes = [50, 75, 100, 125, 150, 200, 250, 300, 350, 400, 450, 500]
+
+
+class FontToolBar(QToolBar):
+
+ def __init__(self, pointSize, parent=None):
+ super(FontToolBar, self).__init__(parent)
+ auxiliaryWidth = self.fontMetrics().width('0') * 8
+ self.leftTextField = QLineEdit(self)
+ self.leftTextField.setMaximumWidth(auxiliaryWidth)
+ self.textField = QComboBox(self)
+ self.textField.setEditable(True)
+ completer = self.textField.completer()
+ completer.setCaseSensitivity(Qt.CaseSensitive)
+ self.textField.setCompleter(completer)
+ # XXX: had to use Maximum because Preferred did entend the widget(?)
+ self.textField.setSizePolicy(QSizePolicy.Expanding,
+ QSizePolicy.Maximum)
+ items = QSettings().value("metricsWindow/comboBoxItems", comboBoxItems,
+ str)
+ self.textField.addItems(items)
+ self.rightTextField = QLineEdit(self)
+ self.rightTextField.setMaximumWidth(auxiliaryWidth)
+ self.leftTextField.textEdited.connect(self.textField.editTextChanged)
+ self.rightTextField.textEdited.connect(self.textField.editTextChanged)
+ self.comboBox = QComboBox(self)
+ self.comboBox.setEditable(True)
+ self.comboBox.setValidator(QIntValidator(self))
+ for p in pointSizes:
+ self.comboBox.addItem(str(p))
+ self.comboBox.setEditText(str(pointSize))
+
+ self.configBar = QPushButton(self)
+ self.configBar.setFlat(True)
+ self.configBar.setIcon(QIcon(":/resources/settings.svg"))
+ self.configBar.setStyleSheet("padding: 2px 0px; padding-right: 10px")
+ self.toolsMenu = QMenu(self)
+ showKerning = self.toolsMenu.addAction(
+ "Show Kerning", self.showKerning)
+ showKerning.setCheckable(True)
+ showMetrics = self.toolsMenu.addAction(
+ "Show Metrics", self.showMetrics)
+ showMetrics.setCheckable(True)
+ self.toolsMenu.addSeparator()
+ wrapLines = self.toolsMenu.addAction("Wrap lines", self.wrapLines)
+ wrapLines.setCheckable(True)
+ noWrapLines = self.toolsMenu.addAction("No wrap", self.noWrapLines)
+ noWrapLines.setCheckable(True)
+ self.toolsMenu.addSeparator()
+ verticalFlip = self.toolsMenu.addAction(
+ "Vertical flip", self.verticalFlip)
+ verticalFlip.setCheckable(True)
+ """
+ lineHeight = QWidgetAction(self.toolsMenu)
+ lineHeight.setText("Line height:")
+ lineHeightSlider = QSlider(Qt.Horizontal, self)
+ # QSlider works with integers so we'll just divide by 100 what comes
+ # out of it
+ lineHeightSlider.setMinimum(80)
+ lineHeightSlider.setMaximum(160)
+ lineHeightSlider.setValue(100)
+ #lineHeightSlider.setContentsMargins(30, 0, 30, 0)
+ lineHeightSlider.valueChanged.connect(self.lineHeight)
+ lineHeight.setDefaultWidget(lineHeightSlider)
+ self.toolsMenu.addAction(lineHeight)
+ """
+
+ wrapLinesGroup = QActionGroup(self)
+ wrapLinesGroup.addAction(wrapLines)
+ wrapLinesGroup.addAction(noWrapLines)
+ wrapLines.setChecked(True)
+ # self.toolsMenu.setActiveAction(wrapLines)
+ self.configBar.setMenu(self.toolsMenu)
+
+ self.addWidget(self.leftTextField)
+ self.addWidget(self.textField)
+ self.addWidget(self.rightTextField)
+ self.addWidget(self.comboBox)
+ self.addWidget(self.configBar)
+
+ def showEvent(self, event):
+ super(FontToolBar, self).showEvent(event)
+ self.textField.setFocus(True)
+
+ def setPointSize(self, pointSize):
+ self.comboBox.blockSignals(True)
+ self.comboBox.setEditText(str(pointSize))
+ self.comboBox.blockSignals(False)
+
+ def showKerning(self):
+ action = self.sender()
+ self.parent().canvas.setShowKerning(action.isChecked())
+
+ def showMetrics(self):
+ action = self.sender()
+ self.parent().canvas.setShowMetrics(action.isChecked())
+
+ def verticalFlip(self):
+ action = self.sender()
+ self.parent().canvas.setVerticalFlip(action.isChecked())
+
+ def lineHeight(self, value):
+ self.parent().canvas.setLineHeight(value / 100)
+
+ def wrapLines(self):
+ self.parent().canvas.setWrapLines(True)
+
+ def noWrapLines(self):
+ self.parent().canvas.setWrapLines(False)
+
+
+class GlyphsCanvas(QWidget):
+
+ def __init__(self, font, pointSize=defaultPointSize, parent=None):
+ super(GlyphsCanvas, self).__init__(parent)
+ self.setAttribute(Qt.WA_KeyCompression)
+ # TODO: should we take focus by tabbing
+ self.setFocusPolicy(Qt.ClickFocus)
+ # XXX: make canvas font-agnostic as in defconAppkit and use
+ # glyph.getParent() instead
+ self.font = font
+ self.fetchFontMetrics()
+ self.glyphs = []
+ self.ptSize = pointSize
+ self.calculateScale()
+ self.padding = 10
+ self._editing = False
+ self._showKerning = False
+ self._showMetrics = False
+ self._verticalFlip = False
+ self._lineHeight = 1.1
+ self._positions = None
+ self._selected = None
+ self.doubleClickCallback = None
+ self.pointSizeChangedCallback = None
+ self.selectionChangedCallback = None
+
+ self._wrapLines = True
+ self._scrollArea = QScrollArea(self.parent())
+ self._scrollArea.resizeEvent = self.resizeEvent
+ self._scrollArea.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
+ self._scrollArea.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
+ self._scrollArea.setWidget(self)
+ self.resize(581, 400)
+
+ def scrollArea(self):
+ return self._scrollArea
+
+ def calculateScale(self):
+ scale = self.ptSize / self.upm
+ if scale < .01:
+ scale = 0.01
+ self.scale = scale
+
+ def setShowKerning(self, showKerning):
+ self._showKerning = showKerning
+ self.update()
+
+ def setShowMetrics(self, showMetrics):
+ self._showMetrics = showMetrics
+ self.update()
+
+ def setVerticalFlip(self, verticalFlip):
+ self._verticalFlip = verticalFlip
+ self.update()
+
+ def setLineHeight(self, lineHeight):
+ self._lineHeight = lineHeight
+ self.update()
+
+ def setWrapLines(self, wrapLines):
+ if self._wrapLines == wrapLines:
+ return
+ self._wrapLines = wrapLines
+ if self._wrapLines:
+ self.resize(self._scrollArea.viewport().width(), self.height())
+ self._scrollArea.setHorizontalScrollBarPolicy(
+ Qt.ScrollBarAlwaysOff)
+ self._scrollArea.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
+ else:
+ self.resize(self.width(), self._scrollArea.viewport().height())
+ self._scrollArea.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
+ self._scrollArea.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
+ self.update()
+
+ def fetchFontMetrics(self):
+ self.ascender = self.font.info.ascender
+ if self.ascender is None:
+ self.ascender = 750
+ self.descender = self.font.info.descender
+ if self.descender is None:
+ self.descender = 250
+ self.upm = self.font.info.unitsPerEm
+ if self.upm is None or not self.upm > 0:
+ self.upm = 1000
+
+ def setGlyphs(self, newGlyphs):
+ self.glyphs = newGlyphs
+ self._selected = None
+ self.update()
+
+ def setPointSize(self, pointSize):
+ self.ptSize = int(pointSize)
+ self.calculateScale()
+ self.update()
+
+ def setSelected(self, selected):
+ self._selected = selected
+ if self._positions is not None:
+ cur_len = 0
+ line = -1
+ for index, li in enumerate(self._positions):
+ if cur_len + len(li) > self._selected:
+ pos, width = li[self._selected - cur_len]
+ line = index
+ break
+ cur_len += len(li)
+ if line > -1:
+ x = self.padding + pos + width / 2
+ y = self.padding + (line + .5) * self.ptSize * self._lineHeight
+ self._scrollArea.ensureVisible(
+ x, y, width / 2 + 20,
+ .5 * self.ptSize * self._lineHeight + 20)
+ self.update()
+
+ def resizeEvent(self, event):
+ if self._wrapLines:
+ self.resize(self._scrollArea.viewport().width(), self.height())
+ else:
+ self.resize(self.width(), self._scrollArea.viewport().height())
+
+ def wheelEvent(self, event):
+ if event.modifiers() & Qt.ControlModifier:
+ # TODO: should it snap to predefined pointSizes?
+ # is the scaling factor okay?
+ # XXX: current alg. is not reversible...
+ decay = event.angleDelta().y() / 120.0
+ scale = round(self.ptSize / 10)
+ if scale == 0 and decay >= 0:
+ scale = 1
+ newPointSize = self.ptSize + int(decay) * scale
+ if newPointSize <= 0:
+ return
+
+ self.setPointSize(newPointSize)
+ if self.pointSizeChangedCallback is not None:
+ self.pointSizeChangedCallback(newPointSize)
+ event.accept()
+ else:
+ super(GlyphsCanvas, self).wheelEvent(event)
+
+ # Tal Leming. Edited.
+ def lookupKerningValue(self, first, second):
+ kerning = self.font.kerning
+ groups = self.font.groups
+ # quickly check to see if the pair is in the kerning dictionary
+ pair = (first, second)
+ if pair in kerning:
+ return kerning[pair]
+ # get group names and make sure first and second are glyph names
+ firstGroup = secondGroup = None
+ if first.startswith("@MMK_L"):
+ firstGroup = first
+ first = None
+ else:
+ for group, groupMembers in groups.items():
+ if group.startswith("@MMK_L"):
+ if first in groupMembers:
+ firstGroup = group
+ break
+ if second.startswith("@MMK_R"):
+ secondGroup = second
+ second = None
+ else:
+ for group, groupMembers in groups.items():
+ if group.startswith("@MMK_R"):
+ if second in groupMembers:
+ secondGroup = group
+ break
+ # make an ordered list of pairs to look up
+ pairs = [
+ (first, second),
+ (first, secondGroup),
+ (firstGroup, second),
+ (firstGroup, secondGroup)
+ ]
+ # look up the pairs and return any matches
+ for pair in pairs:
+ if pair in kerning:
+ return kerning[pair]
+ return 0
+
+ def _arrowKeyPressEvent(self, event):
+ key = event.key()
+ modifiers = event.modifiers()
+ self._editing = True
+ if self._selected is not None:
+ glyph = self.glyphs[self._selected]
+ # TODO: not really DRY w other widgets
+ delta = event.count()
+ if modifiers & Qt.ShiftModifier:
+ delta *= 10
+ if modifiers & Qt.ControlModifier:
+ delta *= 10
+ if key == Qt.Key_Left:
+ delta = -delta
+ if modifiers & Qt.AltModifier:
+ if glyph.leftMargin is not None:
+ glyph.leftMargin += delta
+ else:
+ glyph.width += delta
+ self._editing = False
+ event.accept()
+
+ def keyPressEvent(self, event):
+ key = event.key()
+ if key in arrowKeys:
+ self._arrowKeyPressEvent(event)
+ else:
+ super().keyPressEvent(event)
+
+ def mousePressEvent(self, event):
+ if event.button() == Qt.LeftButton:
+ # XXX: investigate, baselineShift is unused
+ # if self._verticalFlip:
+ # baselineShift = -self.descender
+ # else:
+ # baselineShift = self.ascender
+ found = False
+ line = \
+ (event.y() - self.padding) // (self.ptSize * self._lineHeight)
+ # XXX: Shouldnt // yield an int?
+ line = int(line)
+ if line >= len(self._positions):
+ self._selected = None
+ # XXX: find a way to DRY notification of self._selected changed
+ # w ability to block notifications as well
+ if self.selectionChangedCallback is not None:
+ self.selectionChangedCallback(self._selected)
+ event.accept()
+ self.update()
+ return
+ x = event.x() - self.padding
+ for index, data in enumerate(self._positions[line]):
+ pos, width = data
+ if pos <= x and pos + width > x:
+ count = 0
+ for i in range(line):
+ count += len(self._positions[i])
+ self._selected = count + index
+ found = True
+ break
+ if not found:
+ self._selected = None
+ if self.selectionChangedCallback is not None:
+ self.selectionChangedCallback(self._selected)
+ event.accept()
+ self.update()
+ # restore focus to ourselves, the table widget did take it when we
+ # sent notification
+ # TODO: maybe not set focus on notifiee instead
+ self.setFocus(Qt.MouseFocusReason)
+ else:
+ super(GlyphsCanvas, self).mousePressEvent(event)
+
+ def mouseDoubleClickEvent(self, event):
+ if event.button() == Qt.LeftButton and self._selected is not None:
+ if self.doubleClickCallback is not None:
+ self.doubleClickCallback(self.glyphs[self._selected])
+ else:
+ super(GlyphsCanvas, self).mouseDoubleClickEvent(event)
+
+ def paintEvent(self, event):
+ linePen = QPen(Qt.black)
+ linePen.setWidth(3)
+ width = self.width() / self.scale
+
+ def paintLineMarks(painter):
+ painter.save()
+ painter.scale(self.scale, yDirection * self.scale)
+ painter.setPen(linePen)
+ painter.drawLine(0, self.ascender, width, self.ascender)
+ painter.drawLine(0, 0, width, 0)
+ painter.drawLine(0, self.descender, width, self.descender)
+ painter.restore()
+
+ painter = QPainter(self)
+ painter.setRenderHint(QPainter.Antialiasing)
+ painter.fillRect(0, 0, self.width(), self.height(), Qt.white)
+ if self._verticalFlip:
+ baselineShift = -self.descender
+ yDirection = 1
+ else:
+ baselineShift = self.ascender
+ yDirection = -1
+ painter.translate(self.padding, self.padding +
+ baselineShift * self.scale * self._lineHeight)
+ # TODO: scale painter here to avoid g*scale everywhere below
+
+ cur_width = 0
+ lines = 1
+ self._positions = [[]]
+ if self._showMetrics:
+ paintLineMarks(painter)
+ for index, glyph in enumerate(self.glyphs):
+ # line wrapping
+ gWidth = glyph.width * self.scale
+ doKern = index > 0 and self._showKerning and cur_width > 0
+ if doKern:
+ kern = self.lookupKerningValue(
+ self.glyphs[index - 1].name, glyph.name) * self.scale
+ else:
+ kern = 0
+ if (self._wrapLines and cur_width + gWidth + kern +
+ 2 * self.padding > self.width()) or glyph.unicode == 2029:
+ painter.translate(-cur_width, self.ptSize * self._lineHeight)
+ if self._showMetrics:
+ paintLineMarks(painter)
+ self._positions.append([(0, gWidth)])
+ cur_width = gWidth
+ lines += 1
+ else:
+ if doKern:
+ painter.translate(kern, 0)
+ self._positions[-1].append((cur_width, gWidth))
+ cur_width += gWidth + kern
+ glyphPath = glyph.getRepresentation("defconQt.QPainterPath")
+ painter.save()
+ painter.scale(self.scale, yDirection * self.scale)
+ if self._showMetrics:
+ halfDescent = self.descender / 2
+ painter.drawLine(0, 0, 0, halfDescent)
+ painter.drawLine(glyph.width, 0, glyph.width, halfDescent)
+ if self._selected is not None and index == self._selected:
+ painter.fillRect(0, self.descender, glyph.width,
+ self.upm, glyphSelectionColor)
+ painter.fillPath(glyphPath, Qt.black)
+ painter.restore()
+ painter.translate(gWidth, 0)
+
+ innerHeight = self._scrollArea.viewport().height()
+ if not self._wrapLines:
+ innerWidth = self._scrollArea.viewport().width()
+ width = max(innerWidth, cur_width + self.padding * 2)
+ else:
+ width = self.width()
+ self.resize(width, max(innerHeight, lines * self.ptSize *
+ self._lineHeight + 2 * self.padding))
+
+
+class SpaceTableWidgetItem(QTableWidgetItem):
+
+ def setData(self, role, value):
+ if role & Qt.EditRole:
+ # don't set empty data
+ # XXX: maybe fetch the value from cell back to the editor
+ if value == "":
+ return
+ super(SpaceTableWidgetItem, self).setData(role, value)
+
+
+class GlyphCellItemDelegate(QStyledItemDelegate):
+
+ def createEditor(self, parent, option, index):
+ editor = super(GlyphCellItemDelegate, self).createEditor(
+ parent, option, index)
+ # editor.setAlignment(Qt.AlignCenter)
+ editor.setValidator(QIntValidator(self))
+ return editor
+
+ # TODO: implement =... lexer
+ # TODO: Alt+left or Alt+right don't SelectAll of the new cell
+ # cell by default. Implement this.
+ # TODO: cycle b/w editable cell area
+ def eventFilter(self, editor, event):
+ if event.type() == QEvent.KeyPress:
+ chg = None
+ count = event.count()
+ key = event.key()
+ if key == Qt.Key_Up:
+ chg = count
+ elif key == Qt.Key_Down:
+ chg = -count
+ elif not key == Qt.Key_Return:
+ return False
+ if chg is not None:
+ modifiers = event.modifiers()
+ if modifiers & Qt.AltModifier:
+ return False
+ elif modifiers & Qt.ShiftModifier:
+ chg *= 10
+ if modifiers & Qt.ControlModifier:
+ chg *= 10
+ cur = int(editor.text())
+ editor.setText(str(cur + chg))
+ self.commitData.emit(editor)
+ editor.selectAll()
+ return True
+ return False
+
+
+class SpaceTable(QTableWidget):
+
+ def __init__(self, parent=None):
+ super(SpaceTable, self).__init__(4, 1, parent)
+ self.setAttribute(Qt.WA_KeyCompression)
+ self.setItemDelegate(GlyphCellItemDelegate(self))
+ data = [None, "Width", "Left", "Right"]
+ # Don't grey-out disabled cells
+ palette = self.palette()
+ fgColor = palette.color(QPalette.Text)
+ palette.setColor(QPalette.Disabled, QPalette.Text, fgColor)
+ self.setPalette(palette)
+ for index, title in enumerate(data):
+ item = SpaceTableWidgetItem(title)
+ item.setFlags(Qt.NoItemFlags)
+ self.setItem(index, 0, item)
+ # let's use this one column to compute the width of others
+ self._cellWidth = .5 * self.columnWidth(0)
+ self.setColumnWidth(0, self._cellWidth)
+ self.horizontalHeader().hide()
+ self.verticalHeader().hide()
+ self._coloredColumn = None
+
+ # always show a scrollbar to fix layout
+ self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
+ self.setSizePolicy(QSizePolicy(
+ QSizePolicy.Preferred, QSizePolicy.Fixed))
+ self.glyphs = []
+ self.fillGlyphs()
+ self.resizeRowsToContents()
+ self.currentItemChanged.connect(self._itemChanged)
+ self.cellChanged.connect(self._cellEdited)
+ self.setSelectionMode(QAbstractItemView.SingleSelection)
+ # edit cell on single click, not double
+ self.setEditTriggers(QAbstractItemView.CurrentChanged)
+ self._editing = False
+ self.selectionChangedCallback = None
+
+ def setGlyphs(self, newGlyphs):
+ self.glyphs = newGlyphs
+ # TODO: we don't need to reallocate cells, split alloc and fill
+ self.updateCells()
+
+ def updateCells(self, keepColor=False):
+ self.blockSignals(True)
+ self.setEditTriggers(QAbstractItemView.NoEditTriggers)
+ coloredColumn = self._coloredColumn
+ self.fillGlyphs()
+ if keepColor and coloredColumn is not None and \
+ coloredColumn < self.columnCount():
+ self.colorColumn(coloredColumn)
+ self.setEditTriggers(QAbstractItemView.CurrentChanged)
+ self.blockSignals(False)
+
+ def _cellEdited(self, row, col):
+ if row == 0 or col == 0:
+ return
+ item = self.item(row, col).text()
+ # Glyphs that do not have outlines leave empty cells, can't convert
+ # that to a scalar
+ if not item:
+ return
+ item = int(item)
+ # -1 because the first col contains descriptive text
+ glyph = self.glyphs[col - 1]
+ # != comparisons avoid making glyph dirty when editor content is
+ # unchanged
+ self._editing = True
+ if row == 1:
+ if item != glyph.width:
+ glyph.width = item
+ elif row == 2:
+ if item != glyph.leftMargin:
+ glyph.leftMargin = item
+ elif row == 3:
+ if item != glyph.rightMargin:
+ glyph.rightMargin = item
+ self._editing = False
+ # defcon callbacks do the update
+
+ def _itemChanged(self, current, previous):
+ if current is not None:
+ cur = current.column()
+ if previous is not None:
+ prev = previous.column()
+ if current is not None and cur == prev:
+ return
+ self.colorColumn(current if current is None else cur)
+ if self.selectionChangedCallback is not None:
+ if current is not None:
+ self.selectionChangedCallback(cur - 1)
+ else:
+ self.selectionChangedCallback(None)
+
+ def colorColumn(self, column):
+ emptyBrush = QBrush(Qt.NoBrush)
+ selectionColor = QColor(235, 235, 235)
+ for i in range(4):
+ if self._coloredColumn is not None:
+ item = self.item(i, self._coloredColumn)
+ # cached column might be invalid if user input deleted it
+ if item is not None:
+ item.setBackground(emptyBrush)
+ if column is not None:
+ self.item(i, column).setBackground(selectionColor)
+ self._coloredColumn = column
+
+ def sizeHint(self):
+ # http://stackoverflow.com/a/7216486/2037879
+ height = sum(self.rowHeight(k) for k in range(self.rowCount()))
+ height += self.horizontalScrollBar().sizeHint().height()
+ margins = self.contentsMargins()
+ height += margins.top() + margins.bottom()
+ return QSize(self.width(), height)
+
+ def setCurrentGlyph(self, glyphIndex):
+ self.blockSignals(True)
+ if glyphIndex is not None:
+ # so we can scroll to the item
+ self.setCurrentCell(1, glyphIndex + 1)
+ self.setCurrentItem(None)
+ if glyphIndex is not None:
+ self.colorColumn(glyphIndex + 1)
+ else:
+ self.colorColumn(glyphIndex)
+ self.blockSignals(False)
+
+ def fillGlyphs(self):
+ def glyphTableWidgetItem(content, disableCell=False):
+ if isinstance(content, float):
+ content = round(content)
+ if content is not None:
+ content = str(content)
+ item = SpaceTableWidgetItem(content)
+ if disableCell:
+ item.setFlags(Qt.NoItemFlags)
+ elif content is None:
+ item.setFlags(Qt.ItemIsEnabled)
+ # TODO: should fields be centered? I find left-aligned more
+ # natural to read, personally...
+ # item.setTextAlignment(Qt.AlignCenter)
+ return item
+
+ self._coloredColumn = None
+ self.setColumnCount(len(self.glyphs) + 1)
+ for index, glyph in enumerate(self.glyphs):
+ # TODO: see about allowing glyph name edit here
+ self.setItem(0, index + 1, glyphTableWidgetItem(glyph.name, True))
+ self.setItem(1, index + 1, glyphTableWidgetItem(glyph.width))
+ self.setItem(2, index + 1, glyphTableWidgetItem(glyph.leftMargin))
+ self.setItem(3, index + 1, glyphTableWidgetItem(glyph.rightMargin))
+ self.setColumnWidth(index + 1, self._cellWidth)
+
+ def wheelEvent(self, event):
+ # A mouse can only scroll along the y-axis. Use x-axis if we have one
+ # (e.g. from touchpad), otherwise use y-axis.
+ angleDelta = event.angleDelta().x() or event.angleDelta().y()
+ cur = self.horizontalScrollBar().value()
+ self.horizontalScrollBar().setValue(cur - angleDelta / 120)
+ event.accept()
diff --git a/Lib/defconQt/spaceCenter.py b/Lib/defconQt/spaceCenter.py
deleted file mode 100644
index e360653..0000000
--- a/Lib/defconQt/spaceCenter.py
+++ /dev/null
@@ -1,826 +0,0 @@
-from defconQt import icons_db # noqa
-from defconQt.glyphCollectionView import cellSelectionColor
-from defconQt.glyphView import MainGfxWindow
-from getpass import getuser
-from PyQt5.QtCore import QEvent, QSettings, QSize, Qt
-from PyQt5.QtGui import (
- QBrush, QColor, QIcon, QIntValidator, QKeySequence, QPainter, QPalette,
- QPen)
-from PyQt5.QtWidgets import (
- QAbstractItemView, QActionGroup, QApplication, QComboBox, QLineEdit, QMenu,
- QPushButton, QScrollArea, QStyledItemDelegate, QTableWidget,
- QTableWidgetItem, QVBoxLayout, QSizePolicy, QToolBar, QWidget)
-
-comboBoxItems = [
- "abcdefghijklmnopqrstuvwxyz",
- "ABCDEFGHIJKLMNOPQRSTUVWXYZ",
- "0123456789",
- "nn/? nono/? oo",
- "HH/? HOHO/? OO",
-]
-
-defaultPointSize = 150
-glyphSelectionColor = QColor(cellSelectionColor)
-glyphSelectionColor.setAlphaF(.09)
-
-
-class MainSpaceWindow(QWidget):
-
- def __init__(self, font, string=None, pointSize=defaultPointSize,
- parent=None):
- super(MainSpaceWindow, self).__init__(parent, Qt.Window)
-
- if string is None:
- try:
- string = getuser()
- except:
- string = "World"
- string = "Hello %s" % string
- # TODO: drop self.font and self.glyphs, store in the widgets only
- self.font = font
- self.glyphs = []
- self.toolbar = FontToolBar(pointSize, self)
- self.canvas = GlyphsCanvas(font, pointSize, self)
- self.table = SpaceTable(self)
- self.toolbar.comboBox.currentIndexChanged[
- str].connect(self.canvas.setPointSize)
- self.canvas.doubleClickCallback = self._glyphOpened
- self.canvas.pointSizeChangedCallback = self.toolbar.setPointSize
- self.canvas.selectionChangedCallback = self.table.setCurrentGlyph
- self.table.selectionChangedCallback = self.canvas.setSelected
-
- self.toolbar.textField.editTextChanged.connect(self._textChanged)
- self.toolbar.textField.setEditText(string)
- app = QApplication.instance()
- app.currentGlyphChanged.connect(self._textChanged)
-
- layout = QVBoxLayout(self)
- layout.addWidget(self.toolbar)
- layout.addWidget(self.canvas.scrollArea())
- layout.addWidget(self.table)
- layout.setContentsMargins(0, 0, 0, 0)
- layout.setSpacing(0)
- self.setLayout(layout)
- self.resize(600, 500)
-
- self.font.info.addObserver(self, "_fontInfoChanged", "Info.Changed")
-
- self.setWindowTitle("Space center – %s %s" % (
- self.font.info.familyName, self.font.info.styleName))
-
- def setupFileMenu(self):
- fileMenu = QMenu("&File", self)
- fileMenu.addAction("&Save...", self.save, QKeySequence.Save)
- fileMenu.addAction("E&xit", self.close, QKeySequence.Quit)
- self.menuBar().addMenu(fileMenu)
-
- def close(self):
- self.font.info.removeObserver(self, "Info.Changed")
- self._unsubscribeFromGlyphs()
- super(MainSpaceWindow, self).close()
-
- def _fontInfoChanged(self, notification):
- self.canvas.fetchFontMetrics()
- self.canvas.update()
-
- def _glyphChanged(self, notification):
- self.canvas.update()
- self.table.updateCells()
-
- def _glyphOpened(self, glyph):
- glyphViewWindow = MainGfxWindow(glyph, self.parent())
- glyphViewWindow.show()
-
- def _textChanged(self):
- # unsubscribe from the old glyphs
- self._unsubscribeFromGlyphs()
- # subscribe to the new glyphs
- left = self.textToGlyphNames(self.toolbar.leftTextField.text())
- newText = self.textToGlyphNames(self.toolbar.textField.currentText())
- right = self.textToGlyphNames(self.toolbar.rightTextField.text())
- leftGlyphs = []
- for name in left:
- if name in self.font:
- leftGlyphs.append(self.font[name])
- rightGlyphs = []
- for name in right:
- if name in self.font:
- rightGlyphs.append(self.font[name])
- finalGlyphs = []
- for name in newText:
- if name in self.font:
- finalGlyphs.extend(leftGlyphs)
- finalGlyphs.append(self.font[name])
- finalGlyphs.extend(rightGlyphs)
- self._subscribeToGlyphs(finalGlyphs)
- # set the records into the view
- self.canvas.setGlyphs(self.glyphs)
- self.table.setGlyphs(self.glyphs)
-
- # Tal Leming. Edited.
- def textToGlyphNames(self, text):
- def catchCompile():
- if compileStack[0] == "?":
- glyph = app.currentGlyph()
- if glyph is not None:
- glyphNames.append(glyph.name)
- elif compileStack:
- glyphNames.append("".join(compileStack))
-
- app = QApplication.instance()
- # escape //
- text = text.replace("//", "/slash ")
- #
- glyphNames = []
- compileStack = None
- for c in text:
- # start a glyph name compile.
- if c == "/":
- # finishing a previous compile.
- if compileStack is not None:
- # only add the compile if something has been added to the
- # stack.
- if compileStack:
- glyphNames.append("".join(compileStack))
- # reset the stack.
- compileStack = []
- # adding to or ending a glyph name compile.
- elif compileStack is not None:
- # space. conclude the glyph name compile.
- if c == " ":
- # only add the compile if something has been added to the
- # stack.
- catchCompile()
- compileStack = None
- # add the character to the stack.
- else:
- compileStack.append(c)
- # adding a character that needs to be converted to a glyph name.
- else:
- glyphName = self.font.unicodeData.glyphNameForUnicode(ord(c))
- glyphNames.append(glyphName)
- # catch remaining compile.
- if compileStack is not None and compileStack:
- catchCompile()
- return glyphNames
-
- def _subscribeToGlyphs(self, glyphs):
- self.glyphs = glyphs
-
- handledGlyphs = set()
- for glyph in self.glyphs:
- if glyph in handledGlyphs:
- continue
- handledGlyphs.add(glyph)
- glyph.addObserver(self, "_glyphChanged", "Glyph.Changed")
-
- def _unsubscribeFromGlyphs(self):
- handledGlyphs = set()
- for glyph in self.glyphs:
- if glyph in handledGlyphs:
- continue
- handledGlyphs.add(glyph)
- glyph.removeObserver(self, "Glyph.Changed")
- # self.glyphs = None
-
- def setGlyphs(self, glyphs):
- # unsubscribe from the old glyphs
- self._unsubscribeFromGlyphs()
- # subscribe to the new glyphs
- self._subscribeToGlyphs(glyphs)
- glyphNames = []
- for glyph in glyphs:
- if glyph.unicode:
- glyphNames.append(chr(glyph.unicode))
- else:
- glyphNames.append("".join(("/", glyph.name, " ")))
- self.toolbar.textField.setEditText("".join(glyphNames))
- # set the records into the view
- self.canvas.setGlyphs(self.glyphs)
- self.table.setGlyphs(self.glyphs)
-
-pointSizes = [50, 75, 100, 125, 150, 200, 250, 300, 350, 400, 450, 500]
-
-
-class FontToolBar(QToolBar):
-
- def __init__(self, pointSize, parent=None):
- super(FontToolBar, self).__init__(parent)
- auxiliaryWidth = self.fontMetrics().width('0') * 8
- self.leftTextField = QLineEdit(self)
- self.leftTextField.setMaximumWidth(auxiliaryWidth)
- self.textField = QComboBox(self)
- self.textField.setEditable(True)
- completer = self.textField.completer()
- completer.setCaseSensitivity(Qt.CaseSensitive)
- self.textField.setCompleter(completer)
- # XXX: had to use Maximum because Preferred did entend the widget(?)
- self.textField.setSizePolicy(QSizePolicy.Expanding,
- QSizePolicy.Maximum)
- items = QSettings().value("spaceCenter/comboBoxItems", comboBoxItems,
- str)
- self.textField.addItems(items)
- self.rightTextField = QLineEdit(self)
- self.rightTextField.setMaximumWidth(auxiliaryWidth)
- self.leftTextField.textEdited.connect(self.textField.editTextChanged)
- self.rightTextField.textEdited.connect(self.textField.editTextChanged)
- self.comboBox = QComboBox(self)
- self.comboBox.setEditable(True)
- self.comboBox.setValidator(QIntValidator(self))
- for p in pointSizes:
- self.comboBox.addItem(str(p))
- self.comboBox.setEditText(str(pointSize))
-
- self.configBar = QPushButton(self)
- self.configBar.setFlat(True)
- self.configBar.setIcon(QIcon(":/resources/settings.svg"))
- self.configBar.setStyleSheet("padding: 2px 0px; padding-right: 10px")
- self.toolsMenu = QMenu(self)
- showKerning = self.toolsMenu.addAction(
- "Show Kerning", self.showKerning)
- showKerning.setCheckable(True)
- showMetrics = self.toolsMenu.addAction(
- "Show Metrics", self.showMetrics)
- showMetrics.setCheckable(True)
- self.toolsMenu.addSeparator()
- wrapLines = self.toolsMenu.addAction("Wrap lines", self.wrapLines)
- wrapLines.setCheckable(True)
- noWrapLines = self.toolsMenu.addAction("No wrap", self.noWrapLines)
- noWrapLines.setCheckable(True)
- self.toolsMenu.addSeparator()
- verticalFlip = self.toolsMenu.addAction(
- "Vertical flip", self.verticalFlip)
- verticalFlip.setCheckable(True)
- """
- lineHeight = QWidgetAction(self.toolsMenu)
- lineHeight.setText("Line height:")
- lineHeightSlider = QSlider(Qt.Horizontal, self)
- # QSlider works with integers so we'll just divide by 100 what comes
- # out of it
- lineHeightSlider.setMinimum(80)
- lineHeightSlider.setMaximum(160)
- lineHeightSlider.setValue(100)
- #lineHeightSlider.setContentsMargins(30, 0, 30, 0)
- lineHeightSlider.valueChanged.connect(self.lineHeight)
- lineHeight.setDefaultWidget(lineHeightSlider)
- self.toolsMenu.addAction(lineHeight)
- """
-
- wrapLinesGroup = QActionGroup(self)
- wrapLinesGroup.addAction(wrapLines)
- wrapLinesGroup.addAction(noWrapLines)
- wrapLines.setChecked(True)
- # self.toolsMenu.setActiveAction(wrapLines)
- self.configBar.setMenu(self.toolsMenu)
-
- self.addWidget(self.leftTextField)
- self.addWidget(self.textField)
- self.addWidget(self.rightTextField)
- self.addWidget(self.comboBox)
- self.addWidget(self.configBar)
-
- def showEvent(self, event):
- super(FontToolBar, self).showEvent(event)
- self.textField.setFocus(True)
-
- def setPointSize(self, pointSize):
- self.comboBox.blockSignals(True)
- self.comboBox.setEditText(str(pointSize))
- self.comboBox.blockSignals(False)
-
- def showKerning(self):
- action = self.sender()
- self.parent().canvas.setShowKerning(action.isChecked())
-
- def showMetrics(self):
- action = self.sender()
- self.parent().canvas.setShowMetrics(action.isChecked())
-
- def verticalFlip(self):
- action = self.sender()
- self.parent().canvas.setVerticalFlip(action.isChecked())
-
- def lineHeight(self, value):
- self.parent().canvas.setLineHeight(value / 100)
-
- def wrapLines(self):
- self.parent().canvas.setWrapLines(True)
-
- def noWrapLines(self):
- self.parent().canvas.setWrapLines(False)
-
-
-class GlyphsCanvas(QWidget):
-
- def __init__(self, font, pointSize=defaultPointSize, parent=None):
- super(GlyphsCanvas, self).__init__(parent)
- # XXX: make canvas font-agnostic as in defconAppkit and use
- # glyph.getParent() instead
- self.font = font
- self.fetchFontMetrics()
- self.glyphs = []
- self.ptSize = pointSize
- self.calculateScale()
- self.padding = 10
- self._showKerning = False
- self._showMetrics = False
- self._verticalFlip = False
- self._lineHeight = 1.1
- self._positions = None
- self._selected = None
- self.doubleClickCallback = None
- self.pointSizeChangedCallback = None
- self.selectionChangedCallback = None
-
- self._wrapLines = True
- self._scrollArea = QScrollArea(self.parent())
- self._scrollArea.resizeEvent = self.resizeEvent
- self._scrollArea.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
- self._scrollArea.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
- self._scrollArea.setWidget(self)
- self.resize(581, 400)
-
- def scrollArea(self):
- return self._scrollArea
-
- def calculateScale(self):
- scale = self.ptSize / self.upm
- if scale < .01:
- scale = 0.01
- self.scale = scale
-
- def setShowKerning(self, showKerning):
- self._showKerning = showKerning
- self.update()
-
- def setShowMetrics(self, showMetrics):
- self._showMetrics = showMetrics
- self.update()
-
- def setVerticalFlip(self, verticalFlip):
- self._verticalFlip = verticalFlip
- self.update()
-
- def setLineHeight(self, lineHeight):
- self._lineHeight = lineHeight
- self.update()
-
- def setWrapLines(self, wrapLines):
- if self._wrapLines == wrapLines:
- return
- self._wrapLines = wrapLines
- if self._wrapLines:
- self.resize(self._scrollArea.viewport().width(), self.height())
- self._scrollArea.setHorizontalScrollBarPolicy(
- Qt.ScrollBarAlwaysOff)
- self._scrollArea.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
- else:
- self.resize(self.width(), self._scrollArea.viewport().height())
- self._scrollArea.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
- self._scrollArea.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
- self.update()
-
- def fetchFontMetrics(self):
- self.ascender = self.font.info.ascender
- if self.ascender is None:
- self.ascender = 750
- self.descender = self.font.info.descender
- if self.descender is None:
- self.descender = 250
- self.upm = self.font.info.unitsPerEm
- if self.upm is None or not self.upm > 0:
- self.upm = 1000
-
- def setGlyphs(self, newGlyphs):
- self.glyphs = newGlyphs
- self._selected = None
- self.update()
-
- def setPointSize(self, pointSize):
- self.ptSize = int(pointSize)
- self.calculateScale()
- self.update()
-
- def setSelected(self, selected):
- self._selected = selected
- if self._positions is not None:
- cur_len = 0
- line = -1
- for index, li in enumerate(self._positions):
- if cur_len + len(li) > self._selected:
- pos, width = li[self._selected - cur_len]
- line = index
- break
- cur_len += len(li)
- if line > -1:
- x = self.padding + pos + width / 2
- y = self.padding + (line + .5) * self.ptSize * self._lineHeight
- self._scrollArea.ensureVisible(
- x, y, width / 2 + 20,
- .5 * self.ptSize * self._lineHeight + 20)
- self.update()
-
- def resizeEvent(self, event):
- if self._wrapLines:
- self.resize(self._scrollArea.viewport().width(), self.height())
- else:
- self.resize(self.width(), self._scrollArea.viewport().height())
-
- def wheelEvent(self, event):
- if event.modifiers() & Qt.ControlModifier:
- # TODO: should it snap to predefined pointSizes?
- # is the scaling factor okay?
- # XXX: current alg. is not reversible...
- decay = event.angleDelta().y() / 120.0
- scale = round(self.ptSize / 10)
- if scale == 0 and decay >= 0:
- scale = 1
- newPointSize = self.ptSize + int(decay) * scale
- if newPointSize <= 0:
- return
-
- self.setPointSize(newPointSize)
- if self.pointSizeChangedCallback is not None:
- self.pointSizeChangedCallback(newPointSize)
- event.accept()
- else:
- super(GlyphsCanvas, self).wheelEvent(event)
-
- # Tal Leming. Edited.
- def lookupKerningValue(self, first, second):
- kerning = self.font.kerning
- groups = self.font.groups
- # quickly check to see if the pair is in the kerning dictionary
- pair = (first, second)
- if pair in kerning:
- return kerning[pair]
- # get group names and make sure first and second are glyph names
- firstGroup = secondGroup = None
- if first.startswith("@MMK_L"):
- firstGroup = first
- first = None
- else:
- for group, groupMembers in groups.items():
- if group.startswith("@MMK_L"):
- if first in groupMembers:
- firstGroup = group
- break
- if second.startswith("@MMK_R"):
- secondGroup = second
- second = None
- else:
- for group, groupMembers in groups.items():
- if group.startswith("@MMK_R"):
- if second in groupMembers:
- secondGroup = group
- break
- # make an ordered list of pairs to look up
- pairs = [
- (first, second),
- (first, secondGroup),
- (firstGroup, second),
- (firstGroup, secondGroup)
- ]
- # look up the pairs and return any matches
- for pair in pairs:
- if pair in kerning:
- return kerning[pair]
- return 0
-
- def mousePressEvent(self, event):
- # Take focus to quit eventual cell editing
- # XXX: shouldnt set focus if we are in input field...
- self.setFocus(Qt.MouseFocusReason)
- if event.button() == Qt.LeftButton:
- # XXX: investigate, baselineShift is unused
- # if self._verticalFlip:
- # baselineShift = -self.descender
- # else:
- # baselineShift = self.ascender
- found = False
- line = \
- (event.y() - self.padding) // (self.ptSize * self._lineHeight)
- # XXX: Shouldnt // yield an int?
- line = int(line)
- if line >= len(self._positions):
- self._selected = None
- # XXX: find a way to DRY notification of self._selected changed
- # w ability to block notifications as well
- if self.selectionChangedCallback is not None:
- self.selectionChangedCallback(self._selected)
- event.accept()
- self.update()
- return
- x = event.x() - self.padding
- for index, data in enumerate(self._positions[line]):
- pos, width = data
- if pos <= x and pos + width > x:
- count = 0
- for i in range(line):
- count += len(self._positions[i])
- self._selected = count + index
- found = True
- break
- if not found:
- self._selected = None
- if self.selectionChangedCallback is not None:
- self.selectionChangedCallback(self._selected)
- event.accept()
- self.update()
- else:
- super(GlyphsCanvas, self).mousePressEvent(event)
-
- def mouseDoubleClickEvent(self, event):
- if event.button() == Qt.LeftButton and self._selected is not None:
- if self.doubleClickCallback is not None:
- self.doubleClickCallback(self.glyphs[self._selected])
- else:
- super(GlyphsCanvas, self).mouseDoubleClickEvent(event)
-
- def paintEvent(self, event):
- linePen = QPen(Qt.black)
- linePen.setWidth(3)
- width = self.width() / self.scale
-
- def paintLineMarks(painter):
- painter.save()
- painter.scale(self.scale, yDirection * self.scale)
- painter.setPen(linePen)
- painter.drawLine(0, self.ascender, width, self.ascender)
- painter.drawLine(0, 0, width, 0)
- painter.drawLine(0, self.descender, width, self.descender)
- painter.restore()
-
- painter = QPainter(self)
- painter.setRenderHint(QPainter.Antialiasing)
- painter.fillRect(0, 0, self.width(), self.height(), Qt.white)
- if self._verticalFlip:
- baselineShift = -self.descender
- yDirection = 1
- else:
- baselineShift = self.ascender
- yDirection = -1
- painter.translate(self.padding, self.padding +
- baselineShift * self.scale * self._lineHeight)
- # TODO: scale painter here to avoid g*scale everywhere below
-
- cur_width = 0
- lines = 1
- self._positions = [[]]
- if self._showMetrics:
- paintLineMarks(painter)
- for index, glyph in enumerate(self.glyphs):
- # line wrapping
- gWidth = glyph.width * self.scale
- doKern = index > 0 and self._showKerning and cur_width > 0
- if doKern:
- kern = self.lookupKerningValue(
- self.glyphs[index - 1].name, glyph.name) * self.scale
- else:
- kern = 0
- if (self._wrapLines and
- cur_width + gWidth + kern + 2 * self.padding >
- self.width()):
- painter.translate(-cur_width, self.ptSize * self._lineHeight)
- if self._showMetrics:
- paintLineMarks(painter)
- self._positions.append([(0, gWidth)])
- cur_width = gWidth
- lines += 1
- else:
- if doKern:
- painter.translate(kern, 0)
- self._positions[-1].append((cur_width, gWidth))
- cur_width += gWidth + kern
- glyphPath = glyph.getRepresentation("defconQt.QPainterPath")
- painter.save()
- painter.scale(self.scale, yDirection * self.scale)
- if self._showMetrics:
- halfDescent = self.descender / 2
- painter.drawLine(0, 0, 0, halfDescent)
- painter.drawLine(glyph.width, 0, glyph.width, halfDescent)
- if self._selected is not None and index == self._selected:
- painter.fillRect(0, self.descender, glyph.width,
- self.upm, glyphSelectionColor)
- painter.fillPath(glyphPath, Qt.black)
- painter.restore()
- painter.translate(gWidth, 0)
-
- innerHeight = self._scrollArea.viewport().height()
- if not self._wrapLines:
- innerWidth = self._scrollArea.viewport().width()
- width = max(innerWidth, cur_width + self.padding * 2)
- else:
- width = self.width()
- self.resize(width, max(innerHeight, lines * self.ptSize *
- self._lineHeight + 2 * self.padding))
-
-
-class SpaceTableWidgetItem(QTableWidgetItem):
-
- def setData(self, role, value):
- if role & Qt.EditRole:
- # don't set empty data
- # XXX: maybe fetch the value from cell back to the editor
- if value == "":
- return
- super(SpaceTableWidgetItem, self).setData(role, value)
-
-
-class GlyphCellItemDelegate(QStyledItemDelegate):
-
- def createEditor(self, parent, option, index):
- editor = super(GlyphCellItemDelegate, self).createEditor(
- parent, option, index)
- # editor.setAlignment(Qt.AlignCenter)
- editor.setValidator(QIntValidator(self))
- return editor
-
- # TODO: implement =... lexer
- # TODO: Alt+left or Alt+right don't SelectAll of the new cell
- # cell by default. Implement this.
- # TODO: cycle b/w editable cell area
- def eventFilter(self, editor, event):
- if event.type() == QEvent.KeyPress:
- chg = None
- count = event.count()
- key = event.key()
- if key == Qt.Key_Up:
- chg = count
- elif key == Qt.Key_Down:
- chg = -count
- elif not key == Qt.Key_Return:
- return False
- if chg is not None:
- modifiers = event.modifiers()
- if modifiers & Qt.AltModifier:
- return False
- elif modifiers & Qt.ShiftModifier:
- chg *= 10
- if modifiers & Qt.ControlModifier:
- chg *= 10
- cur = int(editor.text())
- editor.setText(str(cur + chg))
- self.commitData.emit(editor)
- editor.selectAll()
- return True
- return False
-
-
-class SpaceTable(QTableWidget):
-
- def __init__(self, parent=None):
- super(SpaceTable, self).__init__(4, 1, parent)
- self.setAttribute(Qt.WA_KeyCompression)
- self.setItemDelegate(GlyphCellItemDelegate(self))
- data = [None, "Width", "Left", "Right"]
- # Don't grey-out disabled cells
- palette = self.palette()
- fgColor = palette.color(QPalette.Text)
- palette.setColor(QPalette.Disabled, QPalette.Text, fgColor)
- self.setPalette(palette)
- for index, title in enumerate(data):
- item = SpaceTableWidgetItem(title)
- item.setFlags(Qt.NoItemFlags)
- self.setItem(index, 0, item)
- # let's use this one column to compute the width of others
- self._cellWidth = .5 * self.columnWidth(0)
- self.setColumnWidth(0, self._cellWidth)
- self.horizontalHeader().hide()
- self.verticalHeader().hide()
-
- # always show a scrollbar to fix layout
- self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
- self.setSizePolicy(QSizePolicy(
- QSizePolicy.Preferred, QSizePolicy.Fixed))
- self.glyphs = []
- self.fillGlyphs()
- self.resizeRowsToContents()
- self.currentItemChanged.connect(self._itemChanged)
- self.cellChanged.connect(self._cellEdited)
- self.setSelectionMode(QAbstractItemView.SingleSelection)
- # edit cell on single click, not double
- self.setEditTriggers(QAbstractItemView.CurrentChanged)
- self._editing = False
- self.selectionChangedCallback = None
- self._coloredColumn = None
-
- def setGlyphs(self, newGlyphs):
- self.glyphs = newGlyphs
- # TODO: we don't need to reallocate cells, split alloc and fill
- self.updateCells()
-
- def updateCells(self):
- self.blockSignals(True)
- if self._editing:
- coloredColumn = self._coloredColumn
- self.fillGlyphs()
- if self._editing:
- self._coloredColumn = coloredColumn
- self.blockSignals(False)
-
- def _cellEdited(self, row, col):
- if row == 0 or col == 0:
- return
- item = self.item(row, col).text()
- # Glyphs that do not have outlines leave empty cells, can't convert
- # that to a scalar
- if not item:
- return
- item = int(item)
- # -1 because the first col contains descriptive text
- glyph = self.glyphs[col - 1]
- # != comparisons avoid making glyph dirty when editor content is
- # unchanged
- self._editing = True
- if row == 1:
- if item != glyph.width:
- glyph.width = item
- elif row == 2:
- if item != glyph.leftMargin:
- glyph.leftMargin = item
- elif row == 3:
- if item != glyph.rightMargin:
- glyph.rightMargin = item
- self._editing = False
- # defcon callbacks do the update
-
- def _itemChanged(self, current, previous):
- if current is not None:
- cur = current.column()
- if previous is not None:
- prev = previous.column()
- if current is not None and cur == prev:
- return
- self.colorColumn(current if current is None else cur)
- if self.selectionChangedCallback is not None:
- if current is not None:
- self.selectionChangedCallback(cur - 1)
- else:
- self.selectionChangedCallback(None)
-
- def colorColumn(self, column):
- emptyBrush = QBrush(Qt.NoBrush)
- selectionColor = QColor(235, 235, 235)
- for i in range(4):
- if self._coloredColumn is not None:
- item = self.item(i, self._coloredColumn)
- # cached column might be invalid if user input deleted it
- if item is not None:
- item.setBackground(emptyBrush)
- if column is not None:
- self.item(i, column).setBackground(selectionColor)
- self._coloredColumn = column
-
- def sizeHint(self):
- # http://stackoverflow.com/a/7216486/2037879
- height = sum(self.rowHeight(k) for k in range(self.rowCount()))
- height += self.horizontalScrollBar().sizeHint().height()
- margins = self.contentsMargins()
- height += margins.top() + margins.bottom()
- return QSize(self.width(), height)
-
- def setCurrentGlyph(self, glyphIndex):
- self.blockSignals(True)
- if glyphIndex is not None:
- # so we can scroll to the item
- self.setCurrentCell(1, glyphIndex + 1)
- self.setCurrentItem(None)
- if glyphIndex is not None:
- self.colorColumn(glyphIndex + 1)
- else:
- self.colorColumn(glyphIndex)
- self.blockSignals(False)
-
- def fillGlyphs(self):
- def glyphTableWidgetItem(content, disableCell=False):
- if isinstance(content, float):
- content = round(content)
- if content is not None:
- content = str(content)
- item = SpaceTableWidgetItem(content)
- if disableCell:
- item.setFlags(Qt.NoItemFlags)
- elif content is None:
- item.setFlags(Qt.ItemIsEnabled)
- # TODO: should fields be centered? I find left-aligned more
- # natural to read, personally...
- # item.setTextAlignment(Qt.AlignCenter)
- return item
-
- self.setColumnCount(len(self.glyphs) + 1)
- for index, glyph in enumerate(self.glyphs):
- # TODO: see about allowing glyph name edit here
- self.setItem(0, index + 1, glyphTableWidgetItem(glyph.name, True))
- self.setItem(1, index + 1, glyphTableWidgetItem(glyph.width))
- self.setItem(2, index + 1, glyphTableWidgetItem(glyph.leftMargin))
- self.setItem(3, index + 1, glyphTableWidgetItem(glyph.rightMargin))
- self.setColumnWidth(index + 1, self._cellWidth)
-
- def wheelEvent(self, event):
- # A mouse can only scroll along the y-axis. Use x-axis if we have one
- # (e.g. from touchpad), otherwise use y-axis.
- angleDelta = event.angleDelta().x() or event.angleDelta().y()
- cur = self.horizontalScrollBar().value()
- self.horizontalScrollBar().setValue(cur - angleDelta / 120)
- event.accept()
--
cgit v1.2.3