Пример #1
0
class ToucheTool:
    def __init__(self):
        self.w = Window((180, 340), u"Touché!", minSize=(180, 340), maxSize=(1000, 898))
        p = 10
        w = 160

        # options
        self.w.options = Group((0, 0, 180, 220))

        buttons = {
            "checkSelBtn": {"text": "Check selected glyphs\nfor touching pairs", "callback": self.checkSel, "y": p},
            "checkAllBtn": {
                "text": "Check entire font\n(can take several minutes!)",
                "callback": self.checkAll,
                "y": 60,
            },
        }
        for button, data in buttons.iteritems():
            setattr(
                self.w.options,
                button,
                SquareButton((p, data["y"], w, 40), data["text"], callback=data["callback"], sizeStyle="small"),
            )

        self.w.options.zeroCheck = CheckBox((p, 108, w, 20), "Ignore zero-width glyphs", value=True, sizeStyle="small")
        self.w.options.progress = ProgressSpinner((82, 174, 16, 16), sizeStyle="small")

        # results
        self.w.results = Group((0, 220, 180, -0))
        self.w.results.show(False)

        textBoxes = {"stats": 24, "result": 42}
        for box, y in textBoxes.iteritems():
            setattr(self.w.results, box, TextBox((p, y, w, 14), "", sizeStyle="small"))

        moreButtons = {
            "spaceView": {"text": "View all in Space Center", "callback": self.showAllPairs, "y": 65},
            "exportTxt": {"text": "Export as MM pair list", "callback": self.exportPairList, "y": 90},
        }
        for button, data in moreButtons.iteritems():
            setattr(
                self.w.results,
                button,
                SquareButton((p, data["y"], w, 20), data["text"], callback=data["callback"], sizeStyle="small"),
            )

        # list and preview
        self.w.outputList = List(
            (180, 0, 188, -0),
            [{"left glyph": "", "right glyph": ""}],
            columnDescriptions=[{"title": "left glyph"}, {"title": "right glyph"}],
            showColumnTitles=False,
            allowsMultipleSelection=False,
            enableDelete=False,
            selectionCallback=self.showPair,
        )
        self.w.preview = MultiLineView((368, 0, -0, -0), pointSize=256)

        self.w.open()

    # callbacks

    def checkAll(self, sender=None):
        self.check(useSelection=False)

    def checkSel(self, sender=None):
        self.check(useSelection=True)

    def check(self, useSelection):
        self.w.results.show(False)
        self._resizeWindow(enlarge=False)
        self.checkFont(useSelection=useSelection, excludeZeroWidth=self.w.options.zeroCheck.get())

    def showPair(self, sender=None):
        try:
            index = sender.getSelection()[0]
            glyphs = [self.f[gName] for gName in self.touchingPairs[index]]
            self.w.preview.set(glyphs)
        except IndexError:
            pass

    def showAllPairs(self, sender=None):
        # open all resulting pairs in Space Center
        rawString = ""
        for g1, g2 in self.touchingPairs:
            rawString += "/%s/%s/space" % (g1, g2)
        s = OpenSpaceCenter(self.f)
        s.setRaw(rawString)

    def exportPairList(self, sender=None):
        # Save the list of found touching pairs in a text file which can be read by MetricsMachine as a pair list
        path = PutFile("Choose save location", "TouchingPairs.txt")
        if path is not None:
            reportString = "#KPL:P: TouchingPairs\n"
            for g1, g2 in self.touchingPairs:
                reportString += "%s %s\n" % (g1, g2)
            fi = open(path, "w+")
            fi.write(reportString)
            fi.close()

    # checking

    def _hasSufficientWidth(self, g):
        # to ignore combining accents and the like
        if self.excludeZeroWidth:
            # also skips 1-unit wide glyphs which many use instead of 0
            if g.width < 2:
                return False
        return True

    def _trimGlyphList(self, glyphList):
        newGlyphList = []
        for g in glyphList:
            if g.box is not None and self._hasSufficientWidth(g):
                newGlyphList.append(g)
        return newGlyphList

    def _resizeWindow(self, enlarge=True):
        posSize = self.w.getPosSize()
        targetWidth = 700 if enlarge else 180
        self.w.setPosSize((posSize[0], posSize[1], targetWidth, posSize[3]))

    # ok let's do this

    def checkFont(self, useSelection=False, excludeZeroWidth=True):
        f = CurrentFont()
        if f is not None:
            # initialize things
            self.w.options.progress.start()
            time0 = time.time()
            self.excludeZeroWidth = excludeZeroWidth
            self.f = f

            glyphNames = f.selection if useSelection else f.keys()
            glyphList = [f[x] for x in glyphNames]
            glyphList = self._trimGlyphList(glyphList)

            self.touchingPairs = Touche(f).findTouchingPairs(glyphList)

            # display output
            self.w.results.stats.set("%d glyphs checked" % len(glyphList))
            self.w.results.result.set("%d touching pairs found" % len(self.touchingPairs))
            self.w.results.show(True)

            outputList = [{"left glyph": g1, "right glyph": g2} for (g1, g2) in self.touchingPairs]
            self.w.outputList.set(outputList)
            if len(self.touchingPairs) > 0:
                self.w.outputList.setSelection([0])
            else:
                self.w.preview.set("")

            outputButtons = [self.w.results.spaceView, self.w.results.exportTxt]
            for b in outputButtons:
                b.enable(False) if len(self.touchingPairs) == 0 else b.enable(True)
            self.w.preview.setFont(f)
            self.w.options.progress.stop()
            self._resizeWindow(enlarge=True)

            time1 = time.time()
            print u"Touché: finished checking %d glyphs in %.2f seconds" % (len(glyphList), time1 - time0)

        else:
            Message(u"Touché: Can’t find a font to check")
