wxPythonで色分けができるコードエディタをつくってみる

なぜか自分がwxPythonで開発しているとき、『テキストエディタを作りたい』となぜか思っていました。
それで、
いままでwxPythonでその時に定の文字列に色付けできるコードを探してみました。
wx.TextCtrlでは無理っぽい。特定の文字列に色をつけるコードは考えたけどそれ色付けに時間がかかるのです。それで文字の量が多いと時間がとてもかかって使いものにはならなくなりました。

で、
使えそうなwx.stc.StyledTextCtrlを利用したいと思います。

そして、完成図はこれ！

![スクリーンショット (573).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1061021/f9733ee8-e405-0a6a-7f67-0c0b6d305c87.png)

テキストエディタっぽいですね。今回はこれを作成します。

##wx.stc.StyledTextCtrl
このウィジェットはコードエディタを作成するのにあっているものです。
Python、Ruby、Innoなど言語の切り替えができて、そこでコメントやキーワードなどを色付けできます。
他にも、マクロの記録機能までついているようですが僕はそこまで知りません。

##IMEの未確定文字が左上に表示される
wx.stc.StyledTextCtrlはIME入力が考えられていないようで、未確定文字が左上に表示されてしまいます。
今回はそこも含めてコードをかきたいと思います。

##ソースコード

必要に応じてフォントサイズ、フォント、カラーを変更する必要があります。

