/* * 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);}); } }