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 | |
| 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
| -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 | ||||
| -rw-r--r-- | README.md | 2 | ||||
| -rw-r--r-- | setup.py | 5 | 
12 files changed, 960 insertions, 746 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_()) @@ -16,7 +16,7 @@ Dependencies:  Run: -`python -m Lib\defconQt` +`cd Lib && python -m defconQt`  The install script is not very reliable just now, but nor is the package. Direct execution is the best  supported way at the moment.
\ No newline at end of file @@ -33,14 +33,15 @@ except:  setup(name="defconQt", -    version="0.1", +    version="0.1.0",      description="A set of Qt interface objects for working with font data.",      author="Adrien Tétar",      author_email="adri-from-59@hotmail.fr",  #    url="", -    license="GNU LGPL 2.1/GNU GPL v3", +    license="GNU LGPL 2.1/GNU GPL v3",      packages=[          "defconQt", +        "defconQt.objects",          "defconQt.representationFactories",      ],      package_dir={"":"Lib"} | 