```Python:editor.py
from __future__ import with_statement, division, print_function
import wx
import wx.stc as stc
from ctypes import windll
from ctypes import Structure, c_long, c_byte, c_wchar_p, c_ulong, byref
from ctypes import windll
import sys
import pyautogui
import chardet

#ここから

class POINT(Structure):
    _fields_ = [
        ("x", c_long),
        ("y", c_long),
    ]

class RECT(Structure):
    _fields_ = [
        ("left", c_long),
        ("top", c_long),
        ("right", c_long),
        ("bottom", c_long),
    ]

class COMPOSITIONFORM(Structure):
    _fields_ = [
        ("dwStyle", c_ulong),
        ("ptCurrentPos", POINT),
        ("rcArea", RECT),
    ]

class LOGFONT(Structure):
    _fields_ = [
        ("lfHeight", c_long),
        ("lfWidth", c_long),
        ("lfEscapement", c_long),
        ("lfOrientation", c_long),
        ("lfWeight", c_long),
        ("lfItalic", c_byte),
        ("lfUnderline", c_byte),
        ("lfStrikeOut", c_byte),
        ("lfCharSet", c_byte),
        ("lfOutPrecision", c_byte),
        ("lfClipPrecision", c_byte),
        ("lfQuality", c_byte),
        ("lfPitchAndFamily", c_byte),
        ("lfFaceName", c_wchar_p),
    ]

#ここまでがIMEについての設定を行っている

class TextCtrl(stc.StyledTextCtrl):
    def __init__(self, *a, **kw):
        stc.StyledTextCtrl.__init__(self, *a, **kw)

        self.char_height = self.GetZoom()
        self.maemae = self.GetZoom()
        self.font_facename = u"ＭＳゴシック"
        self.Bind(stc.EVT_STC_PAINTED, self.OnEvent)
        self.Bind(stc.EVT_STC_ZOOM,self.YSetZoom)
        wx.CallAfter(self.SetImeComposition)
        self.StyleSetBackground(stc.STC_STYLE_DEFAULT,wx.Colour("white"))
        self.StyleSetBackground(stc.STC_P_DEFAULT,wx.Colour("white"))
        self.StyleSetForeground(stc.STC_P_DEFAULT,wx.Colour("black"))
    def IsImeOn(self):
        hIMC = windll.imm32.ImmGetContext(self.GetHandle())
        status = windll.imm32.ImmGetOpenStatus(hIMC)
        windll.imm32.ImmReleaseContext(self.GetHandle(), hIMC)
        return status
    def YSetZoom(self,event):#IMEの文字の大きさの調整を行う
        zm = self.GetZoom()
        i = zm * 2 + 15
        self.char_height = i
    def SetImeComposition(self):
        if not self.IsImeOn():
            return
        hIMC = windll.imm32.ImmGetContext(
            self.GetHandle())

        lf = LOGFONT()
        lf.lfHeight = -self.char_height
        lf.lfWidth = 0
        lf.lfPitchAndFamily = 1 & 48
            # FIXED_PITCH & FF_MODERN
        lf.lfFaceName = self.font_facename
        windll.imm32.ImmSetCompositionFontW(hIMC,
            byref(lf))

        cf = COMPOSITIONFORM()
        cf.dwStyle = 2      # CFS_POINT

        p = self.GetCurrentPos()
        x, y = self.PointFromPosition(p)
        y = y + 3
        cf.ptCurrentPos = POINT(x, y)
        windll.imm32.ImmSetCompositionWindow(hIMC,
            byref(cf))
        windll.imm32.ImmReleaseContext(self.GetHandle(),
            hIMC)

    def OnEvent(self, event):
        event.Skip()
        self.SetImeComposition()

class MyFrame(wx.Frame):
    def Color(self):
        self.s.SetLexer(stc.STC_LEX_PYTHON)#Pythonモードにするためのコマンド
        self.s.SetThemeEnabled(True)
        self.s.SetStyleBits(8)
        self.s.StyleClearAll()#念のため全てのテキストをクリアしておく。
        self.s.StyleSetForeground(stc.STC_P_IDENTIFIER,wx.Colour("green"))
        self.s.StyleSetBackground(stc.STC_P_IDENTIFIER,wx.Colour("#444444"))
        faces = { 'mono' : 'Courier New',
                  'helv' : 'Arial',
                  'size' : 10,
                  }
        kw = ["import ","from","for","in","as","Exception","try","except","class","True","False","None","def","-1","return","break",
                "if","else","elif","pass"]#キーワード1のリスト
        kw2 = ["print","input","str","int","float","type","Exception"]#キーワード2のリスト
        self.s.SetKeyWords(0, " ".join(kw))
        self.s.SetKeyWords(1," ".join(kw2))
        self.s.StyleSetBackground(stc.STC_STYLE_DEFAULT,wx.Colour("#444444"))#標準の背景色設定
        self.s.StyleSetBackground(stc.STC_P_DEFAULT,wx.Colour("#444444"))#標準の背景色設定
        self.s.StyleSetForeground(stc.STC_P_DEFAULT,wx.Colour("green"))#標準の文字色設定
        self.s.StyleSetForeground(stc.STC_P_STRING, wx.Colour(0,   0,   127));#文字列("と"で囲まれた文字の色設定)
        self.s.StyleSetForeground(stc.STC_P_WORD, wx.Colour("#FFA500"));#キーワード1の文字色設定
        self.s.StyleSetForeground(stc.STC_P_WORD2, wx.Colour("#FF1493"));#キーワード2の文字色設定
        self.s.StyleSetBackground(stc.STC_P_WORD, wx.Colour("#444444"));#キーワード1の背景色設定
        self.s.StyleSetBackground(stc.STC_P_WORD2, wx.Colour("#444444"));#キーワード2の背景色設定
        self.s.StyleSetSpec(stc.STC_P_COMMENTLINE,"fore:#007F00,back:#444444,face:%(helv)s,size:%(size)d" % faces)#コメントの色設定
        self.s.StyleSetSpec(stc.STC_PAS_COMMENT, "fore:#008000,size:%(helv)s" % faces)#コメントの色設定
        self.s.StyleSetSpec(stc.STC_P_STRING, "fore:#98FB98,back:#444444,size:%(size)d" % faces)#文字列の色設定
        self.s.StyleSetSpec(stc.STC_P_CHARACTER, "fore:#98FB98,back:#444444,size:%(size)d" % faces)#文字？の色設定
        self.s.StyleSetSpec(stc.STC_P_STRINGEOL,"fore:#98FB98,back:#444444,size:%(size)d" % faces)#文字列のなにかの色設定
        self.s.StyleSetSpec(stc.STC_P_COMMENTBLOCK,"fore:#98FB98,back:#444444,size:%(size)d" % faces)#コメントの色設定
        self.s.StyleSetSpec(stc.STC_P_TRIPLEDOUBLE,"fore:#98FB98,back:#444444,size:%(size)d" % faces)#"""で囲まれた文字の色設定
        self.s.StyleSetSpec(stc.STC_P_DEFNAME,"fore:#00BFFF,back:#444444,size:9" %faces)#defの名前の色設定
        self.s.StyleSetSpec(stc.STC_P_CLASSNAME,"fore:#00BFFF,back:#444444,size:9" %faces)#classの名前の色設定
        self.s.StyleSetSpec(stc.STC_P_TRIPLE,"fore:#98FB98,back:#444444,size:%(size)d" %faces)#トリプル？の色設定
        self.s.StyleSetSpec(stc.STC_P_NUMBER, "fore:#DA70D6,back:#444444,size:%(size)d" %faces)#数値の色設定
        self.s.StyleSetSpec(stc.STC_P_OPERATOR, "fore:#DA70D6,back:#444444,size:%(size)d" %faces)#記号の色設定(多分)
        self.s.highlight = 30 # a specific style I want to add
        self.s.StyleSetSpec(self.s.highlight, "fore:#0000FF,back:#444444,bold,size:%(size)d" % faces)#？の色設定

    def __init__(self):
        wx.Frame.__init__(self,None,-1,"Editor",size=(800,500))
        self.s = TextCtrl(self,-1)
        self.Color()

def main():
    app = wx.App()
    f = MyFrame()
    f.Show(True)
    app.MainLoop()

if __name__ == "__main__":
    main()
```
self.s.SetStyleBits(8)で警告が出るようですがよくわかりません。

