Seite 1 von 1

Kleiner ReST-Editor

Verfasst: Mittwoch 21. März 2007, 22:53
von midan23
Hallo zusammen,

Da ich mich zur Zeit mit ReST und wxPython befasse dachte ich mir, "man könnte doch beides verbinden" ...

Dabei ist folgendes entstanden:

Code: Alles auswählen

#!/usr/bin/env pythonw
# -*- coding: utf-8 -*-

import threading
import time

import wx
import wx.html
from docutils.core import publish_parts


class Rst2Html(threading.Thread):
    def __init__(self, show, edit):
        threading.Thread.__init__(self)
        self.show = show
        self.edit = edit
        self.finish = threading.Event()

    def run(self):
        while not self.finish.isSet():
            txt = self.edit.GetValue()
            html = publish_parts(txt, writer_name="html")["html_body"]
            self.show.SetPage(html)
            time.sleep(1)

    def stop(self):
        self.finish.set()

class MainWindow(wx.Frame):
    def __init__(self):
        wx.Frame.__init__(self, None, title="RST-Edit")
        
        splitter = wx.SplitterWindow(self)

        show_panel = wx.html.HtmlWindow(splitter)

        edit_panel = wx.TextCtrl(splitter, style=wx.TE_MULTILINE)
        font = wx.Font(10, wx.MODERN, wx.NORMAL, wx.NORMAL, False)
        edit_panel.SetFont(font)

        splitter.SplitHorizontally(show_panel, edit_panel)

        thread = Rst2Html(show_panel, edit_panel)
        thread.start()


class MyApp(wx.App):
    def OnInit(self):
        frame = MainWindow()
        frame.Show(True)
        self.SetTopWindow(frame)
        return True


def main():
    app = MyApp(filename="error.txt")
    app.MainLoop()


if __name__ == "__main__":
    main()
Bei einigen Keinigkeiten hoffe ich auf eure Hilfe:
  • Ich würde den Thread gern bei Programmende beenden, weiss aber (noch) nicht wie ...
  • Während man im Editor-Teil was eintippt ist es durchaus wahrscheinlich, das es beim Konvertieren zu Fehlern kommt ... dabei kommt es zu einer Ausnahme, die den Konvertier-Thread abbricht ...
    Man könnte zwar diese Ausnahme abfangen, aber ob das die einzige Lösung ist ?
  • Auch beim Source-Code bin ich mir nicht sicher, ob alles so ist, wie sein sollte ...

Re: Kleiner ReST-Editor

Verfasst: Donnerstag 22. März 2007, 01:07
von gerold
midan23 hat geschrieben:
  • Ich würde den Thread gern bei Programmende beenden, weiss aber (noch) nicht wie ...
Hi midan23!

Das könnte z.B. so funktionieren:

Code: Alles auswählen

        self.thread = Rst2Html(show_panel, edit_panel)
        self.thread.start()
        
        self.Bind(wx.EVT_CLOSE, self.on_frame_close)
    
    def on_frame_close(self, event):
        event.Skip()
        self.thread.stop()
Wie gefällt dir diese Lösung mit dem wx.Timer?

Code: Alles auswählen

#!/usr/bin/env pythonw
# -*- coding: utf-8 -*-

import wx
import wx.html
from docutils.core import publish_parts


class MainWindow(wx.Frame):
    
    def __init__(self):
        
        wx.Frame.__init__(self, None, title = "RST-Edit")
       
        splitter = wx.SplitterWindow(self)
        
        show_window = wx.html.HtmlWindow(splitter)
        self.show_window = show_window
        
        edit_ctrl = wx.TextCtrl(splitter, style = wx.TE_MULTILINE)
        font = wx.Font(10, wx.MODERN, wx.NORMAL, wx.NORMAL, False)
        edit_ctrl.SetFont(font)
        edit_ctrl.Bind(wx.EVT_TEXT, self.reset_timer)
        self.edit_ctrl = edit_ctrl
        
        splitter.SplitHorizontally(show_window, edit_ctrl)
        
        self.parse_timer = wx.Timer()
        self.parse_timer.Bind(wx.EVT_TIMER, self.rst2html)
    
    
    def reset_timer(self, event = None):
        
        self.parse_timer.Stop()
        self.parse_timer.Start(1000, oneShot = True)
    
    
    def rst2html(self, event = None):
        
        txt = self.edit_ctrl.GetValue()
        html = publish_parts(txt, writer_name="html")["html_body"]
        self.show_window.SetPage(html)


class MyApp(wx.PySimpleApp):
    
    def OnInit(self):
        
        frame = MainWindow()
        frame.Show(True)
        
        return True