Пример #2
0
class OCCToucheTool():
    def __init__(self):
        NSUserDefaults.standardUserDefaults().registerDefaults_(
            {"ToucheWindowHeight": 340})
        self.windowHeight = NSUserDefaults.standardUserDefaults(
        ).integerForKey_("ToucheWindowHeight")
        self.minWindowHeight = 340
        if self.windowHeight < self.minWindowHeight:
            self.windowHeight = self.minWindowHeight
        self.closedWindowHeight = 100
        self.w = Window((180, self.windowHeight),
                        u'2ché!',
                        minSize=(180, 340),
                        maxSize=(250, 898))
        self.w.bind("resize", self.windowResized)
        self.isResizing = False
        p = 10
        w = 160

        # options
        self.w.options = Group((0, 0, 180, 220))

        buttons = {
            "checkSelBtn": {
                "text": "Check selected glyphs",
                "callback": self.checkSel,
                "y": p
            },
        }
        for button, data in buttons.iteritems():
            setattr(
                self.w.options, button,
                Button((p, data["y"], w - 22, 22),
                       data["text"],
                       callback=data["callback"],
                       sizeStyle="small"))

        self.w.options.zeroCheck = CheckBox((p, 35, w, 20),
                                            "Ignore zero-width glyphs",
                                            value=True,
                                            sizeStyle="small")
        self.w.options.progress = ProgressSpinner((w - 8, 13, 16, 16),
                                                  sizeStyle="small")

        # Modification(Nic): prev / next buttons
        self.w.options.prevButton = Button((w + 22, 35, 10, 10),
                                           "Prev Button",
                                           callback=self.prevPair_)
        self.w.options.prevButton.bind("uparrow", [])

        self.w.options.nextButton = Button((w + 22, 35, 10, 10),
                                           "Next Button",
                                           callback=self.nextPair_)
        self.w.options.nextButton.bind("downarrow", [])

        # /Modification(Nic)

        # Modification(Nic): prev / next buttons

        self.w.options.decrementKerningHigh = Button(
            (w + 22, 35, 10, 10),
            "Decrement Kerning High",
            callback=self.decrementKerningByHigh_)
        self.w.options.decrementKerningHigh.bind("leftarrow", ["command"])

        self.w.options.decrementKerningLow = Button(
            (w + 22, 35, 10, 10),
            "Decrement Kerning Low",
            callback=self.decrementKerningByLow_)
        self.w.options.decrementKerningLow.bind("leftarrow", [])

        self.w.options.incrementKerningHigh = Button(
            (w + 22, 35, 10, 10),
            "Increment Kerning High",
            callback=self.incrementKerningByHigh_)
        self.w.options.incrementKerningHigh.bind("rightarrow", ["command"])

        self.w.options.incrementKerningLow = Button(
            (w + 22, 35, 10, 10),
            "Increment Kerning Low",
            callback=self.incrementKerningByLow_)
        self.w.options.incrementKerningLow.bind("rightarrow", [])

        # /Modification(Nic)

        # results
        self.w.results = Group((0, 220, 180, -0))
        self.w.results.show(False)

        textBoxes = {"stats": -34, "result": -18}
        for box, y in textBoxes.iteritems():
            setattr(self.w.results, box,
                    TextBox((p, y, w, 14), "", sizeStyle="small"))

        # list and preview
        self.w.outputList = List((0, 58, -0, -40), [{
            "left glyph": "",
            "right glyph": ""
        }],
                                 columnDescriptions=[{
                                     "title": "left glyph",
                                     "width": 90
                                 }, {
                                     "title": "right glyph"
                                 }],
                                 showColumnTitles=False,
                                 allowsMultipleSelection=False,
                                 enableDelete=False,
                                 selectionCallback=self.showPair)
        self.w.outputList._setColumnAutoresizing()
        self._resizeWindow(False)
        self.w.open()

    # callbacks

    # Modification(Nic): Incrementing and Decrementing the Pair Index in the list.

    def prevPair_(self, sender=None):
        currentIndices = self.w.outputList.getSelection()
        prevIndices = map(lambda i: max(i - 1, 0), currentIndices)
        if (len(self.w.outputList) > 0):
            self.w.outputList.setSelection(prevIndices)

    def nextPair_(self, sender=None):
        currentIndices = self.w.outputList.getSelection()
        nextIndices = map(lambda i: min(i + 1,
                                        len(self.w.outputList) - 1),
                          currentIndices)
        if (len(self.w.outputList) > 0):
            self.w.outputList.setSelection(nextIndices)

    # /Modification(Nic)

    # Modification(Nic): Incrementing and Decrementing the Pair Index in the list.

    def decrementKerningByLow_(self, sender=None):
        print('dec by low')
        currentIndices = self.w.outputList.getSelection()
        currentPair = self.w.outputList[currentIndices[0]]
        increment = Glyphs.intDefaults['GSKerningIncrementLow']
        self.bumpKerningForPair(currentPair['left glyph'],
                                currentPair['right glyph'], -increment)

    def decrementKerningByHigh_(self, sender=None):
        print('dec by high')
        currentIndices = self.w.outputList.getSelection()
        currentPair = self.w.outputList[currentIndices[0]]
        increment = Glyphs.intDefaults['GSKerningIncrementHigh']
        self.bumpKerningForPair(currentPair['left glyph'],
                                currentPair['right glyph'], -increment)

    def incrementKerningByLow_(self, sender=None):
        print('inc by low')
        currentIndices = self.w.outputList.getSelection()
        currentPair = self.w.outputList[currentIndices[0]]
        increment = Glyphs.intDefaults['GSKerningIncrementLow']
        self.bumpKerningForPair(currentPair['left glyph'],
                                currentPair['right glyph'], increment)

    def incrementKerningByHigh_(self, sender=None):
        print('inc by high')
        currentIndices = self.w.outputList.getSelection()
        currentPair = self.w.outputList[currentIndices[0]]
        increment = Glyphs.intDefaults['GSKerningIncrementHigh']
        self.bumpKerningForPair(currentPair['left glyph'],
                                currentPair['right glyph'], increment)

    # /Modification(Nic)

    # Modification(Nic): Routine to increment/decrement kerning properly
    def bumpKerningForPair(self, left, right, increment):
        leftGlyph, rightGlyph = Glyphs.font[left], Glyphs.font[right]
        k = Glyphs.font.kerningForPair(Glyphs.font.selectedFontMaster.id,
                                       leftGlyph.rightKerningKey,
                                       rightGlyph.leftKerningKey)
        if k > 10000: k = 0  # Glyphs uses MAXINT to signal no kerning.
        Glyphs.font.setKerningForPair(Glyphs.font.selectedFontMaster.id,
                                      leftGlyph.rightKerningKey,
                                      rightGlyph.leftKerningKey, k + increment)

    # Modification(Nic)

    def checkAll(self, sender=None):
        self.check(useSelection=False)

    def checkSel(self, sender=None):
        self.check(useSelection=True)

    def check(self, useSelection):
        self._resizeWindow(enlarge=False)
        self.checkFont(useSelection=useSelection,
                       excludeZeroWidth=self.w.options.zeroCheck.get())

    def showPair(self, sender=None):
        try:
            index = sender.getSelection()[0]
            glyphs = [self.f[gName] for gName in self.touchingPairs[index]]
            ActiveFont = self.f._font
            EditViewController = ActiveFont.currentTab
            if EditViewController is None:
                tabText = "/%s/%s" % (glyphs[0].name, glyphs[1].name)
                ActiveFont.newTab(tabText)
            else:
                textStorage = EditViewController.graphicView()
                if not hasattr(textStorage,
                               "replaceCharactersInRange_withString_"
                               ):  # compatibility with API change in 2.5
                    textStorage = EditViewController.graphicView().textStorage(
                    )
                LeftChar = ActiveFont.characterForGlyph_(glyphs[0]._object)
                RightChar = ActiveFont.characterForGlyph_(glyphs[1]._object)
                if LeftChar != 0 and RightChar != 0:
                    selection = textStorage.selectedRange()
                    if selection.length < 2:
                        selection.length = 2
                        if selection.location > 0:
                            selection.location -= 1

                    NewString = ""
                    if LeftChar < 0xffff and RightChar < 0xffff:
                        NewString = u"%s%s" % (unichr(LeftChar),
                                               unichr(RightChar))
                    else:
                        print "Upper plane codes are not supported yet"

                    textStorage.replaceCharactersInRange_withString_(
                        selection, NewString)
                    selection.length = 0
                    selection.location += 1
                    textStorage.setSelectedRange_(selection)
            #self.w.preview.set(glyphs)
        except IndexError:
            pass

    # checking

    def _hasSufficientWidth(self, g):
        # to ignore combining accents and the like
        if self.excludeZeroWidth:
            # also skips 1-unit wide glyphs which many use instead of 0
            if g.width < 2 or g._object.subCategory == "Nonspacing":
                return False
        return True

    def _trimGlyphList(self, glyphList):
        newGlyphList = []
        for g in glyphList:
            if g.box is not None and self._hasSufficientWidth(g):
                newGlyphList.append(g)
        return newGlyphList

    def windowResized(self, window):
        posSize = self.w.getPosSize()
        Height = posSize[3]
        if Height > self.closedWindowHeight and self.isResizing is False:
            print "set new Height", Height
            NSUserDefaults.standardUserDefaults().setInteger_forKey_(
                Height, "ToucheWindowHeight")
            self.windowHeight = Height

    def _resizeWindow(self, enlarge=True):
        posSize = self.w.getPosSize()
        if enlarge:
            self.w.results.show(True)
            self.w.outputList.show(True)
            targetHeight = self.windowHeight
            if targetHeight < 340:
                targetHeight = 340
        else:
            self.w.results.show(False)
            self.w.outputList.show(False)
            targetHeight = self.closedWindowHeight
        self.isResizing = True
        self.w.setPosSize((posSize[0], posSize[1], posSize[2], targetHeight))
        self.isResizing = False

    # ok let's do this

    def checkFont(self, useSelection=False, excludeZeroWidth=True):
        f = CurrentFont()
        if f is not None:
            # initialize things
            self.w.options.progress.start()
            time0 = time.time()
            self.excludeZeroWidth = excludeZeroWidth
            self.f = f

            glyphNames = f.selection if useSelection else f.keys()
            glyphList = [f[x] for x in glyphNames]
            glyphList = self._trimGlyphList(glyphList)

            self.touchingPairs = OCCTouche(f).findTouchingPairs(glyphList)

            # display output
            self.w.results.stats.set("%d glyphs checked" % len(glyphList))
            self.w.results.result.set("%d touching pairs found" %
                                      len(self.touchingPairs))
            self.w.results.show(True)

            outputList = [{
                "left glyph": g1,
                "right glyph": g2
            } for (g1, g2) in self.touchingPairs]
            self.w.outputList.set(outputList)
            if len(self.touchingPairs) > 0:
                self.w.outputList.setSelection([0])

            #self.w.preview.setFont(f)
            self.w.options.progress.stop()
            self._resizeWindow(enlarge=True)

            time1 = time.time()
            print u'2ché: finished checking %d glyphs in %.2f seconds' % (
                len(glyphList), time1 - time0)

        else:
            Message(u'2ché: Can’t find a font to check')
