Ejemplo n.º 1
0
    def font(self, value):
        self._font = value
        self.cfont = ColorFont(self._font)

        self.glyph = None
        self.glyphPreview = None
        self.width = 0
        self.palette_index = 0  # currently active palette

        self.currentPaletteChanged = False
        self.currentGlyphChanged = False

        self.layer_glyphs = []
        self.layer_colors = []
        self.layer_glyphs_glyph_window = []
        self.layer_colors_glyph_window = []

        if self._font is not None:
            self.metrics = (
                self._font.info.descender,
                self._font.info.xHeight,
                self._font.info.capHeight,
                self._font.info.ascender,
                self._font.info.unitsPerEm,
            )
        else:
            self.metrics = (-200, 500, 700, 800, 1000)
        self.scale = 180 / (self.metrics[3] - self.metrics[0])

        # load color data from rfont
        if self._font:
            self.cfont.read_from_rfont()
Ejemplo n.º 2
0
    def _reset_color_data(self, sender=None):
        # completely remove color info from UFO
        if self.font is not None:
            # font lib
            if "%s.colorpalette" % self.libkey in self.font.lib.keys():
                del self.font.lib["%s.colorpalette" % self.libkey]
            if "%s.color" % self.libkey in self.font.lib.keys():
                del self.font.lib["%s.color" % self.libkey]
            if "%s.colorbg" % self.libkey in self.font.lib.keys():
                del self.font.lib["%s.colorbg" % self.libkey]
            # glyph lib
            for g in self.font:
                if "%s.layers" % self.libkey in g.lib.keys():
                    del g.lib["%s.layers" % self.libkey]
                    self.layer_glyphs = []
                    self.layer_colors = []
            self.font.update()

        self.cfont = ColorFont(self.font)
        self.color = self.getNSColor(self.cfont.color)
        self.colorbg = self.getNSColor(self.cfont.colorbg)

        # Reset UI
        self.w.colorpalette.set([{"Index": 0xffff, "Color": self.color}])
        self._callback_update_ui_glyph_list()
        self._callback_ui_glyph_list_selection()

        self.d.generate_sbix_sizes.set(self._ui_get_sbix_sizes())
        self.d.auto_layer_regex_box.set(self.cfont.auto_layer_regex)
        self.w.auto_layer_button.enable(True)
        self._callback_update_ui_formats()
Ejemplo n.º 3
0
    def _import_from_font(self, file_paths=None):
        if file_paths is not None:
            self.cfont = ColorFont(CurrentFont())
            self.cfont.import_from_otf(file_paths[0])
            self.cfont.save_to_rfont()
            self.cfont.save_all_glyphs_to_rfont()

            self._ui_update_palette_chooser()
            self._ui_update_palette(self.palette_index)
            self._callback_update_ui_glyph_list()

            if len(self.cfont) > 0:
                self.w.glyph_list.setSelection([0])