def main():
    
    app = MyApp()
    app.MainLoop()


if __name__ == "__main__":
    main()
mfg
Gerold
:-)

Re: Kleiner ReST-Editor

Verfasst: Donnerstag 22. März 2007, 01:20
von gerold
midan23 hat geschrieben:

Code: Alles auswählen

    def run(self):
        while not self.finish.isSet():
            txt = self.edit.GetValue()
            html = publish_parts(txt, writer_name="html")["html_body"]
            self.show.SetPage(html)
            time.sleep(1)
Hi midan23!

Unter wxPython kriegst du **nicht vorhersehbare Schwierigkeiten**, wenn du von einem neu gestarteten Thread aus, Änderungen an Objekten vornimmst, die vom Hauptthread verwaltet werden.

Damit meine ich hauptsächlich die Zeile ``self.show.SetPage(html)``. Du darfst von einem (selbst gestarteten) Thread aus keine *direkten* Änderungen an der GUI vornehmen. Du musst dich darum kümmern, dass eine Trennung zwischen den Threads bestehen beleibt, sonst geht dir dein Programm unter nicht vorhersehbaren Umständen einfach ein. Am Besten, du verwendest wx.CallAfter(), das sich um diese Trennung kümmert.
So könnte besagte Zeile aussehen und auch noch z.B. unter Linux funktionieren:

Code: Alles auswählen

wx.CallAfter(self.show.SetPage, html)
mfg
Gerold
:-)

Verfasst: Donnerstag 22. März 2007, 09:34
von midan23
Hi gerold,

danke für die Tipps ...

Das mit wx.CallAfter() könnte erklären, warum ich das Programm gestern "abschiessen" durfte ... :oops:

Die Lösung mit wx.Timer() gefällt mir ... ist ungefähr das, was ich ursprünglich machen wollte ...

Mir ist aufgefallen, du die Klasse MyApp von wx.PySimpeApp ableitest, und nicht von wx.App.
Ausserdem hast du self.SetTopWindow nicht drin.

Warum ?

Verfasst: Donnerstag 22. März 2007, 12:17
von gerold
midan23 hat geschrieben:Mir ist aufgefallen, du die Klasse MyApp von wx.PySimpeApp ableitest, und nicht von wx.App.
Ausserdem hast du self.SetTopWindow nicht drin.
Hi midan23!

Ich bin das Verhalten von wx.PySimpleApp gewöhnt und es ist so angepasst, dass man nicht unbedingt eine ``OnInit``-Methode benötigt. wx.App wurde zwar in einer der letzten Versionen auch so umgestellt, dass ``OnInit`` nicht mehr überschrieben werden muss, aber wie geschrieben, habe ich mich schon an wx.PySimpleApp gewöhnt. Dann kommt noch dazu, dass wx.PySimpleApp nicht automatisch ``print``-Anweisungen in ein GUI-Fenster umleitet. Ich persönlich bevorzuge es, wenn die Meldungen in der Konsole ausgegeben werden. Erst später, wenn das Programm evt. nur noch als EXE läuft und deshalb keine Konsolenausgabe mehr zustande kommt, stelle ich den Parameter "redirect" auf True.

``SetTopWindow`` ist schon lange nicht mehr notwendig. Es wird automatisch das erste, von ``wx.TopLevelWindow`` abgeleitete Objekt zum TopWindow erklärt. So wird automatisch das erste Frame oder der erste Dialog zum TopWindow. Nur wenn man nicht möchte, dass das Programm geschlossen wird, wenn das erste Fenster geschlossen wird, dann muss man ein anderes Fenster zum TopWindow erklären.

mfg
Gerold
:-)

Verfasst: Donnerstag 22. März 2007, 13:45
von midan23
Ich seh schon ... da gibts noch einiges das ich lernen kann ...

Aber vorher warte ich auf mein Exemplar von "wxPython in Action" ...

Verfasst: Freitag 23. März 2007, 14:34
von name
midan23 hat geschrieben:Ich seh schon ... da gibts noch einiges das ich lernen kann ...

Aber vorher warte ich auf mein Exemplar von "wxPython in Action" ...
Gute Wahl :)
Das Buch ist einfach gut gemacht, viele Beispiele, Bilder der Widgets etc etc.
Du wirst den Kauf nicht beraeuen :)

Verfasst: Mittwoch 28. März 2007, 12:43
von midan23
Ich habs nicht lassen können ...

Code: Alles auswählen

#!/usr/bin/env pythonw
# -*- coding: utf-8 -*-

import wx
import wx.html
from docutils.core import publish_parts