Пример #3
0
class KerningController(BaseWindowController):
    """this is the main controller of TT kerning editor, it handles different controllers and dialogues with font data"""

    # these attributes take good care of undo/redo stack
    archive = []
    recordIndex = 0

    displayedWord = ''
    displayedPairs = []
    activePair = None

    canvasScalingFactor = CANVAS_SCALING_FACTOR_INIT

    fontsOrder = None
    navCursor_X = 0  # related to pairs
    navCursor_Y = 0  # related to active fonts

    isPreviewOn = False
    areVerticalLettersDrawn = True
    areGroupsShown = True
    areCollisionsShown = False
    isKerningDisplayActive = True
    isSidebearingsActive = True
    isCorrectionActive = True
    isMetricsActive = True
    isColorsActive = True
    prevGraphicsValues = None

    isSymmetricalEditingOn = False
    isSwappedEditingOn = False
    isVerticalAlignedEditingOn = False

    autoSave = True
    autoSaveSpan = 5  # mins

    kerningLogger = None

    def __init__(self):
        super(KerningController, self).__init__()
        # init time for the autosave
        self.initTime = datetime.now()
        # init logging
        self._initLogger()
        # init fonts
        if AllFonts() == []:
            message(
                'No fonts, no party!',
                'Please, open some fonts before starting the mighty MultiFont Kerning Controller'
            )
            return None

        self.w = Window((0, 0, PLUGIN_WIDTH, PLUGIN_HEIGHT),
                        PLUGIN_TITLE,
                        minSize=(PLUGIN_WIDTH, PLUGIN_HEIGHT))
        self.w.bind('resize', self.mainWindowResize)

        # load opened fonts
        self.initFontsOrder()

        # exception window (will appear only if needed)
        self.exceptionWindow = ChooseExceptionWindow(
            ['group2glyph', 'glyph2group', 'glyph2glyph'],
            callback=self.exceptionWindowCallback)
        self.exceptionWindow.enable(False)

        # jump to line window (will appear only if invoked)
        self.jumpToLineWindow = JumpToLineWindow(
            callback=self.jumpToLineWindowCallback)
        self.jumpToLineWindow.enable(False)

        self.jumping_Y = MARGIN_VER
        self.jumping_X = MARGIN_HOR
        self.w.wordListController = WordListController(
            (self.jumping_X, self.jumping_Y, LEFT_COLUMN, 260),
            callback=self.wordListControllerCallback)
        self.displayedWord = self.w.wordListController.get()

        self.jumping_Y += self.w.wordListController.getPosSize()[3] + 4
        self.w.word_font_separationLine = HorizontalLine(
            (self.jumping_X, self.jumping_Y, LEFT_COLUMN,
             vanillaControlsSize['HorizontalLineThickness']))

        self.jumping_Y += MARGIN_VER
        fontsOrderControllerHeight = FONT_ROW_HEIGHT * len(
            self.fontsOrder) + MARGIN_HOR
        self.w.fontsOrderController = FontsOrderController(
            (self.jumping_X, self.jumping_Y, LEFT_COLUMN,
             fontsOrderControllerHeight),
            self.fontsOrder,
            callback=self.fontsOrderControllerCallback)
        self.jumping_Y += fontsOrderControllerHeight
        self.w.fonts_controller_separationLine = HorizontalLine(
            (self.jumping_X, self.jumping_Y, LEFT_COLUMN,
             vanillaControlsSize['HorizontalLineThickness']))

        self.jumping_Y += MARGIN_VER
        self.w.joystick = JoystickController(
            (self.jumping_X, self.jumping_Y, LEFT_COLUMN, 348),
            fontObj=self.fontsOrder[self.navCursor_Y],
            isSymmetricalEditingOn=self.isSymmetricalEditingOn,
            isSwappedEditingOn=self.isSwappedEditingOn,
            isVerticalAlignedEditingOn=self.isVerticalAlignedEditingOn,
            autoSave=self.autoSave,
            autoSaveSpan=self.autoSaveSpan,
            activePair=None,
            callback=self.joystickCallback)

        self.jumping_Y += self.w.joystick.getPosSize()[3] + MARGIN_VER
        self.w.graphicsManager = GraphicsManager(
            (self.jumping_X, -202, LEFT_COLUMN, 202),
            isKerningDisplayActive=self.isKerningDisplayActive,
            areVerticalLettersDrawn=self.areVerticalLettersDrawn,
            areGroupsShown=self.areGroupsShown,
            areCollisionsShown=self.areCollisionsShown,
            isSidebearingsActive=self.isSidebearingsActive,
            isCorrectionActive=self.isCorrectionActive,
            isMetricsActive=self.isMetricsActive,
            isColorsActive=self.isColorsActive,
            callback=self.graphicsManagerCallback)

        self.jumping_X += LEFT_COLUMN + MARGIN_COL * 2
        self.jumping_Y = MARGIN_VER

        self.w.displayedWordCaption = TextBox(
            (self.jumping_X, self.jumping_Y, 200,
             vanillaControlsSize['TextBoxRegularHeight']), self.displayedWord)

        self.w.scalingFactorController = FactorController(
            (-MARGIN_HOR - 72, self.jumping_Y, 72,
             vanillaControlsSize['TextBoxRegularHeight']),
            self.canvasScalingFactor,
            callback=self.scalingFactorControllerCallback)

        self.jumping_Y += self.w.displayedWordCaption.getPosSize(
        )[3] + MARGIN_COL
        self.initWordDisplays()
        self.w.joystick.setActivePair(
            self.getActiveWordDisplay().getActivePair())

        # observers!
        addObserver(self, 'openCloseFontCallback', "fontDidOpen")
        addObserver(self, 'openCloseFontCallback', "fontDidClose")
        self.setUpBaseWindowBehavior()
        self.w.open()

    def _initLogger(self):
        # create a logger
        self.kerningLogger = logging.getLogger('kerningLogger')
        self.kerningLogger.setLevel(logging.INFO)
        # create file handler which logs info messages
        fileHandle = logging.FileHandler('kerningLogger.log')
        fileHandle.setLevel(logging.INFO)
        # create console handler with a higher log level, only errors
        consoleHandler = logging.StreamHandler()
        consoleHandler.setLevel(logging.ERROR)
        # create formatter and add it to the handlers
        formatter = logging.Formatter(u'%(asctime)s – %(message)s')
        fileHandle.setFormatter(formatter)
        consoleHandler.setFormatter(formatter)
        # add the handlers to the logger
        self.kerningLogger.addHandler(fileHandle)
        self.kerningLogger.addHandler(consoleHandler)

    def windowCloseCallback(self, sender):
        removeObserver(self, "fontDidOpen")
        removeObserver(self, "fontWillClose")
        self.exceptionWindow.close()
        super(KerningController, self).windowCloseCallback(sender)
        self.w.close()

    def openCloseFontCallback(self, sender):
        self.deleteWordDisplays()
        self.initFontsOrder()
        self.w.fontsOrderController.setFontsOrder(self.fontsOrder)
        self.initWordDisplays()

        fontsOrderControllerHeight = FONT_ROW_HEIGHT * len(
            self.fontsOrder) + MARGIN_HOR
        prevFontsOrderPos = self.w.fontsOrderController.getPosSize()
        self.w.fontsOrderController.setPosSize(
            (prevFontsOrderPos[0], prevFontsOrderPos[1], prevFontsOrderPos[2],
             fontsOrderControllerHeight))

        prevSepLinePos = self.w.fonts_controller_separationLine.getPosSize()
        self.w.fonts_controller_separationLine.setPosSize(
            (prevSepLinePos[0],
             prevFontsOrderPos[1] + fontsOrderControllerHeight,
             prevSepLinePos[2], prevSepLinePos[3]))

        prevJoystickPos = self.w.joystick.getPosSize()
        self.w.joystick.setPosSize(
            (prevJoystickPos[0],
             prevFontsOrderPos[1] + fontsOrderControllerHeight + MARGIN_VER,
             prevJoystickPos[2], prevJoystickPos[3]))

    def initFontsOrder(self):
        if self.fontsOrder is None:
            fontsOrder = [f for f in AllFonts() if f.path is not None]
            self.fontsOrder = sorted(fontsOrder,
                                     key=lambda f: os.path.basename(f.path))
        else:
            newFontsOrder = [f for f in AllFonts() if f in self.fontsOrder] + [
                f for f in AllFonts() if f not in self.fontsOrder
            ]
            self.fontsOrder = newFontsOrder

        for eachFont in self.fontsOrder:
            status, report = checkGroupConflicts(eachFont)
            if status is False:
                self.kerningLogger.error('groups conflict in {}'.format(
                    eachFont.path))
                self.kerningLogger.error(report)

    def deleteWordDisplays(self):
        for eachI in xrange(len(self.fontsOrder)):
            try:
                delattr(self.w, 'wordCtrl_{:0>2d}'.format(eachI + 1))
                self.jumping_Y = MARGIN_VER + vanillaControlsSize[
                    'TextBoxRegularHeight']
            except Exception as e:
                self.kerningLogger.error(traceback.format_exc())

    def initWordDisplays(self):
        windowWidth, windowHeight = self.w.getPosSize()[2], self.w.getPosSize(
        )[3]
        netTotalWindowHeight = windowHeight - MARGIN_COL - MARGIN_VER * 2 - vanillaControlsSize[
            'TextBoxRegularHeight'] - MARGIN_HOR * (len(self.fontsOrder) - 1)

        try:
            singleWindowHeight = netTotalWindowHeight / len(self.fontsOrder)
        except ZeroDivisionError:
            singleWindowHeight = 0

        rightColumnWidth = windowWidth - LEFT_COLUMN - MARGIN_COL

        self.jumping_Y = MARGIN_VER + vanillaControlsSize[
            'TextBoxRegularHeight'] + MARGIN_COL
        for eachI in xrange(len(self.fontsOrder)):

            if eachI == self.navCursor_Y:
                initPairIndex = self.navCursor_X
            else:
                initPairIndex = None

            try:
                wordCtrl = WordDisplay(
                    (self.jumping_X, self.jumping_Y, rightColumnWidth,
                     singleWindowHeight),
                    displayedWord=self.displayedWord,
                    canvasScalingFactor=self.canvasScalingFactor,
                    fontObj=self.fontsOrder[eachI],
                    isKerningDisplayActive=self.isKerningDisplayActive,
                    areVerticalLettersDrawn=self.areVerticalLettersDrawn,
                    areGroupsShown=self.areGroupsShown,
                    areCollisionsShown=self.areCollisionsShown,
                    isSidebearingsActive=self.isSidebearingsActive,
                    isCorrectionActive=self.isCorrectionActive,
                    isMetricsActive=self.isMetricsActive,
                    isColorsActive=self.isColorsActive,
                    isSymmetricalEditingOn=self.isSymmetricalEditingOn,
                    isSwappedEditingOn=self.isSwappedEditingOn,
                    indexPair=initPairIndex)

            except Exception:
                self.kerningLogger.error(traceback.format_exc())

            self.jumping_Y += singleWindowHeight + MARGIN_HOR
            setattr(self.w, 'wordCtrl_{:0>2d}'.format(eachI + 1), wordCtrl)

    def updateWordDisplays(self):
        for eachI in xrange(len(self.fontsOrder)):
            eachDisplay = getattr(self.w, 'wordCtrl_{:0>2d}'.format(eachI + 1))
            eachDisplay.setSymmetricalEditingMode(self.isSymmetricalEditingOn)
            eachDisplay.setSwappedEditingMode(self.isSwappedEditingOn)
            eachDisplay.setScalingFactor(self.canvasScalingFactor)
            eachDisplay.setGraphicsBooleans(
                self.isKerningDisplayActive, self.areVerticalLettersDrawn,
                self.areGroupsShown, self.areCollisionsShown,
                self.isSidebearingsActive, self.isCorrectionActive,
                self.isMetricsActive, self.isColorsActive)
            eachDisplay.wordCanvasGroup.update()

    def nextWord(self, isRecording=True):
        self.w.wordListController.nextWord()
        self.displayedWord = self.w.wordListController.get()
        self.isSwappedEditingOn = False
        self.w.joystick.setSwappedEditing(self.isSwappedEditingOn)
        self.updateEditorAccordingToDiplayedWord()
        if isRecording is True:
            self.appendRecord('nextWord')

    def previousWord(self, isRecording=True):
        self.w.wordListController.previousWord()
        self.displayedWord = self.w.wordListController.get()
        self.isSwappedEditingOn = False
        self.w.joystick.setSwappedEditing(self.isSwappedEditingOn)
        self.updateEditorAccordingToDiplayedWord()
        if isRecording is True:
            self.appendRecord('previousWord')

    def jumpToLine(self, lineIndex, isRecording=True):
        if isRecording is True:
            self.appendRecord(
                'jumpToLine',
                (self.w.wordListController.getActiveIndex(), lineIndex))

        self.w.wordListController.jumpToLine(lineIndex)
        self.displayedWord = self.w.wordListController.get()
        self.isSwappedEditingOn = False
        self.w.joystick.setSwappedEditing(self.isSwappedEditingOn)
        self.updateEditorAccordingToDiplayedWord()

    def oneStepGroupSwitch(self, location):
        if self.isVerticalAlignedEditingOn is True:
            for eachI in xrange(len(self.fontsOrder)):
                eachDisplay = getattr(self.w,
                                      'wordCtrl_{:0>2d}'.format(eachI + 1))
                eachDisplay.switchGlyphFromGroup(location, self.navCursor_X)
        else:
            self.getActiveWordDisplay().switchGlyphFromGroup(
                location, self.navCursor_X)
        self.w.joystick.setActivePair(
            self.getActiveWordDisplay().getActivePair())

    def updateEditorAccordingToDiplayedWord(self):
        self.w.displayedWordCaption.set(self.displayedWord)
        if len(self.displayedWord) - 1 < (self.navCursor_X + 1):
            self.navCursor_X = len(self.displayedWord) - 2
        for eachI in xrange(len(self.fontsOrder)):
            eachDisplay = getattr(self.w, 'wordCtrl_{:0>2d}'.format(eachI + 1))

            if self.isVerticalAlignedEditingOn is False:
                if eachI == self.navCursor_Y:
                    eachDisplay.setActivePairIndex(self.navCursor_X)
            else:
                eachDisplay.setActivePairIndex(self.navCursor_X)

            eachDisplay.setDisplayedWord(self.displayedWord)
        self.updateWordDisplays()

        self.getActiveWordDisplay().setActivePairIndex(self.navCursor_X)
        self.w.joystick.setActivePair(
            self.getActiveWordDisplay().getActivePair())

    def getActiveWordDisplay(self):
        return getattr(self.w, 'wordCtrl_{:0>2d}'.format(self.navCursor_Y + 1))

    def setGraphicsManagerForPreviewMode(self):

        self.prevGraphicsValues = (self.isKerningDisplayActive,
                                   self.areVerticalLettersDrawn,
                                   self.areGroupsShown,
                                   self.areCollisionsShown,
                                   self.isSidebearingsActive,
                                   self.isCorrectionActive,
                                   self.isMetricsActive, self.isColorsActive)

        # set preview mode variables
        self.isKerningDisplayActive = True
        self.areVerticalLettersDrawn = False
        self.areGroupsShown = False
        self.areCollisionsShown = False
        self.isSidebearingsActive = False
        self.isCorrectionActive = False
        self.isMetricsActive = False
        self.isColorsActive = False
        self.w.graphicsManager.set(
            self.isKerningDisplayActive, self.areVerticalLettersDrawn,
            self.areGroupsShown, self.areCollisionsShown,
            self.isSidebearingsActive, self.isCorrectionActive,
            self.isMetricsActive, self.isColorsActive)

    def restoreGraphicsManagerValues(self):
        self.isKerningDisplayActive, self.areVerticalLettersDrawn, self.areGroupsShown, self.areCollisionsShown, self.isSidebearingsActive, self.isCorrectionActive, self.isMetricsActive, self.isColorsActive = self.prevGraphicsValues
        self.prevGraphicsValues = None
        self.w.graphicsManager.set(
            self.isKerningDisplayActive, self.areVerticalLettersDrawn,
            self.areGroupsShown, self.areCollisionsShown,
            self.isSidebearingsActive, self.isCorrectionActive,
            self.isMetricsActive, self.isColorsActive)

    def switchPreviewAttribute(self, isRecording=True):
        if self.isPreviewOn is True:
            self.isPreviewOn = False
            self.restoreGraphicsManagerValues()
            self.w.graphicsManager.switchControls(True)
        else:
            self.isPreviewOn = True
            self.setGraphicsManagerForPreviewMode()
            self.w.graphicsManager.switchControls(False)
        self.updateWordDisplays()

        self.updateWordDisplays()
        if isRecording is True:
            self.appendRecord('preview')

    def switchSolvedAttribute(self, isRecording=True):
        self.w.wordListController.switchActiveWordSolvedAttribute()
        if isRecording is True:
            self.appendRecord('solved')

    def switchSwappedEditing(self, isRecording=True):
        self.isSwappedEditingOn = not self.isSwappedEditingOn
        if self.isSwappedEditingOn is True and self.isSymmetricalEditingOn is True:
            self.isSymmetricalEditingOn = False
            self.w.joystick.setSymmetricalEditing(self.isSymmetricalEditingOn)
        self.updateWordDisplays()
        if isRecording is True:
            self.appendRecord('swappedEditing')

    def switchSymmetricalEditing(self, isRecording=True):
        self.isSymmetricalEditingOn = not self.isSymmetricalEditingOn
        if self.isSwappedEditingOn is True and self.isSymmetricalEditingOn is True:
            self.isSwappedEditingOn = False
            self.w.joystick.setSwappedEditing(self.isSwappedEditingOn)
        self.updateWordDisplays()
        if isRecording is True:
            self.appendRecord('symmetricalEditing')

    def switchVerticalAlignedEditing(self, isRecording=True):
        self.isVerticalAlignedEditingOn = not self.isVerticalAlignedEditingOn
        for eachI in xrange(len(self.fontsOrder)):
            eachDisplay = getattr(self.w, 'wordCtrl_{:0>2d}'.format(eachI + 1))
            if self.isVerticalAlignedEditingOn is True:
                eachDisplay.setActivePairIndex(self.navCursor_X)
            else:
                if eachI != self.navCursor_Y:
                    eachDisplay.setActivePairIndex(None)
        self.updateWordDisplays()
        if isRecording is True:
            self.appendRecord('verticalAlignedEditing')

    def exceptionTrigger(self):
        activeFont = self.fontsOrder[self.navCursor_Y]
        selectedPair = self.getActiveWordDisplay().getActivePair()
        correction, kerningReference, pairKind = getCorrection(
            selectedPair, activeFont)
        isException, doesExists, parentPair = isPairException(
            kerningReference, activeFont)

        # delete exception
        if isException:
            if self.isVerticalAlignedEditingOn is True:
                selectedFonts = self.fontsOrder
            else:
                selectedFonts = [self.fontsOrder[self.navCursor_Y]]

            for eachFont in selectedFonts:
                isException, doesExists, parentPair = isPairException(
                    kerningReference, eachFont)
                if isException:
                    deletePair(kerningReference, eachFont)
                    self.appendRecord('deletePair',
                                      (kerningReference, eachFont, correction))

        else:
            if not correction:
                # set standard pair to zero
                self.setPairCorrection(0)
                correction, kerningReference, pairKind = getCorrection(
                    selectedPair, activeFont)
                isException, doesExists, parentPair = isPairException(
                    kerningReference, activeFont)

            # trigger exception window
            exceptionOptions = possibleExceptions(selectedPair,
                                                  kerningReference, activeFont)
            if len(exceptionOptions) == 1:
                self.exceptionWindow.set(exceptionOptions[0])
                self.exceptionWindow.trigger()
            elif len(exceptionOptions) > 1:
                self.exceptionWindow.setOptions(exceptionOptions)
                self.exceptionWindow.enable(True)
            else:
                self.showMessage(
                    'no possible exceptions',
                    'kerning exceptions can be triggered only starting from class kerning corrections'
                )

        self.updateWordDisplays()

    # manipulate data
    def deletePair(self, isRecording=True):
        if self.autoSave is True:
            self.checkAutoSave()

        selectedPair = self.getActiveWordDisplay().getActivePair()
        if self.isVerticalAlignedEditingOn is True:
            selectedFonts = self.fontsOrder
        else:
            selectedFonts = [self.fontsOrder[self.navCursor_Y]]

        for eachFont in selectedFonts:
            previousAmount, kerningReference, pairKind = getCorrection(
                selectedPair, eachFont)
            deletePair(kerningReference, eachFont)
            if isRecording is True:
                self.appendRecord('deletePair',
                                  (kerningReference, eachFont, previousAmount))
            self.kerningLogger.info(
                DELETE_PAIR_LOG.format(leftGlyphName=selectedPair[0],
                                       rightGlyphName=selectedPair[1],
                                       familyName=eachFont.info.familyName,
                                       styleName=eachFont.info.styleName))

            if self.isSwappedEditingOn is True:
                swappedCorrectionKey = selectedPair[1], selectedPair[0]
                previousAmount, kerningReference, pairKind = getCorrection(
                    swappedCorrectionKey, eachFont)
                deletePair(kerningReference, eachFont)
                if isRecording is True:
                    self.appendRecord('deletePair', kerningReference, eachFont,
                                      previousAmount)
                self.kerningLogger.info(
                    DELETE_PAIR_LOG.format(leftGlyphName=kerningReference[0],
                                           rightGlyphName=kerningReference[1],
                                           familyName=eachFont.info.familyName,
                                           styleName=eachFont.info.styleName))

            if self.isSymmetricalEditingOn is True:
                symmetricalCorrectionKey = findSymmetricalPair(selectedPair)
                previousAmount, kerningReference, pairKind = getCorrection(
                    symmetricalCorrectionKey, eachFont)
                deletePair(kerningReference, eachFont)
                if isRecording is True:
                    self.appendRecord('deletePair', kerningReference, eachFont,
                                      previousAmount)
                self.kerningLogger.info(
                    DELETE_PAIR_LOG.format(leftGlyphName=kerningReference[0],
                                           rightGlyphName=kerningReference[1],
                                           familyName=eachFont.info.familyName,
                                           styleName=eachFont.info.styleName))

        self.updateWordDisplays()

    def setPairCorrection(self, amount, isRecording=True):
        if self.autoSave is True:
            self.checkAutoSave()

        selectedPair = self.getActiveWordDisplay().getActivePair()
        if self.isVerticalAlignedEditingOn is True:
            selectedFonts = self.fontsOrder
        else:
            selectedFonts = [self.fontsOrder[self.navCursor_Y]]

        for eachFont in selectedFonts:
            if isRecording is True:
                previousAmount = getCorrection(selectedPair, eachFont)[0]
                self.appendRecord('setCorrection',
                                  (selectedPair, eachFont, previousAmount))
            setCorrection(selectedPair, eachFont, amount)
            self.kerningLogger.info(
                SET_CORRECTION_LOG.format(leftGlyphName=selectedPair[0],
                                          rightGlyphName=selectedPair[1],
                                          familyName=eachFont.info.familyName,
                                          styleName=eachFont.info.styleName,
                                          amount=amount))

            if self.isSwappedEditingOn is True:
                swappedCorrectionKey = selectedPair[1], selectedPair[0]
                if isRecording is True:
                    previousAmount = getCorrection(swappedCorrectionKey,
                                                   eachFont)[0]
                    self.appendRecord(
                        'setCorrection',
                        (swappedCorrectionKey, eachFont, previousAmount))
                setCorrection(swappedCorrectionKey, eachFont, amount)
                self.kerningLogger.info(
                    SET_CORRECTION_LOG.format(
                        leftGlyphName=swappedCorrectionKey[0],
                        rightGlyphName=swappedCorrectionKey[1],
                        familyName=eachFont.info.familyName,
                        styleName=eachFont.info.styleName,
                        amount=amount))

            if self.isSymmetricalEditingOn is True:
                symmetricalCorrectionKey = findSymmetricalPair(selectedPair)
                if symmetricalCorrectionKey:
                    if isRecording is True:
                        previousAmount = getCorrection(
                            symmetricalCorrectionKey, eachFont)[0]
                        self.appendRecord('setCorrection',
                                          (symmetricalCorrectionKey, eachFont,
                                           previousAmount))
                    setCorrection(symmetricalCorrectionKey, eachFont, amount)
                    self.kerningLogger.info(
                        SET_CORRECTION_LOG.format(
                            leftGlyphName=symmetricalCorrectionKey[0],
                            rightGlyphName=symmetricalCorrectionKey[1],
                            familyName=eachFont.info.familyName,
                            styleName=eachFont.info.styleName,
                            amount=amount))

        self.updateWordDisplays()

    def modifyPairCorrection(self, amount, isRecording=True):
        if self.autoSave is True:
            self.checkAutoSave()

        selectedPair = self.getActiveWordDisplay().getActivePair()

        if self.isVerticalAlignedEditingOn is True:
            selectedFonts = self.fontsOrder
        else:
            selectedFonts = [self.fontsOrder[self.navCursor_Y]]

        for eachFont in selectedFonts:
            correction, correctionKey, pairKind = getCorrection(
                selectedPair, eachFont)
            correction = 0 if correction is None else correction
            if isRecording is True:
                previousAmount = getCorrection(selectedPair, eachFont)[0]
                self.appendRecord('setCorrection',
                                  (selectedPair, eachFont, previousAmount))
            setCorrection(selectedPair, eachFont, correction + amount)
            self.kerningLogger.info(
                SET_CORRECTION_LOG.format(leftGlyphName=selectedPair[0],
                                          rightGlyphName=selectedPair[1],
                                          familyName=eachFont.info.familyName,
                                          styleName=eachFont.info.styleName,
                                          amount=amount))

            if self.isSwappedEditingOn is True:
                swappedPair = selectedPair[1], selectedPair[0]
                if isRecording is True:
                    previousAmount = getCorrection(selectedPair, eachFont)[0]
                    self.appendRecord('setCorrection',
                                      (swappedPair, eachFont, previousAmount))
                setCorrection(swappedPair, eachFont, correction + amount)
                self.kerningLogger.info(
                    SET_CORRECTION_LOG.format(
                        leftGlyphName=swappedPair[0],
                        rightGlyphName=swappedPair[1],
                        familyName=eachFont.info.familyName,
                        styleName=eachFont.info.styleName,
                        amount=amount))

            if self.isSymmetricalEditingOn is True:
                symmetricalCorrectionKey = findSymmetricalPair(selectedPair)
                if symmetricalCorrectionKey:
                    if isRecording is True:
                        previousAmount = getCorrection(
                            symmetricalCorrectionKey, eachFont)[0]
                        self.appendRecord('setCorrection',
                                          (symmetricalCorrectionKey, eachFont,
                                           previousAmount))
                    setCorrection(symmetricalCorrectionKey, eachFont,
                                  correction + amount)
                    self.kerningLogger.info(
                        SET_CORRECTION_LOG.format(
                            leftGlyphName=symmetricalCorrectionKey[0],
                            rightGlyphName=symmetricalCorrectionKey[1],
                            familyName=eachFont.info.familyName,
                            styleName=eachFont.info.styleName,
                            amount=amount))

        self.w.joystick.updateCorrectionValue()
        self.updateWordDisplays()

    # cursor methods
    def cursorLeftRight(self, direction, isRecording=True):
        assert direction in ['left', 'right']

        if direction == 'left':
            step = -1
            if isRecording is True:
                self.appendRecord('cursorLeft')
        else:
            step = +1
            if isRecording is True:
                self.appendRecord('cursorRight')

        self.navCursor_X = (self.navCursor_X +
                            step) % (len(self.displayedWord) - 1)
        for eachI in xrange(len(self.fontsOrder)):
            eachDisplay = getattr(self.w, 'wordCtrl_{:0>2d}'.format(eachI + 1))
            if self.isVerticalAlignedEditingOn is False:
                if eachI == self.navCursor_Y:
                    eachDisplay.setActivePairIndex(self.navCursor_X)
            else:
                eachDisplay.setActivePairIndex(self.navCursor_X)
        self.w.joystick.setActivePair(
            self.getActiveWordDisplay().getActivePair())
        self.updateWordDisplays()

    def cursorUpDown(self, direction, isRecording=True):
        assert direction in ['up', 'down']
        if direction == 'up':
            step = -1
            if isRecording is True:
                self.appendRecord('cursorUp')
        else:
            step = +1
            if isRecording is True:
                self.appendRecord('cursorDown')

        if self.isVerticalAlignedEditingOn is False:
            self.getActiveWordDisplay().setActivePairIndex(None)  # old
            self.navCursor_Y = (self.navCursor_Y + step) % len(self.fontsOrder)
            self.getActiveWordDisplay().setActivePairIndex(
                self.navCursor_X)  # new
            self.w.joystick.setFontObj(self.fontsOrder[self.navCursor_Y])
            self.updateWordDisplays()

    ### callbacks
    def exceptionWindowCallback(self, sender):
        if self.isVerticalAlignedEditingOn is False:
            selectedFonts = [self.fontsOrder[self.navCursor_Y]]
        else:
            selectedFonts = self.fontsOrder
        selectedPair = self.getActiveWordDisplay().getActivePair()
        if sender.lastEvent == 'submit':
            for indexFont, eachFont in enumerate(selectedFonts):
                exceptionKey = sender.get()
                correction, kerningReference, pairKind = getCorrection(
                    selectedPair, eachFont)
                setRawCorrection(exceptionKey, eachFont, correction)
                self.appendRecord('createException',
                                  (exceptionKey, eachFont, correction))
                if indexFont == self.navCursor_Y:
                    self.w.joystick.updateCorrectionValue()
            self.updateWordDisplays()

    def jumpToLineWindowCallback(self, sender):
        if sender.get() is not None:
            self.jumpToLine(sender.get())
        self.jumpToLineWindow.enable(False)

    def mainWindowResize(self, mainWindow):
        windowWidth, windowHeight = mainWindow.getPosSize(
        )[2], mainWindow.getPosSize()[3]
        rightColumnWidth = windowWidth - LEFT_COLUMN

        # caption
        prevdisplayedWordCaptionSize = self.w.displayedWordCaption.getPosSize()
        self.w.displayedWordCaption.setPosSize(
            (prevdisplayedWordCaptionSize[0], prevdisplayedWordCaptionSize[1],
             rightColumnWidth, prevdisplayedWordCaptionSize[3]))

        # displayers
        initY = MARGIN_VER + vanillaControlsSize[
            'TextBoxRegularHeight'] + MARGIN_COL
        netTotalWindowHeight = windowHeight - initY - MARGIN_VER - MARGIN_HOR * (
            len(self.fontsOrder) - 1)

        try:
            singleWordDisplayHeight = netTotalWindowHeight / len(
                self.fontsOrder)
        except ZeroDivisionError:
            singleWordDisplayHeight = 0

        y = initY
        for eachI in xrange(len(self.fontsOrder)):
            eachDisplay = getattr(self.w, 'wordCtrl_{:0>2d}'.format(eachI + 1))
            eachDisplay.adjustSize(
                (self.jumping_X, y, rightColumnWidth, singleWordDisplayHeight))
            eachDisplay.setCtrlSize(rightColumnWidth, singleWordDisplayHeight)
            y += singleWordDisplayHeight + MARGIN_HOR

    def scalingFactorControllerCallback(self, sender):
        self.canvasScalingFactor = sender.getScalingFactor()
        self.updateWordDisplays()

    def wordListControllerCallback(self, sender):
        self.displayedWord = sender.get()
        self.updateEditorAccordingToDiplayedWord()

    def fontsOrderControllerCallback(self, sender):
        self.deleteWordDisplays()
        self.fontsOrder = []
        for indexFont, eachFont in enumerate(sender.getFontsOrder()):
            if sender.getIsDisplayedOrder()[indexFont] is True:
                self.fontsOrder.append(eachFont)
        self.initWordDisplays()

    def graphicsManagerCallback(self, sender):
        self.isKerningDisplayActive, self.areVerticalLettersDrawn, self.areGroupsShown, self.areCollisionsShown, self.isSidebearingsActive, self.isCorrectionActive, self.isMetricsActive, self.isColorsActive = sender.get(
        )
        self.updateWordDisplays()

    def joystickCallback(self, sender):
        joystickEvent = sender.getLastEvent()
        assert joystickEvent in JOYSTICK_EVENTS

        if joystickEvent == 'minusMajor':
            if self.isKerningDisplayActive is True:
                self.modifyPairCorrection(-MAJOR_STEP)
            else:
                self.showMessage('Be aware!',
                                 KERNING_NOT_DISPLAYED_ERROR,
                                 callback=None)

        elif joystickEvent == 'minusMinor':
            if self.isKerningDisplayActive is True:
                self.modifyPairCorrection(-MINOR_STEP)
            else:
                self.showMessage('Be aware!',
                                 KERNING_NOT_DISPLAYED_ERROR,
                                 callback=None)

        elif joystickEvent == 'plusMinor':
            if self.isKerningDisplayActive is True:
                self.modifyPairCorrection(MINOR_STEP)
            else:
                self.showMessage('Be aware!',
                                 KERNING_NOT_DISPLAYED_ERROR,
                                 callback=None)

        elif joystickEvent == 'plusMajor':
            if self.isKerningDisplayActive is True:
                self.modifyPairCorrection(MAJOR_STEP)
            else:
                self.showMessage('Be aware!',
                                 KERNING_NOT_DISPLAYED_ERROR,
                                 callback=None)

        elif joystickEvent == 'preview':
            self.switchPreviewAttribute()

        elif joystickEvent == 'solved':
            self.switchSolvedAttribute()
            self.nextWord()

        elif joystickEvent == 'symmetricalEditing':
            self.switchSymmetricalEditing()

        elif joystickEvent == 'swappedEditing':
            self.switchSwappedEditing()

        elif joystickEvent == 'verticalAlignedEditing':
            self.switchVerticalAlignedEditing()

        elif joystickEvent == 'previousWord':
            self.previousWord()

        elif joystickEvent == 'cursorUp':
            self.cursorUpDown('up')

        elif joystickEvent == 'cursorLeft':
            self.cursorLeftRight('left')

        elif joystickEvent == 'cursorRight':
            self.cursorLeftRight('right')

        elif joystickEvent == 'cursorDown':
            self.cursorUpDown('down')

        elif joystickEvent == 'nextWord':
            self.nextWord()

        elif joystickEvent == 'deletePair':
            if self.isKerningDisplayActive is True:
                self.deletePair()
                self.w.joystick.setActivePair(
                    self.getActiveWordDisplay().getActivePair())

        elif joystickEvent == 'switchLftGlyph':
            self.oneStepGroupSwitch(location='left')

        elif joystickEvent == 'switchRgtGlyph':
            self.oneStepGroupSwitch(location='right')

        elif joystickEvent == 'keyboardEdit':
            if self.isKerningDisplayActive is True:
                correctionAmount = self.w.joystick.getKeyboardCorrection()
                if correctionAmount is None:
                    self.deletePair()
                else:
                    self.setPairCorrection(correctionAmount)
                self.updateWordDisplays()
            else:
                self.showMessage('Be aware!',
                                 KERNING_NOT_DISPLAYED_ERROR,
                                 callback=None)
                self.w.joystick.updateCorrectionValue()

        elif joystickEvent == 'jumpToLineTrigger':
            self.jumpToLineWindow.enable(True)

        elif joystickEvent == 'exceptionTrigger':
            self.exceptionTrigger()

        # from here on events are not archived in undo/redo stack
        elif joystickEvent == 'undo':
            self.undo()

        elif joystickEvent == 'redo':
            self.redo()

        elif joystickEvent == 'autoSave':
            self.autoSave, self.autoSaveSpan = self.w.joystick.getAutoSaveState(
            )

    # autosaving fonts
    def checkAutoSave(self):
        justNow = datetime.now()
        if (justNow - self.initTime).seconds > self.autoSaveSpan * 60:
            self.saveFontsOrder()
            self.initTime = datetime.now()

    def saveFontsOrder(self):
        for eachFont in self.fontsOrder:
            eachFont.save()
        self.kerningLogger.info("all fonts saved")

    # undo/redo stack
    def appendRecord(self, actionName, data=None):
        if self.recordIndex < 0:
            self.archive = self.archive[:self.recordIndex]
            self.recordIndex = 0
        if data is None:
            self.archive.append(actionName)
        else:
            self.archive.append((actionName, data))

    def undo(self):
        if abs(self.recordIndex) <= len(self.archive) - 1:
            self.recordIndex -= 1
            self.pullRecordFromArchive('undo')

    def redo(self):
        if self.recordIndex < 0:
            self.recordIndex += 1
            self.pullRecordFromArchive('redo')

    def pullRecordFromArchive(self, direction):
        """we miss these methods: switchLftGlyph, switchRgtGlyph"""

        assert direction in ['redo', 'undo']
        if direction == 'redo':
            record = self.archive[
                self.recordIndex -
                1]  # othwerwise we won't get back to the initial status
        else:
            record = self.archive[self.recordIndex]

        # these records, we can simply invert (they related to events in UI)
        if isinstance(record, types.StringType) is True:
            if record == 'nextWord':
                if direction == 'undo':
                    self.previousWord(isRecording=False)
                else:
                    self.nextWord(isRecording=False)
            elif record == 'previousWord':
                if direction == 'undo':
                    self.nextWord(isRecording=False)
                else:
                    self.previousWord(isRecording=False)
            elif record == 'preview':
                self.switchPreviewAttribute(isRecording=False)
            elif record == 'solved':
                self.switchSolvedAttribute(isRecording=False)
            elif record == 'swappedEditing':
                self.switchSwappedEditing(isRecording=False)
            elif record == 'symmetricalEditing':
                self.switchSymmetricalEditing(isRecording=False)
            elif record == 'verticalAlignedEditing':
                self.switchVerticalAlignedEditing(isRecording=False)

            elif record == 'cursorLeft':
                if direction == 'undo':
                    self.cursorLeftRight('right', isRecording=False)
                else:
                    self.cursorLeftRight('left', isRecording=False)

            elif record == 'cursorRight':
                if direction == 'undo':
                    self.cursorLeftRight('left', isRecording=False)
                else:
                    self.cursorLeftRight('right', isRecording=False)

            elif record == 'cursorUp':
                if direction == 'undo':
                    self.cursorUpDown('down', isRecording=False)
                else:
                    self.cursorUpDown('up', isRecording=False)

            elif record == 'cursorDown':
                if direction == 'undo':
                    self.cursorUpDown('up', isRecording=False)
                else:
                    self.cursorUpDown('down', isRecording=False)

        # these relate to data manipulation...
        else:
            recordTitle, data = record

            if recordTitle == 'setCorrection':
                pair, font, amount = data
                setCorrection(pair, font, amount)
                self.updateWordDisplays()

            elif recordTitle == 'createException':
                pair, font, amount = data
                if direction == 'undo':
                    deletePair(pair, font)
                else:
                    setRawCorrection(pair, font, amount)

            elif recordTitle == 'deletePair':
                pair, font, amount = data
                if direction == 'undo':
                    setRawCorrection(pair, font, amount)
                else:
                    deletePair(pair, font)

            elif recordTitle == 'jumpToLine':
                previousIndex, nextIndex = data
                if direction == 'undo':
                    self.jumpToLine(previousIndex)
                else:
                    self.jumpToLine(nextIndex, isRecording=False)