##ソースコードでは何をやっているのか

`class MyFrame`に行く前まではTextCtrlとIMEの設定を行っています。
ズームレベルに合わせてIMEの未確定文字の大きさを調整しています。
`kw = ・・・`の所では強調するキーワードを設定していますが、まだ足りない気がしますのでここの追加は手動でお願いします。
`def color`の所ではどこを何色にするのかを決めています。Python用にしていますが、他にもできるので手動でお願いします。

さらにカーソルの色とテキストコントロールの色がかぶってしまい、見えずらくなってしまったのでここ分かる人いたらコメントなどで教えてください。
##行番号の表示

本当にテキストエディタをつくるようのウィジェットといっていいほど欲しい機能が詰まっています。
それで、行番号を表示させることができます。
行番号を表示させるにはいろいろないろの設定の後に以下のコードを追加する必要があります。

```Python
self.s.SetMarginType(1, stc.STC_MARGIN_NUMBER)#行番号を表示させるために最初にやるなにか
self.s.SetMarginWidth(1, 30)#行番号を表示するスペースの設定
self.s.StyleSetSpec(stc.STC_STYLE_LINENUMBER,  "fore:#999999")#行番号を表示させるスペースの色設定
```

##おわりに
メニューバーと組み合わせてつくったらかんたんにテキストエディタ高機能テキストエディタが作れてしまいます。Pythonモード以外にもRuby,Perl,C,VBなどのモードでも作ってみたらいいかもしれません。その方法などについては別の記事で説明したいと思います。ということで今回の記事はこのPythonコードエディタをつくるというところまでで！

最低限でもテキストエディタをつくりたいのであればファイルの保存や開く...などを実装する必要があります。

なのであとは自由にいじってください。誤字やここ変えた方がいい！って感じのがある人はコメントお願いします。

###動作環境
Windows 10
Python 3.9

###更新履歴
2021/04/03 コードの分かりやすさを改善＆行番号についてを追加

---
ブログ開設しときました。まだ全然記事あげてませんが、これから色々とあげる予定です。是非見に来てください。→　programming-self-study.blogspot.com/
wxPythonの基本については → https://programming-self-study.blogspot.com/2021/06/wxpython.html をみてください
もっとシンプルでよい場合は → https://programming-self-study.blogspot.com/2021/06/wxpythonnotepad.html をみてください