Page 1 of 1

PyGTK: Desktop WYSIWYG editor with WebKit & HTML5

Posted: Sat Nov 30, 2013 9:00 am
by mikethedj4
Tutorial: http://arstechnica.com/information-tech ... nd-html-5/

Found this while searching on how to make a WYSIWYG text editor for iframes. I thought I'd share as this maybe helpful for someone.
Code: Select all
#!/usr/bin/env python

#
# Example Text Editor
# Ryan Paul (SegPhault) - 07/12/2009
#

import gtk, webkit, os

class ExampleEditor(gtk.Window):
  def __init__(self):
    gtk.Window.__init__(self)
    self.set_title("A Basic WYSIWYG Editor")
    self.connect("destroy", gtk.main_quit)
    self.resize(500, 500)
    self.filename = None

    self.editor = webkit.WebView()
    self.editor.set_editable(True)
    self.editor.load_html_string("Learn how to make this at - <a href='http://arstechnica.com/information-technology/2009/07/how-to-build-a-desktop-wysiwyg-editor-with-webkit-and-html-5/'>http://arstechnica.com/information-technology/2009/07/how-to-build-a-desktop-wysiwyg-editor-with-webkit-and-html-5/</a><br><br> Hello World!", "file:///")

    scroll = gtk.ScrolledWindow()
    scroll.add(self.editor)
    scroll.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)

    self.ui = self.generate_ui()
    self.add_accel_group(self.ui.get_accel_group())
    self.toolbar1 = self.ui.get_widget("/toolbar_main")
    self.toolbar2 = self.ui.get_widget("/toolbar_format")
    self.menubar = self.ui.get_widget("/menubar_main")

    self.layout = gtk.VBox()
    self.layout.pack_start(self.menubar, False)
    self.layout.pack_start(self.toolbar1, False)
    self.layout.pack_start(self.toolbar2, False)
    self.layout.pack_start(scroll, True, True)
    self.add(self.layout)

  def generate_ui(self):
    ui_def = """
    <ui>
      <menubar name="menubar_main">
        <menu action="menuFile">
          <menuitem action="new" />
          <menuitem action="open" />
          <menuitem action="save" />
        </menu>
        <menu action="menuEdit">
          <menuitem action="cut" />
          <menuitem action="copy" />
          <menuitem action="paste" />
        </menu>
        <menu action="menuInsert">
          <menuitem action="insertimage" />
        </menu>
        <menu action="menuFormat">
          <menuitem action="bold" />
          <menuitem action="italic" />
          <menuitem action="underline" />
          <menuitem action="strikethrough" />
          <separator />
          <menuitem action="font" />
          <menuitem action="color" />
          <separator />
          <menuitem action="justifyleft" />
          <menuitem action="justifyright" />
          <menuitem action="justifycenter" />
          <menuitem action="justifyfull" />
        </menu>
      </menubar>
      <toolbar name="toolbar_main">
        <toolitem action="new" />
        <toolitem action="open" />
        <toolitem action="save" />
        <separator />
        <toolitem action="undo" />
        <toolitem action="redo" />
        <separator />
        <toolitem action="cut" />
        <toolitem action="copy" />
        <toolitem action="paste" />
      </toolbar>
      <toolbar name="toolbar_format">
        <toolitem action="bold" />
        <toolitem action="italic" />
        <toolitem action="underline" />
        <toolitem action="strikethrough" />
        <separator />
        <toolitem action="font" />
        <toolitem action="color" />
        <separator />
        <toolitem action="justifyleft" />
        <toolitem action="justifyright" />
        <toolitem action="justifycenter" />
        <toolitem action="justifyfull" />
        <separator />
        <toolitem action="insertimage" />
        <toolitem action="insertlink" />
      </toolbar>
    </ui>
    """

    actions = gtk.ActionGroup("Actions")
    actions.add_actions([
      ("menuFile", None, "_File"),
      ("menuEdit", None, "_Edit"),
      ("menuInsert", None, "_Insert"),
      ("menuFormat", None, "_Format"),

      ("new", gtk.STOCK_NEW, "_New", None, None, self.on_new),
      ("open", gtk.STOCK_OPEN, "_Open", None, None, self.on_open),
      ("save", gtk.STOCK_SAVE, "_Save", None, None, self.on_save),

      ("undo", gtk.STOCK_UNDO, "_Undo", None, None, self.on_action),
      ("redo", gtk.STOCK_REDO, "_Redo", None, None, self.on_action),

      ("cut", gtk.STOCK_CUT, "_Cut", None, None, self.on_action),
      ("copy", gtk.STOCK_COPY, "_Copy", None, None, self.on_action),
      ("paste", gtk.STOCK_PASTE, "_Paste", None, None, self.on_paste),

      ("bold", gtk.STOCK_BOLD, "_Bold", "<ctrl>B", None, self.on_action),
      ("italic", gtk.STOCK_ITALIC, "_Italic", "<ctrl>I", None, self.on_action),
      ("underline", gtk.STOCK_UNDERLINE, "_Underline", "<ctrl>U", None, self.on_action),
      ("strikethrough", gtk.STOCK_STRIKETHROUGH, "_Strike", "<ctrl>T", None, self.on_action),
      ("font", gtk.STOCK_SELECT_FONT, "Select _Font", "<ctrl>F", None, self.on_select_font),
      ("color", gtk.STOCK_SELECT_COLOR, "Select _Color", None, None, self.on_select_color),

      ("justifyleft", gtk.STOCK_JUSTIFY_LEFT, "Justify _Left", None, None, self.on_action),
      ("justifyright", gtk.STOCK_JUSTIFY_RIGHT, "Justify _Right", None, None, self.on_action),
      ("justifycenter", gtk.STOCK_JUSTIFY_CENTER, "Justify _Center", None, None, self.on_action),
      ("justifyfull", gtk.STOCK_JUSTIFY_FILL, "Justify _Full", None, None, self.on_action),

      ("insertimage", "insert-image", "Insert _Image", None, None, self.on_insert_image),
      ("insertlink", "insert-link", "Insert _Link", None, None, self.on_insert_link),
    ])

    actions.get_action("insertimage").set_property("icon-name", "insert-image")
    actions.get_action("insertlink").set_property("icon-name", "insert-link")

    ui = gtk.UIManager()
    ui.insert_action_group(actions)
    ui.add_ui_from_string(ui_def)
    return ui

  def on_action(self, action):
    self.editor.execute_script(
      "document.execCommand('%s', false, false);" % action.get_name())

  def on_paste(self, action):
    self.editor.paste_clipboard()

  def on_new(self, action):
    self.editor.load_html_string("", "file:///")

  def on_select_font(self, action):
    dialog = gtk.FontSelectionDialog("Select a font")
    if dialog.run() == gtk.RESPONSE_OK:
      fname, fsize = dialog.fontsel.get_family().get_name(), dialog.fontsel.get_size()
      self.editor.execute_script("document.execCommand('fontname', null, '%s');" % fname)
      self.editor.execute_script("document.execCommand('fontsize', null, '%s');" % fsize)
    dialog.destroy()

  def on_select_color(self, action):
    dialog = gtk.ColorSelectionDialog("Select Color")
    if dialog.run() == gtk.RESPONSE_OK:
      gc = str(dialog.colorsel.get_current_color())
      color = "#" + "".join([gc[1:3], gc[5:7], gc[9:11]])
      self.editor.execute_script("document.execCommand('forecolor', null, '%s');" % color)
    dialog.destroy()

  def on_insert_link(self, action):
    dialog = gtk.Dialog("Enter a URL:", self, 0,
      (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OK, gtk.RESPONSE_OK))

    entry = gtk.Entry()
    dialog.vbox.pack_start(entry)
    dialog.show_all()

    if dialog.run() == gtk.RESPONSE_OK:
      self.editor.execute_script(
        "document.execCommand('createLink', true, '%s');" % entry.get_text())
    dialog.destroy()

  def on_insert_image(self, action):
    dialog = gtk.FileChooserDialog("Select an image file", self, gtk.FILE_CHOOSER_ACTION_OPEN,
      (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OPEN, gtk.RESPONSE_OK))

    if dialog.run() == gtk.RESPONSE_OK:
      fn = dialog.get_filename()
      if os.path.exists(fn):
        self.editor.execute_script(
          "document.execCommand('insertImage', null, '%s');" % fn)
    dialog.destroy()

  def on_open(self, action):
    dialog = gtk.FileChooserDialog("Select an HTML file", self, gtk.FILE_CHOOSER_ACTION_OPEN,
      (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OPEN, gtk.RESPONSE_OK))

    if dialog.run() == gtk.RESPONSE_OK:
      fn = dialog.get_filename()
      if os.path.exists(fn):
        self.filename = fn
        with open(fn) as fd:
          self.editor.load_html_string(fd.read(), "file:///")
    dialog.destroy()

  def on_save(self, action):
    if self.filename:
      with open(self.filename) as fd: fd.write(self.get_html())
    else:
      dialog = gtk.FileChooserDialog("Select an HTML file", self, gtk.FILE_CHOOSER_ACTION_SAVE,
        (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_SAVE, gtk.RESPONSE_OK))

      if dialog.run() == gtk.RESPONSE_OK:
        self.filename = dialog.get_filename()
        with open(self.filename, "w+") as fd: fd.write(self.get_html())
      dialog.destroy()

  def get_html(self):
    self.editor.execute_script("document.title=document.documentElement.innerHTML;")
    return self.editor.get_main_frame().get_title()

e = ExampleEditor()
e.show_all()
gtk.main()