1/*
2 *  RenderProgress.java
3 *  Copyright (C) 2005 Amin Ahmad.
4 *
5 *  This library is free software; you can redistribute it and/or
6 *  modify it under the terms of the GNU Lesser General Public
7 *  License as published by the Free Software Foundation; either
8 *  version 2.1 of the License, or (at your option) any later version.
9 *
10 *  This library is distributed in the hope that it will be useful,
11 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13 *  Lesser General Public License for more details.
14 *
15 *  You should have received a copy of the GNU Lesser General Public
16 *  License along with this library; if not, write to the Free Software
17 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
18 *
19 *  Amin Ahmad can be contacted at amin.ahmad@gmail.com or on the web at
20 *  www.ahmadsoft.org.
21 */
22package org.ahmadsoft.foprocessor.ui.dialogs;
23
24import java.io.ByteArrayOutputStream;
25import java.io.PrintStream;
26import java.lang.reflect.InvocationTargetException;
27import java.text.SimpleDateFormat;
28import java.util.Date;
29import java.util.List;
30import java.util.concurrent.CountDownLatch;
31import java.util.logging.Handler;
32import java.util.logging.Level;
33import java.util.logging.LogRecord;
34import java.util.logging.Logger;
35
36import org.ahmadsoft.foprocessor.FoProcessorPlugin;
37import org.ahmadsoft.foprocessor.core.FileRenderSpecification;
38import org.ahmadsoft.foprocessor.operations.ConversionOperation;
39import org.eclipse.core.resources.IProject;
40import org.eclipse.core.runtime.CoreException;
41import org.eclipse.core.runtime.IProgressMonitor;
42import org.eclipse.debug.ui.IDebugUIConstants;
43import org.eclipse.debug.ui.console.ConsoleColorProvider;
44import org.eclipse.jface.dialogs.IDialogConstants;
45import org.eclipse.jface.dialogs.TitleAreaDialog;
46import org.eclipse.jface.operation.IRunnableContext;
47import org.eclipse.jface.operation.IRunnableWithProgress;
48import org.eclipse.jface.operation.ModalContext;
49import org.eclipse.jface.resource.JFaceResources;
50import org.eclipse.jface.text.BadLocationException;
51import org.eclipse.jface.text.Document;
52import org.eclipse.jface.text.ITextViewer;
53import org.eclipse.jface.text.TextViewer;
54import org.eclipse.swt.SWT;
55import org.eclipse.swt.custom.StyleRange;
56import org.eclipse.swt.graphics.Color;
57import org.eclipse.swt.graphics.Image;
58import org.eclipse.swt.graphics.Point;
59import org.eclipse.swt.layout.GridData;
60import org.eclipse.swt.layout.GridLayout;
61import org.eclipse.swt.widgets.Button;
62import org.eclipse.swt.widgets.Composite;
63import org.eclipse.swt.widgets.Control;
64import org.eclipse.swt.widgets.Display;
65import org.eclipse.swt.widgets.Label;
66import org.eclipse.swt.widgets.ProgressBar;
67import org.eclipse.swt.widgets.Shell;
68
69/**
70 * @author Amin Ahmad
71 */
72public class RenderProgress extends TitleAreaDialog implements IRunnableContext, ConversionOperation.Listener {
73
74    private Image image = null;
75    private ITextViewer console;
76    private Document document;
77
78    private ProgressBar progressBar;
79    private Label consoleTitle;
80
81    private ConsoleColorProvider colorProvider = new ConsoleColorProvider();
82
83    private CountDownLatch createSignal = new CountDownLatch(1);
84
85    private final int RUN_BACKGROUND_ID = 3141;
86	private List<FileRenderSpecification> renderSpecs;
87
88	private volatile boolean cancelled;
89
90    /**
91     * @param parentShell
92     */
93    public RenderProgress(Shell parentShell, List<FileRenderSpecification> renderSpecs) {
94        super(parentShell);
95        setShellStyle(SWT.TITLE | SWT.BORDER | SWT.APPLICATION_MODAL | SWT.RESIZE);
96        this.renderSpecs = renderSpecs;
97    }
98
99    protected Point getInitialSize() {
100        return getShell().computeSize(460, 500, true);
101    }
102
103    protected Control createContents(Composite parent) {
104        Control result = super.createContents(parent);
105
106        createSignal.countDown();
107
108        return result;
109    }
110
111    protected void createButtonsForButtonBar(Composite parent) {
112//        Button btnBackground = createButton(parent, RUN_BACKGROUND_ID, "Run in &Background",
113//                false);
114//        btnBackground.setEnabled(false);
115    	super.createButtonsForButtonBar(parent);
116    }
117
118    protected Control createDialogArea(Composite parent) {
119        Composite compositeParent = (Composite)super.createDialogArea(parent);
120
121        // Setup the title area
122        //
123        setTitleImage(getImage());
124        setTitle("Rendering");
125        setMessage("Rendering documents.");
126
127        Composite pageContainer = new Composite(compositeParent, SWT.NONE);
128        GridData gd = new GridData(GridData.FILL_BOTH);
129        pageContainer.setLayoutData(gd);
130        pageContainer.setFont(parent.getFont());
131
132        // Document
133        //
134        document = new Document();
135
136        //layout for page
137        //
138        GridLayout layout = new GridLayout();
139        layout.numColumns = 1;
140
141        pageContainer.setLayout(layout);
142
143        progressCaption = new Label(pageContainer, SWT.NONE);
144        progressCaption.setText("Progress:");
145        progressCaption.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
146
147        progressBar = new ProgressBar(pageContainer, SWT.SMOOTH | SWT.HORIZONTAL);
148        gd = new GridData(GridData.FILL_HORIZONTAL);
149        gd.heightHint = 10;
150        progressBar.setLayoutData(gd);
151
152        consoleTitle = new Label(pageContainer, SWT.NONE);
153        consoleTitle.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
154
155        console = new TextViewer(pageContainer, SWT.BORDER | SWT.H_SCROLL | SWT.V_SCROLL);
156        console.getTextWidget().setFont(JFaceResources.getFont(IDebugUIConstants.PREF_CONSOLE_FONT));
157        console.getTextWidget().setLayoutData(new GridData(GridData.FILL_BOTH));
158        console.setDocument(document);
159
160        // stream.setColor(fColorProvider.getColor(IDebugUIConstants.ID_STANDARD_ERROR_STREAM));
161
162//        btnAlwaysBackground = new Button(pageContainer, SWT.CHECK);
163//		btnAlwaysBackground.setText("Always render in the background.");
164
165        btnBuildAutomatically = new Button(pageContainer, SWT.CHECK);
166		btnBuildAutomatically.setText("Integrate rendering for this document into the build cycle.");
167
168        boolean buildSelVal = true;
169        for (FileRenderSpecification spec: renderSpecs) {
170            try {
171				if (!FoProcessorPlugin.getDefault().isAutoBuild(spec.getInputFile())) {
172					buildSelVal = false;
173					break;
174				}
175			} catch (CoreException e) {
176				buildSelVal = false;
177				break;
178			}
179        }
180
181        btnBuildAutomatically.setSelection(buildSelVal);
182
183        // Build the separator line
184        //
185        Label separator= new Label(compositeParent, SWT.HORIZONTAL | SWT.SEPARATOR);
186        separator.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
187
188        getShell().setText("Apache FOP 0.95");	// TODO update from version to version
189
190        return compositeParent;
191    }
192
193    public Image getImage() {
194        if (image == null) {
195            image = FoProcessorPlugin.getDefault().getImageDescriptor("full/wizban/RenderProgress.gif").createImage();
196        }
197        return image;
198    }
199
200    public void setConsoleTitle() {
201        SimpleDateFormat df = new SimpleDateFormat("MMM dd, yyyy HH:mm:ss");
202        consoleTitle.setText("XSL-FO Rendering [Powered by Apache FOP] (" +df.format(new Date())+ ")");
203    }
204
205    /**
206     * Renders the document. Should not be run on the UI thread.
207     *
208     * @param mimeType
209     * @param extension
210     * @param resources
211     */
212    public void beginRendering() {
213
214        try {
215            createSignal.await();
216        } catch (InterruptedException e) {
217            error(null, e);
218        }
219
220        // Setup the FOP Logger
221        //
222
223        Handler lh = new LogHandler();
224        lh.setLevel(Level.ALL);
225        Logger.getLogger("org.apache.fop").setLevel(Level.INFO);
226        Logger.getLogger("org.apache.fop.area.AreaTreeHandler").setLevel(Level.FINE);
227        Logger.getLogger("org.apache.fop.fo.FOTreeBuilder").setLevel(Level.FINE);
228        Logger.getLogger("org.apache.fop").addHandler(lh);
229
230
231        // Set the titles and the button states
232        //
233        setConsoleTitle();
234
235        Button okButton = getButton(IDialogConstants.OK_ID);
236        if (okButton != null) {
237            okButton.setEnabled(false);
238        }
239
240        Button cnButton = getButton(IDialogConstants.CANCEL_ID);
241        if (cnButton != null) {
242            cnButton.setEnabled(true);
243        }
244
245        Button bkgButton = getButton(RUN_BACKGROUND_ID);
246        if (bkgButton != null) {
247        	bkgButton.setEnabled(true);
248        }
249
250        conversionOp = new ConversionOperation(renderSpecs, null, RenderProgress.this);
251		IRunnableWithProgress runnable = new IRunnableWithProgress() {
252            public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException {
253                conversionOp.setProgressMonitor(monitor);
254                if (!cancelled) {
255                	conversionOp.run();
256                }
257            }
258        };
259
260        try {
261            run(true, true, runnable);
262        } catch (Exception e) {
263            error(null, e);
264        }
265    }
266
267
268    /**
269     * @inheritDoc
270     */
271    public boolean close() {
272        getImage().dispose();
273        return super.close();
274    }
275    /**
276     * @inheritDoc
277     */
278    public void cancelPressed() {
279    	cancelled = true;
280    	if (conversionOp != null) {
281    		conversionOp.setInterrupted();
282    	}
283    	super.cancelPressed();
284    }
285
286    /**
287     * @inheritDoc
288     */
289    public void okPressed() {
290    	// Process builder.
291    	//
292    	boolean buildValue = btnBuildAutomatically.getSelection();
293
294    	for (FileRenderSpecification spec: renderSpecs) {
295            IProject project = spec.getInputFile().getProject();
296
297            try {
298                if (buildValue == true) {
299                	FoProcessorPlugin.getDefault().addFopNature(project);
300                }
301            	FoProcessorPlugin.getDefault().setAutoBuild(spec.getInputFile(), buildValue, spec);
302            } catch (CoreException e) {
303            	e.printStackTrace();
304            }
305        }
306
307    	super.okPressed();
308    }
309
310    // IRunnableContext
311    //
312
313    public void run(boolean fork, boolean cancelable, IRunnableWithProgress runnable) throws InvocationTargetException, InterruptedException {
314        // Create the Progress Monitor
315        //
316        ProgressMonitor monitor = new ProgressMonitor();
317
318        ModalContext.run(runnable, fork, monitor, getShell().getDisplay());
319    }
320
321    // Logging Methods --------------------------------------------------------
322
323    private static final String nl = System.getProperty("line.separator");
324    private Label progressCaption;
325	private Button btnBuildAutomatically;
326	private ConversionOperation conversionOp;
327
328    private String toString(Throwable t) {
329        ByteArrayOutputStream bos = new ByteArrayOutputStream();
330        PrintStream ps = new PrintStream(bos);
331        t.printStackTrace(ps);
332        ps.close();
333
334        return new String(bos.toByteArray());
335    }
336
337    private void append(final String s, final Color color) {
338        if (s == null) {
339            return;
340        }
341        Display.getDefault().asyncExec(
342            new Runnable() {
343                public void run() {
344                    try {
345                        StyleRange styleRange = new StyleRange();
346                        styleRange.start = document.getLength();
347                        styleRange.length = s.length() + nl.length();
348                        styleRange.foreground = color;
349
350                        document.replace(document.getLength(), 0, s);
351                        document.replace(document.getLength(), 0, nl);
352
353                        if (console.getTextWidget() != null) { // amazingly, this happens...
354                            console.getTextWidget().setStyleRange(styleRange);
355                        }
356                    } catch (BadLocationException e) {
357                        e.printStackTrace();
358                    }
359                }
360            }
361        );
362    }
363
364    /**
365     * @inheritdoc
366     */
367    public void error(String arg0) {
368        append(arg0, colorProvider.getColor(IDebugUIConstants.ID_STANDARD_ERROR_STREAM));
369    }
370
371    /**
372     * @inheritdoc
373     */
374    public void error(String arg0, Throwable arg1) {
375        append(arg0, colorProvider.getColor(IDebugUIConstants.ID_STANDARD_ERROR_STREAM));
376        append(toString(arg1), colorProvider.getColor(IDebugUIConstants.ID_STANDARD_ERROR_STREAM));
377
378    }
379
380    /**
381     * @inheritdoc
382     */
383    public void fatalError(String arg0) {
384        append(arg0, colorProvider.getColor(IDebugUIConstants.ID_STANDARD_ERROR_STREAM));
385    }
386
387    /**
388     * @inheritdoc
389     */
390    public void fatalError(String arg0, Throwable arg1) {
391        append(arg0, colorProvider.getColor(IDebugUIConstants.ID_STANDARD_ERROR_STREAM));
392        append(toString(arg1), colorProvider.getColor(IDebugUIConstants.ID_STANDARD_ERROR_STREAM));
393    }
394
395    public class LogHandler extends Handler {
396
397        @Override
398        public void publish(LogRecord record) {
399            Color color = null;
400            if (record.getLevel().intValue() <= Level.INFO.intValue()) {
401                color = colorProvider.getColor(IDebugUIConstants.ID_STANDARD_OUTPUT_STREAM);
402            } else {
403                color = colorProvider.getColor(IDebugUIConstants.ID_STANDARD_ERROR_STREAM);
404            }
405            if (record.getLevel().intValue() < Level.INFO.intValue())
406            	append("  " + record.getMessage(), color);
407            else
408            	append(record.getMessage(), color);
409            if (record.getThrown() != null) {
410                append(RenderProgress.this.toString(record.getThrown()), color);
411            }
412        }
413
414        @Override
415        public void flush() {
416            // TODO Auto-generated method stub
417
418        }
419
420        @Override
421        public void close() throws SecurityException {
422            // TODO Auto-generated method stub
423
424        }
425
426    }
427
428    // Progress Monitor -------------------------------------------------------
429
430    public class ProgressMonitor implements IProgressMonitor {
431
432        private String name;
433        private boolean canceled;
434
435        /**
436         * @inheritdoc
437         */
438        public void beginTask(final String name, final int totalWork) {
439            Display.getDefault().asyncExec(
440                new Runnable() {
441                    public void run() {
442                        progressBar.setMinimum(0);
443                        progressBar.setSelection(0);
444                        progressBar.setMaximum(totalWork);
445
446                        progressCaption.setText("Progress: " + name);
447                        ProgressMonitor.this.name = name;
448                    }
449                }
450            );
451        }
452
453        /**
454         * @inheritdoc
455         */
456        public void done() {
457            Display.getDefault().asyncExec(
458                new Runnable() {
459                    public void run() {
460                        progressBar.setSelection(progressBar.getMaximum());
461                        progressCaption.setText("Progress: Done");
462                    }
463                }
464            );
465        }
466
467        /**
468         * @inheritdoc
469         */
470        public void internalWorked(double work) {
471            worked((int) Math.round(work));
472        }
473
474        /**
475         * @inheritdoc
476         */
477        public boolean isCanceled() {
478            return canceled;
479        }
480
481        /**
482         * @inheritdoc
483         */
484        public void setCanceled(boolean value) {
485            this.canceled = value;
486            if (value) {
487                Display.getDefault().asyncExec(
488                    new Runnable() {
489                        public void run() {
490                            progressCaption.setText("Progress: " + ProgressMonitor.this.name + "... Requesting cancellation");
491                        }
492                    }
493                );
494            }
495        }
496
497        /**
498         * @inheritdoc
499         */
500        public void setTaskName(String name) {
501            this.name = name;
502        }
503
504        /**
505         * @inheritdoc
506         */
507        public void subTask(final String name) {
508            Display.getDefault().asyncExec(
509                new Runnable() {
510                    public void run() {
511                        progressCaption.setText("Progress: " + ProgressMonitor.this.name + "... " + name);
512                    }
513                }
514            );
515        }
516
517        /**
518         * @inheritdoc
519         */
520        public void worked(final int work) {
521            Display.getDefault().asyncExec(
522                new Runnable() {
523                    public void run() {
524                        progressBar.setSelection(progressBar.getSelection() + work);
525                    }
526                }
527            );
528        }
529
530    }
531
532	public void onFinish() {
533        Display.getDefault().asyncExec(
534            new Runnable() {
535                public void run() {
536                    Button okButton = getButton(IDialogConstants.OK_ID);
537                    if (okButton != null) {
538                        okButton.setEnabled(true);
539                        okButton.setFocus();
540                    }
541
542                    Button cnButton = getButton(IDialogConstants.CANCEL_ID);
543                    if (cnButton != null) {
544                        cnButton.setEnabled(false);
545                    }
546
547                    Button bkgButton = getButton(RUN_BACKGROUND_ID);
548                    if (bkgButton != null) {
549                    	bkgButton.setEnabled(false);
550                    }
551                }
552            }
553        );
554	}
555
556	public void onError(String msg, Throwable throwable) {
557		error(msg, throwable);
558	}
559}
560