Пример #4
0
class ToucheTool():
    def __init__(self):
        self.w = Window((180, 340),
                        u'Touché!',
                        minSize=(180, 340),
                        maxSize=(1000, 898))
        p = 10
        w = 160

        # options
        self.w.options = Group((0, 0, 180, 220))

        buttons = {
            "checkSelBtn": {
                "text": "Check selected glyphs\nfor touching pairs",
                "callback": self.checkSel,
                "y": p
            },
            "checkAllBtn": {
                "text": "Check entire font\n(can take several minutes!)",
                "callback": self.checkAll,
                "y": 60
            }
        }
        for button, data in buttons.items():
            setattr(
                self.w.options, button,
                SquareButton((p, data["y"], w, 40),
                             data["text"],
                             callback=data["callback"],
                             sizeStyle="small"))

        self.w.options.zeroCheck = CheckBox((p, 108, w, 20),
                                            "Ignore zero-width glyphs",
                                            value=True,
                                            sizeStyle="small")
        self.w.options.progress = ProgressSpinner((82, 174, 16, 16),
                                                  sizeStyle="small")

        # results
        self.w.results = Group((0, 220, 180, -0))
        self.w.results.show(False)

        textBoxes = {"stats": 24, "result": 42}
        for box, y in textBoxes.items():
            setattr(self.w.results, box,
                    TextBox((p, y, w, 14), "", sizeStyle="small"))

        moreButtons = {
            "spaceView": {
                "text": "View all in Space Center",
                "callback": self.showAllPairs,
                "y": 65
            },
            "exportTxt": {
                "text": "Export as MM pair list",
                "callback": self.exportPairList,
                "y": 90
            }
        }
        for button, data in moreButtons.items():
            setattr(
                self.w.results, button,
                SquareButton((p, data["y"], w, 20),
                             data["text"],
                             callback=data["callback"],
                             sizeStyle="small"))

        # list and preview
        self.w.outputList = List((180, 0, 188, -0), [{
            "left glyph": "",
            "right glyph": ""
        }],
                                 columnDescriptions=[{
                                     "title": "left glyph"
                                 }, {
                                     "title": "right glyph"
                                 }],
                                 showColumnTitles=False,
                                 allowsMultipleSelection=False,
                                 enableDelete=False,
                                 selectionCallback=self.showPair)
        self.w.preview = MultiLineView((368, 0, -0, -0), pointSize=256)

        self.w.open()

    # callbacks

    def checkAll(self, sender=None):
        self.check(useSelection=False)

    def checkSel(self, sender=None):
        self.check(useSelection=True)

    def check(self, useSelection):
        self.w.results.show(False)
        self._resizeWindow(enlarge=False)
        self.checkFont(useSelection=useSelection,
                       excludeZeroWidth=self.w.options.zeroCheck.get())

    def showPair(self, sender=None):
        try:
            index = sender.getSelection()[0]
            glyphs = [self.f[gName] for gName in self.touchingPairs[index]]
            self.w.preview.set(glyphs)
        except IndexError:
            pass

    def showAllPairs(self, sender=None):
        # open all resulting pairs in Space Center
        rawString = ""
        for g1, g2 in self.touchingPairs:
            rawString += "/%s/%s/space" % (g1, g2)
        s = OpenSpaceCenter(self.f)
        s.setRaw(rawString)

    def exportPairList(self, sender=None):
        # Save the list of found touching pairs in a text file which can be read by MetricsMachine as a pair list
        path = PutFile(message="Choose save location",
                       fileName="TouchingPairs.txt")
        if path is not None:
            reportString = "#KPL:P: TouchingPairs\n"
            for g1, g2 in self.touchingPairs:
                reportString += "%s %s\n" % (g1, g2)
            with open(path, 'w+') as fi:
                try:
                    fi.write(reportString)
                except:  #py2!
                    fi.write(unicode(reportString))

    # checking

    def _hasSufficientWidth(self, g):
        # to ignore combining accents and the like
        if self.excludeZeroWidth:
            # also skips 1-unit wide glyphs which many use instead of 0
            if g.width < 2:
                return False
        return True

    def _trimGlyphList(self, glyphList):
        newGlyphList = []
        for g in glyphList:
            bounds = g.bounds if version > '2.0' else g.box
            if bounds is not None and self._hasSufficientWidth(g):
                newGlyphList.append(g)
        return newGlyphList

    def _resizeWindow(self, enlarge=True):
        posSize = self.w.getPosSize()
        targetWidth = 700 if enlarge else 180
        self.w.setPosSize((posSize[0], posSize[1], targetWidth, posSize[3]))

    # ok let's do this

    def checkFont(self, useSelection=False, excludeZeroWidth=True):
        f = CurrentFont()
        if f is not None:
            # initialize things
            self.w.options.progress.start()
            time0 = time.time()
            self.excludeZeroWidth = excludeZeroWidth
            self.f = f

            glyphNames = f.selection if useSelection else f.keys()
            glyphList = [f[x] for x in glyphNames]
            glyphList = self._trimGlyphList(glyphList)

            self.touchingPairs = Touche(f).findTouchingPairs(glyphList)

            # display output
            self.w.results.stats.set("%d glyphs checked" % len(glyphList))
            self.w.results.result.set("%d touching pairs found" %
                                      len(self.touchingPairs))
            self.w.results.show(True)

            outputList = [{
                "left glyph": g1,
                "right glyph": g2
            } for (g1, g2) in self.touchingPairs]
            self.w.outputList.set(outputList)
            if len(self.touchingPairs) > 0:
                self.w.outputList.setSelection([0])
            else:
                self.w.preview.set("")

            outputButtons = [
                self.w.results.spaceView, self.w.results.exportTxt
            ]
            for b in outputButtons:
                b.enable(False) if len(
                    self.touchingPairs) == 0 else b.enable(True)
            self.w.preview.setFont(f)
            self.w.preview.setApplyKerning(True)
            self.w.options.progress.stop()
            self._resizeWindow(enlarge=True)

            time1 = time.time()
            print(u'Touché: finished checking %d glyphs in %.2f seconds' %
                  (len(glyphList), time1 - time0))

        else:
            Message(u'Touché: Can’t find a font to check')
