aboutsummaryrefslogtreecommitdiffstats
path: root/Lib/defconQt/fontView.py
diff options
context:
space:
mode:
Diffstat (limited to 'Lib/defconQt/fontView.py')
-rw-r--r--Lib/defconQt/fontView.py755
1 files changed, 315 insertions, 440 deletions
diff --git a/Lib/defconQt/fontView.py b/Lib/defconQt/fontView.py
index cf08fe9..ba773c3 100644
--- a/Lib/defconQt/fontView.py
+++ b/Lib/defconQt/fontView.py
@@ -1,55 +1,149 @@
-import math
-import os
-import unicodedata
-
from defcon import Font
from defconQt.featureTextEditor import MainEditWindow
from defconQt.fontInfo import TabDialog
+from defconQt.glyphCollectionView import GlyphCollectionWidget
from defconQt.glyphView import MainGfxWindow
from defconQt.groupsView import GroupsWindow
+from defconQt.objects.defcon import CharacterSet
from defconQt.spaceCenter import MainSpaceWindow
+from fontTools.agl import AGL2UV
# TODO: remove globs when things start to stabilize
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
+import os
+import unicodedata
cannedDesign = [
dict(type="cannedDesign", allowPseudoUnicode=True)
]
sortItems = ["alphabetical", "category", "unicode", "script", "suffix",
"decompositionBase", "weightedSuffix", "ligature"]
+latin1 = CharacterSet(
+["space","exclam","quotesingle","quotedbl","numbersign","dollar",
+"percent","ampersand","parenleft","parenright","asterisk","plus","comma",
+"hyphen","period","slash","zero","one","two","three","four","five",
+"six","seven","eight","nine","colon","semicolon","less","equal",
+"greater","question","at","A","B","C","D","E","F","G","H","I","J",
+"K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z",
+"bracketleft","backslash","bracketright","asciicircum","underscore","grave",
+"a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t",
+"u","v","w","x","y","z","braceleft","bar","braceright","asciitilde","exclamdown",
+"cent","sterling","currency","yen","brokenbar","section","dieresis","copyright",
+"ordfeminine","guillemotleft","logicalnot","registered","macron","degree",
+"plusminus","twosuperior","threesuperior","acute","mu","paragraph",
+"periodcentered","cedilla","onesuperior","ordmasculine","guillemotright",
+"onequarter","onehalf","threequarters","questiondown","Agrave","Aacute",
+"Acircumflex","Atilde","Adieresis","Aring","AE","Ccedilla","Egrave","Eacute",
+"Ecircumflex","Edieresis","Igrave","Iacute","Icircumflex","Idieresis","Eth",
+"Ntilde","Ograve","Oacute","Ocircumflex","Otilde","Odieresis","multiply",
+"Oslash","Ugrave","Uacute","Ucircumflex","Udieresis","Yacute","Thorn",
+"germandbls","agrave","aacute","acircumflex","atilde","adieresis","aring","ae",
+"ccedilla","egrave","eacute","ecircumflex","edieresis","igrave","iacute",
+"icircumflex","idieresis","eth","ntilde","ograve","oacute","ocircumflex",
+"otilde","odieresis","divide","oslash","ugrave","uacute","ucircumflex",
+"udieresis","yacute","thorn","ydieresis","dotlessi","circumflex","caron",
+"breve","dotaccent","ring","ogonek","tilde","hungarumlaut","quoteleft",
+"quoteright","minus"], "Latin-1")
+
+# TODO: implement Frederik's Glyph Construction Builder
+class AddGlyphDialog(QDialog):
+ def __init__(self, currentGlyphs=None, parent=None):
+ super(AddGlyphDialog, self).__init__(parent)
+ self.setWindowModality(Qt.WindowModal)
+ self.setWindowTitle("Add glyphs…")
+ self.currentGlyphs = currentGlyphs
+ self.currentGlyphNames = [glyph.name for glyph in currentGlyphs]
+
+ layout = QGridLayout(self)
+ self.importCharDrop = QComboBox(self)
+ self.importCharDrop.addItem("Import glyphnames…")
+ self.importCharDrop.addItem("Latin-1", latin1)
+ self.importCharDrop.currentIndexChanged[int].connect(self.importCharacters)
+ self.addGlyphEdit = QPlainTextEdit(self)
+ self.addGlyphEdit.setFocus(True)
+
+ self.sortFontBox = QCheckBox("Sort font", self)
+ self.overwriteBox = QCheckBox("Override", self)
+ buttonBox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
+ buttonBox.accepted.connect(self.accept)
+ buttonBox.rejected.connect(self.reject)
-cellGridColor = QColor(130, 130, 130)
-cellHeaderBaseColor = QColor(230, 230, 230)
-cellHeaderLineColor = QColor(220, 220, 220)
-cellHeaderHighlightLineColor = QColor(240, 240, 240)
-cellSelectionColor = QColor.fromRgbF(.2, .3, .7, .15)
-
-GlyphCellBufferHeight = .2
-GlyphCellHeaderHeight = 14
+ l = 0
+ layout.addWidget(self.importCharDrop, l, 3)
+ l += 1
+ layout.addWidget(self.addGlyphEdit, l, 0, 1, 4)
+ l += 1
+ layout.addWidget(self.sortFontBox, l, 0)
+ layout.addWidget(self.overwriteBox, l, 1)
+ layout.addWidget(buttonBox, l, 3)
+ self.setLayout(layout)
-class SortingWindow(QDialog):
- def __init__(self, parent=None):
- super(SortingWindow, self).__init__(parent)
+ @staticmethod
+ def getNewGlyphNames(parent=None, currentGlyphs=None):
+ dialog = AddGlyphDialog(currentGlyphs, parent)
+ result = dialog.exec_()
+ sortFont = False
+ newGlyphNames = []
+ for name in dialog.addGlyphEdit.toPlainText().split():
+ if name not in dialog.currentGlyphNames:
+ newGlyphNames.append(name)
+ if dialog.sortFontBox.isChecked():
+ # XXX: if we get here with previous sort being by character set,
+ # should it just stick?
+ sortFont = True
+ return (newGlyphNames, sortFont, result)
+
+ def importCharacters(self, index):
+ if index == 0: return
+ charset = self.importCharDrop.currentData()
+ editorNames = self.addGlyphEdit.toPlainText().split()
+ currentNames = set(self.currentGlyphNames) ^ set(editorNames)
+ changed = False
+ for name in charset.glyphNames:
+ if name not in currentNames:
+ changed = True
+ editorNames.append(name)
+ if changed:
+ self.addGlyphEdit.setPlainText(" ".join(editorNames))
+ cursor = self.addGlyphEdit.textCursor()
+ cursor.movePosition(QTextCursor.End, QTextCursor.MoveAnchor)
+ self.addGlyphEdit.setTextCursor(cursor)
+ self.importCharDrop.setCurrentIndex(0)
+ self.addGlyphEdit.setFocus(True)
+
+class SortDialog(QDialog):
+ def __init__(self, desc=None, parent=None):
+ super(SortDialog, self).__init__(parent)
self.setWindowModality(Qt.WindowModal)
self.setWindowTitle("Sort…")
-
+
self.smartSortBox = QRadioButton("Smart sort", self)
self.characterSetBox = QRadioButton("Character set", self)
- self.characterSetBox.setEnabled(False)
+ self.characterSetBox.toggled.connect(self.characterSetToggle)
+ self.characterSetDrop = QComboBox(self)
+ self.characterSetDrop.setEnabled(False)
+ # XXX: fetch from settings
+ self.characterSetDrop.insertItem(0, "Latin 1")
self.customSortBox = QRadioButton("Custom…", self)
self.customSortBox.toggled.connect(self.customSortToggle)
-
+
self.customSortGroup = QGroupBox(parent=self)
- desc = self.parent().characterWidget.sortDescriptor
- if desc[0]["type"] == "cannedDesign":
+ self.customSortGroup.setEnabled(False)
+ descriptorsCount = 6
+ if desc is None:
+ # sort fetched from public.glyphOrder. no-op
+ pass
+ elif isinstance(desc, CharacterSet):
+ self.characterSetBox.setChecked(True)
+ self.characterSetDrop.setEnabled(True)
+ # TODO: match cset name when QSettings support lands
+ elif desc[0]["type"] == "cannedDesign":
self.smartSortBox.setChecked(True)
- self.customSortGroup.setEnabled(False)
- descriptorsCount = 6
else:
self.customSortBox.setChecked(True)
+ self.customSortGroup.setEnabled(True)
descriptorsCount = len(desc)
- #elif desc ==
self.customDescriptors = [[] for i in range(descriptorsCount)]
self.customSortLayout = QGridLayout()
for i, line in enumerate(self.customDescriptors):
@@ -81,19 +175,20 @@ class SortingWindow(QDialog):
btn.setText("−")
btn.pressed.connect(self._deleteRow)
self.customSortGroup.setLayout(self.customSortLayout)
-
+
buttonBox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
buttonBox.accepted.connect(self.accept)
buttonBox.rejected.connect(self.reject)
-
+
layout = QVBoxLayout(self)
layout.addWidget(self.smartSortBox)
layout.addWidget(self.characterSetBox)
+ layout.addWidget(self.characterSetDrop)
layout.addWidget(self.customSortBox)
layout.addWidget(self.customSortGroup)
layout.addWidget(buttonBox)
self.setLayout(layout)
-
+
def _addRow(self):
i = len(self.customDescriptors)
line = []
@@ -113,8 +208,7 @@ class SortingWindow(QDialog):
self.customSortLayout.addWidget(line[2], i, 2)
self.customSortLayout.addWidget(line[3], i, 3)
if i == 7: self.sender().setEnabled(False)
-
-
+
def _deleteRow(self):
rel = self.sender().property("index")
desc = self.customDescriptors
@@ -127,356 +221,53 @@ class SortingWindow(QDialog):
del self.customDescriptors[-1]
self.addLineButton.setEnabled(True)
self.adjustSize()
-
+
def indexFromItemName(self, name):
for index, item in enumerate(sortItems):
if name == item: return index
print("Unknown descriptor name: %s", name)
return 0
-
- def accept(self):
- if self.smartSortBox.isChecked():
- descriptors = cannedDesign
- elif self.customSortBox.isChecked():
+
+ @staticmethod
+ def getDescriptor(parent=None, sortDescriptor=None):
+ dialog = SortDialog(sortDescriptor, parent)
+ result = dialog.exec_()
+ if dialog.characterSetBox.isChecked():
+ # TODO: dispatch csets when QSettings support lands
+ ret = latin1
+ elif dialog.customSortBox.isChecked():
descriptors = []
- for line in self.customDescriptors:
+ for line in dialog.customDescriptors:
descriptors.append(dict(type=line[0].currentText(), ascending=line[1].isChecked(),
allowPseudoUnicode=line[2].isChecked()))
- self.parent().characterWidget.updateGlyphsFromFont(descriptors)
- super(SortingWindow, self).accept()
-
+ ret = descriptors
+ else:
+ ret = cannedDesign
+ return (ret, result)
+
+ def characterSetToggle(self):
+ checkBox = self.sender()
+ self.characterSetDrop.setEnabled(checkBox.isChecked())
+
def customSortToggle(self):
checkBox = self.sender()
self.customSortGroup.setEnabled(checkBox.isChecked())
-class CharacterWidget(QWidget):
- characterSelected = pyqtSignal(int, str)
- glyphOpened = pyqtSignal(str)
-
- def __init__(self, font, squareSize=56, scrollArea=None, parent=None):
- super(CharacterWidget, self).__init__(parent)
-
- self.font = font
- self.glyphs = []
- self.scrollArea = scrollArea
- self.scrollArea.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
- self.scrollArea.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
- self.squareSize = squareSize
- self.columns = 10
- self._selection = set()
- self.lastKey = -1
- self.moveKey = -1
- self.sortDescriptor = cannedDesign
-
- self._maybeDragPosition = None
- self.setFocusPolicy(Qt.ClickFocus)
-
- def updateFont(self, font):
- self.font = font
- self.updateGlyphsFromFont()
-
- def updateGlyphsFromFont(self, descriptor=None):
- if descriptor is not None: self.sortDescriptor = descriptor
- self.glyphs = [self.font[k] for k in self.font.unicodeData.sortGlyphNames(self.font.keys(), self.sortDescriptor)]
- self._selection = set()
- self.adjustSize()
- self.update()
-
- def setGlyphs(self, glyphs):
- self.glyphs = glyphs
- self._selection = set()
- self.adjustSize()
- self.update()
-
- def _sizeEvent(self, width, squareSize=None):
- if self.scrollArea is not None: sw = self.scrollArea.verticalScrollBar().width() + self.scrollArea.contentsMargins().right()
- else: sw = 0
- if squareSize is not None: self.squareSize = squareSize
- columns = (width - sw) // self.squareSize
- if not columns > 0: return
- self.columns = columns
- self.adjustSize()
-
- def sizeHint(self):
- # Calculate sizeHint with max(height, scrollArea.height()) because if scrollArea is
- # bigger than widget height after an update, we risk leaving old painted content on screen
- return QSize(self.columns * self.squareSize,
- max(math.ceil(len(self.glyphs) / self.columns) * self.squareSize, self.scrollArea.height()))
-
- def markSelection(self, color):
- for key in self._selection:
- glyph = self.glyphs[key]
- if color is None:
- if "public.markColor" in glyph.lib:
- del glyph.lib["public.markColor"]
- else:
- glyph.lib["public.markColor"] = ",".join(str(c) for c in color.getRgbF())
- self.update()
-
- # TODO: eventually get rid of the signal
- def computeCharacterSelected(self):
- lKey, mKey = self.lastKey, self.moveKey
- mKey = self.moveKey if self.moveKey < len(self.glyphs) else len(self.glyphs)-1
- lKey = self.lastKey if self.lastKey < len(self.glyphs) else len(self.glyphs)-1
- if lKey == -1:
- elements = set()
- elif lKey > mKey:
- elements = set(range(mKey, lKey+1))
- else:
- elements = set(range(lKey, mKey+1))
- elements ^= self._selection
- if len(elements)>1: self.characterSelected.emit(len(elements), "")
- elif len(elements)>0: self.characterSelected.emit(1, self.glyphs[elements.pop()].name)
- else: self.characterSelected.emit(0, "")
-
- def keyPressEvent(self, event):
- if event.key() == Qt.Key_A and event.modifiers() & Qt.ControlModifier:
- self._selection = set(range(len(self.glyphs)))
- self.computeCharacterSelected()
- self.update()
- event.accept()
- elif event.key() == Qt.Key_D and event.modifiers() & Qt.ControlModifier:
- self._selection = set()
- self.computeCharacterSelected()
- self.update()
- event.accept()
- else:
- super(CharacterWidget, self).keyPressEvent(event)
-
- def mousePressEvent(self, event):
- if event.button() == Qt.LeftButton:
- key = (event.y() // self.squareSize) * self.columns + event.x() // self.squareSize
- if key > len(self.glyphs)-1: return
- modifiers = event.modifiers()
- if modifiers & Qt.ShiftModifier and len(self._selection) == 1:
- self.lastKey = self._selection.pop()
- self.moveKey = key
- elif modifiers & Qt.ControlModifier:
- self.lastKey = key
- self.moveKey = self.lastKey
- elif key in self._selection and not modifiers & Qt.ShiftModifier:
- self._maybeDragPosition = event.pos()
- event.accept()
- return
- else:
- self._selection = set()
- self.lastKey = key
- self.moveKey = self.lastKey
-
- self.computeCharacterSelected()
- event.accept()
- self.update()
- else:
- super(CharacterWidget, self).mousePressEvent(event)
-
- def mouseMoveEvent(self, event):
- if event.buttons() & Qt.LeftButton:
- if self._maybeDragPosition is not None:
- if ((event.pos() - self._maybeDragPosition).manhattanLength() \
- < QApplication.startDragDistance()): return
- # TODO: needs ordering or not?
- glyphList = " ".join(self.glyphs[key].name for key in self._selection)
- drag = QDrag(self)
- mimeData = QMimeData()
- mimeData.setData("text/plain", glyphList)
- drag.setMimeData(mimeData)
-
- dropAction = drag.exec_()
- self._maybeDragPosition = None
- event.accept()
- return
- key = (event.y() // self.squareSize) * self.columns + min(event.x() // self.squareSize, self.columns-1)
- if key > len(self.glyphs)-1: return
- self.moveKey = key
-
- self.computeCharacterSelected()
- event.accept()
- self.update()
- else:
- super(CharacterWidget, self).mouseMoveEvent(event)
-
- def mouseReleaseEvent(self, event):
- if event.button() == Qt.LeftButton:
- self._maybeDragPosition = None
- if self.lastKey == -1:
- if self._maybeDragPosition is None:
- key = (event.y() // self.squareSize) * self.columns + event.x() // self.squareSize
- if key > len(self.glyphs)-1: return
- self._selection = {key}
- self.computeCharacterSelected()
- else:
- lastKey = self.lastKey if self.lastKey < len(self.glyphs) else len(self.glyphs)-1
- moveKey = self.moveKey if self.moveKey < len(self.glyphs) else len(self.glyphs)-1
- if event.modifiers() & Qt.ControlModifier:
- if moveKey > lastKey:
- self._selection ^= set(range(lastKey, moveKey+1))
- else:
- self._selection ^= set(range(moveKey, lastKey+1))
- else:
- if moveKey > lastKey:
- self._selection = set(range(lastKey, moveKey+1))
- else:
- self._selection = set(range(moveKey, lastKey+1))
- self.lastKey = -1
- self.moveKey = -1
- event.accept()
- self.update()
- else:
- super(CharacterWidget, self).mouseReleaseEvent(event)
-
- def mouseDoubleClickEvent(self, event):
- if event.button() == Qt.LeftButton:
- key = (event.y() // self.squareSize) * self.columns + event.x() // self.squareSize
- if key > len(self.glyphs)-1: event.ignore(); return
- self._selection -= {key}
- self.lastKey = key
- self.moveKey = self.lastKey
- event.accept()
- self.glyphOpened.emit(self.glyphs[key].name)
- else:
- super(CharacterWidget, self).mousePressEvent(event)
-
- # TODO: see if more of this process can be delegated to a factory
- def paintEvent(self, event):
- painter = QPainter(self)
- painter.setRenderHint(QPainter.Antialiasing)
-
- redrawRect = event.rect()
- beginRow = redrawRect.top() // self.squareSize
- endRow = redrawRect.bottom() // self.squareSize
- beginColumn = redrawRect.left() // self.squareSize
- endColumn = redrawRect.right() // self.squareSize
-
- # painter.setPen(cellGridColor)
- # painter.drawLine(redrawRect.left(), redrawRect.top(), redrawRect.left()+self.squareSize \
- # *min(len(self.glyphs), self.columns), redrawRect.top()+self.squareSize*(math.ceil(len(self.glyphs)/self.columns)))
- # painter.drawLine(0, 0, redrawRect.right(), 0)
-
- # selection code
- if self.moveKey != -1:
- if self.moveKey > self.lastKey:
- curSelection = set(range(self.lastKey, self.moveKey+1))
- else:
- curSelection = set(range(self.moveKey, self.lastKey+1))
- elif self.lastKey != -1: # XXX: necessary?
- curSelection = {self.lastKey}
- else:
- curSelection = set()
- curSelection ^= self._selection
-
- gradient = QLinearGradient(0, 0, 0, GlyphCellHeaderHeight)
- gradient.setColorAt(0.0, cellHeaderBaseColor)
- gradient.setColorAt(1.0, cellHeaderLineColor)
- dirtyGradient = QLinearGradient(0, 0, 0, GlyphCellHeaderHeight)
- dirtyGradient.setColorAt(0.0, cellHeaderBaseColor.darker(125))
- dirtyGradient.setColorAt(1.0, cellHeaderLineColor.darker(125))
- #markGradient = QRadialGradient(self.squareSize/2, GlyphCellHeaderHeight/2,
- # self.squareSize-GlyphCellHeaderHeight, self.squareSize/2, self.squareSize)
- markGradient = QLinearGradient(0, 0, 0, self.squareSize-GlyphCellHeaderHeight)
- headerFont = QFont()
- headerFont.setFamily('Lucida Sans Unicode')
- headerFont.insertSubstitution('Lucida Sans Unicode', 'Luxi Sans')
- headerFont.setPointSize(8)
- metrics = QFontMetrics(headerFont)
-
- for row in range(beginRow, endRow + 1):
- for column in range(beginColumn, endColumn + 1):
- key = row * self.columns + column
- if key > len(self.glyphs)-1: break
-
- painter.save()
- painter.translate(column * self.squareSize, row * self.squareSize)
- # background
- painter.fillRect(0, 0, self.squareSize, self.squareSize, Qt.white)
- glyph = self.glyphs[key]
- if "public.markColor" in glyph.lib:
- colorStr = glyph.lib["public.markColor"].split(",")
- if len(colorStr) == 4:
- comp = []
- for c in colorStr:
- comp.append(float(c.strip()))
- markColor = QColor.fromRgbF(*comp)
- markGradient.setColorAt(1.0, markColor)
- markGradient.setColorAt(0.0, markColor.lighter(125))
- painter.fillRect(0, GlyphCellHeaderHeight, self.squareSize,
- self.squareSize - GlyphCellHeaderHeight, QBrush(markGradient))
-
- # header gradient
- if glyph.dirty: col = dirtyGradient
- else: col = gradient
- painter.fillRect(0, 0, self.squareSize,
- GlyphCellHeaderHeight, QBrush(col))
- # header lines
- if glyph.dirty: col = cellHeaderHighlightLineColor.darker(110)
- else: col = cellHeaderHighlightLineColor
- painter.setPen(col)
- minOffset = painter.pen().width()
- painter.setRenderHint(QPainter.Antialiasing, False)
- painter.drawLine(0, 0, 0, GlyphCellHeaderHeight - 1)
- painter.drawLine(self.squareSize - 2, 0, self.squareSize - 2, GlyphCellHeaderHeight -1)
- painter.setPen(QColor(170, 170, 170))
- painter.drawLine(0, GlyphCellHeaderHeight, self.squareSize, GlyphCellHeaderHeight)
- painter.setRenderHint(QPainter.Antialiasing)
- # header text
- painter.setFont(headerFont)
- painter.setPen(QColor(80, 80, 80))
- name = metrics.elidedText(self.glyphs[key].name, Qt.ElideRight, self.squareSize - 2)
- painter.drawText(1, 0, self.squareSize - 2, GlyphCellHeaderHeight - minOffset,
- Qt.TextSingleLine | Qt.AlignCenter, name)
- painter.restore()
-
- painter.setPen(cellGridColor)
- rightEdgeX = column * self.squareSize + self.squareSize
- bottomEdgeY = row * self.squareSize + self.squareSize
- painter.drawLine(rightEdgeX, row * self.squareSize + 1, rightEdgeX, bottomEdgeY)
- painter.drawLine(rightEdgeX, bottomEdgeY, column * self.squareSize + 1, bottomEdgeY)
-
- # selection code
- painter.setRenderHint(QPainter.Antialiasing, False)
- if key in curSelection:
- painter.fillRect(column * self.squareSize + 1,
- row * self.squareSize + 1, self.squareSize - 3,
- self.squareSize - 3, cellSelectionColor)
- painter.setRenderHint(QPainter.Antialiasing)
-
- glyph = self.glyphs[key].getRepresentation("defconQt.QPainterPath")
- if self.font.info.unitsPerEm is None: break
- if not self.font.info.unitsPerEm > 0: self.font.info.unitsPerEm = 1000
- factor = (self.squareSize-GlyphCellHeaderHeight)/(self.font.info.unitsPerEm*(1+2*GlyphCellBufferHeight))
- x_offset = (self.squareSize-self.glyphs[key].width*factor)/2
- # If the glyph overflows horizontally we need to adjust the scaling factor
- if x_offset < 0:
- factor *= 1+2*x_offset/(self.glyphs[key].width*factor)
- x_offset = 0
- # TODO: the * 1.8 below is somewhat artificial
- y_offset = self.font.info.descender*factor * 1.8
- painter.save()
- painter.setClipRect(column * self.squareSize, row * self.squareSize+GlyphCellHeaderHeight,
- self.squareSize, self.squareSize-GlyphCellHeaderHeight)
- painter.translate(column * self.squareSize + x_offset, row * self.squareSize + self.squareSize + y_offset)
- painter.scale(factor, -factor)
- painter.fillPath(glyph, Qt.black)
- painter.restore()
-
class MainWindow(QMainWindow):
- def __init__(self, font=Font()):
+ def __init__(self, font):
super(MainWindow, self).__init__()
-
- self.font = font
- self.font.addObserver(self, "_fontChanged", "Font.Changed")
- # TODO: have the scrollarea be part of the widget itself?
- # or better yet, switch to QGraphicsScene
- self.scrollArea = QScrollArea(self)
squareSize = 56
- self.characterWidget = CharacterWidget(self.font, squareSize, self.scrollArea, self)
- self.characterWidget.updateGlyphsFromFont()
- self.characterWidget.setFocus()
- self.scrollArea.setWidget(self.characterWidget)
+ self.collectionWidget = GlyphCollectionWidget(self)
+ self._font = None
+ self._sortDescriptor = None
+ self.font = font
+ self.collectionWidget.characterSelectedCallback = self._selectionChanged
+ self.collectionWidget.doubleClickCallback = self._glyphOpened
+ self.collectionWidget.setFocus()
+ menuBar = self.menuBar()
# TODO: make shortcuts platform-independent
fileMenu = QMenu("&File", self)
- self.menuBar().addMenu(fileMenu)
-
fileMenu.addAction("&New…", self.newFile, QKeySequence.New)
fileMenu.addAction("&Open…", self.openFile, QKeySequence.Open)
# TODO: add functionality
@@ -485,47 +276,48 @@ class MainWindow(QMainWindow):
fileMenu.addAction("&Save", self.saveFile, QKeySequence.Save)
fileMenu.addAction("Save &As…", self.saveFileAs, QKeySequence.SaveAs)
fileMenu.addAction("E&xit", self.close, QKeySequence.Quit)
+ menuBar.addMenu(fileMenu)
selectionMenu = QMenu("&Selection", self)
- self.menuBar().addMenu(selectionMenu)
-
markColorMenu = QMenu("Mark color", self)
pixmap = QPixmap(24, 24)
- none = markColorMenu.addAction("None", self.colorFill)
+ none = markColorMenu.addAction("None", self.markColor)
none.setData(None)
- red = markColorMenu.addAction("Red", self.colorFill)
+ red = markColorMenu.addAction("Red", self.markColor)
pixmap.fill(Qt.red)
red.setIcon(QIcon(pixmap))
red.setData(QColor(Qt.red))
- yellow = markColorMenu.addAction("Yellow", self.colorFill)
+ yellow = markColorMenu.addAction("Yellow", self.markColor)
pixmap.fill(Qt.yellow)
yellow.setIcon(QIcon(pixmap))
yellow.setData(QColor(Qt.yellow))
- green = markColorMenu.addAction("Green", self.colorFill)
+ green = markColorMenu.addAction("Green", self.markColor)
pixmap.fill(Qt.green)
green.setIcon(QIcon(pixmap))
green.setData(QColor(Qt.green))
selectionMenu.addMenu(markColorMenu)
- selectionMenu.addSeparator()
- selectionMenu.addAction("Sort…", self.sortCharacters)
+ menuBar.addMenu(selectionMenu)
fontMenu = QMenu("&Font", self)
- self.menuBar().addMenu(fontMenu)
-
- # TODO: work out sensible shortcuts
- fontMenu.addAction("Font &info", self.fontInfo, "Ctrl+I")
- fontMenu.addAction("Font &features", self.fontFeatures, "Ctrl+F")
+ # 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+U")
+ fontMenu.addAction("Font &info", self.fontInfo, "Ctrl+M")
+ fontMenu.addAction("Font &features", self.fontFeatures, "Ctrl+F")
fontMenu.addSeparator()
- fontMenu.addAction("&Space center", self.spaceCenter, "Ctrl+Y")
- fontMenu.addAction("&Groups window", self.fontGroups, "Ctrl+G")
+ fontMenu.addAction("Sort…", self.sortCharacters)
+ menuBar.addMenu(fontMenu)
- helpMenu = QMenu("&Help", self)
- self.menuBar().addMenu(helpMenu)
+ windowMenu = QMenu("&Windows", self)
+ windowMenu.addAction("&Space center", self.spaceCenter, "Ctrl+Y")
+ windowMenu.addAction("&Groups window", self.fontGroups, "Ctrl+G")
+ menuBar.addMenu(windowMenu)
+ helpMenu = QMenu("&Help", self)
helpMenu.addAction("&About", self.about)
helpMenu.addAction("About &Qt", QApplication.instance().aboutQt)
-
+ menuBar.addMenu(helpMenu)
+
self.sqSizeSlider = QSlider(Qt.Horizontal, self)
self.sqSizeSlider.setMinimum(36)
self.sqSizeSlider.setMaximum(96)
@@ -533,15 +325,13 @@ class MainWindow(QMainWindow):
self.sqSizeSlider.setValue(squareSize)
self.sqSizeSlider.valueChanged.connect(self._squareSizeChanged)
self.selectionLabel = QLabel(self)
- self.statusBar().addPermanentWidget(self.sqSizeSlider)
- self.statusBar().addWidget(self.selectionLabel)
+ statusBar = self.statusBar()
+ statusBar.addPermanentWidget(self.sqSizeSlider)
+ statusBar.addWidget(self.selectionLabel)
- self.setCentralWidget(self.scrollArea)
- self.characterWidget.characterSelected.connect(self._selectionChanged)
- self.characterWidget.glyphOpened.connect(self._glyphOpened)
- self.setWindowTitle()
- # TODO: dump the hardcoded path
- #self.setWindowIcon(QIcon("C:\\Users\\Adrien\\Downloads\\defconQt\\Lib\\defconQt\\resources\\icon.png"))
+ self.setCentralWidget(self.collectionWidget.scrollArea())
+ self.resize(605, 430)
+ self.setWindowTitle() # TODO: maybe clean this up
def newFile(self):
ok = self.maybeSaveBeforeExit()
@@ -553,28 +343,36 @@ class MainWindow(QMainWindow):
self.font.info.capHeight = 750
self.font.info.xHeight = 500
self.setWindowTitle("Untitled.ufo")
- self.characterWidget.updateFont(self.font)
+ self.sortDescriptor = latin1
def openFile(self, path=None):
if not path:
- path, _ = QFileDialog.getOpenFileName(self, "Open File", '',
+ path, ok = QFileDialog.getOpenFileName(self, "Open File", '',
"UFO Fonts (metainfo.plist)")
-
+ if not ok: return
if path:
# TODO: error handling
path = os.path.dirname(path)
+ # TODO: I note that a change of self.font often goes with setWindowTitle().
+ # Be more DRY.
self.font = Font(path)
- self.characterWidget.updateFont(self.font)
self.setWindowTitle()
def saveFile(self, path=None):
if path is None and self.font.path is None:
self.saveFileAs()
else:
+ glyphs = self.collectionWidget.glyphs
+ # TODO: save sortDescriptor somewhere in lib as well
+ glyphNames = []
+ for glyph in glyphs:
+ glyphNames.append(glyph.name)
+ self.font.lib["public.glyphOrder"] = glyphNames
self.font.save(path=path)
-# self.font.dirty = False
+ self.font.dirty = False
+ for glyph in self.font:
+ glyph.dirty = False
self.setWindowModified(False)
-# self.font.path = path # done by defcon
def saveFileAs(self):
path, ok = QFileDialog.getSaveFileName(self, "Save File", '',
@@ -583,20 +381,21 @@ class MainWindow(QMainWindow):
self.saveFile(path)
self.setWindowTitle()
#return ok
-
+
def close(self):
self.font.removeObserver(self, "Font.Changed")
QApplication.instance().quit()
-
+
def closeEvent(self, event):
ok = self.maybeSaveBeforeExit()
if not ok: event.ignore()
else: event.accept()
-
+
def maybeSaveBeforeExit(self):
if self.font.dirty:
title = "Me"
if self.font.path is not None:
+ # TODO: maybe cache this font name in the Font
currentFont = os.path.basename(self.font.path.rstrip(os.sep))
else:
currentFont = "Untitled.ufo"
@@ -613,42 +412,110 @@ class MainWindow(QMainWindow):
return True
return False
return True
-
- def colorFill(self):
- action = self.sender()
- self.characterWidget.markSelection(action.data())
-
+
+ def _get_font(self):
+ return self._font
+
+ # TODO: consider that user may want to change font without sortDescriptor
+ # be calculated and set magically (and therefore, arbitrarily)
+ # In that case is it reasonable to just leave self._font?
+ def _set_font(self, font):
+ if self._font is not None:
+ self._font.removeObserver(self, "Font.Changed")
+ self._font = font
+ self._font.addObserver(self, "_fontChanged", "Font.Changed")
+ if "public.glyphOrder" in self._font.lib:
+ self.sortDescriptor = CharacterSet(
+ self._font.lib["public.glyphOrder"])
+ else:
+ # TODO: cannedDesign or carry sortDescriptor from previous font?
+ self.sortDescriptor = cannedDesign
+
+ font = property(_get_font, _set_font, doc="The fontView font. Subscribes \
+ to the new font, updates the sorting order and cells widget when set.")
+
+ def _get_sortDescriptor(self):
+ return self._sortDescriptor
+
+ def _set_sortDescriptor(self, desc):
+ if isinstance(desc, CharacterSet):
+ cnt = 0
+ glyphs = []
+ for glyphName in desc.glyphNames:
+ if not glyphName in self._font:
+ # create a template glyph
+ self.newStandardGlyph(glyphName)
+ self._font[glyphName].template = True
+ else:
+ cnt += 1
+ glyphs.append(self._font[glyphName])
+ if cnt < len(self._font):
+ # somehow some glyphs in the font are not present in the glyph
+ # order, loop again to add these at the end
+ for glyph in self._font:
+ if not glyph in glyphs:
+ glyphs.append(glyph)
+ else:
+ glyphs = [self._font[k] for k in self._font.unicodeData
+ .sortGlyphNames(self._font.keys(), desc)]
+ self.collectionWidget.glyphs = glyphs
+ self._sortDescriptor = desc
+
+ sortDescriptor = property(_get_sortDescriptor, _set_sortDescriptor,
+ doc="The sortDescriptor. Takes glyphs from the font and sorts them \
+ when set.")
+
+ def markColor(self):
+ color = self.sender().data()
+ glyphs = self.collectionWidget.glyphs
+ for key in self.collectionWidget.selection:
+ glyph = glyphs[key]
+ if color is None:
+ if "public.markColor" in glyph.lib:
+ del glyph.lib["public.markColor"]
+ else:
+ glyph.lib["public.markColor"] = ",".join(str(c) for c in color.getRgbF())
+
+ def newStandardGlyph(self, name):
+ self.font.newGlyph(name)
+ self.font[name].width = 500
+ # TODO: we should not force-add unicode, also list ought to be
+ # changeable from AGL2UV
+ if name in AGL2UV: self.font[name].unicode = AGL2UV[name]
+
def _fontChanged(self, notification):
- self.characterWidget.update()
+ self.collectionWidget.update()
self.setWindowModified(True)
- def _glyphOpened(self, name):
- glyphViewWindow = MainGfxWindow(self.font, self.font[name], self)
+ def _glyphOpened(self, glyph):
+ glyphViewWindow = MainGfxWindow(glyph, self)
glyphViewWindow.show()
-
- def _selectionChanged(self, count, glyph):
- if count == 0: self.selectionLabel.setText("")
- else: self.selectionLabel.setText("%s%s%s%d %s" % (glyph, " " if count <= 1 else "", "(", count, "selected)"))
+
+ def _selectionChanged(self, selection):
+ if selection is not None:
+ if isinstance(selection, str):
+ count = 1
+ text = "%s " % selection
+ else:
+ count = selection
+ text = ""
+ if not count == 0:
+ text = "%s(%d selected)" % (text, count)
+ else: text = ""
+ self.selectionLabel.setText(text)
def _squareSizeChanged(self):
val = self.sqSizeSlider.value()
- self.characterWidget._sizeEvent(self.width(), val)
+ self.collectionWidget._sizeEvent(self.width(), val)
QToolTip.showText(QCursor.pos(), str(val), self)
def resizeEvent(self, event):
- if self.isVisible(): self.characterWidget._sizeEvent(event.size().width())
+ if self.isVisible(): self.collectionWidget._sizeEvent(event.size().width())
super(MainWindow, self).resizeEvent(event)
-
+
def setWindowTitle(self, title=None):
if title is None: title = os.path.basename(self.font.path.rstrip(os.sep))
super(MainWindow, self).setWindowTitle("[*]{}".format(title))
-
- def sortCharacters(self):
- if not (hasattr(self, 'sortingWindow') and self.sortingWindow.isVisible()):
- self.sortingWindow = SortingWindow(self)
- self.sortingWindow.show()
- else:
- self.sortingWindow.raise_()
def fontInfo(self):
# If a window is already opened, bring it to the front, else spawn one.
@@ -656,20 +523,20 @@ class MainWindow(QMainWindow):
# it seems we're just leaking memory after each close... (both raise_ and
# show allocate memory instead of using the hidden widget it seems)
if not (hasattr(self, 'fontInfoWindow') and self.fontInfoWindow.isVisible()):
- self.fontInfoWindow = TabDialog(self.font, self)
- self.fontInfoWindow.show()
+ self.fontInfoWindow = TabDialog(self.font, self)
+ self.fontInfoWindow.show()
else:
- # Should data be rewind if user calls font info while one is open?
- # I'd say no, but this has yet to be settled.
- self.fontInfoWindow.raise_()
+ # Should data be rewind if user calls font info while one is open?
+ # I'd say no, but this has yet to be settled.
+ self.fontInfoWindow.raise_()
def fontFeatures(self):
# TODO: see up here
if not (hasattr(self, 'fontFeaturesWindow') and self.fontFeaturesWindow.isVisible()):
- self.fontFeaturesWindow = MainEditWindow(self.font, self)
- self.fontFeaturesWindow.show()
+ self.fontFeaturesWindow = MainEditWindow(self.font, self)
+ self.fontFeaturesWindow.show()
else:
- self.fontFeaturesWindow.raise_()
+ self.fontFeaturesWindow.raise_()
def spaceCenter(self):
# TODO: see up here
@@ -679,27 +546,36 @@ class MainWindow(QMainWindow):
self.spaceCenterWindow.show()
else:
self.spaceCenterWindow.raise_()
- if self.characterWidget._selection:
+ selection = self.collectionWidget.selection
+ if selection:
glyphs = []
- for item in sorted(self.characterWidget._selection):
- glyphs.append(self.characterWidget.glyphs[item])
+ for item in sorted(selection):
+ glyph = self.collectionWidget.glyphs[item]
+ glyphs.append(glyph)
self.spaceCenterWindow.setGlyphs(glyphs)
-
+
def fontGroups(self):
# TODO: see up here
if not (hasattr(self, 'fontGroupsWindow') and self.fontGroupsWindow.isVisible()):
- self.fontGroupsWindow = GroupsWindow(self.font, self)
- self.fontGroupsWindow.show()
+ self.fontGroupsWindow = GroupsWindow(self.font, self)
+ self.fontGroupsWindow.show()
else:
- self.fontGroupsWindow.raise_()
-
+ self.fontGroupsWindow.raise_()
+
+ def sortCharacters(self):
+ sortDescriptor, ok = SortDialog.getDescriptor(self, self.sortDescriptor)
+ if ok:
+ self.sortDescriptor = sortDescriptor
+
def addGlyph(self):
- gName, ok = QInputDialog.getText(self, "Add glyph", "Name of the glyph:")
- # Not overwriting existing glyphs. Should it warn in this case? (rf)
- if ok and gName != '':
- self.font.newGlyph(gName)
- self.font[gName].width = 500
- self.characterWidget.updateGlyphsFromFont()
+ glyphs = self.collectionWidget.glyphs
+ newGlyphNames, sortFont, ok = AddGlyphDialog.getNewGlyphNames(self, glyphs)
+ if ok:
+ for name in newGlyphNames:
+ # XXX: if sortFont
+ self.newStandardGlyph(name)
+ glyphs.append(self.font[name])
+ self.collectionWidget.glyphs = glyphs
def about(self):
QMessageBox.about(self, "About Me",
@@ -707,4 +583,3 @@ class MainWindow(QMainWindow):
"<p>I am a new UFO-centric font editor and I aim to bring the <b>robofab</b> " \
"ecosystem to all main operating systems, in a fast and dependency-free " \
"package.</p>")
-