class ReSTPanel(wx.Panel):
    def __init__(self, parent, edit_first=False, split_vert=False):
        wx.Panel.__init__(self, parent)

        self.splitter = wx.SplitterWindow(self)
        self.splitter.SetMinimumPaneSize(20)
        self.splitter.SetSashGravity(0.5)

        self.show_part = wx.html.HtmlWindow(self.splitter)

        self.edit_part = wx.TextCtrl(self.splitter, style=wx.TE_MULTILINE)
        font = wx.Font(10, wx.MODERN, wx.NORMAL, wx.NORMAL, False)
        self.edit_part.SetFont(font)
        self.edit_part.Bind(wx.EVT_TEXT, self.reset_timer)

        self.set_split(edit_first, split_vert)

        self.parse_timer = wx.Timer()
        self.parse_timer.Bind(wx.EVT_TIMER, self.rst2html)

        sizer = wx.BoxSizer()
        sizer.Add(self.splitter, 1, wx.EXPAND)
        self.SetSizer(sizer)

    def reset_timer(self, event=None):
        self.parse_timer.Stop()
        self.parse_timer.Start(1000, oneShot=True)

    def rst2html(self, event=None):
        txt = self.edit_part.GetValue()
        html = publish_parts(txt, writer_name="html")["html_body"]
        self.show_part.SetPage(html)

    def set_split(self, edit_first=False, split_vertical=False):
        if self.splitter.IsSplit():
            if self.splitter.GetWindow1() is self.show_part and edit_first:
                self.splitter.Unsplit(self.show_part)
            elif self.splitter.GetWindow1 is self.edit_part and not edit_first:
                self.splitter.Unsplit(self.edit_part)
            else:
                self.splitter.Unsplit()
        if split_vertical:
            if edit_first:
                self.splitter.SplitVertically(self.edit_part, self.show_part)
            else:
                self.splitter.SplitVertically(self.show_part, self.edit_part)
        else:
            if edit_first:
                self.splitter.SplitHorizontally(self.edit_part, self.show_part)
            else:
                self.splitter.SplitHorizontally(self.show_part, self.edit_part)
                
    def get_source(self):
        return self.edit_part.GetValue()

    def set_source(self, value):
        self.edit_part.SetValue(value)

    def get_html_parts(self):
        txt = self.edit_part.GetValue()
        return publish_parts(txt, writer_name="html")


class TestFrame(wx.Frame):
    def __init__(self, parent, id, title):
        wx.Frame.__init__(self, parent, id, title=title)

        self.split_top = False
        self.split_ver = False

        but1 = wx.Button(self, -1, "Edit first")
        but1.Bind(wx.EVT_BUTTON, self.edit_first)

        but2 = wx.Button(self, -1, "Edit last")
        but2.Bind(wx.EVT_BUTTON, self.edit_last)
        
        but3 = wx.Button(self, -1, "Horizontal")
        but3.Bind(wx.EVT_BUTTON, self.split_horizontal)

        but4 = wx.Button(self, -1, "Vertical")
        but4.Bind(wx.EVT_BUTTON, self.split_vertical)

        self.rst = ReSTPanel(self, self.split_top, self.split_ver)

        button_sizer = wx.BoxSizer(wx.HORIZONTAL)
        button_sizer.Add(but1, 1, wx.EXPAND)
        button_sizer.Add(but2, 1, wx.EXPAND)
        button_sizer.Add(but3, 1, wx.EXPAND)
        button_sizer.Add(but4, 1, wx.EXPAND)

        frame_sizer = wx.BoxSizer(wx.VERTICAL)
        frame_sizer.Add(button_sizer, 0, wx.EXPAND)
        frame_sizer.Add(self.rst, 1, wx.EXPAND)

        self.SetSizer(frame_sizer)

    def update_split(self):
        self.rst.set_split(self.split_top, self.split_ver)

    def edit_first(self, event):
        self.split_top = True
        self.update_split()

    def edit_last(self, event):
        self.split_top = False
        self.update_split()

    def split_horizontal(self, event):
        self.split_ver = False
        self.update_split()

    def split_vertical(self, event):
        self.split_ver = True
        self.update_split()


def main():
    app = wx.PySimpleApp()
    frame = TestFrame(None, -1, "ReSt-Edit")
    frame.Show(True)
    app.MainLoop()

if __name__ == "__main__":
    main()
Ich hab den Editor nicht mehr von wx.Frame, sondern von wx.Panel abgeleitet, um ihn auch anderweitig verwenden zu können.
Die set_split-Methode sieht mir noch etwas abenteuerlich aus ... ausserdem überlege ich mir gerade, ob ich den ReST-Editor nicht besser von wx.SplitterWindow ableiten sollte ...

Da hab ich offensichtlich noch einiges vor mir ...