Пример #5
0
class MultiFontMetricsWindow(BaseWindowController):

    # attrs
    textModeOptions = ['Loaded Strings', 'Typewriter']
    textMode = textModeOptions[0]

    selectedGlyph = None
    subscribedGlyph = None

    fontsDB = None
    unicodeMinimum = {}

    fontsOrder = None
    glyphNamesToDisplay = []

    showMetrics = False
    applyKerning = False
    leftToRight = True
    verticalMode = False

    multiLineOptions = {}

    bodySizeOptions = [
        10, 11, 12, 13, 18, 24, 30, 36, 48, 60, 72, 144, 216, 360
    ]
    lineHeightOptions = range(0, 301, 10)

    bodySize = bodySizeOptions[10]
    lineHeight = lineHeightOptions[0]

    fontsAmountOptions = range(1, 9)

    def __init__(self):
        super(MultiFontMetricsWindow, self).__init__()

        # ui vars
        originLeft = 0
        originRight = 0
        width = 956
        height = 640

        netWidth = width - MARGIN_LFT - MARGIN_RGT
        jumpingY = MARGIN_TOP

        # let's see if there are opened fonts (fontDB, ctrlFontsList, fontsOrder)
        self.loadFontsOrder()
        self.updateUnicodeMinimum()

        # defining plugin windows
        self.w = Window((originLeft, originRight, width, height),
                        "Multi Font Metrics Window",
                        minSize=(800, 400))
        self.w.bind('resize', self.mainWindowResize)

        switchButtonWdt = 140
        self.w.switchButton = PopUpButton(
            (MARGIN_LFT, jumpingY, switchButtonWdt,
             vanillaControlsSize['PopUpButtonRegularHeight']),
            self.textModeOptions,
            sizeStyle='regular',
            callback=self.switchButtonCallback)

        # free text
        textCtrlX = MARGIN_LFT + MARGIN_COL + switchButtonWdt
        self.w.typewriterCtrl = Typewriter(
            (textCtrlX, jumpingY, -(RIGHT_COLUMN + MARGIN_COL + MARGIN_RGT),
             vanillaControlsSize['EditTextRegularHeight']),
            self.unicodeMinimum,
            callback=self.typewriterCtrlCallback)
        self.w.typewriterCtrl.show(False)

        # strings ctrls
        self.w.textStringsControls = TextStringsControls(
            (textCtrlX, jumpingY, -(RIGHT_COLUMN + MARGIN_COL + MARGIN_RGT),
             vanillaControlsSize['PopUpButtonRegularHeight'] + 1),
            self.unicodeMinimum,
            callback=self.textStringsControlsCallback)
        self.stringDisplayMode, self.glyphNamesToDisplay = self.w.textStringsControls.get(
        )

        # multi line
        jumpingY += vanillaControlsSize['ButtonRegularHeight'] + MARGIN_ROW

        self.calcSpacingMatrixHeight()
        self.multiLineOptions = {
            'Show Kerning': self.applyKerning,
            'Center': False,
            'Show Space Matrix': False,
            'Single Line': False,
            'displayMode': u'Multi Line',
            'Stroke': False,
            'xHeight Cut': False,
            'Upside Down': False,
            'Beam': False,
            'Water Fall': False,
            'Inverse': False,
            'Show Template Glyphs': True,
            'Multi Line': True,
            'Right to Left': False,
            'Show Metrics': self.showMetrics,
            'Show Control glyphs': True,
            'Fill': True,
            'Left to Right': self.leftToRight
        }
        self.w.lineView = MultiLineView(
            (MARGIN_LFT, jumpingY, -(RIGHT_COLUMN + MARGIN_COL + MARGIN_RGT),
             -MARGIN_BTM - MARGIN_HALFROW - self.spacingMatrixHeight),
            pointSize=self.bodySize,
            lineHeight=self.lineHeight,
            doubleClickCallback=self.lineViewDoubleClickCallback,
            selectionCallback=self.lineViewSelectionCallback,
            bordered=True,
            applyKerning=self.applyKerning,
            hasHorizontalScroller=False,
            hasVerticalScroller=True,
            displayOptions=self.multiLineOptions,
            updateUserDefaults=False,
            menuForEventCallback=None)

        # static options
        # body
        self.w.bodyCtrl = ComboBoxWithCaption(
            (-(RIGHT_COLUMN + MARGIN_RGT), jumpingY, RIGHT_COLUMN,
             vanillaControlsSize['ComboBoxSmallHeight'] + 1),
            'Body Size:',
            self.bodySizeOptions,
            '{}'.format(self.bodySize),
            sizeStyle='small',
            callback=self.bodyCtrlCallback)

        # line height
        jumpingY += vanillaControlsSize['ComboBoxSmallHeight'] + int(
            MARGIN_HALFROW)
        self.w.lineHgtCtrl = ComboBoxWithCaption(
            (-(RIGHT_COLUMN + MARGIN_RGT), jumpingY, RIGHT_COLUMN,
             vanillaControlsSize['ComboBoxSmallHeight'] + 1),
            'Line Height:',
            self.lineHeightOptions,
            '{}'.format(self.lineHeight),
            sizeStyle='small',
            callback=self.lineHgtCtrlCallback)

        # show metrics
        jumpingY += vanillaControlsSize['ComboBoxSmallHeight'] + MARGIN_ROW
        self.w.showMetricsCheck = CheckBox(
            (-(RIGHT_COLUMN + MARGIN_RGT), jumpingY, RIGHT_COLUMN,
             vanillaControlsSize['CheckBoxSmallHeight']),
            "Show Metrics",
            value=self.showMetrics,
            sizeStyle='small',
            callback=self.showMetricsCheckCallback)

        # show kerning checkbox
        jumpingY += vanillaControlsSize['CheckBoxSmallHeight'] + MARGIN_ROW * .3
        self.w.applyKerningCheck = CheckBox(
            (-(RIGHT_COLUMN + MARGIN_RGT), jumpingY, RIGHT_COLUMN,
             vanillaControlsSize['CheckBoxSmallHeight']),
            "Show Kerning",
            value=self.applyKerning,
            sizeStyle='small',
            callback=self.applyKerningCheckCallback)

        jumpingY += vanillaControlsSize['CheckBoxSmallHeight'] + MARGIN_ROW * .3
        self.w.leftToRightCheck = CheckBox(
            (-(RIGHT_COLUMN + MARGIN_RGT), jumpingY, RIGHT_COLUMN,
             vanillaControlsSize['CheckBoxSmallHeight']),
            "Left to right",
            value=self.leftToRight,
            sizeStyle='small',
            callback=self.leftToRightCheckCallback)

        # separationLine
        jumpingY += vanillaControlsSize['CheckBoxSmallHeight'] + int(
            MARGIN_HALFROW)
        self.w.separationLineOne = HorizontalLine(
            (-(RIGHT_COLUMN + MARGIN_RGT), jumpingY, RIGHT_COLUMN, 1))

        jumpingY += int(MARGIN_HALFROW)
        fontsOrderControllerHeight = FONT_ROW_HEIGHT * len(self.fontsOrder) + 2
        self.w.fontsOrderController = FontsOrderController(
            (-(RIGHT_COLUMN + MARGIN_RGT), jumpingY, RIGHT_COLUMN,
             fontsOrderControllerHeight),
            self.fontsOrder,
            callback=self.fontsOrderControllerCallback)

        jumpingY += fontsOrderControllerHeight
        self.w.separationLineTwo = HorizontalLine(
            (-(RIGHT_COLUMN + MARGIN_RGT), jumpingY, RIGHT_COLUMN, 1))

        # joystick
        jumpingY += MARGIN_HALFROW
        self.w.joystick = SpacingJoystick(
            (-(RIGHT_COLUMN + MARGIN_RGT), jumpingY, RIGHT_COLUMN, 0),
            verticalMode=self.verticalMode,
            marginCallback=self.joystickMarginCallback,
            verticalCallback=self.joystickVerticalCallback)

        # edit metrics
        self.w.spacingMatrix = SpacingMatrix(
            (MARGIN_LFT, -(MARGIN_BTM + self.spacingMatrixHeight),
             self.w.getPosSize()[2] - MARGIN_LFT - MARGIN_RGT,
             self.spacingMatrixHeight),
            glyphNamesToDisplay=self.glyphNamesToDisplay,
            verticalMode=self.verticalMode,
            fontsOrder=self.fontsOrder,
            callback=self.spacingMatrixCallback)

        # add observer
        addObserver(self, 'newFontOpened', "newFontDidOpen")
        addObserver(self, 'openCloseFontCallback', "fontDidOpen")
        addObserver(self, 'openCloseFontCallback', "fontDidClose")

        # lit up!
        self.updateSubscriptions()
        self.updateLineView()
        self.setUpBaseWindowBehavior()
        self.w.open()

    # defcon observers (glyph obj)
    def updateSubscriptions(self):
        self.unsubscribeGlyphs()

        # subscribe
        self.subscribedGlyph = []
        for eachFont in self.fontsOrder:
            for eachGlyphName in self.glyphNamesToDisplay:
                if eachFont.has_key(eachGlyphName):
                    eachGlyph = eachFont[eachGlyphName]
                    if eachGlyph not in self.subscribedGlyph:  # you can't subscribe a glyph twice
                        eachGlyph.addObserver(self, "glyphChangedCallback",
                                              "Glyph.Changed")
                        self.subscribedGlyph.append(eachGlyph)

    def unsubscribeGlyphs(self):
        if self.subscribedGlyph:
            for eachGlyph in self.subscribedGlyph:
                eachGlyph.removeObserver(self, "Glyph.Changed")

    def glyphChangedCallback(self, notification):
        self.w.lineView.update()

    # observers funcs
    def mainWindowResize(self, mainWindow):
        windowWidth = mainWindow.getPosSize()[2]
        self.w.spacingMatrix.adjustSize(
            (windowWidth - MARGIN_LFT - MARGIN_RGT, self.spacingMatrixHeight))

    def openCloseFontCallback(self, sender):
        self.loadFontsOrder()
        self.w.fontsOrderController.setFontsOrder(self.fontsOrder)
        self.w.spacingMatrix.setFontsOrder(self.fontsOrder)
        self.updateUnicodeMinimum()
        self.updateSubscriptions()
        self.adjustSpacingMatrixHeight()
        self.w.spacingMatrix.update()
        self.adjustFontsOrderControllerHeight()
        self.updateLineView()

    def loadFontsOrder(self):
        if self.fontsOrder is None:
            fontsOrder = [f for f in AllFonts() if f.path is not None]
            self.fontsOrder = sorted(fontsOrder,
                                     key=lambda f: os.path.basename(f.path))
        else:
            newFontsOrder = [f for f in AllFonts() if f in self.fontsOrder] + [
                f for f in AllFonts() if f not in self.fontsOrder
            ]
            self.fontsOrder = newFontsOrder

    def newFontOpened(self, notification):
        message(
            'The MultiFont Metrics Window works only with saved font, please save the new font and re-open the plugin'
        )

    def calcSpacingMatrixHeight(self):
        self.spacingMatrixHeight = vanillaControlsSize[
            'EditTextSmallHeight'] + len(
                self.fontsOrder
            ) * vanillaControlsSize['EditTextSmallHeight'] * 2

    def spacingMatrixCallback(self, sender):
        self.w.lineView.update()

    # other funcs
    def adjustSpacingMatrixHeight(self):
        self.calcSpacingMatrixHeight()
        lineViewPosSize = self.w.lineView.getPosSize()
        self.w.lineView.setPosSize(
            (lineViewPosSize[0], lineViewPosSize[1], lineViewPosSize[2],
             -MARGIN_BTM - MARGIN_HALFROW - self.spacingMatrixHeight))
        spacingMatrixPosSize = self.w.spacingMatrix.getPosSize()
        self.w.spacingMatrix.setPosSize(
            (spacingMatrixPosSize[0], -MARGIN_BTM - self.spacingMatrixHeight,
             spacingMatrixPosSize[2], self.spacingMatrixHeight))

    def adjustFontsOrderControllerHeight(self):
        fontsOrderControllerHeight = FONT_ROW_HEIGHT * len(
            self.fontsOrder) + MARGIN_COL
        fontsOrderControllerPosSize = self.w.fontsOrderController.getPosSize()
        self.w.fontsOrderController.setPosSize(
            (fontsOrderControllerPosSize[0], fontsOrderControllerPosSize[1],
             fontsOrderControllerPosSize[2], fontsOrderControllerHeight))

    def windowCloseCallback(self, sender):
        self.unsubscribeGlyphs()
        removeObserver(self, "newFontDidOpen")
        removeObserver(self, "fontDidOpen")
        removeObserver(self, "fontDidClose")
        super(MultiFontMetricsWindow, self).windowCloseCallback(sender)

    def updateUnicodeMinimum(self):
        # collect everything
        allUnicodeData = {}
        for eachFont in self.fontsOrder:
            for eachKey, eachValue in eachFont.naked().unicodeData.items():
                if eachKey not in allUnicodeData:
                    allUnicodeData[eachKey] = eachValue

        # filter
        self.unicodeMinimum = {}
        for eachKey, eachValue in allUnicodeData.items():
            for eachFont in self.fontsOrder:
                if eachKey not in eachFont.naked().unicodeData:
                    break
            if eachValue:
                self.unicodeMinimum[eachKey] = eachValue

    def updateLineView(self):
        try:
            displayedGlyphs = []
            if self.stringDisplayMode == 'Waterfall':
                for indexFont, eachFont in enumerate(self.fontsOrder):
                    assert isinstance(
                        eachFont,
                        RFont), 'object in self.fontsOrder not a RFont'
                    if indexFont != 0:
                        newLineGlyph = self.w.lineView.createNewLineGlyph()
                        displayedGlyphs.append(newLineGlyph)

                    for eachGlyphName in self.glyphNamesToDisplay:
                        if eachFont.has_key(eachGlyphName):
                            displayedGlyphs.append(eachFont[eachGlyphName])
                        elif eachGlyphName == '.newLine':
                            newLineGlyph = self.w.lineView.createNewLineGlyph()
                            displayedGlyphs.append(newLineGlyph)
                        else:
                            if eachFont.has_key('.notdef'):
                                displayedGlyphs.append(eachFont['.notdef'])
                            else:
                                displayedGlyphs.append(NOT_DEF_GLYPH)

            else:
                for eachGlyphName in self.glyphNamesToDisplay:
                    for indexFont, eachFont in enumerate(self.fontsOrder):
                        assert isinstance(
                            eachFont,
                            RFont), 'object in self.fontsOrder not a RFont'
                        if eachFont.has_key(eachGlyphName):
                            displayedGlyphs.append(eachFont[eachGlyphName])
                        else:
                            if eachFont.has_key('.notdef'):
                                displayedGlyphs.append(eachFont['.notdef'])
                            else:
                                displayedGlyphs.append(NOT_DEF_GLYPH)

            self.w.lineView.set(displayedGlyphs)

            # add kerning if needed
            if self.applyKerning is True:
                glyphRecords = self.w.lineView.contentView().getGlyphRecords()

                leftRecord = glyphRecords[0]
                rightRecord = glyphRecords[1]

                for indexRecords, eachGlyphRecord in enumerate(glyphRecords):
                    if indexRecords == len(
                            glyphRecords) and indexRecords == len(
                                0):  # avoid first and last
                        continue

                    leftRecord = glyphRecords[indexRecords - 1]
                    rightRecord = glyphRecords[indexRecords]
                    leftGlyph = leftRecord.glyph
                    rightGlyph = rightRecord.glyph

                    # if they come from the same font...
                    leftFont = leftGlyph.getParent()
                    rightFont = rightGlyph.getParent()
                    if leftFont is rightFont:
                        if (leftGlyph.name,
                                rightGlyph.name) in leftFont.flatKerning:
                            leftRecord.xAdvance += leftFont.flatKerning[(
                                leftGlyph.name, rightGlyph.name)]

            # manually refresh the line view ctrl, it should help
            self.w.lineView.update()

            # update spacing matrix
            self.w.spacingMatrix.canvas.update()

        except Exception, error:
            print traceback.format_exc()