commit 7cdbd0cf4e898cd41092b5c8d5ff442cba1c9295 Author: Sebastian Reichel Date: Fri Dec 18 22:48:12 2009 +0100 initial import diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a61d864 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +src/pdf-presenter +src/*.c +*.swp diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..d914c0e --- /dev/null +++ b/Makefile @@ -0,0 +1,10 @@ +all: + cd src ; make all + +clean: + cd src ; make clean + +install: + cd src ; make install + +.PHONY: all clean install diff --git a/README b/README new file mode 100644 index 0000000..9b66f6e --- /dev/null +++ b/README @@ -0,0 +1,6 @@ +pdf-presenter is an advanved pdf presentation utility. It is +supposed to be used in an environment with multiple monitors +(e.g. a notebook monitor and a projector). These must be in +non cloning mode. If you have such a system and you want +to display your PDF presentation you will probably like +this program. diff --git a/TODO b/TODO new file mode 100644 index 0000000..400304d --- /dev/null +++ b/TODO @@ -0,0 +1,10 @@ + * Configuration Window + - Fullscreen Option for Control Window [checkbutton] + - Auto Transition [checkbutton] + - Monitor Selection Control [combobox] + - Monitor Selection Presentation [combobox] + - Keybindings + * Add Notes Mode + * Add Slide Overview Mode + * Use Monitor Class + * Improved Build System diff --git a/src/ControlWindow.vala b/src/ControlWindow.vala new file mode 100644 index 0000000..3abf6fc --- /dev/null +++ b/src/ControlWindow.vala @@ -0,0 +1,336 @@ +/* + * pdf-presenter - advanced pdf presentation util + * + * Copyright (C) 2009 Sebastian Reichel + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +using Gtk; +using Cairo; + +class ControlToolbar : Toolbar { + private ToolButton prev; + private Entry entry; + private ToolButton next; + private ToolButton notes; + private ToolButton slides; + private Label curr_time; + private Label pres_time; + private ToolButton config; + + private int page = 1; + private int pages = 0; + + private int timestamp1 = 0; + private int timestamp2 = 0; + + public signal void clicked(string button); + public signal void page_changed(int page); + + public ControlToolbar(int n_pages) { + /* Toolbar Configuration */ + set_show_arrow(false); + + pages = n_pages; + + /* Init Toolbar Elements */ + prev = new ToolButton.from_stock(STOCK_GO_BACK); + entry = new Entry(); + entry.set_width_chars(3); + next = new ToolButton.from_stock(STOCK_GO_FORWARD); + notes = new ToolButton.from_stock(STOCK_FILE); + notes.set_label("Notes"); + slides = new ToolButton.from_stock(STOCK_DIALOG_INFO); + slides.set_label("Slides"); + config = new ToolButton.from_stock(STOCK_PREFERENCES); + curr_time = new Label("Time"); + pres_time = new Label("Duration"); + pres_time.set_markup("Duration"); + + var separator1 = new SeparatorToolItem(); + var separator2 = new SeparatorToolItem(); + + var entry_container = new ToolItem(); + entry_container.add(entry); + + var vbox_container = new ToolItem(); + var vbox = new VBox(false, 0); + vbox.add(curr_time); + vbox.add(pres_time); + vbox_container.add(vbox); + + /* Fill Toolbar */ + add(prev); + add(entry_container); + add(next); + add(notes); + add(slides); + add(separator1); + add(vbox_container); + add(separator2); + add(config); + + update_toolbar(); + + /* we don't want focus on a button */ + entry.grab_focus(); + + /* update clock */ + Timeout.add_seconds(1,update_time); + timestamp2 = (int) time_t(); + update_time(); + + /* bind button signals */ + next.clicked.connect(() => {clicked("next");}); + prev.clicked.connect(() => {clicked("previous");}); + config.clicked.connect(() => {clicked("config");}); + + this.clicked.connect(button_handler); + entry.key_press_event.connect(entry_handler); + } + + bool entry_handler(Gdk.EventKey event) { + if(event.keyval == Gdk.keyval_from_name("Return")) { + int new_page = entry.get_text().to_int(); + if(page < 1) page = 1; + if(page > pages) page = pages; + set_page(new_page); + page_changed(new_page-1); + return true; + } + if(event.keyval < '0' || event.keyval > '9' && event.keyval != Gdk.keyval_from_name("BackSpace")) { + return true; + } + return false; + } + + void button_handler(string btn) { + if(btn == "next") + set_page(page+1); + if(btn == "previous") + set_page(page-1); + + page_changed(page-1); + } + + string int2time(int timestamp, bool local=false) { + Time t; + + if(local) + t = Time.local((time_t) timestamp); + else + t = Time.gm((time_t) timestamp); + + return "%02d:%02d:%02d".printf(t.hour, t.minute, t.second); + } + + private bool update_time() { + timestamp1++; + timestamp2++; + + pres_time.set_markup(""+int2time(timestamp1)+""); + curr_time.set_text(int2time(timestamp2,true)); + + return true; + } + + private void update_toolbar() { + if(page <= 1) prev.set_sensitive(false); + else prev.set_sensitive(true); + + if(page >= pages) next.set_sensitive(false); + else next.set_sensitive(true); + + entry.set_text("%d".printf(page)); + } + + public void set_page(int n) { + page = n; + update_toolbar(); + } +} + +class ControlView : DrawingArea { + Poppler.Document document; + int page = 0; + int pages = 0; + int window_w; + int window_h; + + public ControlView (Poppler.Document doc) { + document = doc; + pages = document.get_n_pages(); + this.expose_event.connect(draw_view); + } + + public void set_page(int n) { + page = n; + redraw(); + } + + public void redraw() { + unowned Gdk.Region r = this.window.get_clip_region(); + this.window.invalidate_region(r, true); + this.window.process_updates(true); + } + + public bool draw_view(Widget w, Gdk.EventExpose e) { + var ctx = Gdk.cairo_create(w.window); + + int width = current_slide(ctx, w.window); + int height = small_slide(ctx, w.window, width, 0, "Next Slide", page+1); + small_slide(ctx, w.window, width, height, "Previous Slide", page-1); + + return true; + } + + /* returns the width of the box */ + public int current_slide(Context ctx, Gdk.Window w) { + var ctx2 = Gdk.cairo_create(w); + var poppler_page = document.get_page(page); + + int width, height; + double width2, height2; + poppler_page.get_size(out width2, out height2); + width = (int) width2; + height = (int) height2; + + get_parent_window().get_size(out window_w, out window_h); + + double wf = (window_w * 11/20) / width2; + double hf = (window_h * 7/10) / height2; + + double faktor = wf > hf ? hf : wf; + + ctx2.translate(27,25+14+2); + ctx2.scale(faktor, faktor); + poppler_page.render(ctx2); + + ctx.set_source_surface(ctx2.get_target(), 0, 0); + ctx.stroke(); + + SlideBox(ctx, "Current Slide (%d of %d)".printf(page+1, pages), 25, 25, (int) (width*faktor), (int) (height*faktor)); + ctx.stroke(); + + return (int) (width*faktor)+25; + } + + public int small_slide(Context ctx, Gdk.Window win, int x_diff, int y_diff, string msg, int n) { + int x = x_diff + 25; // 25px right of the last item + int y = y_diff + 25; // 25px below of the last item + int w,h; + + get_parent_window().get_size(out w, out h); + w -= x + 25; // 25px from the right window border + + if((n > page && n < pages) || (n < page && n >= 0)) { + var ctx2 = Gdk.cairo_create(win); + var poppler_page = document.get_page(n); + + int width, height; + double width2, height2; + poppler_page.get_size(out width2, out height2); + width = (int) width2; + height = (int) height2; + + double wf = (w) / width2; + double hf = (h/2-50-28) / height2; + double faktor = wf > hf ? hf : wf; + + ctx2.translate(x+2,y+14+2); + ctx2.scale(faktor, faktor); + poppler_page.render(ctx2); + + ctx.set_source_surface(ctx2.get_target(), 0, 0); + ctx.stroke(); + + SlideBox(ctx, msg, x, y, (int) (width*faktor), (int) (height*faktor)); + ctx.stroke(); + + return (int) (height*faktor)+25+14; + } else { + /* No Slide */ + } + + return 0; + } + + public void cairo_write(Context ctx, int x, int y, string text) { + ctx.move_to(x,y); + ctx.show_text(text); + } + + public void SlideBox(Context ctx, string msg, int x, int y, int w, int h) { + ctx.set_source_rgb(0, 0, 0); + ctx.set_line_width(4); + ctx.set_tolerance(0.1); + ctx.set_line_join(LineJoin.ROUND); + + ctx.translate(x, y); + + ctx.new_path(); + ctx.select_font_face("", FontSlant.NORMAL, FontWeight.BOLD); + ctx.set_font_size(14.0); + cairo_write(ctx, 0, 0, msg); + + ctx.translate(0, 14); + + ctx.move_to (0, 0); + ctx.rel_line_to (w+3, 0); + ctx.rel_line_to (0, h+3); + ctx.rel_line_to (-w-3, 0); + ctx.close_path (); + + ctx.translate(-x, -y-14); + } + +} + +class ControlWindow : Window { + public void button_clicked(string btn) { + if(btn != "next" && btn != "previous") + message("%s clicked!", btn); + } + + public signal void page_changed(int page); + + public ControlWindow(Poppler.Document document) { + this.title = "PDF Presentator (Control)"; + this.position = WindowPosition.CENTER; + this.destroy.connect(Gtk.main_quit); + set_default_size(800, 600); + var vbox = new VBox(false, 0); + + var drawingarea = new ControlView(document); + + var aspect = new AspectFrame("Control Bar", (float) 0.5, (float) 1.0, (float) 0.5, true); + var toolbar = new ControlToolbar(document.get_n_pages()); + aspect.add(toolbar); + + vbox.pack_start(drawingarea, true, true, 0); + vbox.pack_start(aspect, false, true, 0); + + add(vbox); + + toolbar.clicked.connect(button_clicked); + toolbar.page_changed.connect(drawingarea.set_page); + toolbar.page_changed.connect((n) => {page_changed(n);}); + } +} diff --git a/src/ErrorDialog.vala b/src/ErrorDialog.vala new file mode 100644 index 0000000..38275f0 --- /dev/null +++ b/src/ErrorDialog.vala @@ -0,0 +1,43 @@ +/* + * pdf-presenter - advanced pdf presentation util + * + * Copyright (C) 2009 Sebastian Reichel + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +using Gtk; + +class ErrorDialog : Dialog { + public ErrorDialog(string title, string message) { + var label = new Label(message); + var hbox = new HBox(false, 0); + var img = new Image.from_stock(STOCK_DIALOG_ERROR, IconSize.DIALOG); + hbox.pack_start(img, false, true, 10); + hbox.pack_start(label, false, true, 10); + + this.set_icon_name("gtk-dialog-error"); + this.title = title; + this.vbox.pack_start(hbox, false, true, 10); + this.add_button (STOCK_CLOSE, ResponseType.CLOSE); + this.destroy.connect(Gtk.main_quit); + this.response.connect(Gtk.main_quit); + this.show_all(); + } +} diff --git a/src/Makefile b/src/Makefile new file mode 100644 index 0000000..20aa21e --- /dev/null +++ b/src/Makefile @@ -0,0 +1,15 @@ +PREFIX:= +BINARY:=pdf-presenter + +all: $(BINARY) + +$(BINARY): *.vala + valac -o $(BINARY) --pkg gtk+-2.0 --pkg poppler-glib *.vala + +clean: + rm -f $(BINARY) + +install: $(BINARY) + install -m 755 $(BINARY) $(PREFIX)/usr/bin/$(BINARY) + +.PHONY: all clean install diff --git a/src/Monitor.vala b/src/Monitor.vala new file mode 100644 index 0000000..82d0b16 --- /dev/null +++ b/src/Monitor.vala @@ -0,0 +1,104 @@ +/* + * pdf-presenter - advanced pdf presentation util + * + * Copyright (C) 2009 Sebastian Reichel + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +class Monitor { + Gdk.Screen screen; + string[] monitors; + + signal void connected(string name); + signal void disconnected(string name); + + public Monitor(Gdk.Screen scr) { + screen = scr; + monitors = get_list(); + screen.monitors_changed.connect(on_monitor_changed); + } + + private string[] get_list() { + string[] output = {}; + for(int i=0; i + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +using Gtk; + +class PresentationView : DrawingArea { + Poppler.Document doc; + int page; + + public PresentationView(Poppler.Document document, int n) { + doc = document; + page = n; + this.expose_event.connect(draw_view); + } + + public void redraw() { + unowned Gdk.Region r = this.window.get_clip_region(); + this.window.invalidate_region(r, true); + this.window.process_updates(true); + } + + public void set_page(int n) { + page = n; + redraw(); + } + + public bool draw_view(Widget w, Gdk.EventExpose e) { + int ww = 0, wh = 0; + int x = 0, y = 0; + var ctx = Gdk.cairo_create(w.window); + var poppler_page = doc.get_page(page); + + /* get window size */ + get_parent_window().get_size(out ww, out wh); + + /* get document size */ + double width2, height2; + poppler_page.get_size(out width2, out height2); + + /* calculate faktor */ + double wf = ww / width2; + double hf = wh / height2; + double faktor = wf > hf ? hf : wf; + + /* center image */ + int width = (int) (faktor*width2); + int height = (int) (faktor*height2); + if(ww > width) + x = (ww-width)/2; + if(wh > height) + y = (wh-height)/2; + ctx.translate(x,y); + + /* scale page */ + ctx.scale(faktor, faktor); + + /* draw page */ + poppler_page.render(ctx); + ctx.stroke(); + + /* black background */ + ctx.translate(-x,-y); + ctx.set_source_rgb(0, 0, 0); + if(x > 0) { + ctx.rectangle(0, 0, x, (int)height2+1); + ctx.fill(); + ctx.rectangle(x+(int)width2, 0, x, (int)height2+1); + ctx.fill(); + } + if(y > 0) { + ctx.rectangle(0, 0, (int)width2+1, y); + ctx.fill(); + ctx.rectangle(0, y+(int)height2, (int)width2+1, y); + ctx.fill(); + } + + return true; + } +} + +class PresentationWindow : Window { + private PresentationView view; + + public PresentationWindow(Poppler.Document doc, int page) { + title = "PDF Presentator (View)"; + position = WindowPosition.CENTER; + view = new PresentationView(doc, page); + add(view); + fullscreen(); + + /* FIXME: fullscreen, correct display */ + set_default_size(800,600); + + return; + } + + public void set_page(int n) { + view.set_page(n); + } +} diff --git a/src/pdf-presenter.vala b/src/pdf-presenter.vala new file mode 100644 index 0000000..5d1f13a --- /dev/null +++ b/src/pdf-presenter.vala @@ -0,0 +1,77 @@ +/* + * pdf-presenter - advanced pdf presentation util + * + * Copyright (C) 2009 Sebastian Reichel + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +int main(string[] args) { + /* FIXME: use first parameter */ + string filename; + Poppler.Document document = null; + Gtk.init(ref args); + + if(args.length != 2) { + new ErrorDialog("Wrong Usage", "Usage: %s ".printf(args[0])); + Gtk.main(); + return 1; + } + + filename = args[1]; + + try { + filename = Filename.to_uri(filename); + document = new Poppler.Document.from_file(filename, ""); + } catch(GLib.ConvertError e) { + new ErrorDialog("This is not a correct filename", "Please specify a valid filename."); + Gtk.main(); + return 1; + } catch(GLib.Error e) { + new ErrorDialog("File not found", e.message); + Gtk.main(); + return 1; + } + + /* Monitor class can be used like this + Gdk.Display dp = Gdk.Display.get_default(); + Gdk.Screen screen = dp.get_screen(0); + Monitor m = new Monitor(screen); + m.connected.connect(add_display); + m.disconnected.connect(del_display); + + foreach(string s in m.get_all()) { + int x,y,w,h; + m.get_size(s, out x, out y, out w, out h); + debug("%s: [%d/%d] [%d/%d]",s,x,y,w,h); + } + */ + + var control_window = new ControlWindow(document); + control_window.show_all(); + + /* uncomment to get presentation window + var presentation_window = new PresentationWindow(document, 0); + presentation_window.show_all(); + control_window.page_changed.connect((n) => {presentation_window.set_page(n);}); + */ + + Gtk.main(); + return 0; +}