Ejemplo n.º 4
0
class ColorFontEditor(BaseWindowController):
    def __init__(self):
        self.libkey = "com.fontfont.colorfont"
        self._font = None

        self.show_only_glyphs_with_layers = True

        self.color = NSColor.blackColor()
        self.colorbg = NSColor.whiteColor()
        self._selected_color_index = None

        # live update the canvas when glyphs are edited
        self._debug_enable_live_editing = True

        self._auto_layer_regex_ok = True

        # self.oldDisplaySettings = getGlyphViewDisplaySettings()
        # setGlyphViewDisplaySettings({"On Curve Points": False, "Off Curve Points": False})

        self.w = get_ui(self, "None")
        self.d = get_drawer(self)
        self.setUpBaseWindowBehavior()

        # self._callback_ui_glyph_list_selection()

        addObserver(self, "_observer_glyph_changed", "currentGlyphChanged")
        addObserver(self, "_observer_draw_glyph_window", "drawBackground")
        addObserver(self, "_observer_draw_glyph_window", "drawInactive")
        addObserver(self, "_observer_font_will_close", "fontWillClose")
        addObserver(self, "_observer_font_did_open", "fontDidOpen")

        # grey out controls that are not implemented yet
        self.d.generateGoogleFormat.enable(False)
        self.d.preferPlacedImages.enable(False)

        # disable regex check box, because it is read only
        self.d.auto_layer_regex_ok.enable(False)

        # If sbix or cbdt is inactive, disable bitmap sizes box
        # self._check_bitmap_ui_active()

        self.w.open()

        self.font = CurrentFont()
        # self._update_ui()
        # if CurrentGlyph() is not None:
        #     self.glyphPreview = CurrentGlyph().name
        #     if self.glyphPreview in self.cfont.keys():
        #         self.layer_glyphs_glyph_window = self.cfont[self.glyphPreview].layers
        #         self._cache_color_info_glyph_window()
        #     UpdateCurrentGlyphView()

    @property
    def font(self):
        return self._font

    @font.setter
    def font(self, value):
        self._font = value
        self.cfont = ColorFont(self._font)

        self.glyph = None
        self.glyphPreview = None
        self.width = 0
        self.palette_index = 0  # currently active palette

        self.currentPaletteChanged = False
        self.currentGlyphChanged = False

        self.layer_glyphs = []
        self.layer_colors = []
        self.layer_glyphs_glyph_window = []
        self.layer_colors_glyph_window = []

        if self._font is not None:
            self.metrics = (
                self._font.info.descender,
                self._font.info.xHeight,
                self._font.info.capHeight,
                self._font.info.ascender,
                self._font.info.unitsPerEm,
            )
        else:
            self.metrics = (-200, 500, 700, 800, 1000)
        self.scale = 180 / (self.metrics[3] - self.metrics[0])

        # load color data from rfont
        if self._font:
            self.cfont.read_from_rfont()

    def _update_ui(self):
        # update ui
        self._callback_update_ui_formats()
        self.d.generate_sbix_sizes.set(self._ui_get_sbix_sizes())
        self.d.auto_layer_regex_box.set(self.cfont.auto_layer_regex)
        self.d._add_base_layer.set(self.cfont.auto_layer_include_baseglyph)
        self.d.preferPlacedImages.set(self.cfont.prefer_placed_images)

        self._ui_update_palette_chooser()
        self._ui_update_palette(self.palette_index)
        self._callback_update_ui_glyph_list()

        if len(self.cfont) > 0:
            self.w.glyph_list.setSelection([0])

        if len(self.cfont) > 0:
            self.w.auto_layer_button.enable(False)

        if self._font:
            title = basename(self._font.fileName)
        else:
            title = "None"
        self.w.setTitle("%s – RoboChrome" % title)

    # def  _getColorPopupList(self):
    #    # build a list of current palette's colors that can be assigned
    #    # to the color popup in the layer list
    #    # FIXME: It seems the cell's popup list can't be changed easily after
    #    # the list has been built.
    #    return [NSAttributedString.alloc().initWithString_attributes_(str(entry["layer_index"]), {NSForegroundColorAttributeName: entry["Color"]}) for entry in self.w.colorpalette]

    def _show_font_info(self, sender=None):
        print(self.cfont)

    def _choose_file_to_import(self, sender=None):
        self.showGetFile(["public.opentype-font", "public.truetype-ttf-font"],
                         self._import_from_font)

    def _import_from_font(self, file_paths=None):
        if file_paths is not None:
            self.cfont = ColorFont(CurrentFont())
            self.cfont.import_from_otf(file_paths[0])
            self.cfont.save_to_rfont()
            self.cfont.save_all_glyphs_to_rfont()

            self._ui_update_palette_chooser()
            self._ui_update_palette(self.palette_index)
            self._callback_update_ui_glyph_list()

            if len(self.cfont) > 0:
                self.w.glyph_list.setSelection([0])

    def _choose_file_to_export(self, sender=None):
        pathkey = "com.typemytype.robofont.compileSettings.path"
        _font = -1
        if pathkey in self.font.lib:
            _font = self.font.lib.get(pathkey)
            if not exists(_font):
                _font = -1
        if _font == -1:
            self.showPutFile(
                ["public.opentype-font", "public.truetype-ttf-font"],
                self._export_to_font)
        else:
            self._export_to_font(_font)

    def _export_to_font(self, file_path=None):
        print("_export_to_font", file_path)
        if file_path is not None:
            if len(self.cfont.palettes[0]) > 0:
                print("Exporting to", file_path)
                self.cfont.export_to_otf(
                    file_path,
                    palette_index=self.palette_index,
                    parent_window=self.w,
                )
            else:
                print("ERROR: No color data in UFO.")

    def _choose_png_to_export(self, sender=None):
        self.showPutFile(["public.png"], self._save_png, "%s.png" % self.glyph)

    def _save_png(self, png_path=None):
        # save current glyph as PNG
        if png_path is not None:
            self.cfont.export_png(self.glyph, png_path, self.palette_index,
                                  self.font.info.unitsPerEm)

    def _ui_update_layer_list(self):
        # set layer UI for current glyph
        _ui_list = []
        if self.glyph in self.cfont.keys():
            for i in range(len(self.cfont[self.glyph].layers)):
                g = self.cfont[self.glyph].layers[i]
                if g in self.font.keys():
                    _ui_list.append({
                        "layer_index":
                        str(i),
                        "layer_color_index":
                        self.cfont[self.glyph].colors[i],
                        # "Color": self._getColorPopupList(),
                        "Layer Glyph":
                        g,
                    })
                else:
                    print(
                        "Warning: Missing layer glyph '%s' referenced in glyph '%s'."
                        % (g, self.glyph))
        # print("DEBUG: self.w.layer_list.set(_ui_list)")
        self.w.layer_list.set(_ui_list)
        # cache for faster drawing
        self._cache_layer_info()
        self._cache_color_info()

    def _ui_layer_list_save_to_cfont(self):
        # save the ui layer list to colorfont
        # print("DEBUG ColorFontEditor._ui_layer_list_save_to_cfont")
        if self.glyph is not None:
            if self.glyph in self.cfont.keys():
                layerGlyphs = []
                _layer_colors = []
                for layerDict in sorted(self.w.layer_list.get(),
                                        key=lambda k: int(k["layer_index"])):
                    layerGlyphs.append(layerDict["Layer Glyph"])
                    _layer_colors.append(int(layerDict["layer_color_index"]))
                if len(layerGlyphs) > 0 or len(_layer_colors) > 0:
                    _modified = False
                    if self.cfont[self.glyph].layers != layerGlyphs:
                        self.cfont[self.glyph].layers = layerGlyphs
                        _modified = True
                    if self.cfont[self.glyph].colors != _layer_colors:
                        self.cfont[self.glyph].colors = _layer_colors
                        _modified = True
                    if _modified:
                        self.cfont.save_glyph_to_rfont(self.glyph)
                else:
                    # empty layers, delete from lib
                    # print("DEBUG Delete info for glyph", self.glyph)
                    del self.cfont[self.glyph]
                    self.cfont.save_glyph_to_rfont(self.glyph)
            # else:
            #     print("  Glyph is not in ColorFont, not saving:", self.glyph)
        # else:
        #     print("  Glyph is None.")

    def _ui_get_sbix_sizes(self):
        # get the display string for a python list
        return str(self.cfont.bitmap_sizes).strip("[]")

    def _reset_color_data(self, sender=None):
        # completely remove color info from UFO
        if self.font is not None:
            # font lib
            if "%s.colorpalette" % self.libkey in self.font.lib.keys():
                del self.font.lib["%s.colorpalette" % self.libkey]
            if "%s.color" % self.libkey in self.font.lib.keys():
                del self.font.lib["%s.color" % self.libkey]
            if "%s.colorbg" % self.libkey in self.font.lib.keys():
                del self.font.lib["%s.colorbg" % self.libkey]
            # glyph lib
            for g in self.font:
                if "%s.layers" % self.libkey in g.lib.keys():
                    del g.lib["%s.layers" % self.libkey]
                    self.layer_glyphs = []
                    self.layer_colors = []
            self.font.update()

        self.cfont = ColorFont(self.font)
        self.color = self.getNSColor(self.cfont.color)
        self.colorbg = self.getNSColor(self.cfont.colorbg)

        # Reset UI
        self.w.colorpalette.set([{"Index": 0xffff, "Color": self.color}])
        self._callback_update_ui_glyph_list()
        self._callback_ui_glyph_list_selection()

        self.d.generate_sbix_sizes.set(self._ui_get_sbix_sizes())
        self.d.auto_layer_regex_box.set(self.cfont.auto_layer_regex)
        self.w.auto_layer_button.enable(True)
        self._callback_update_ui_formats()

    def addColorToPalette(self, sender=None):
        # add a new color to the current palette

        # find a new palette index
        paletteIndices = sorted(self.cfont.palettes[0].keys(),
                                key=lambda k: int(k))
        if len(paletteIndices) > 0:
            newIndex = int(paletteIndices[-1]) + 1
        else:
            newIndex = 0

        if newIndex < 0xffff:
            # add new color to current palette
            self.w.colorpalette.append({
                "Index": str(newIndex),
                "Color": NSColor.yellowColor()
            })
            # add new color to all other palettes
            for p in self.cfont.palettes:
                p[newIndex] = NSColor.yellowColor()
            self.cfont.save_settings = True
            self.currentPaletteChanged = True
        else:
            print("ERROR: Color Index 0xffff is reserved.")

    def _get_palette_color_ui_index_for_layer_color_index(self, index):
        # find the index of a color in the palette
        # (= ui list index, not layer_color_index)
        _palette = self.w.colorpalette.get()
        for i in range(len(_palette)):
            if int(_palette[i]["Index"]) == index:
                return i
        return None

    def _cache_layer_info(self):
        # self.layer_glyphs is used for drawing
        _layers = sorted(self.w.layer_list.get(),
                         key=lambda k: int(k["layer_index"]))
        if _layers == []:
            self.layer_glyphs = [{
                "layer_color_index": 0xffff,
                # "Color": self._getColorPopupList(),
                "layer_index": 0,
                "Layer Glyph": self.glyph
            }]
        else:
            self.layer_glyphs = _layers

    def _cache_color_info(self):
        # print("DEBUG _cache_color_info")
        # write colors for current glyph to self.layer_colors for faster drawing
        colorDict = self.getColorDict()
        _layer_colors = []
        for g in self.layer_glyphs:
            colorIndex = int(g["layer_color_index"])
            if colorIndex == 0xffff:
                _layer_colors.append(self.color)
            else:
                if colorIndex in colorDict.keys():
                    _layer_colors.append(colorDict[colorIndex])
                else:
                    print(
                        "Missing color index in palette %i: %i (in glyph /%s)"
                        % (self.palette_index, colorIndex, g["Layer Glyph"]))
        self.layer_colors = _layer_colors

        # update color list in layer list popup
        # self.w.layer_list["Color"].set(self._getColorPopupList())

    def _cache_color_info_glyph_window(self):
        # print("DEBUG _cache_color_info_glyph_window")
        # write colors for current glyph to self.layer_colors_glyph_window for faster drawing
        _layer_colors = []
        if self.glyphPreview is not None and self.glyphPreview in self.cfont.keys(
        ):
            colorDict = self.getColorDict()
            for colorIndex in self.cfont[self.glyphPreview].colors:
                if colorIndex == 0xffff:
                    _layer_colors.append(self.color)
                else:
                    if colorIndex in colorDict.keys():
                        _layer_colors.append(colorDict[colorIndex])
                    else:
                        print(
                            "Missing color in palette %i: %i (in glyph /%s)" %
                            (self.palette_index, colorIndex,
                             self.glyphPreview))
        self.layer_colors_glyph_window = _layer_colors

    # layer callbacks

    def _callback_layer_add(self, sender):
        if self.glyph is not None:
            self.cfont.save_settings = True
            if CurrentGlyph() is not None:
                newlayer = CurrentGlyph().name
            else:
                newlayer = self.glyph
            _color = self.getSelectedColorIndex()
            if _color is None:
                _color = 0xffff
            self.w.layer_list.append({
                "layer_index":
                str(len(self.w.layer_list) + 1),
                # "Color": self._getColorPopupList(),
                "layer_color_index":
                _color,
                "Layer Glyph":
                newlayer,
            })
            # self._ui_layer_list_save_to_cfont()
            if self.glyph not in self.cfont.keys():
                # print("DEBUG: Add new layer glyph to cfont")
                self.cfont.add_glyph(self.glyph)
            # self._ui_layer_list_save_to_cfont()
            sel = self.w.glyph_list.getSelection()
            self._callback_update_ui_glyph_list()
            self.w.glyph_list.setSelection(sel)

    def _callback_layer_edit(self, sender=None):
        # editing a layer (= change color index or glyph name or z-index)
        # print("DEBUG: _callback_layer_edit")
        # print("  Sender:", sender.get())
        self._cache_layer_info()
        self._cache_color_info()
        self.w.preview.update()

    def _callback_layer_select(self, sender):
        # a layer has been selected in the layers list. Select corresponding color in the palette.
        sel = sender.getSelection()
        layers = sender.get()
        if sel == []:
            self.w.colorpalette.setSelection([])
            self._selected_color_index = None
        else:
            i = int(layers[sel[0]]["layer_color_index"])
            self._selected_color_index = self._get_palette_color_ui_index_for_layer_color_index(
                i)
            if self._selected_color_index is None:
                self.w.colorpalette.setSelection([])
            else:
                self.w.colorpalette.setSelection([self._selected_color_index])
        self.w.preview.update()

    """example dropinfo:
        Something was dropped on the layer list:
{'rowIndex': 0, 'source': <objective-c class NSColorPanel at 0x7fff72b29ef0>, 'data': {
    "$archiver" = NSKeyedArchiver;
    "$objects" =     (
        "$null",
                {
            "$class" = "<CFKeyedArchiverUID 0x608000c2d220 [0x7fff70e30f00]>{value = 2}";
            NSColorSpace = 1;
            NSRGB = <30203020 3000>;
        },
                {
            "$classes" =             (
                NSColor,
                NSObject
            );
            "$classname" = NSColor;
        }
    );
    "$top" =     {
        root = "<CFKeyedArchiverUID 0x608000c33260 [0x7fff70e30f00]>{value = 1}";
    };
    "$version" = 100000;
}, 'dropOnRow': False, 'isProposal': True}
    """

    def _callback_layer_drop(self, sender=None, dropInfo=None):
        # a color has been dropped on the layer list
        if dropInfo["isProposal"]:
            # TODO: check if drop is acceptable
            return True
        else:
            print("DEBUG: dropped color on row %i" % dropInfo["rowIndex"])
            # TODO: accept the drop (actually do something)
            return True

    def _choose_svg_to_import(self, sender=None):
        self.showGetFile(["public.svg-image"], self._layer_add_svg_from_file)

    def _layer_add_svg_from_file(self, file_paths=None):
        # Add an SVG from external file
        if file_paths is not None:
            self.cfont.add_svg(self.glyph, file_paths[0])
            sel = self.w.glyph_list.getSelection()
            self._callback_update_ui_glyph_list()
            self.w.glyph_list.setSelection(sel)

    def getNSColor(self, hexrgba):
        # get NSColor for a HTML-style hex rgb(a) color
        r = int(hexrgba[1:3], 16) / 255
        g = int(hexrgba[3:5], 16) / 255
        b = int(hexrgba[5:7], 16) / 255
        if len(hexrgba) == 9:
            a = int(hexrgba[7:9], 16) / 255
        else:
            a = 1
        return NSColor.colorWithCalibratedRed_green_blue_alpha_(r, g, b, a)

    def getHexColor(self, nscolor):
        if nscolor.colorSpaceName != NSCalibratedRGBColorSpace:
            nscolor = nscolor.colorUsingColorSpaceName_(
                NSCalibratedRGBColorSpace)
        r = int(round(255 * float(nscolor.redComponent())))
        g = int(round(255 * float(nscolor.greenComponent())))
        b = int(round(255 * float(nscolor.blueComponent())))
        a = int(round(255 * float(nscolor.alphaComponent())))
        if a == 1:
            return "#%02x%02x%02x" % (r, g, b)
        else:
            return "#%02x%02x%02x%02x" % (r, g, b, a)

    def _callback_set_show_only_glyphs_with_layers(self, sender):
        self.show_only_glyphs_with_layers = sender.get()
        self._callback_update_ui_glyph_list()

    def _callback_toggle_settings(self, sender):
        # show or hide the settings drawer
        self.d.toggle()

    def _callback_set_sbix_sizes(self, sender):
        sizes_str = sender.get().split(",")
        sizes = []
        for entry in sizes_str:
            entry = entry.strip("[], ")
            if entry != "":
                sizes.append(int(entry))
        self.cfont.bitmap_sizes = sizes

    def _callback_color_changed_foreground(self, sender):
        if sender is not None:
            self.color = sender.get()
            self._ui_update_palette(self.palette_index)
            i = self.w.colorpalette.getSelection()
            if i != []:
                if int(self.w.colorpalette.get()[i[0]]["Index"]) == 0xffff:
                    self.w.colorPaletteColorChooser.set(self.color)
            self._cache_color_info()
            self.w.preview.update()

    def _callback_color_changed_background(self, sender):
        if sender is not None:
            self.colorbg = sender.get()
            self._cache_color_info()
            self.w.preview.update()

    def _callback_color_select_in_palette(self, sender):
        # a color has been selected in the current palette
        i = sender.getSelection()
        if not i:
            # empty selection
            self.w.colorPaletteColorChooser.enable(False)
        else:
            sel = sender.get()
            selIndex = int(sel[i[0]]["Index"])
            if selIndex == 0xffff:
                # use foreground color
                self.w.colorPaletteColorChooser.set(self.w.colorChooser.get())
                self.w.colorPaletteColorChooser.enable(False)
            else:
                self.w.colorPaletteColorChooser.set(sel[i[0]]["Color"])
                self.w.colorPaletteColorChooser.enable(True)

    def _ui_update_palette_chooser(self):
        self.w.paletteswitch.setItems(
            ["Palette %s" % i for i in range(len(self.cfont.palettes))])

    def paletteEdit(self, sender):
        # print("DEBUG ColorFontEditor.paletteEdit")
        sel = sender.getSelection()
        if sel != []:
            i = sel[0]
            if i < len(self.w.colorpalette):
                if self.w.colorpalette[i] != sender.get()[i]:
                    self.w.colorpalette[i] = sender.get()[i]
                    self.currentPaletteChanged = True
                    print("  Palette changed")
            else:
                print("Ignored edit of foreground color")
        self.w.preview.update()

    def paletteEditColorCell(self, sender):
        # double-click on a color cell in the palette
        print(sender)

    def _paletteWriteToColorFont(self):
        # print("DEBUG _paletteWriteToColorFont")
        # make a dict for active palette and write it to self.cfont.palettes
        _dict = {}
        for _color in sorted(self.w.colorpalette.get(),
                             key=lambda _key: int(_key["Index"])):
            if int(_color["Index"]) != 0xffff:
                _dict[str(_color["Index"])] = self.getHexColor(_color["Color"])
        self.cfont.palettes[self.palette_index] = _dict
        self.cfont.save_to_rfont()

    def _paletteSwitchCallback(self, sender):
        # activate a different palette
        # save current palette
        if self.currentPaletteChanged:
            self._paletteWriteToColorFont()
        self._ui_update_palette(sender.get())
        # print("DEBUG Active Palette is now #%i" % self.palette_index)

    def paletteDuplicate(self, sender):
        if self.currentPaletteChanged:
            self._paletteWriteToColorFont()
        sp = self.w.paletteswitch.get()
        if sp < len(self.cfont.palettes) and sp >= 0:
            print("Duplicate palette %i ..." % sp)
            colorpalette = self.cfont.palettes[sp].copy()
        else:
            colorpalette = {}
        self.cfont.palettes.append(colorpalette)
        self._ui_update_palette_chooser()
        # new palette should be active
        self._ui_update_palette(len(self.cfont.palettes) - 1)

    def _ui_update_palette(self, palette_index):
        # load a different palette from the color font and show it in UI
        # save the currently selected color index
        selectedColorIndex = self.w.colorpalette.getSelection()
        self.palette_index = palette_index
        if self.palette_index < len(self.cfont.palettes):
            colorpalette = self.cfont.palettes[self.palette_index]
        else:
            colorpalette = {}
        newColorpalette = [{
            "Index": str(k),
            "Color": self.getNSColor(colorpalette[k])
        } for k in sorted(colorpalette.keys())]
        newColorpalette.append({"Index": 0xffff, "Color": self.color})

        self.w.colorpalette.set(newColorpalette)
        self.w.colorpalette.setSelection(selectedColorIndex)
        self.w.paletteswitch.set(self.palette_index)

        self._cache_color_info()
        self._cache_color_info_glyph_window()
        self.currentPaletteChanged = False
        self.w.preview.update()
        UpdateCurrentGlyphView()

    def paletteDelete(self, sender):
        pass

    def getSelectedColorIndex(self):
        i = self.w.colorpalette.getSelection()
        if i == []:
            return None
        else:
            return self.w.colorpalette.get()[i[0]]["Index"]

    def _callback_color_changed_layer(self, sender):
        # a color has been edited in the palette
        if sender is not None:
            _selected_color = self.w.colorpalette.getSelection()
            if _selected_color != []:
                _colors = self.w.colorpalette.get()
                _colors[_selected_color[0]]["Color"] = sender.get()
                self.w.colorpalette.set(_colors)
            self.currentPaletteChanged = True
            self._cache_color_info()
            self.w.preview.update()

    def windowCloseCallback(self, sender):
        # print("DEBUG windowCloseCallback")
        removeObserver(self, "fontDidOpen")
        removeObserver(self, "fontWillClose")
        removeObserver(self, "currentGlyphChanged")
        removeObserver(self, "drawBackground")
        removeObserver(self, "drawInactive")
        # setGlyphViewDisplaySettings(self.oldDisplaySettings)
        self._ui_layer_list_save_to_cfont()
        if self.cfont.save_settings and self.currentPaletteChanged:
            self._paletteWriteToColorFont()
        if self.cfont.save_settings:
            self.cfont.save_to_rfont()
        super(ColorFontEditor, self).windowCloseCallback(sender)

    def _callback_ui_glyph_list_selection(self, sender=None):
        # selection changed in the ui glyph list
        if self.glyph is not None:
            # save current glyph layers
            self._ui_layer_list_save_to_cfont()
        sel = self.w.glyph_list.getSelection()
        if sel == []:
            self.w.layer_list.set([])
            self.glyph = None
            self.width = 0
            self.w.add_layer_button.enable(False)
        else:
            self.glyph = self.w.glyph_list[sel[0]]["Name"]
            self.width = self.font[self.glyph].width
            self._ui_update_layer_list()
            self.w.add_layer_button.enable(True)
        self.w.preview.update()

    def _callback_update_ui_glyph_list(self, sender=None):
        _match = self.w.glyph_list_search_box.get()
        # print("DEBUG: _callback_update_ui_glyph_list")
        glyphlist = []
        if self.font is not None:
            # if no color glyphs in font, show all glyphs in list
            if len(self.cfont.keys()) == 0:
                self.show_only_glyphs_with_layers = False
                self.w.show_only_glyphs_with_layers.set(False)
                self.w.show_only_glyphs_with_layers.enable(False)
            else:
                self.w.show_only_glyphs_with_layers.enable(True)
            cfglyphs = self.cfont.keys()
            for n in self.font.glyphOrder:
                if n in cfglyphs:
                    _glyph_has_layers = True
                else:
                    _glyph_has_layers = False
                if not self.show_only_glyphs_with_layers or _glyph_has_layers:
                    if _match == "":
                        glyphlist.append({
                            "Layers": _glyph_has_layers,
                            "Name": n
                        })
                    else:
                        if _match in n:
                            glyphlist.append({
                                "Layers": _glyph_has_layers,
                                "Name": n
                            })
        self.w.glyph_list.set(glyphlist)
        if glyphlist != []:
            self.w.glyph_list.setSelection([0])

    def _callback_update_ui_formats(self, sender=None):
        self.d.generateMSFormat.set(self.cfont.write_colr)
        self.d.generateAppleFormat.set(self.cfont.write_sbix)
        self.d.generateSVGFormat.set(self.cfont.write_svg)
        self.d.generateGoogleFormat.set(self.cfont.write_cbdt)
        self._check_bitmap_ui_active()
        self._check_export_ui_active()

    def _callback_select_formats(self, sender=None):
        self.cfont.write_colr = self.d.generateMSFormat.get()
        self.cfont.write_sbix = self.d.generateAppleFormat.get()
        self.cfont.write_svg = self.d.generateSVGFormat.get()
        self.cfont.write_cbdt = self.d.generateGoogleFormat.get()
        self._check_bitmap_ui_active()
        self._check_export_ui_active()

    def _check_bitmap_ui_active(self):
        _ui_active = self.cfont.write_sbix or self.cfont.write_cbdt
        self.d.generate_sbix_sizes.enable(_ui_active)
        self.d.preferPlacedImages.enable(_ui_active)

    def _check_export_ui_active(self):
        _ui_active = self.cfont.write_sbix or self.cfont.write_cbdt or self.cfont.write_colr or self.cfont.write_svg
        self.w.export_button.enable(_ui_active)

    def setFill(self, nscolor, opacity_factor=1):
        # set fill color for mojoDrawingTools, optionally with changed opacity
        if nscolor.colorSpaceName != NSCalibratedRGBColorSpace:
            nscolor = nscolor.colorUsingColorSpaceName_(
                NSCalibratedRGBColorSpace)
        fill(nscolor.redComponent(), nscolor.greenComponent(),
             nscolor.blueComponent(),
             nscolor.alphaComponent() * opacity_factor)

    def getColorDict(self):
        # returns the current UI color palette as dictionary {index: nscolor}
        return {
            int(_color["Index"]): _color["Color"]
            for _color in self.w.colorpalette.get()
        }

    def draw(self):
        # draw the color glyph on the canvas
        if self._font is not None:
            save()
            self.setFill(self.colorbg)
            rect(0, 0, 310, 200)
            self.setFill(self.color)
            scale(self.scale)
            translate(50.5, -self.metrics[0] + 20.5)
            self._canvas_draw_metrics()
            for i in range(len(self.layer_glyphs)):
                layerGlyph = self.layer_glyphs[i]["Layer Glyph"]
                if self._selected_color_index is None:
                    op_factor = 1.0
                else:
                    if self._selected_color_index == self.layer_glyphs[i][
                            "layer_color_index"]:
                        op_factor = 1.0
                    else:
                        op_factor = 0.2
                if layerGlyph in self.font:
                    if i < len(self.layer_colors):
                        _color = self.layer_colors[i]
                        self.setFill(_color, op_factor)
                        drawGlyph(self.font[layerGlyph])
            restore()

    def _canvas_draw_metrics(self):
        save()
        strokeWidth(1 / self.scale)
        stroke(0.8, 0.8, 0.8)
        line((0, 0), (self.width, 0))
        line((0, self.metrics[0]), (self.width, self.metrics[0]))
        line((0, self.metrics[1]), (self.width, self.metrics[1]))
        line((0, self.metrics[2]), (self.width, self.metrics[2]))
        line((0, self.metrics[3]), (self.width, self.metrics[3]))
        line((0, self.metrics[3]), (0, self.metrics[0]))
        line((self.width, self.metrics[3]), (self.width, self.metrics[0]))
        restore()

    def _observer_font_did_open(self, info=None):
        if self.font is None:
            self.font = info['font']
            self._update_ui()

    def _observer_font_will_close(self, info=None):
        # When a font closes, save color font information
        # if it is the currently active font in RoboChrome.
        font_to_close = info['font']
        if self.font == font_to_close:
            self._ui_layer_list_save_to_cfont()
            if self.cfont.save_settings and self.currentPaletteChanged:
                self._paletteWriteToColorFont()
            if self.cfont.save_settings:
                self.cfont.save_to_rfont()
            self.font = None
            self._update_ui()

    def _observer_glyph_changed(self, info=None):
        # Current Glyph has changed
        if info is not None:
            if info["glyph"] is not None:
                self.glyphPreview = CurrentGlyph().name
                if self.glyphPreview in self.cfont.keys():
                    self.layer_glyphs_glyph_window = self.cfont[
                        self.glyphPreview].layers
                    self._cache_color_info_glyph_window()
        UpdateCurrentGlyphView()

    def _callback_goto_glyph(self, sender=None):
        newGlyphName = sender.get()[sender.getSelection()[0]]["Name"]
        if CurrentGlyphWindow():
            CurrentGlyphWindow().setGlyphByName(newGlyphName)
        else:
            # TODO: open glyph window?
            pass

    def _callback_select_glyphs_in_font_window(self, sender=None):
        # select all glyphs which have layers.
        self.font.selection = self.cfont.keys()

    def _observer_draw_glyph_window(self, info):
        # draw the color glyph in the glyph window
        # print("DEBUG: _observer_draw_glyph_window")
        if self.glyphPreview in self.cfont.keys():
            # print("DEBUG: draw glyph")
            save()
            self.setFill(self.color)
            for i in range(len(self.layer_glyphs_glyph_window)):
                layerGlyph = self.layer_glyphs_glyph_window[i]
                if layerGlyph in self.font:
                    if i < len(self.layer_colors_glyph_window):
                        _color = self.layer_colors_glyph_window[i]
                        self.setFill(_color)
                        drawGlyph(self.font[layerGlyph])
            restore()
        if self._debug_enable_live_editing:
            self.w.preview.update()

    def _callback_auto_layers(self, sender=None):
        print("Auto layers: %s" % self.cfont.auto_layer_regex)
        if self.cfont.auto_layer_regex is not None:
            self.cfont.auto_layers()
            self.cfont.auto_palette()
            self.cfont.save_to_rfont()
            self.cfont.save_all_glyphs_to_rfont()
            self._ui_update_palette_chooser()
            self._ui_update_palette(self.palette_index)
            self._ui_update_layer_list()
            self._callback_update_ui_glyph_list()
            if len(self.cfont) > 0:
                self.w.glyph_list.setSelection([0])
                self.w.auto_layer_button.enable(False)
        else:
            print("ERROR: Invalid auto layer regex")

    def _callback_auto_palette(self, sender=None):
        self.cfont.auto_palette()
        self.cfont.save_to_rfont()
        self._ui_update_palette_chooser()
        self._ui_update_palette(self.palette_index)

    def _callback_check_regex(self, sender=None):
        # check if the entered regex does compile
        test_re = sender.get()
        try:
            compile(test_re)
            self.d.auto_layer_regex_ok.set(True)
            self.w.auto_layer_button.enable(True)
            self.cfont.auto_layer_regex = test_re
            self.d.regex_test_button.enable(True)
        except:
            self.d.auto_layer_regex_ok.set(False)
            self.w.auto_layer_button.enable(False)
            self.cfont.auto_layer_regex = None
            self.d.regex_test_button.enable(False)

    def _callback_test_regex(self, sender=None):
        # select glyphs based on current regex
        regex = compile(self.cfont.auto_layer_regex)
        _glyph_list = [
            glyphname for glyphname in self.font.glyphOrder
            if regex.search(glyphname)
        ]
        # print("_callback_test_regex matched %i glyphs." % len(_glyph_list))
        self.font.selection = _glyph_list

    def _callback_prefer_placed_images(self, sender=None):
        self.cfont.prefer_placed_images = sender.get()

    def _callback_auto_layer_include_baseglyph(self, sender=None):
        self.cfont.auto_layer_include_baseglyph = sender.get()