1   /**
2    * JHylaFax - A java client for HylaFAX.
3    *
4    * Copyright (C) 2005 by Steffen Pingel <steffenp@gmx.de>
5    *
6    * This program is free software; you can redistribute it and/or modify
7    * it under the terms of the GNU General Public License as published by
8    * the Free Software Foundation; either version 2 of the License, or
9    * (at your option) any later version.
10   *
11   * This program is distributed in the hope that it will be useful,
12   * but WITHOUT ANY WARRANTY; without even the implied warranty of
13   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14   * GNU General Public License for more details.
15   *
16   * You should have received a copy of the GNU General Public License
17   * along with this program; if not, write to the Free Software
18   * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
19   */
20  package net.sf.jhylafax;
21  
22  import static net.sf.jhylafax.JHylaFAX.i18n;
23  import gnu.hylafax.HylaFAXClient;
24  import java.awt.event.ActionEvent;
25  import java.awt.event.ActionListener;
26  import java.io.BufferedInputStream;
27  import java.io.ByteArrayInputStream;
28  import java.io.File;
29  import java.io.FileInputStream;
30  import java.io.FileNotFoundException;
31  import java.io.FileOutputStream;
32  import java.io.IOException;
33  import java.io.InputStream;
34  import java.io.OutputStream;
35  import java.text.SimpleDateFormat;
36  import java.util.ArrayList;
37  import java.util.Date;
38  import java.util.Iterator;
39  import java.util.List;
40  import javax.swing.Action;
41  import javax.swing.JCheckBox;
42  import javax.swing.JFrame;
43  import javax.swing.JLabel;
44  import javax.swing.JOptionPane;
45  import javax.swing.JPanel;
46  import javax.swing.JScrollPane;
47  import javax.swing.JTextArea;
48  import javax.swing.JTextField;
49  import net.sf.jhylafax.fax.FaxCover;
50  import net.sf.jhylafax.fax.FaxJob;
51  import net.sf.jhylafax.fax.HylaFAXClientHelper;
52  import org.apache.commons.logging.Log;
53  import org.apache.commons.logging.LogFactory;
54  import org.xnap.commons.gui.Builder;
55  import org.xnap.commons.gui.Dialogs;
56  import org.xnap.commons.gui.ErrorDialog;
57  import org.xnap.commons.gui.FileChooserPanel;
58  import org.xnap.commons.gui.action.AbstractXNapAction;
59  import org.xnap.commons.io.Job;
60  import org.xnap.commons.io.ProgressMonitor;
61  import org.xnap.commons.io.SubTaskProgressMonitor;
62  import org.xnap.commons.io.UserAbortException;
63  import com.jgoodies.forms.builder.DefaultFormBuilder;
64  import com.jgoodies.forms.layout.CellConstraints;
65  import com.jgoodies.forms.layout.FormLayout;
66  
67  /**
68   * A dialog for sending faxes.
69   * 
70   * @author Steffen Pingel
71   */
72  public class SendDialog extends AbstractFaxDialog {
73  
74  	private final static Log logger = LogFactory.getLog(AbstractQueuePanel.class);
75  
76  	private JLabel documentLabel;
77  	private JPanel coverPanel;
78  	private JTextField coverSenderTextField;
79  	private JLabel coverSenderLabel;
80  	private JTextField coverRecepientTextField;
81  	private JLabel coverRecepientLabel;
82  	private JTextField coverSubjectTextField;
83  	private JLabel coverSubjectLabel;
84  	private JTextArea coverCommentTextArea;
85  	private JLabel coverCommentLabel;
86  	private JPanel documentPanel;
87  	private DefaultFormBuilder documentPanelBuilder;
88  	private List<FileChooserPanel> documentFileChooserPanels;
89  	private JCheckBox includeCoverCheckBox;
90  	private JLabel includeCoverLabel;
91  	private JScrollPane coverCommentScrollPane;
92  	private PreviewCoverAction previewCoverAction;
93  
94  	private MoreDocumentsAction moreDocumentsAction;
95  
96  	private boolean quitAfterSending;
97  	
98  	public SendDialog(JFrame owner) {
99  		super(owner);
100 		
101 		moreDocumentsAction = new MoreDocumentsAction();
102 		previewCoverAction = new PreviewCoverAction();
103 		
104 		addNumberTextField();
105 		
106 		addDocumentsPanel();
107 
108 		builder.append("", Builder.createButton(moreDocumentsAction));
109 		builder.nextLine();
110 
111 		addDateControls();
112 
113 		includeCoverCheckBox = new JCheckBox();
114 		includeCoverLabel = builder.append("", includeCoverCheckBox);
115 		builder.nextLine();
116 		
117 		initializeCoverPanel();
118 		coverPanel.setVisible(false);
119 		previewCoverAction.setEnabled(false);
120 		includeCoverCheckBox.addActionListener(new ActionListener() {
121 			public void actionPerformed(ActionEvent e) {
122 				coverPanel.setVisible(includeCoverCheckBox.isSelected());
123 				previewCoverAction.setEnabled(includeCoverCheckBox.isSelected());
124 				pack();
125 			}
126 		});
127 		
128 		getButtonPanel().add(Builder.createButton(previewCoverAction), 0);
129 		
130 		FaxJob job = new FaxJob(); 
131 		HylaFAXClientHelper.initializeFromSettings(job);
132 		setJob(job);
133 		
134 		updateLabels();
135 		pack();
136 	}
137 	
138 	private void addDocumentsPanel()
139 	{
140 		documentFileChooserPanels = new ArrayList<FileChooserPanel>();
141 		
142 		FormLayout layout = new FormLayout("left:max(40dlu;pref), 3dlu, pref:grow", "");
143 		documentPanelBuilder = new DefaultFormBuilder(layout);
144 		FileChooserPanel documentFileChooserPanel = new MyFileChooserPanel(DEFAULT_COLUMNS);
145 		documentFileChooserPanel.getFileChooser().setMultiSelectionEnabled(true);
146 		documentFileChooserPanels.add(documentFileChooserPanel);
147 		documentLabel = documentPanelBuilder.append("", documentFileChooserPanel);
148 
149 		this.builder.appendRow(builder.getLineGapSpec());
150 		this.builder.nextLine();
151 		this.builder.appendRow("fill:pref:grow");
152 		this.builder.append(documentPanelBuilder.getPanel(), 6);
153 
154 		builder.nextLine();
155 	}
156 
157 	private void initializeCoverPanel()
158 	{
159 		FormLayout layout = new FormLayout("left:max(40dlu;pref), 3dlu, pref:grow", "");
160 		DefaultFormBuilder builder = new DefaultFormBuilder(layout);
161 		coverPanel = builder.getPanel();
162 		this.builder.appendRow(builder.getLineGapSpec());
163 		this.builder.nextLine();
164 		this.builder.appendRow("fill:pref:grow");
165 		this.builder.append(coverPanel, 6);
166 		
167 		coverSenderTextField = new JTextField(DEFAULT_COLUMNS);
168 		coverSenderTextField.setText(Settings.FULLNAME.getValue());
169 		coverSenderLabel = builder.append("", coverSenderTextField);
170 		builder.nextLine();
171 		
172 		coverRecepientTextField = new JTextField(DEFAULT_COLUMNS);
173 		coverRecepientLabel = builder.append("", coverRecepientTextField);
174 		builder.nextLine();
175 		
176 		coverSubjectTextField = new JTextField(DEFAULT_COLUMNS);
177 		coverSubjectLabel = builder.append("", coverSubjectTextField);
178 		builder.nextLine();
179 		
180 		coverCommentTextArea= new JTextArea(3, DEFAULT_COLUMNS);
181 		coverCommentLabel = builder.append("");
182 		builder.appendRow("fill:pref:grow"); // second row for text area
183 		CellConstraints cc = new CellConstraints();
184 		coverCommentScrollPane = new JScrollPane(coverCommentTextArea);
185 		builder.add(coverCommentScrollPane,
186 				cc.xywh(builder.getColumn(), builder.getRow(), 1, 2));
187 		builder.nextLine(2);
188 	}
189 
190 	@Override
191 	public boolean apply() {
192 		if (!super.apply()) {
193 			return false;
194 		}
195 
196 		if (!includeCoverCheckBox.isSelected()) {
197 			boolean filenameProvided = false;
198 			for (FileChooserPanel documentFileChooserPanel : documentFileChooserPanels) {
199 				if (documentFileChooserPanel.getTextField().getText().trim().length() != 0) {
200 					filenameProvided = true;
201 					if (Settings.CONFIRM_NONPS.getValue()) {
202 						if (!checkPostScript(documentFileChooserPanel.getTextField().getText())) {
203 							documentFileChooserPanel.getTextField().requestFocus();
204 							return false;
205 						}
206 					}
207 				}
208 			}
209 			if (!filenameProvided) {
210 				Dialogs.showError(this, i18n.tr("You must at least send a document or a cover"), 
211 						i18n.tr("JHylaFAX Error"));
212 				documentFileChooserPanels.get(0).getTextField().requestFocus();
213 				return false;
214 			}
215 		}
216 	
217 		Fax fax = createFax();
218 		if (fax != null) {
219 			if (send(fax)) {
220 				if (quitAfterSending) {
221 					JHylaFAX.getInstance().exit();
222 				}
223 				return true;
224 			}
225 		}
226 		
227 		return false;
228 	}		
229 	
230 	private boolean checkPostScript(String filename) {
231 		try {
232 			if (!HylaFAXClientHelper.isPostscript(filename)) {
233 				if (Dialogs.showConfirmDialog(this, 
234 						i18n.tr("Do you really want to send the non PostScript file \"{0}\"?", filename),
235 						i18n.tr("JHylaFAX - Send non PostScript file"), 
236 						JOptionPane.YES_NO_OPTION, 
237 						Settings.CONFIRM_NONPS) == JOptionPane.NO_OPTION) {
238 					return false;
239 				}
240 			}
241 		}
242 		catch (IOException e) {
243 			logger.debug("Error checking for PostScript", e);
244 			ErrorDialog.showError(this, i18n.tr("Could not check for PostScript"), 
245 					i18n.tr("JHylaFAX Error"),
246 					e);
247 			return false;
248 		}
249 		return true;
250 	}
251 
252 	private Fax createFax() {
253 		Fax fax = new Fax();
254 		
255 		for (FileChooserPanel documentFileChooserPanel : documentFileChooserPanels) {
256 			String filename = documentFileChooserPanel.getTextField().getText();
257 			if (filename.trim().length() > 0) {
258 				File file = new File(filename);
259 				fax.documents.add(file);
260 			}
261 		}
262 		
263 		if (includeCoverCheckBox.isSelected() && !createFaxCover(fax)) {
264 			return null;
265 		}
266 		
267 		return fax;
268 	}
269 	
270 	private boolean createFaxCover(Fax fax) {
271 		if (fax == null) {
272 			throw new IllegalArgumentException("value may not be null");
273 		}
274 		
275 		String filename;
276 		if (Settings.USE_INTERNAL_COVER.getValue()) {
277 			filename = "faxcover.ps";
278 			fax.coverIn = Thread.currentThread().getContextClassLoader().getResourceAsStream(filename); 
279 		} else {
280 			filename = Settings.COVER_PATH.getValue();
281 			try {
282 				fax.coverIn = new FileInputStream(filename);
283 			}
284 			catch (FileNotFoundException e) {
285 				fax.coverIn = null;
286 			}
287 		}
288 		if (fax.coverIn == null) {
289 			Dialogs.showError(this, i18n.tr("Could not read cover file: {0}", filename), 
290 					i18n.tr("JHylaFAX Error"));
291 			return false;	
292 		}
293 			
294 		fax.cover = new FaxCover(Settings.PAPER.getValue());
295 		fax.cover.from = coverSenderTextField.getText();
296 		fax.cover.to = coverRecepientTextField.getText();
297 		fax.cover.regarding = coverSubjectTextField.getText();
298 		fax.cover.comments = coverCommentTextArea.getText();
299 		fax.cover.todays_date = SimpleDateFormat.getDateTimeInstance().format(new Date());
300 		for (Iterator<File> it = fax.documents.iterator(); it.hasNext();) {
301 			fax.cover.addDocument(it.next().getAbsolutePath());
302 		}
303 		
304 		return true;
305 	}
306 	
307 	private boolean send(final Fax fax) {	
308 		Job<?> ioJob = new Job() {
309 			public Object run(ProgressMonitor monitor) throws Exception
310 			{
311 				monitor.setTotalSteps(13 + fax.documents.size() * 10);
312 				
313 				HylaFAXClient client = JHylaFAX.getInstance().getConnection(monitor);
314 				monitor.work(1);
315 
316 				client.mode(HylaFAXClient.MODE_STREAM);
317 				client.type(HylaFAXClient.TYPE_IMAGE);
318 
319 				String serverCoverFilename = null;
320 				if (fax.cover != null && fax.coverIn != null) {
321 					monitor.setText(i18n.tr("Generating cover"));
322 					SubTaskProgressMonitor coverMonitor = new SubTaskProgressMonitor(monitor, 5, 0);
323 					StringBuffer data = fax.cover.generate(fax.coverIn, coverMonitor);
324 					coverMonitor.done();
325 					
326 				    // Cover senden
327                     byte[] buffer = data.toString().getBytes(FaxCover.CHARSET);
328 					TransferMonitor transferMonitor = new TransferMonitor(monitor, 5, buffer.length);
329 					client.addTransferListener(transferMonitor);
330 					InputStream in = new ByteArrayInputStream(buffer);
331 					try {
332 					    serverCoverFilename = client.putTemporary(in);
333 					}
334 					finally {
335 						transferMonitor.transferCompleted();
336 						client.removeTransferListener(transferMonitor);
337 						in.close();
338 					}
339 					// check if monitor was cancelled
340 					monitor.work(0);
341 				}
342 				else {
343 					monitor.work(10);
344 				}
345 
346 				monitor.setText(i18n.tr("Uploading documents"));
347 				List<String> serverFilenames = new ArrayList<String>();
348 				for (File file : fax.documents) {
349 					TransferMonitor transferMonitor = new TransferMonitor(monitor, 10, file.length());
350 					client.addTransferListener(transferMonitor);
351 					InputStream in = new BufferedInputStream(new FileInputStream(file));
352 					try {
353 						serverFilenames.add(client.putTemporary(in));
354 					}
355 					finally {
356 						transferMonitor.transferCompleted();
357 						client.removeTransferListener(transferMonitor);
358 						in.close();
359 					}
360 					// check if monitor was cancelled
361 					monitor.work(0);
362 				}
363 				
364 				gnu.hylafax.Job sendJob = client.createJob();
365 				HylaFAXClientHelper.applyParameter(sendJob, getJob());
366 			    if (serverCoverFilename != null) {
367 			    	if (Settings.SEND_COVER_AS_DOCUMENT.getValue()) {
368 			    		sendJob.addDocument(serverCoverFilename);
369 			    	}
370 			    	else {
371 			    		sendJob.setProperty("COVER ", serverCoverFilename);
372 			    	}
373 			    }
374 			    for (String filename : serverFilenames) {
375 			    	sendJob.addDocument(filename);
376 			    }
377 				monitor.work(1);
378 				
379 				client.submit(sendJob);
380 				monitor.work(1);
381 				
382 				return null;
383 			}
384 		};
385 		
386 		try {
387 			JHylaFAX.getInstance().runJob(SendDialog.this, ioJob);
388 			JHylaFAX.getInstance().updateTables();
389 		} 
390 		catch (UserAbortException e) {
391 			return false;
392 		}
393 		catch (Exception e) {
394 			logger.debug("Error sending fax", e);
395 			ErrorDialog.showError(this, i18n.tr("Could not send fax"), 
396 					i18n.tr("JHylaFAX Error"),
397 					e);
398 			return false;
399 		}
400 		return true;
401 	}
402 	
403 	private File saveCover(final Fax fax) {	
404 		if (fax.cover == null || fax.coverIn == null) {
405 			throw new IllegalArgumentException("fax.cover and fax.coverIn must not be null");
406 		}
407 		
408 		Job<File> ioJob = new Job<File>() {
409 			public File run(ProgressMonitor monitor) throws Exception
410 			{
411 				monitor.setTotalSteps(10);
412 				
413 				monitor.setText(i18n.tr("Generating cover"));
414 				SubTaskProgressMonitor coverMonitor = new SubTaskProgressMonitor(monitor, 5, 0);
415 				StringBuffer data = fax.cover.generate(fax.coverIn, coverMonitor);
416 				coverMonitor.done();
417 				
418 				File outputFile = File.createTempFile("jhylafax", ".ps");
419 				outputFile.deleteOnExit();
420 				OutputStream out = new FileOutputStream(outputFile);
421 				monitor.setText(i18n.tr("Saving cover"));
422 				try {
423 					out.write(data.toString().getBytes(FaxCover.CHARSET));
424 				}
425 				finally {
426 					out.close();
427 				}
428 				monitor.work(5);
429 
430 				return outputFile;
431 			}
432 		};
433 
434 		try {
435 			return JHylaFAX.getInstance().runJob(SendDialog.this, ioJob);
436 		} 
437 		catch (UserAbortException e) {
438 			return null;
439 		}
440 		catch (Exception e) {
441 			logger.debug("Error previewing cover", e);
442 			ErrorDialog.showError(this, i18n.tr("Could not preview cover"), 
443 					i18n.tr("JHylaFAX Error"), e);
444 			return null;
445 		}
446 	}
447 	
448 	public void updateLabels() {
449 		super.updateLabels();
450 
451 		setTitle(i18n.tr("Send Fax"));
452 		documentLabel.setText(i18n.tr("Document"));
453 		includeCoverLabel.setText(i18n.tr("Include Cover"));
454 		coverSenderLabel.setText(i18n.tr("Sender"));
455 		coverRecepientLabel.setText(i18n.tr("Recepient"));
456 		coverSubjectLabel.setText(i18n.tr("Subject"));
457 		coverCommentLabel.setText(i18n.tr("Comment"));
458 		
459 		previewCoverAction.updateLabels();
460 		moreDocumentsAction.updateLabels();
461 	}
462 
463 	public void setDocument(String document) {
464 		documentFileChooserPanels.get(0).getTextField().setText(document);
465 	}
466 
467 	/**
468 	 * A container used for parameter passing.  
469 	 */
470 	private class Fax {
471 		FaxCover cover;
472 		InputStream coverIn;
473 		List<File> documents = new ArrayList<File>();
474 	}
475 	
476 	private class PreviewCoverAction extends AbstractXNapAction implements LocaleChangeListener{
477 		
478 		public PreviewCoverAction() {
479 			//putValue(ICON_FILENAME, "configure.png");
480 		}
481 
482 		public void actionPerformed(ActionEvent event) {
483 			if (!includeCoverCheckBox.isSelected()) {
484 				throw new IllegalStateException("Cover page is not enabled");
485 			}
486 			
487 			String viewerPath = JHylaFAXHelper.getViewerPath("docq");
488 			if (viewerPath == null) {
489 				return;
490 			}
491 			
492 			Fax fax = createFax();
493 			if (fax == null) {
494 				return;
495 			}
496 
497 			File tempFile = saveCover(fax);
498 			if (tempFile != null) {
499 				JHylaFAXHelper.view(viewerPath, new File[] { tempFile });
500 			}
501 		}
502 		
503 		public void updateLabels() {
504 			putValue(Action.NAME, i18n.tr("Preview Cover"));
505 			putValue(Action.SHORT_DESCRIPTION, i18n.tr("Opens an external programm to preview the cover page"));
506 		}
507 		
508 	}
509 
510 	private class MoreDocumentsAction extends AbstractXNapAction implements LocaleChangeListener{
511 		
512 		public MoreDocumentsAction() {
513 		}
514 
515 		public void actionPerformed(ActionEvent event) {
516 			FileChooserPanel documentFileChooserPanel = addDocumentFileChooser();
517 			documentFileChooserPanel.getTextField().requestFocus();
518 		}
519 		
520 		public void updateLabels() {
521 			putValue(Action.NAME, i18n.tr("More"));
522 			putValue(Action.SHORT_DESCRIPTION, i18n.tr("Displays an additional field to enter a document filename"));
523 		}
524 		
525 	}
526 	
527     private class MyFileChooserPanel extends FileChooserPanel {
528     	
529 		public MyFileChooserPanel(int columns)
530 		{
531 			super(columns);
532 		}
533 
534 		@Override
535 		protected void fileSelected(File file)
536 		{
537 			File[] files = getFileChooser().getSelectedFiles();
538 			if (files != null && files.length > 1) {
539 				// files[0] equals file and is handled by the panel
540 				for (int i = 1; i < files.length; i++) {
541 					// add a new panel for all other files
542 					FileChooserPanel chooser = addDocumentFileChooser();
543 					chooser.setFile(files[i]);
544 				}
545 			}
546 			// reset selection
547 			getFileChooser().setSelectedFiles(null);
548 		}
549 		
550     }
551 
552 	protected FileChooserPanel addDocumentFileChooser()
553 	{
554 		FileChooserPanel documentFileChooserPanel = new MyFileChooserPanel(DEFAULT_COLUMNS);
555 		documentFileChooserPanel.setFileChooser(documentFileChooserPanels.get(0).getFileChooser());
556 		documentFileChooserPanels.add(documentFileChooserPanel);
557 		documentPanelBuilder.append("", documentFileChooserPanel);
558 		documentPanelBuilder.nextLine();
559 		pack();
560 		return documentFileChooserPanel;
561 	}
562 
563 	public void addDocument(String filename)
564 	{
565 		FileChooserPanel panel = addDocumentFileChooser();
566 		panel.getTextField().setText(filename);
567 	}
568 
569 	public void setQuitAfterSending(boolean quitAfterSending) 
570 	{
571 		this.quitAfterSending = quitAfterSending;
572 	}
573 
574 }