diff options
| author | Adrien Tétar | 2015-09-21 20:40:35 +0200 |
|---|---|---|
| committer | Adrien Tétar | 2015-09-21 20:40:35 +0200 |
| commit | f83831e31597e8c811f5b3b75cb98a0ff3590a8c (patch) | |
| tree | 81665d9e00cf3752bd245b1c64a920b1781cc669 /Lib | |
| parent | 748bb567eef19dcd8e067934786950c5866dc580 (diff) | |
| download | trufont-f83831e31597e8c811f5b3b75cb98a0ff3590a8c.tar.bz2 | |
meta: refactorings and cleanups, fontView: partial rewrite, new AddGlyphsWindow, extract cells widget to a separate location, support CharacterSet objects fully
Diffstat (limited to 'Lib')
| -rw-r--r-- | Lib/defconQt/__init__.py | 2 | ||||
| -rw-r--r-- | Lib/defconQt/__main__.py | 3 | ||||
| -rw-r--r-- | Lib/defconQt/featureTextEditor.py | 40 | ||||
| -rw-r--r-- | Lib/defconQt/fontView.py | 755 | ||||
| -rw-r--r-- | Lib/defconQt/glyphCollectionView.py | 381 | ||||
| -rw-r--r-- | Lib/defconQt/glyphView.py | 218 | ||||
| -rw-r--r-- | Lib/defconQt/groupsView.py | 91 | ||||
| -rw-r--r-- | Lib/defconQt/objects/__init__.py | 0 | ||||
| -rw-r--r-- | Lib/defconQt/objects/defcon.py | 22 | ||||
| -rw-r--r-- | Lib/defconQt/spaceCenter.py | 187 |
10 files changed, 956 insertions, 743 deletions
diff --git a/Lib/defconQt/__init__.py b/Lib/defconQt/__init__.py index 5f15410..9f7a875 100644 --- a/Lib/defconQt/__init__.py +++ b/Lib/defconQt/__init__.py @@ -1 +1 @@ -version = "0.1" +version = "0.1.0" diff --git a/Lib/defconQt/__main__.py b/Lib/defconQt/__main__.py index babcc73..07f273c 100644 --- a/Lib/defconQt/__main__.py +++ b/Lib/defconQt/__main__.py @@ -19,8 +19,7 @@ representationFactories.registerAllFactories() #with PyCallGraph(output=GraphvizOutput()): app = QApplication(sys.argv) # TODO: http://stackoverflow.com/a/21330349/2037879 -app.setWindowIcon(QIcon("resources/icon.png")) +app.setWindowIcon(QIcon("defconQt/resources/icon.png")) window = MainWindow(Font(ufoFile)) -window.resize(605, 430) window.show() sys.exit(app.exec_()) diff --git a/Lib/defconQt/featureTextEditor.py b/Lib/defconQt/featureTextEditor.py index 1cff1b8..cadb9d5 100644 --- a/Lib/defconQt/featureTextEditor.py +++ b/Lib/defconQt/featureTextEditor.py @@ -17,12 +17,12 @@ class MainEditWindow(QMainWindow): self.setCentralWidget(self.editor) self.setWindowTitle("Font features", self.font) - + def setWindowTitle(self, title, font): - if font is not None: puts = "[*]%s%s%s%s%s" % (title, " – ", self.font.info.familyName, " ", self.font.info.styleName) + if font is not None: puts = "[*]%s – %s %s" % (title, self.font.info.familyName, self.font.info.styleName) else: puts = "[*]%s" % title super(MainEditWindow, self).setWindowTitle(puts) - + def closeEvent(self, event): if self.editor.document().isModified(): closeDialog = QMessageBox(QMessageBox.Question, "Me", "Save your changes?", @@ -37,7 +37,7 @@ class MainEditWindow(QMainWindow): event.accept() else: event.ignore() - + def reload(self): self.font.reloadFeatures() self.editor.setPlainText(self.font.features.text) @@ -93,7 +93,7 @@ class TextEditor(QPlainTextEdit): def write(self, features): features.text = self.toPlainText() - + def lineNumberAreaPaintEvent(self, event): painter = QPainter(self.lineNumbers) painter.fillRect(event.rect(), QColor(230, 230, 230)) @@ -103,23 +103,23 @@ class TextEditor(QPlainTextEdit): painter.drawLine(d.x(), d.y(), a.x(), a.y()) painter.setPen(QColor(100, 100, 100)) painter.setFont(self.font()) - + block = self.firstVisibleBlock() blockNumber = block.blockNumber(); top = int(self.blockBoundingGeometry(block).translated(self.contentOffset()).top()) bottom = top + int(self.blockBoundingRect(block).height()) - + while block.isValid() and top <= event.rect().bottom(): if block.isVisible() and bottom >= event.rect().top(): number = str(blockNumber + 1) - painter.drawText(4, top, self.lineNumbers.width() - 8, + painter.drawText(4, top, self.lineNumbers.width() - 8, self.fontMetrics().height(), Qt.AlignRight, number) block = block.next() top = bottom bottom = top + int(self.blockBoundingRect(block).height()) blockNumber += 1 - + def lineNumberAreaWidth(self): digits = 1 top = max(1, self.blockCount()) @@ -129,25 +129,25 @@ class TextEditor(QPlainTextEdit): # Avoid too frequent geometry changes if digits < 3: digits = 3 return 10 + self.fontMetrics().width('9') * digits - + def updateLineNumberArea(self, rect, dy): if dy: self.lineNumbers.scroll(0, dy); else: - self.lineNumbers.update(0, rect.y(), + self.lineNumbers.update(0, rect.y(), self.lineNumbers.width(), rect.height()) if rect.contains(self.viewport().rect()): self.updateLineNumberAreaWidth(0) - + def updateLineNumberAreaWidth(self, newBlockCount=None): self.setViewportMargins(self.lineNumberAreaWidth(), 0, 0, 0) - + def resizeEvent(self, event): super(TextEditor, self).resizeEvent(event) cr = self.contentsRect() self.lineNumbers.setGeometry(cr.left(), cr.top(), self.lineNumberAreaWidth(), cr.height()) - + def findLineIndentLevel(self, cursor): indent = 0 cursor.select(QTextCursor.LineUnderCursor) @@ -247,7 +247,7 @@ class Highlighter(QSyntaxHighlighter): keywordFormat.setForeground(QColor(30, 150, 220)) keywordFormat.setFontWeight(QFont.Bold) - self.highlightingRules = [(QRegExp("%s%s%s" % ("(", "|".join(keywordPatterns), ")")), keywordFormat)] + self.highlightingRules = [(QRegExp("(%s)" % ("|".join(keywordPatterns))), keywordFormat)] singleLineCommentFormat = QTextCharFormat() singleLineCommentFormat.setForeground(Qt.darkGray) @@ -270,13 +270,3 @@ class Highlighter(QSyntaxHighlighter): index = expression.indexIn(text, index + length) self.setCurrentBlockState(0) - -if __name__ == '__main__': - - import sys - - app = QApplication(sys.argv) - window = MainWindow() - window.resize(640, 512) - window.show() - sys.exit(app.exec_()) 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>") - diff --git a/Lib/defconQt/glyphCollectionView.py b/Lib/defconQt/glyphCollectionView.py new file mode 100644 index 0000000..e49b32a --- /dev/null +++ b/Lib/defconQt/glyphCollectionView.py @@ -0,0 +1,381 @@ +from PyQt5.QtCore import QMimeData, QRectF, QSize, Qt +from PyQt5.QtGui import (QBrush, QColor, QDrag, QFont, QFontMetrics, QKeySequence, + QLinearGradient, QPainter, QPen) +from PyQt5.QtWidgets import QApplication, QMessageBox, QScrollArea, QWidget +import math + +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 + +# TODO: consider extracting each platform-specific thing (fonts, shortcuts) in a +# purposed folder +headerFont = QFont() +headerFont.setFamily('Lucida Sans Unicode') +headerFont.insertSubstitution('Lucida Sans Unicode', 'Lucida Grande') +headerFont.insertSubstitution('Lucida Sans Unicode', 'Luxi Sans') +headerFont.setPointSize(8) +voidFont = QFont(headerFont) +voidFont.setPointSize(24) +metrics = QFontMetrics(headerFont) + +def proceedWithDeletion(self): + closeDialog = QMessageBox(QMessageBox.Question, "", "Delete glyphs", + QMessageBox.Yes | QMessageBox.No, self) + closeDialog.setInformativeText("Are you sure you want to delete them?") + closeDialog.setModal(True) + ret = closeDialog.exec_() + if ret == QMessageBox.Yes: + return True + return False + +""" +A widget that presents a list of glyphs in cells. +""" +class GlyphCollectionWidget(QWidget): + def __init__(self, parent=None): + super(GlyphCollectionWidget, self).__init__(parent) + self._glyphs = [] + # TODO: hide behind a façade + self.squareSize = 56 + self._columns = 10 + self._selection = {} + # TODO: consider replacing this with moveKey + set (which is generated + # when painting anyway) + self.lastKey = -1 + self.moveKey = -1 + + self.characterSelectedCallback = None + self.doubleClickCallback = None + self._maybeDragPosition = None + + self.setFocusPolicy(Qt.ClickFocus) + self._scrollArea = QScrollArea(parent) + self._scrollArea.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) + self._scrollArea.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn) + self._scrollArea.setWidget(self) + + def _get_glyphs(self): + return self._glyphs + + def _set_glyphs(self, value): + self._glyphs = value + self.adjustSize() + self.selection = set() + #self.update() # self.selection changed will do it + + glyphs = property(_get_glyphs, _set_glyphs, doc="A list of glyphs \ + displayed. Clears selection and schedules display refresh when set.") + + def _get_selection(self): + return self._selection + + def _set_selection(self, value): + self._selection = value + self.computeCharacterSelected() + self.update() + + selection = property(_get_selection, _set_selection, doc="A set that contains \ + indexes of selected glyphs. Schedules display refresh when set.") + + def scrollArea(self): + return self._scrollArea + + def scrollToCell(self, index): + raise NotImplementedError + + # TODO: break this down into set width/set square + # TODO: see whether scrollArea gets resizeEvents + def _sizeEvent(self, width, squareSize=None): + sw = self._scrollArea.verticalScrollBar().width() + self._scrollArea.contentsMargins().right() + 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 computeCharacterSelected(self): + if self.characterSelectedCallback is None: + return + 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 + + cnt = len(elements) + if cnt == 1: + self.characterSelectedCallback(self.glyphs[elements.pop()].name) + else: + self.characterSelectedCallback(cnt) + + def keyPressEvent(self, event): + key = event.key() + modifiers = event.modifiers() + if event.matches(QKeySequence.SelectAll): + self.selection = set(range(len(self.glyphs))) + elif key == Qt.Key_D and modifiers & Qt.ControlModifier: + self.selection = set() + # XXX: this is specific to fontView so should be done thru subclassing of a base widget, + # as is done in groupsView + elif key == Qt.Key_Delete: + #if self.characterDeletionCallback is not None: + if self.proceedWithDeletion() and self.selection: + # we need to del in reverse order to keep key references valid + for key in sorted(self._selection, reverse=True): + glyph = self.glyphs[key] + font = glyph.getParent() + if glyph in font: + del self.font[gName] + if modifiers & Qt.ShiftModifier: + # XXX: need a del fn in property + del self.glyphs[key] + self.selection = set() + else: + super(GlyphCollectionWidget, self).keyPressEvent(event) + return + event.accept() + + 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 + + # TODO: make sure lastKey/moveKey are taken care of before rmin this + self.computeCharacterSelected() + event.accept() + self.update() + else: + super(GlyphCollectionWidget, 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.setText(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 < 0 or key > len(self.glyphs)-1: return + self.moveKey = key + + self.computeCharacterSelected() + event.accept() + self.update() + else: + super(GlyphCollectionWidget, 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} + 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 moveKey > lastKey: + sel = set(range(lastKey, moveKey+1)) + else: + sel = set(range(moveKey, lastKey+1)) + self.lastKey = -1 + self.moveKey = -1 + if event.modifiers() & Qt.ControlModifier: + self.selection ^= sel + else: + self.selection = sel + event.accept() + self.update() + else: + super(GlyphCollectionWidget, 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() + if self.doubleClickCallback is not None: + self.doubleClickCallback(self.glyphs[key]) + else: + super(GlyphCollectionWidget, 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 + # XXX: do we need to maintain self._column when we have (endColumn - + # beginColumn)? + beginColumn = redrawRect.left() // self.squareSize + endColumn = redrawRect.right() // self.squareSize + + # 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 = QLinearGradient(0, 0, 0, self.squareSize-GlyphCellHeaderHeight) + + 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 + glyph = self.glyphs[key] + + painter.save() + painter.translate(column * self.squareSize, row * self.squareSize) + painter.fillRect(0, 0, self.squareSize, self.squareSize, Qt.white) + # prepare header colors + brushColor = gradient + linesColor = cellHeaderHighlightLineColor + # mark color + if not glyph.template: + # TODO: fetch via defcon dict + 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)) + if glyph.dirty: + brushColor = dirtyGradient + linesColor = cellHeaderHighlightLineColor.darker(110) + + # header gradient + painter.fillRect(0, 0, self.squareSize, GlyphCellHeaderHeight, + QBrush(brushColor)) + # header lines + painter.setPen(linesColor) + minOffset = painter.pen().width() + # disable antialiasing to avoid lines bleeding over background + 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(glyph.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) + + if not glyph.template: + font = glyph.getParent() + outline = glyph.getRepresentation("defconQt.QPainterPath") + uPM = font.info.unitsPerEm + if uPM is None or not uPM > 0: + uPM = 1000 + descender = font.info.descender + if descender is None or not descender < 0: + descender = -250 + factor = (self.squareSize-GlyphCellHeaderHeight) / (uPM*(1+2*GlyphCellBufferHeight)) + x_offset = (self.squareSize-glyph.width*factor)/2 + # If the glyph overflows horizontally we need to adjust the scaling factor + if x_offset < 0: + factor *= 1+2*x_offset/(glyph.width*factor) + x_offset = 0 + # TODO: the * 1.8 below is somewhat artificial + y_offset = 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(outline, Qt.black) + painter.restore() + else: + painter.save() + painter.setFont(voidFont) + painter.setPen(QPen(Qt.lightGray)) + rect = QRectF(column * self.squareSize, row * self.squareSize+GlyphCellHeaderHeight, + self.squareSize, self.squareSize-GlyphCellHeaderHeight) + # TODO: need to flag template glyphs as to whether they have unicodings or not + if glyph.unicode is not None: + text = chr(glyph.unicode) + else: + text = "✌" + painter.drawText(rect, Qt.AlignCenter, text) + painter.restore() diff --git a/Lib/defconQt/glyphView.py b/Lib/defconQt/glyphView.py index 8212a66..04a15cc 100644 --- a/Lib/defconQt/glyphView.py +++ b/Lib/defconQt/glyphView.py @@ -16,14 +16,13 @@ class GotoWindow(QDialog): dict(type="alphabetical", allowPseudoUnicode=True) ] - def __init__(self, view, parent=None): + def __init__(self, font, parent=None): super(GotoWindow, self).__init__(parent) self.setWindowModality(Qt.WindowModal) self.setWindowTitle("Go to…") - self._view = view - font = self._view._font - self._sortedGlyphs = font.unicodeData.sortGlyphNames(font.keys(), self.alphabetical) - + self.font = font + self._sortedGlyphs = self.font.unicodeData.sortGlyphNames(self.font.keys(), self.alphabetical) + layout = QGridLayout(self) self.glyphLabel = QLabel("Glyph:", self) self.glyphEdit = QLineEdit(self) @@ -34,13 +33,13 @@ class GotoWindow(QDialog): self.containsBox = QRadioButton("Contains", self) self.beginsWithBox.setChecked(True) self.beginsWithBox.toggled.connect(self.updateGlyphList) - + self.glyphList = QListWidget(self) buttonBox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) buttonBox.accepted.connect(self.accept) buttonBox.rejected.connect(self.reject) - + l = 0 layout.addWidget(self.glyphLabel, l, 0, 1, 2) layout.addWidget(self.glyphEdit, l, 2, 1, 4) @@ -67,7 +66,7 @@ class GotoWindow(QDialog): event.accept() else: QLineEdit.keyPressEvent(self.glyphEdit, event) - + def updateGlyphList(self, select=True): self.glyphList.clear() if not self.glyphEdit.isModified(): @@ -79,32 +78,31 @@ class GotoWindow(QDialog): glyphs = [glyph for glyph in self._sortedGlyphs if text in glyph] self.glyphList.addItems(glyphs) if select: self.glyphList.setCurrentRow(0) - + def accept(self): # TODO: zap going thru the view, here and above currentItem = self.glyphList.currentItem() if currentItem is not None: - font = self._view._font targetGlyph = currentItem.text() - if not targetGlyph in font: return - self._view.setGlyph(font[targetGlyph]) + if not targetGlyph in self.font: return + self._view.setGlyph(self.font[targetGlyph]) super(GotoWindow, self).accept() class MainGfxWindow(QMainWindow): - def __init__(self, font=None, glyph=None, parent=None): + def __init__(self, glyph, parent=None): super(MainGfxWindow, self).__init__(parent) self.setAttribute(Qt.WA_DeleteOnClose) self.setAttribute(Qt.WA_KeyCompression) - self.view = GlyphView(font, glyph, self) + self.view = GlyphView(glyph, self) fileMenu = QMenu("&File", self) fileMenu.addAction("E&xit", self.close, QKeySequence.Quit) self.menuBar().addMenu(fileMenu) - + toolsMenu = QMenu("&Tools", self) - + self.selectTool = toolsMenu.addAction("&Selection") self.selectTool.setCheckable(True) self.selectTool.toggled.connect(self.view.setSceneSelection) @@ -112,7 +110,7 @@ class MainGfxWindow(QMainWindow): self.drawingTool = toolsMenu.addAction("&Drawing") self.drawingTool.setCheckable(True) self.drawingTool.toggled.connect(self.view.setSceneDrawing) - + self.rulerTool = toolsMenu.addAction("&Ruler") self.rulerTool.setCheckable(True) self.rulerTool.toggled.connect(self.view.setSceneRuler) @@ -170,13 +168,13 @@ class MainGfxWindow(QMainWindow): rendererGroup.triggered.connect(self.setRenderer) self.setCentralWidget(self.view) - self.setWindowTitle(glyph.name, font) + self.setWindowTitle(glyph.name, glyph.getParent()) self.adjustSize() - + def close(self): self.view._glyph.removeObserver(self, "Glyph.Changed") super(MainGfxWindow, self).close() - + def _glyphChanged(self, notification): self.view._glyphChanged(notification) @@ -245,7 +243,7 @@ class OffCurvePointItem(QGraphicsEllipseItem): self.setBrush(QBrush(offCurvePointColor)) self._needsUngrab = False - + def itemChange(self, change, value): if change == QGraphicsItem.ItemPositionChange: if self.scene()._integerPlane: @@ -266,18 +264,18 @@ class OffCurvePointItem(QGraphicsEllipseItem): elif change == QGraphicsItem.ItemPositionHasChanged: self.parentItem()._CPMoved(value) return value - + def mousePressEvent(self, event): if not self._needsUngrab and self.x() == 0 and self.y() == 0: event.ignore() super(OffCurvePointItem, self).mousePressEvent(event) - + def mouseReleaseEvent(self, event): super(OffCurvePointItem, self).mouseReleaseEvent(event) if self._needsUngrab: self.ungrabMouse() self._needsUngrab = False - + # http://www.qtfr.org/viewtopic.php?pid=21045#p21045 def paint(self, painter, option, widget): #if self.x() == 0 and self.y() == 0: return @@ -290,7 +288,7 @@ class OffCurvePointItem(QGraphicsEllipseItem): pen.setColor(offCurvePointStrokeColor) self.setPen(pen) super(OffCurvePointItem, self).paint(painter, newOption, widget) - + def setPointPath(self, scale=None): if scale is None: scene = self.scene() @@ -318,7 +316,7 @@ class OnCurvePointItem(QGraphicsPathItem): self.setFlag(QGraphicsItem.ItemIsSelectable) self.setFlag(QGraphicsItem.ItemSendsGeometryChanges) self.setBrush(QBrush(onCurvePointColor)) - + def delete(self, preserveShape=True): def findNextOnCurve(self, index=0): for _ in self._contour: @@ -347,7 +345,7 @@ class OnCurvePointItem(QGraphicsPathItem): self._contour.setStartPoint(nextOnCurveIndex) scene._blocked = False scene.removeItem(self) - + def setPointPath(self, scale=None): path = QPainterPath() if scale is None: @@ -365,10 +363,10 @@ class OnCurvePointItem(QGraphicsPathItem): self.prepareGeometryChange() self.setPath(path) self.setPen(QPen(onCurvePointStrokeColor, onCurvePenWidth/scale)) - + def getPointIndex(self): return self._contour.index(self._point) - + def getSegmentIndex(self): # closed contour cycles and so the "previous" segment goes to current point index = 0 if self._contour.open else -1 @@ -376,7 +374,7 @@ class OnCurvePointItem(QGraphicsPathItem): if pt == self._point: break if pt.segmentType is not None: index += 1 return index % len(self._contour.segments) - + def _CPMoved(self, newValue): pointIndex = self.getPointIndex() children = self.childItems() @@ -435,13 +433,13 @@ class OnCurvePointItem(QGraphicsPathItem): self._contour[pointIndex+1].y = self.pos().y()+nextPos.y() self.setShallowDirty() return value - + def setShallowDirty(self): scene = self.scene() scene._blocked = True self._contour.dirty = True scene._blocked = False - + def mouseMoveEvent(self, event): modifiers = event.modifiers() children = self.childItems() @@ -491,13 +489,13 @@ class OnCurvePointItem(QGraphicsPathItem): if view._currentTool == SceneTools.RulerTool or view._currentTool == SceneTools.KnifeTool: return self.setIsSmooth(not self._isSmooth) - + def setIsSmooth(self, isSmooth): self._isSmooth = isSmooth self._point.smooth = self._isSmooth self.setShallowDirty() self.setPointPath() - + # http://www.qtfr.org/viewtopic.php?pid=21045#p21045 def paint(self, painter, option, widget): newOption = QStyleOptionGraphicsItem(option) @@ -520,18 +518,18 @@ class ResizeHandleItem(QGraphicsRectItem): self.setFlag(QGraphicsItem.ItemIsMovable) #self.setFlag(QGraphicsItem.ItemIsSelectable) self.setCursor(Qt.SizeFDiagCursor) - + rect = self.parentItem().boundingRect() self.setPos(rect.width(), rect.height()) - + def itemChange(self, change, value): if change == QGraphicsItem.ItemSelectedChange: if not value: self.setVisible(value) return value - + def mouseMoveEvent(self, event): self.parentItem()._pixmapGeometryChanged(event) - + def setPointPath(self, scale=None): if scale is None: scene = self.scene() @@ -558,7 +556,7 @@ class PixmapItem(QGraphicsPixmapItem): self._rHeight = rect.height() handle = ResizeHandleItem(self) handle.setVisible(False) - + def _pixmapGeometryChanged(self, event): modifiers = event.modifiers() pos = event.scenePos() @@ -577,7 +575,7 @@ class PixmapItem(QGraphicsPixmapItem): dx = (pos.x() - self.x()) / self._rWidth self.setTransform(QTransform().fromScale(dx, dy)) event.accept() - + def itemChange(self, change, value): if change == QGraphicsItem.ItemSelectedChange: children = self.childItems() @@ -602,21 +600,21 @@ class GlyphScene(QGraphicsScene): font.setFamily("Roboto Mono") font.setFixedPitch(True) self.setFont(font) - + self._blocked = False - + def dragEnterEvent(self, event): if event.mimeData().hasUrls(): event.acceptProposedAction() else: super(GlyphScene, self).dragEnterEvent(event) - + def dragMoveEvent(self, event): if event.mimeData().hasUrls(): event.acceptProposedAction() else: super(GlyphScene, self).dragMoveEvent(event) - + def dropEvent(self, event): mimeData = event.mimeData() if mimeData.hasUrls(): @@ -630,13 +628,13 @@ class GlyphScene(QGraphicsScene): newPix = PixmapItem(pos.x(), pos.y(), dragPix) self.addItem(newPix) event.acceptProposedAction() - + def getItemForPoint(self, point): for item in self.items(): if isinstance(item, OnCurvePointItem) and item._point == point: return item return None - + def getViewScale(self): return self.views()[0].transform().m11() @@ -669,24 +667,24 @@ class GlyphScene(QGraphicsScene): event.accept() return elif key == Qt.Key_J: - view = self.views()[0] - dialog = GotoWindow(view, self.parent()) + glyph = self.views()[0]._glyph + dialog = GotoWindow(glyph.getParent(), self.parent()) dialog.exec_() return elif event.matches(QKeySequence.Undo): if len(self._dataForUndo) > 0: undo = self._dataForUndo.pop() - redo = self._glyphObject.getDataToSerializeForUndo() - self._glyphObject.loadDeserializedDataFromUndo(undo) + redo = self._glyphObject.serializeForUndo() + self._glyphObject.deserializeFromUndo(undo) self._dataForRedo.append(redo) event.accept() return elif event.matches(QKeySequence.Redo): if len(self._dataForRedo) > 0: - undo = self._glyphObject.getDataToSerializeForUndo() + undo = self._glyphObject.serializeForUndo() redo = self._dataForRedo.pop() self._dataForUndo.append(undo) - self._glyphObject.loadDeserializedDataFromUndo(redo) + self._glyphObject.deserializeFromUndo(redo) event.accept() return elif event.matches(QKeySequence.SelectAll): @@ -718,7 +716,7 @@ class GlyphScene(QGraphicsScene): if isinstance(item, OffCurvePointItem) and item.parentItem().isSelected(): continue item.moveBy(x,y) event.accept() - + def keyReleaseEvent(self, event): sel = self.selectedItems() if len(sel) == 1 and isinstance(sel[0], OffCurvePointItem) and \ @@ -734,7 +732,7 @@ class GlyphScene(QGraphicsScene): self.rulerMousePress(event) return else: - data = self._glyphObject.getDataToSerializeForUndo() + data = self._glyphObject.serializeForUndo() self._dataForUndo.append(data) self._dataForRedo = [] if view._currentTool == SceneTools.KnifeTool: @@ -815,7 +813,7 @@ class GlyphScene(QGraphicsScene): super(GlyphScene, self).mousePressEvent(event) # Since shift clamps, we might be missing the point in mousePressEvent if forceSelect: item.setSelected(True) - + def mouseMoveEvent(self, event): if self._editing is True: sel = self.selectedItems() @@ -846,7 +844,7 @@ class GlyphScene(QGraphicsScene): self.sendEvent(sel[0], QEvent(QEvent.MouseButtonRelease)) mouseGrabberItem.ungrabMouse() sel[0].setSelected(False) - + # construct a curve segment to the current point if there is not one onCurve = sel[0]._point if not onCurve.segmentType == "curve": @@ -906,7 +904,7 @@ class GlyphScene(QGraphicsScene): items[1].setPos(0, 0) else: super(GlyphScene, self).mouseMoveEvent(event) - + def mouseReleaseEvent(self, event): self._editing = False currentTool = self.views()[0]._currentTool @@ -929,7 +927,7 @@ class GlyphScene(QGraphicsScene): elif currentTool == SceneTools.RulerTool: self.rulerMouseRelease(event) super(GlyphScene, self).mouseReleaseEvent(event) - + def rulerMousePress(self, event): touched = self.itemAt(event.scenePos(), self.views()[0].transform()) if touched is not None and isinstance(touched, OnCurvePointItem) or \ @@ -955,7 +953,7 @@ class GlyphScene(QGraphicsScene): textItem.setFlag(QGraphicsItem.ItemIgnoresTransformations) textItem.setPos(x, y + textItem.boundingRect().height()) event.accept() - + def rulerMouseMove(self, event): # XXX: shouldnt have to do this, it seems mouseTracking is wrongly activated if self._rulerObject is None: return @@ -995,7 +993,7 @@ class GlyphScene(QGraphicsScene): else: py = baseElem.y + textItem.boundingRect().height() textItem.setPos(px, py) event.accept() - + def rulerMouseRelease(self, event): self._cachedRuler = self._rulerObject self._rulerObject = None @@ -1101,16 +1099,15 @@ class GlyphScene(QGraphicsScene): class GlyphView(QGraphicsView): Native, OpenGL, Image = range(3) - def __init__(self, font, glyph, parent=None): + def __init__(self, glyph, parent=None): super(GlyphView, self).__init__(parent) self.renderer = GlyphView.Native - self._font = font self._glyph = glyph self._glyph.addObserver(self, "_glyphChanged", "Glyph.Changed") self._impliedPointSize = 1000 self._pointSize = None - + self._inverseScale = 0.1 self._scale = 10 self._noPointSizePadding = 200 @@ -1118,8 +1115,8 @@ class GlyphView(QGraphicsView): self._drawStroke = True self._showOffCurvePoints = True self._showOnCurvePoints = True - self._fillColor = QColor.fromRgbF(0, 0, 0, .4) - self._componentFillColor = QColor.fromRgbF(.2, .2, .3, .4) + self._fillColor = QColor(200, 200, 200, 120)#QColor.fromRgbF(0, 0, 0, .4) + self._componentFillColor = QColor.fromRgbF(0, 0, 0, .4)#QColor.fromRgbF(.2, .2, .3, .4) self._showMetricsTitles = True self._metricsColor = QColor(70, 70, 70) @@ -1129,30 +1126,29 @@ class GlyphView(QGraphicsView): font.setFamily("Roboto Mono") font.setFixedPitch(True) self.setFont(font) - + self.setTransformationAnchor(QGraphicsView.AnchorUnderMouse) self.setResizeAnchor(QGraphicsView.AnchorUnderMouse) #self.setViewportUpdateMode(QGraphicsView.BoundingRectViewportUpdate) - + self._drawingTool = SceneTools.SelectionTool self.setDragMode(QGraphicsView.RubberBandDrag) - + self.setRenderHint(QPainter.Antialiasing) - #self.translate(0, self.height()*(1+self._font.info.descender/self._font.info.unitsPerEm)) self.scale(1, -1) self.addBackground() self.addBlues() self.addHorizontalMetrics() self.addOutlines() self.addPoints() - + def _glyphChanged(self, notification): # TODO: maybe detect sidebearing changes (space center) and then only # translate elements rather than reconstructing them. # Also we lose selection when reconstructing, rf does not when changing # sp.center values. self.redrawGlyph() - + def redrawGlyph(self): path = self._glyph.getRepresentation("defconQt.NoComponentsQPainterPath") scene = self.scene() @@ -1172,42 +1168,16 @@ class GlyphView(QGraphicsView): # this will not be the case anymore when drag sidebearings pops up scene._widthItem.setRect(0, -1000, self._glyph.width, 3000) - def _getGlyphWidthHeight(self): - if self._glyph.bounds: - left, bottom, right, top = self._glyph.bounds - else: - left = right = bottom = top = 0 - left = min((0, left)) - right = max((right, self._glyph.width)) - bottom = self._font.info.descender - top = max((self._font.info.capHeight, self._font.info.ascender, self._font.info.unitsPerEm + self._font.info.descender)) - width = abs(left) + right - height = -bottom + top - return width, height - - def _calcScale(self): - if self._pointSize is None: - visibleHeight = self.viewport().height() - fitHeight = visibleHeight - glyphWidth, glyphHeight = self._getGlyphWidthHeight() - glyphHeight += self._noPointSizePadding * 2 - self._scale = fitHeight / glyphHeight - else: - self._scale = self._pointSize / float(self._font.info.unitsPerEm) - if self._scale <= 0: - self._scale = .01 - self._inverseScale = 1.0 / self._scale - self._impliedPointSize = self._font.info.unitsPerEm * self._scale - def addBackground(self): scene = self.scene() + font = self._glyph.getParent() width = self._glyph.width item = scene.addRect(-1000, -1000, 3000, 3000, QPen(Qt.black), QBrush(Qt.gray)) item.setZValue(-1000) scene._widthItem = scene.addRect(0, -1000, width, 3000, QPen(Qt.NoPen), QBrush(backgroundColor)) scene._widthItem.setZValue(-999) - self.centerOn(width/2, self._font.info.descender+self._font.info.unitsPerEm/2) - + self.centerOn(width/2, font.info.descender+font.info.unitsPerEm/2) + def addBlues(self): scene = self.scene() #width = self._glyph.width# * self._inverseScale @@ -1229,16 +1199,17 @@ class GlyphView(QGraphicsView): else: item = scene.addRect(-1000, yMin, 3000, yMax - yMin, QPen(Qt.NoPen), QBrush(self._bluesColor)) item.setZValue(-998) - + def addHorizontalMetrics(self): scene = self.scene() + font = self._glyph.getParent() width = self._glyph.width# * self._inverseScale toDraw = [ - ("Descender", self._font.info.descender), + ("Descender", font.info.descender), ("Baseline", 0), - ("x-height", self._font.info.xHeight), - ("Cap height", self._font.info.capHeight), - ("Ascender", self._font.info.ascender) + ("x-height", font.info.xHeight), + ("Cap height", font.info.capHeight), + ("Ascender", font.info.ascender) ] positions = {} for name, position in toDraw: @@ -1376,15 +1347,18 @@ class GlyphView(QGraphicsView): value = round(value) - .5 value = value * self._inverseScale return value - - def setGlyph(self, glyph, font=None): - if font is not None: self._font = font + + def setGlyph(self, glyph): self._glyph.removeObserver(self, "Glyph.Changed") + # TODO: consider creating a new scene instead of zeroing things out + # manually + self._dataForUndo = [] + self._dataForRedo = [] self._glyph = glyph + # XXX: DRY ALERT! + self.scene()._glyphObject = glyph self._glyph.addObserver(self, "_glyphChanged", "Glyph.Changed") - #self.scene().setSceneRect(*self._glyph.bounds) - #self.scene().setSceneRect(0, self._font.info.ascender, self._glyph.width, self._font.info.unitsPerEm) - self.parent().setWindowTitle(glyph.name, self._font) + self.parent().setWindowTitle(self._glyph.name, self._glyph.getParent()) self.redrawGlyph() def setRenderer(self, renderer): @@ -1403,7 +1377,7 @@ class GlyphView(QGraphicsView): def setViewOutline(self, enable): if self.outlineItem: self.outlineItem.setVisible(enable) - + def mousePressEvent(self, event): if (event.button() == Qt.MidButton): dragMode = self.dragMode() @@ -1416,11 +1390,11 @@ class GlyphView(QGraphicsView): def setSceneDrawing(self): self._currentTool = SceneTools.DrawingTool self.setDragMode(QGraphicsView.NoDrag) - + def setSceneRuler(self): self._currentTool = SceneTools.RulerTool self.setDragMode(QGraphicsView.NoDrag) - + def setSceneSelection(self): self._currentTool = SceneTools.SelectionTool self.setDragMode(QGraphicsView.RubberBandDrag) @@ -1440,8 +1414,6 @@ class GlyphView(QGraphicsView): def wheelEvent(self, event): factor = pow(1.2, event.angleDelta().y() / 120.0) - #self._calcScale() - #self._setFrame() self.scale(factor, factor) # TODO: stop displaying SimpleTextItems at certains sizes, maybe anchor them differently as well scale = self.transform().m11() @@ -1452,17 +1424,3 @@ class GlyphView(QGraphicsView): item.setPointPath(scale) self.update() event.accept() - -if __name__ == '__main__': - - import sys - - app = QApplication(sys.argv) - - window = MainGfxWindow() - if len(sys.argv) == 2: - window.openFile(sys.argv[1]) - else: - window.openFile(':/files/bubbles.svg') - window.show() - sys.exit(app.exec_()) diff --git a/Lib/defconQt/groupsView.py b/Lib/defconQt/groupsView.py index bec64dc..e6ee932 100644 --- a/Lib/defconQt/groupsView.py +++ b/Lib/defconQt/groupsView.py @@ -1,4 +1,4 @@ -from defconQt.fontView import CharacterWidget +from defconQt.glyphCollectionView import GlyphCollectionWidget from PyQt5.QtCore import * from PyQt5.QtGui import * from PyQt5.QtWidgets import * @@ -9,12 +9,12 @@ class GroupListWidget(QListWidget): #self.setAlternatingRowColors(True) self.setSelectionMode(QAbstractItemView.SingleSelection) self.setSortingEnabled(True) - + for groupName in groupNames: item = QListWidgetItem(groupName, self) item.setFlags(item.flags() | Qt.ItemIsEditable) #if len(groupNames): self.setCurrentRow(0) - + def keyPressEvent(self, event): key = event.key() if key == Qt.Key_Delete: @@ -36,19 +36,19 @@ class GroupStackWidget(QWidget): self.upm = font.info.unitsPerEm self.padding = 10 self.alignRight = False - + def setAlignment(self, alignRight): self.alignRight = alignRight self.update() - + def setGlyphs(self, glyphs): self.glyphs = glyphs self.maxWidth = max(glyph.width for glyph in self.glyphs) if len(self.glyphs) else 300 self.update() - + def sizeHint(self): return QSize(self.maxWidth+2*self.padding, 400) - + def paintEvent(self, event): # TODO: maybe use self.upm*(1+2*BufferHeight) for the denominator as in fontView scale = self.height() / (self.upm*1.2) @@ -60,7 +60,7 @@ class GroupStackWidget(QWidget): painter.setRenderHint(QPainter.Antialiasing) painter.translate(self.padding, self.padding+(self.ascender*1.2)*scale) painter.scale(scale, -scale) - + col = QColor(Qt.black) col.setAlphaF(.2) for glyph in self.glyphs: @@ -72,32 +72,38 @@ class GroupStackWidget(QWidget): painter.fillPath(glyphPath, col) painter.restore() -class GroupCharacterWidget(CharacterWidget): - def __init__(self, font, squareSize=56, scrollArea=None, parent=None): - super(GroupCharacterWidget, self).__init__(font, squareSize, scrollArea, parent) - self.columns = 9 - self.scrollArea.setAcceptDrops(True) - self.scrollArea.dragEnterEvent = self.pipeDragEnterEvent - self.scrollArea.dropEvent = self.pipeDropEvent +class GroupCollectionWidget(GlyphCollectionWidget): + def __init__(self, parent=None): + super(GroupCollectionWidget, self).__init__(parent) + self._columns = 9 + self._scrollArea.setAcceptDrops(True) + self._scrollArea.dragEnterEvent = self.pipeDragEnterEvent + self._scrollArea.dropEvent = self.pipeDropEvent + + # TODO: upstream this, somehow + self.characterDeletionCallback = None + self.characterDropCallback = None self.resize(self.width(), 200) - + # TODO: The standard QListWidget has scrollbar and does not need three times parent call. # Find out how to handle that properly. def keyPressEvent(self, event): if event.key() == Qt.Key_Delete: - self.parent().parent().parent().characterDeleteEvent(self._selection) + if self.characterDeletionCallback is not None: + self.characterDeletionCallback(self.selection) event.accept() else: - super(GroupCharacterWidget, self).keyPressEvent(event) - + super(GroupCollectionWidget, self).keyPressEvent(event) + def pipeDragEnterEvent(self, event): # TODO: the problem with text/plain is that any sort of text can get here. # (It allows direct compatibility with featureTextEditor though.) if (event.mimeData().hasText()): event.acceptProposedAction() - + def pipeDropEvent(self, event): - self.parent().parent().parent().characterDropEvent(event) + if self.characterDropCallback is not None: + self.characterDropCallback(event) class GroupsWindow(QWidget): leftGroups = ["@MMK_L", "public.kern1"] @@ -112,9 +118,9 @@ class GroupsWindow(QWidget): self.groupsList.currentItemChanged.connect(self._groupChanged) self.groupsList.itemChanged.connect(self._groupRenamed) self.groupsList.setFocus(True) - + self.stackWidget = GroupStackWidget(self.font, parent=self) - + self.addGroupButton = QPushButton("+", self) self.addGroupButton.clicked.connect(self._groupAdd) self.removeGroupButton = QPushButton("−", self) @@ -126,12 +132,12 @@ class GroupsWindow(QWidget): self.alignLeftBox.setChecked(True) self.alignLeftBox.toggled.connect(self._alignmentChanged) self._autoDirection = True - - self.scrollArea = QScrollArea(self) - self.characterWidget = GroupCharacterWidget(self.font, scrollArea=self.scrollArea, parent=self) - self.scrollArea.setWidget(self.characterWidget) + + self.collectionWidget = GroupCollectionWidget(parent=self) + self.collectionWidget.characterDeletionCallback = self.characterDeleteEvent + self.collectionWidget.characterDropCallback = self.characterDropEvent self._cachedName = None - + layout = QGridLayout(self) layout.addWidget(self.groupsList, 0, 0, 5, 4) layout.addWidget(self.stackWidget, 0, 4, 5, 4) @@ -139,17 +145,17 @@ class GroupsWindow(QWidget): layout.addWidget(self.removeGroupButton, 5, 3) layout.addWidget(self.alignLeftBox, 5, 4) layout.addWidget(self.alignRightBox, 5, 7) - layout.addWidget(self.scrollArea, 6, 0, 4, 8) + layout.addWidget(self.collectionWidget.scrollArea(), 6, 0, 4, 8) # TODO: calib this more layout.setColumnStretch(4, 1) self.setLayout(layout) - - self.setWindowTitle("%s%s%s%s" % ("Groups window – ", self.font.info.familyName, " ", self.font.info.styleName)) - + + self.setWindowTitle("Groups window – %s %s" % (self.font.info.familyName, self.font.info.styleName)) + def _alignmentChanged(self): alignRight = self.alignRightBox.isChecked() self.stackWidget.setAlignment(alignRight) - + def _groupAdd(self): groupName = "New group" if groupName in self.font.groups: @@ -163,7 +169,7 @@ class GroupsWindow(QWidget): self.groupsList.setCurrentItem(item) self.groupsList.editItem(item) self.removeGroupButton.setEnabled(True) - + def _groupChanged(self): self._cachedName = self.groupsList.currentItem().text() if self._autoDirection: @@ -180,23 +186,22 @@ class GroupsWindow(QWidget): if gName in self.font: glyphs.append(self.font[gName]) self.stackWidget.setGlyphs(glyphs) - self.characterWidget.setGlyphs(glyphs) - self.characterWidget.update() - + self.collectionWidget.glyphs = glyphs + def _groupRenamed(self): newKey = self.groupsList.currentItem() if newKey is None: return newKey = newKey.text() self.font.groups[newKey] = self.font.groups[self._cachedName] del self.font.groups[self._cachedName] - + def _groupDelete(self): newKey = self.groupsList.currentItem().text() del self.font.groups[newKey] self.groupsList.takeItem(self.groupsList.currentRow()) if not self.font.groups.keys(): self.removeGroupButton.setEnabled(False) self._groupChanged() - + def characterDeleteEvent(self, selection): currentGroup = self.groupsList.currentItem().text() currentGroupList = self.font.groups[currentGroup] @@ -206,7 +211,7 @@ class GroupsWindow(QWidget): del currentGroupList[key] self.font.groups[currentGroup] = currentGroupList self._groupChanged() - + def characterDropEvent(self, event): currentGroup = self.groupsList.currentItem() if currentGroup is None: return @@ -220,11 +225,11 @@ class GroupsWindow(QWidget): self.font.groups[currentGroup] = currentGroupList event.acceptProposedAction() self._groupChanged() - + def resizeEvent(self, event): if self.isVisible(): margins = self.layout().contentsMargins() width = event.size().width() - (margins.left() + margins.right()) - self.characterWidget._sizeEvent(width) + self.collectionWidget._sizeEvent(width) self.stackWidget.update() - super(GroupsWindow, self).resizeEvent(event)
\ No newline at end of file + super(GroupsWindow, self).resizeEvent(event) diff --git a/Lib/defconQt/objects/__init__.py b/Lib/defconQt/objects/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/Lib/defconQt/objects/__init__.py diff --git a/Lib/defconQt/objects/defcon.py b/Lib/defconQt/objects/defcon.py new file mode 100644 index 0000000..487865a --- /dev/null +++ b/Lib/defconQt/objects/defcon.py @@ -0,0 +1,22 @@ +class CharacterSet(object): + __slots__ = ["_name", "_glyphNames"] + + def __init__(self, glyphNames, name=None): + self._name = name + self._glyphNames = glyphNames + + def _get_name(self): + return self._name + + def _set_name(self, name): + self._name = name + + name = property(_get_name, _set_name, doc="Character set name.") + + def _get_glyphNames(self): + return self._glyphNames + + def _set_glyphNames(self, glyphNames): + self._glyphNames = glyphNames + + glyphNames = property(_get_glyphNames, _set_glyphNames, doc="List of glyph names.") diff --git a/Lib/defconQt/spaceCenter.py b/Lib/defconQt/spaceCenter.py index d0ba766..8036e79 100644 --- a/Lib/defconQt/spaceCenter.py +++ b/Lib/defconQt/spaceCenter.py @@ -1,4 +1,4 @@ -from defconQt.fontView import cellSelectionColor +from defconQt.glyphCollectionView import cellSelectionColor from defconQt.glyphView import MainGfxWindow from getpass import getuser from PyQt5.QtCore import *#QAbstractTableModel, QEvent, QSize, Qt @@ -25,18 +25,16 @@ class MainSpaceWindow(QWidget): self.glyphs = [] self._subscribeToGlyphsText(string) self.toolbar = FontToolBar(string, pointSize, self) - self.scrollArea = QScrollArea(self) - self.canvas = GlyphsCanvas(self.font, self.glyphs, self.scrollArea, pointSize, self) - self.scrollArea.setWidget(self.canvas) + self.canvas = GlyphsCanvas(self.font, self.glyphs, pointSize, self) self.table = SpaceTable(self.glyphs, self) - self.canvas.setDoubleClickCallback(self._glyphOpened) - self.canvas.setPointSizeCallback(self.toolbar.setPointSize) - self.canvas.setSelectionCallback(self.table.setCurrentGlyph) - self.table.setSelectionCallback(self.canvas.setSelected) - + self.canvas.doubleClickCallback = self._glyphOpened + self.canvas.pointSizeCallback = self.toolbar.setPointSize + self.canvas.selectionChangedCallback = self.table.setCurrentGlyph + self.table.selectionChangedCallback = self.canvas.setSelected + layout = QVBoxLayout(self) layout.addWidget(self.toolbar) - layout.addWidget(self.scrollArea) + layout.addWidget(self.canvas.scrollArea()) layout.addWidget(self.table) layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(0) @@ -44,7 +42,7 @@ class MainSpaceWindow(QWidget): self.resize(600, 500) self.toolbar.comboBox.currentIndexChanged[str].connect(self.canvas.setPointSize) self.toolbar.textField.textEdited.connect(self._textChanged) - + self.font.info.addObserver(self, "_fontInfoChanged", "Info.Changed") self.setWindowTitle("Space center – %s %s" % (self.font.info.familyName, self.font.info.styleName)) @@ -55,22 +53,22 @@ class MainSpaceWindow(QWidget): fileMenu.addAction("&Save...", self.save, QKeySequence.Save) fileMenu.addAction("E&xit", self.close, QKeySequence.Quit) - + 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(self.font, glyph, self.parent()) + glyphViewWindow = MainGfxWindow(glyph, self.parent()) glyphViewWindow.show() def _textChanged(self, newText): @@ -81,7 +79,7 @@ class MainSpaceWindow(QWidget): # set the records into the view self.canvas.setGlyphs(self.glyphs) self.table.setGlyphs(self.glyphs) - + # Tal Leming. Edited. def textToGlyphNames(self, text): # escape // @@ -118,7 +116,7 @@ class MainSpaceWindow(QWidget): if compileStack is not None and compileStack: glyphNames.append("".join(compileStack)) return glyphNames - + def _subscribeToGlyphsText(self, newText): glyphs = [] glyphNames = self.textToGlyphNames(newText) @@ -127,7 +125,7 @@ class MainSpaceWindow(QWidget): if gName not in self.font: continue glyphs.append(self.font[gName]) self._subscribeToGlyphs(glyphs) - + def _subscribeToGlyphs(self, glyphs): self.glyphs = glyphs @@ -137,7 +135,7 @@ class MainSpaceWindow(QWidget): continue handledGlyphs.add(glyph) glyph.addObserver(self, "_glyphChanged", "Glyph.Changed") - + def _unsubscribeFromGlyphs(self): handledGlyphs = set() for glyph in self.glyphs: @@ -159,7 +157,7 @@ class MainSpaceWindow(QWidget): # set the records into the view self.canvas.setGlyphs(self.glyphs) self.table.setGlyphs(self.glyphs) - + def resizeEvent(self, event): if self.isVisible(): self.canvas._sizeEvent(event) super(MainSpaceWindow, self).resizeEvent(event) @@ -179,7 +177,7 @@ class FontToolBar(QToolBar): self.configBar = QPushButton(self) self.configBar.setFlat(True) - self.configBar.setIcon(QIcon("resources/ic_settings_24px.svg")) + self.configBar.setIcon(QIcon("defconQt/resources/ic_settings_24px.svg")) self.configBar.setStyleSheet("padding: 2px 0px; padding-right: 10px"); self.toolsMenu = QMenu(self) showKerning = self.toolsMenu.addAction("Show Kerning", self.showKerning) @@ -205,34 +203,35 @@ class FontToolBar(QToolBar): self.addWidget(self.textField) self.addWidget(self.comboBox) self.addWidget(self.configBar) - + 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 wrapLines(self): self.parent().canvas.setWrapLines(True) - + def noWrapLines(self): self.parent().canvas.setWrapLines(False) class GlyphsCanvas(QWidget): - def __init__(self, font, glyphs, scrollArea, pointSize=defaultPointSize, parent=None): + def __init__(self, font, glyphs, 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 = glyphs @@ -244,29 +243,33 @@ class GlyphsCanvas(QWidget): self._verticalFlip = False self._positions = None self._selected = None - self._doubleClickCallback = None - self._pointSizeChangedCallback = None - self._selectionChangedCallback = None - + self.doubleClickCallback = None + self.pointSizeChangedCallback = None + self.selectionChangedCallback = None + self._wrapLines = True - self.scrollArea = scrollArea - self.scrollArea.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) - self.scrollArea.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn) + self._scrollArea = QScrollArea(self.parent()) + 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() @@ -275,17 +278,17 @@ class GlyphsCanvas(QWidget): if self._wrapLines == wrapLines: return self._wrapLines = wrapLines if self._wrapLines: - sw = self.scrollArea.verticalScrollBar().width() + self.scrollArea.contentsMargins().right() + sw = self._scrollArea.verticalScrollBar().width() + self._scrollArea.contentsMargins().right() self.resize(self.parent().parent().parent().width() - sw, self.height()) - self.scrollArea.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) - self.scrollArea.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn) + self._scrollArea.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) + self._scrollArea.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn) else: - sh = self.scrollArea.horizontalScrollBar().height() + self.scrollArea.contentsMargins().bottom() + sh = self._scrollArea.horizontalScrollBar().height() + self._scrollArea.contentsMargins().bottom() self.resize(self.width(), self.parent().parent().parent().height() - sh) - self.scrollArea.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) - self.scrollArea.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOn) + 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 @@ -293,20 +296,17 @@ class GlyphsCanvas(QWidget): 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 setPointSizeCallback(self, pointSizeChangedCallback): - self._pointSizeChangedCallback = pointSizeChangedCallback - + def setSelected(self, selected): self._selected = selected if self._positions is not None: @@ -321,20 +321,17 @@ class GlyphsCanvas(QWidget): if line > -1: x = self.padding + pos + width/2 y = self.padding + (line+.5)*self.ptSize - self.scrollArea.ensureVisible(x, y, width/2+20, .5*self.ptSize+20) + self._scrollArea.ensureVisible(x, y, width/2+20, .5*self.ptSize+20) self.update() - - def setSelectionCallback(self, selectionChangedCallback): - self._selectionChangedCallback = selectionChangedCallback - + def _sizeEvent(self, event): if self._wrapLines: - sw = self.scrollArea.verticalScrollBar().width() + self.scrollArea.contentsMargins().right() + sw = self._scrollArea.verticalScrollBar().width() + self._scrollArea.contentsMargins().right() self.resize(event.size().width() - sw, self.height()) else: - sh = self.scrollArea.horizontalScrollBar().height() + self.scrollArea.contentsMargins().bottom() + sh = self._scrollArea.horizontalScrollBar().height() + self._scrollArea.contentsMargins().bottom() self.resize(self.width(), event.size().height() - sh) - + def wheelEvent(self, event): if event.modifiers() & Qt.ControlModifier: # TODO: should it snap to predefined pointSizes? is the scaling factor okay? @@ -347,12 +344,12 @@ class GlyphsCanvas(QWidget): if newPointSize <= 0: return self.setPointSize(newPointSize) - if self._pointSizeChangedCallback is not None: - self._pointSizeChangedCallback(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 @@ -393,7 +390,7 @@ class GlyphsCanvas(QWidget): 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... @@ -404,15 +401,15 @@ class GlyphsCanvas(QWidget): else: baselineShift = self.ascender found = False - line = (event.y() - self.padding) // (self.ptSize*self._lineHeight) + line = (event.y() - self.padding) // (self.ptSize) # 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) + if self.selectionChangedCallback is not None: + self.selectionChangedCallback(self._selected) event.accept() self.update() return @@ -426,22 +423,19 @@ class GlyphsCanvas(QWidget): self._selected = count+index found = True if not found: self._selected = None - if self._selectionChangedCallback is not None: - self._selectionChangedCallback(self._selected) + if self.selectionChangedCallback is not None: + self.selectionChangedCallback(self._selected) event.accept() self.update() else: super(GlyphCanvas, 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]) + if self.doubleClickCallback is not None: + self.doubleClickCallback(self.glyphs[self._selected]) else: super(GlyphCanvas, self).mouseDoubleClickEvent(event) - - def setDoubleClickCallback(self, doubleClickCallback): - self._doubleClickCallback = doubleClickCallback def paintEvent(self, event): linePen = QPen(Qt.black) @@ -455,7 +449,7 @@ class GlyphsCanvas(QWidget): 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) @@ -480,7 +474,7 @@ class GlyphsCanvas(QWidget): 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) + painter.translate(-cur_width, self.ptSize) if self._showMetrics: paintLineMarks(painter) self._positions.append([(0, gWidth)]) cur_width = gWidth @@ -502,14 +496,14 @@ class GlyphsCanvas(QWidget): painter.fillPath(glyphPath, Qt.black) painter.restore() painter.translate(gWidth, 0) - - scrollMargins = self.scrollArea.contentsMargins() - innerHeight = self.scrollArea.height() - scrollMargins.top() - scrollMargins.bottom() + + scrollMargins = self._scrollArea.contentsMargins() + innerHeight = self._scrollArea.height() - scrollMargins.top() - scrollMargins.bottom() if not self._wrapLines: - innerWidth = self.scrollArea.width() - scrollMargins.left() - scrollMargins.right() + innerWidth = self._scrollArea.width() - scrollMargins.left() - scrollMargins.right() width = max(innerWidth, cur_width+self.padding*2) else: width = self.width() - self.resize(width, max(innerHeight, lines*self.ptSize+2*self.padding)) + self.resize(width, max(innerHeight, lines*self.ptSize*self._lineHeight+2*self.padding)) class SpaceTableWidgetItem(QTableWidgetItem): def setData(self, role, value): @@ -526,7 +520,7 @@ class GlyphCellItemDelegate(QStyledItemDelegate): #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. @@ -591,14 +585,14 @@ class SpaceTable(QTableWidget): # edit cell on single click, not double self.setEditTriggers(QAbstractItemView.CurrentChanged) self._blocked = False - self._selectionChangedCallback = None + 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): if self._blocked: return self.blockSignals(True) @@ -606,7 +600,7 @@ class SpaceTable(QTableWidget): self.setCurrentItem(None) self.fillGlyphs() self.blockSignals(False) - + def _cellEdited(self, row, col): if row == 0 or col == 0: return item = self.item(row, col).text() @@ -635,12 +629,12 @@ class SpaceTable(QTableWidget): 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 self.selectionChangedCallback is not None: if current is not None: - self._selectionChangedCallback(cur - 1) + self.selectionChangedCallback(cur - 1) else: - self._selectionChangedCallback(None) - + self.selectionChangedCallback(None) + def colorColumn(self, column): emptyBrush = QBrush(Qt.NoBrush) selectionColor = QColor(235, 235, 235) @@ -671,9 +665,6 @@ class SpaceTable(QTableWidget): if glyphIndex is not None: self.colorColumn(glyphIndex+1) self.blockSignals(False) - - def setSelectionCallback(self, selectionChangedCallback): - self._selectionChangedCallback = selectionChangedCallback def fillGlyphs(self): def glyphTableWidgetItem(content, disableCell=False): @@ -698,16 +689,8 @@ class SpaceTable(QTableWidget): 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): cur = self.horizontalScrollBar().value() self.horizontalScrollBar().setValue(cur - event.angleDelta().y() / 120) event.accept() - -if __name__ == '__main__': - import sys -# registerallfactories - app = QApplication(sys.argv) - window = Window() - window.show() - sys.exit(app.exec_()) |
