fixed bug in on_open.
[rofl0r-HtmlEditor.git] / HtmlEditor.pm
blob4c8e36f19dea476c82fee5acb7040c55bfc1c412
1 package HtmlEditor;
3 #example text editor
4 # Ryan Paul (SegPhault) - 07/12/2009
5 # http://arstechnica.com/open-source/guides/2009/07/how-to-build-a-desktop-wysiwyg-editor-with-webkit-and-html-5.ars
7 # converted to perl by rofl0r
8 # with a little help from http://cpansearch.perl.org/src/GRANTM/App-USBKeyCopyCon-1.02/lib/App/USBKeyCopyCon.pm
10 use strict;
11 use warnings;
12 use Gtk2 -init;
13 use Gtk2::WebKit;
14 use File::Slurp;
16 our @ISA = qw(Gtk2::Window);
18 sub new {
19 my ($pkg) = @_;
20 my $self = $pkg->SUPER::new();
21 $self->set_title("Example Editor");
22 $self->signal_connect(destroy => sub {Gtk2->main_quit; });
23 $self->resize(600,600);
24 $self->{filename} = undef;
25 $self->{editor} = Gtk2::WebKit::WebView->new;
26 $self->{editor}->set_editable(1);
27 $self->{editor}->load_html_string("this is a test.", "file:///");
28 $self->{scroll} = Gtk2::ScrolledWindow->new();
29 $self->{scroll}->add($self->{editor});
30 $self->{scroll}->set_policy('automatic','automatic');
32 bless $self, $pkg;
33 $self->{ui} = $self->generate_ui();
34 $self->add_accel_group($self->{ui}->get_accel_group());
35 $self->{toolbar1} = $self->{ui}->get_widget("/toolbar_main");
36 $self->{toolbar2} = $self->{ui}->get_widget("/toolbar_format");
37 $self->{menubar} = $self->{ui}->get_widget("/menubar_main");
39 $self->{layout} = Gtk2::VBox->new();
40 $self->{layout}->pack_start($self->{menubar}, 0, 0, 0);
41 $self->{layout}->pack_start($self->{toolbar1}, 0, 0, 0);
42 $self->{layout}->pack_start($self->{toolbar2}, 0, 0, 0);
43 $self->{layout}->pack_start($self->{scroll}, 1, 1, 0);
44 $self->add($self->{layout});
46 return $self;
49 sub generate_ui {
50 my ($self) = @_;
51 my $ui_def = <<'UI';
52 <ui>
53 <menubar name="menubar_main">
54 <menu action="menuFile">
55 <menuitem action="new" />
56 <menuitem action="open" />
57 <menuitem action="save" />
58 </menu>
59 <menu action="menuEdit">
60 <menuitem action="cut" />
61 <menuitem action="copy" />
62 <menuitem action="paste" />
63 </menu>
64 <menu action="menuInsert">
65 <menuitem action="insertimage" />
66 <menuitem action="insertvideo" />
67 </menu>
68 <menu action="menuFormat">
69 <menuitem action="bold" />
70 <menuitem action="italic" />
71 <menuitem action="underline" />
72 <menuitem action="strikethrough" />
73 <separator />
74 <menuitem action="font" />
75 <menuitem action="color" />
76 <separator />
77 <menuitem action="justifyleft" />
78 <menuitem action="justifyright" />
79 <menuitem action="justifycenter" />
80 <menuitem action="justifyfull" />
81 </menu>
82 </menubar>
83 <toolbar name="toolbar_main">
84 <toolitem action="new" />
85 <toolitem action="open" />
86 <toolitem action="save" />
87 <separator />
88 <toolitem action="undo" />
89 <toolitem action="redo" />
90 <separator />
91 <toolitem action="cut" />
92 <toolitem action="copy" />
93 <toolitem action="paste" />
94 </toolbar>
95 <toolbar name="toolbar_format">
96 <toolitem action="bold" />
97 <toolitem action="italic" />
98 <toolitem action="underline" />
99 <toolitem action="strikethrough" />
100 <separator />
101 <toolitem action="font" />
102 <toolitem action="color" />
103 <separator />
104 <toolitem action="justifyleft" />
105 <toolitem action="justifyright" />
106 <toolitem action="justifycenter" />
107 <toolitem action="justifyfull" />
108 <separator />
109 <toolitem action="insertimage" />
110 <toolitem action="insertvideo" />
111 <toolitem action="insertlink" />
112 </toolbar>
113 </ui>
115 my $actions = Gtk2::ActionGroup->new("Actions");
117 my @menu_entries = (
118 # name, stock id, label
119 ["menuFile", undef, "_File"],
120 ["menuEdit", undef, "_Edit"],
121 ["menuInsert", undef, "_Insert"],
122 ["menuFormat", undef, "_Format"],
123 # name, stock id, label, accelerator, tooltip, action
124 ["new", "gtk-new", "_New", undef, undef, "new"],
125 ["open", "gtk-open", "_Open", undef, undef, "open"],
126 ["save", "gtk-save", "_Save", undef, undef, "save"],
128 ["undo", "gtk-undo", "_Undo", undef, undef, "action"],
129 ["redo", "gtk-redo", "_Redo", undef, undef, "action"],
131 ["cut", "gtk-cut", "_Cut", undef, undef, "action"],
132 ["copy", "gtk-copy", "_Copy", undef, undef, "action"],
133 ["paste", "gtk-paste", "_Paste", undef, undef, "paste"],
135 ["bold", "gtk-bold", "_Bold", "<ctrl>B", undef, "action"],
136 ["italic", "gtk-italic", "_Italic", "<ctrl>I", undef, "action"],
137 ["underline", "gtk-underline", "_Underline", "<ctrl>U", undef, "action"],
138 ["strikethrough", "gtk-strikethrough", "_Strike", "<ctrl>T", undef, "action"],
139 ["font", "gtk-select-font", "Select _Font", "<ctrl>F", undef, "select_font"],
140 ["color", "gtk-select-color", "Select _Color", undef, undef, "select_color"],
142 ["justifyleft", "gtk-justify-left", "Justify _Left", undef, undef, "action"],
143 ["justifyright", "gtk-justify-right", "Justify _Right", undef, undef, "action"],
144 ["justifycenter", "gtk-justify-center", "Justify _Center", undef, undef, "action"],
145 ["justifyfull", "gtk-justify-fill", "Justify _Full", undef, undef, "action"],
147 ["insertimage", "insert-image", "Insert _Image", undef, undef, "insert_image"],
148 ["insertvideo", "insert-video", "Insert _Video", undef, undef, "insert_video"],
149 ["insertlink", "insert-link", "Insert _Link", undef, undef, "insert_link"]
152 foreach my $item (@menu_entries) {
153 if(exists $item->[5]) {
154 my $action = 'on_' . $item->[5];
155 $item->[5] = sub { $self->$action(@_) };
159 $actions->add_actions(\@menu_entries, undef);
160 $actions->get_action("insertimage")->set_property("icon-name", "insert-image");
161 $actions->get_action("insertvideo")->set_property("icon-name", "insert-object");
162 $actions->get_action("insertlink")->set_property("icon-name", "insert-link");
164 my $ui = Gtk2::UIManager->new;
165 $ui->insert_action_group($actions, 0);
166 $ui->add_ui_from_string ($ui_def);
167 return $ui;
170 sub on_action {
171 my($self, $action) = @_;
172 my $cmd = sprintf("document.execCommand('%s', false, false);", $action->get_name());
173 $self->{editor}->execute_script($cmd);
176 sub on_paste {
177 my($self, $action) = @_;
178 $self->{editor}->paste_clipboard();
181 sub on_new {
182 my($self, $action) = @_;
183 $self->{editor}->load_html_string("", "file:///");
186 sub on_select_font {
187 my($self, $action) = @_;
188 my $dialog = Gtk2::FontSelectionDialog->new("Select a font");
189 if ($dialog->run() eq "ok") {
190 my $font = $dialog->get_font_name();
191 my $fd = Pango::FontDescription->from_string($font);
192 my $fname = $fd->get_family();
193 my $fsize = $fd->get_size();
194 my $cmd = sprintf("document.execCommand('fontname', null, '%s');", $fname);
195 $self->{editor}->execute_script($cmd);
196 $cmd = sprintf("document.execCommand('fontsize', null, '%s');", $fsize);
197 $self->{editor}->execute_script($cmd);
199 $dialog->destroy();
202 sub on_select_color {
203 my($self, $action) = @_;
204 my $dialog = Gtk2::ColorSelectionDialog->new("Select Color");
205 if ($dialog->run() eq "ok") {
206 my $gc = $dialog->colorsel->get_current_color();
207 my $color = sprintf("#%02x%02x%02x", $gc->red % 256, $gc->green % 256 , $gc->blue % 256);
208 my $cmd = sprintf("document.execCommand('forecolor', null, '%s');", $color);
209 $self->{editor}->execute_script($cmd);
211 $dialog->destroy();
214 sub on_insert_link {
215 my($self, $action) = @_;
216 my $dialog = Gtk2::Dialog->new("Enter a URL:", $self, [qw(modal destroy-with-parent)],
217 "gtk-cancel" => 'cancel', "gtk-ok" => 'ok');
218 my $entry = Gtk2::Entry->new();
219 $dialog->vbox->pack_start($entry,0,0,0);
220 $dialog->show_all();
221 if ($dialog->run() eq "ok") {
222 my $cmd = sprintf("document.execCommand('createLink', true, '%s');", $entry->get_text());
223 $self->{editor}->execute_script($cmd);
225 $dialog->destroy();
228 sub on_insert_image {
229 my($self, $action) = @_;
230 my $dialog = Gtk2::FileChooserDialog->new("Select an image file", $self, 'open',
231 "gtk-cancel" => 'cancel', "gtk-open" => 'ok');
232 if ($dialog->run() eq "ok") {
233 my $fn = $dialog->get_filename();
234 if (-e $fn) {
235 my $cmd = sprintf("document.execCommand('insertImage', null, '%s');", $fn);
236 $self->{editor}->execute_script($cmd);
239 $dialog->destroy();
242 sub on_insert_video {
243 my($self, $action) = @_;
246 sub on_open {
247 my($self, $action) = @_;
248 my $dialog = Gtk2::FileChooserDialog->new("Select a HTML file", $self, 'open',
249 "gtk-cancel" => 'cancel', "gtk-open" => 'ok');
250 if ($dialog->run() eq "ok") {
251 my $fn = $dialog->get_filename();
252 if (-e $fn) {
253 $self->{filename} = $fn;
254 $self->{editor}->load_html_string(read_file($fn), "file:///");
257 $dialog->destroy();
260 sub on_save {
261 my($self, $action) = @_;
262 if(defined($self->{filename})) {
263 write_file($self->{filename}, $self->get_html());
264 } else {
265 my $dialog = Gtk2::FileChooserDialog->new("Select a HTML file", $self, 'save',
266 "gtk-cancel" => 'cancel', "gtk-save" => 'ok');
267 if($dialog->run() eq "ok") {
268 $self->{filename} = $dialog->get_filename();
269 write_file($self->{filename}, $self->get_html());
271 $dialog->destroy();
275 sub get_html {
276 my($self) = @_;
277 $self->{editor}->execute_script("oldtitle=document.title;document.title=document.documentElement.innerHTML;");
278 my $html = $self->{editor}->get_main_frame()->get_title();
279 $self->{editor}->execute_script("document.title=oldtitle;");
280 return $html;