1 /*
2 * Copyright 2006-2016 The JGUIraffe Team.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License")
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16 package net.sf.jguiraffe.gui.platform.swing.builder.utils;
17
18 import javax.swing.JDialog;
19 import javax.swing.JOptionPane;
20 import java.awt.Component;
21
22 import net.sf.jguiraffe.gui.builder.utils.MessageOutput;
23 import net.sf.jguiraffe.gui.builder.window.Window;
24 import net.sf.jguiraffe.gui.builder.window.WindowUtils;
25 import org.apache.commons.lang.StringUtils;
26 import org.apache.commons.lang.WordUtils;
27
28 /**
29 * <p>
30 * A Swing specific implementation of the {@code MessageOutput}
31 * interface.
32 * </p>
33 * <p>
34 * This implementation makes use of {@code JOptionPane} for displaying
35 * message boxes.
36 * </p>
37 *
38 * @author Oliver Heger
39 * @version $Id: SwingMessageOutput.java 205 2012-01-29 18:29:57Z oheger $
40 */
41 public class SwingMessageOutput implements MessageOutput
42 {
43 /**
44 * Constant for a line length which disables line wrapping. If this value is
45 * passed to the constructor, the message text is not wrapped into multiple
46 * lines.
47 *
48 * @since 1.3
49 */
50 public static final int NO_LINE_WRAP = -1;
51
52 /** An array with the supported message types. */
53 private static final int[] MESSAGE_TYPES = {
54 MESSAGE_ERROR, MESSAGE_INFO, MESSAGE_PLAIN, MESSAGE_QUESTION,
55 MESSAGE_WARNING
56 };
57
58 /** An array with the JOptionPane message types. */
59 private static final int[] SWING_MESSAGE_TYPES = {
60 JOptionPane.ERROR_MESSAGE, JOptionPane.INFORMATION_MESSAGE,
61 JOptionPane.PLAIN_MESSAGE, JOptionPane.QUESTION_MESSAGE,
62 JOptionPane.WARNING_MESSAGE
63 };
64
65 /** An array with the supported button types. */
66 private static final int[] BUTTON_TYPES = {
67 BTN_OK, BTN_OK_CANCEL, BTN_YES_NO, BTN_YES_NO_CANCEL
68 };
69
70 /** An array with the JOptionPane button types. */
71 private static final int[] SWING_BUTTON_TYPES = {
72 JOptionPane.DEFAULT_OPTION, JOptionPane.OK_CANCEL_OPTION,
73 JOptionPane.YES_NO_OPTION, JOptionPane.YES_NO_CANCEL_OPTION
74 };
75
76 /** An array with the supported return values. */
77 private static final int[] RETURN_VALUES = {
78 RET_CANCEL, RET_OK, RET_YES, RET_NO, RET_CANCEL
79 };
80
81 /** An array with the JOptionPane return values. */
82 private static final int[] SWING_RETURN_VALUES = {
83 JOptionPane.CANCEL_OPTION, JOptionPane.OK_OPTION,
84 JOptionPane.YES_OPTION, JOptionPane.NO_OPTION,
85 JOptionPane.CLOSED_OPTION
86 };
87
88 /** Constant for a starting HTML tag. */
89 private static final String HTML_START = "<html>";
90
91 /** Constant for a closing HTML tag. */
92 private static final String HTML_END = "</html>";
93
94 /** Constant for the default maximum line length. */
95 private static final int DEFAULT_MAX_LINE_LENGTH = 80;
96
97 /** Constant for the initial length of the buffer for text processing. */
98 private static final int BUF_LENGTH = 256;
99
100 /** Constant for the newline character. */
101 private static final String CR = "\n";
102
103 /** The maximum length of a line for the message text. */
104 private final int maximumLineLength;
105
106 /**
107 * Creates a new instance of {@code SwingMessageOutput} and sets a default
108 * maximum line length.
109 */
110 public SwingMessageOutput()
111 {
112 this(DEFAULT_MAX_LINE_LENGTH);
113 }
114
115 /**
116 * Creates a new instance of {@code SwingMessageOutput} with the specified
117 * maximum message line length. Before the message is displayed, it is
118 * ensured that single lines do not exceed this maximum length; if
119 * necessary, the text is split into multiple lines. To disable line
120 * wrapping, the value {@link #NO_LINE_WRAP} can be passed.
121 *
122 * @param maxLineLength the maximum length of a line for the message text
123 * (in characters); must be > 0
124 * @throws IllegalArgumentException if an invalid line length is passed in
125 * @since 1.3
126 */
127 public SwingMessageOutput(int maxLineLength)
128 {
129 if (maxLineLength != NO_LINE_WRAP && maxLineLength < 1)
130 {
131 throw new IllegalArgumentException(
132 "Maximum line length must be > 0!");
133 }
134 maximumLineLength = maxLineLength;
135 }
136
137 /**
138 * Returns the maximum line length for the messages to be displayed.
139 *
140 * @return the maximum line length
141 * @since 1.3
142 */
143 public int getMaximumLineLength()
144 {
145 return maximumLineLength;
146 }
147
148 /**
149 * Displays a message box.
150 *
151 * @param parent the parent window; this should be <b>null</b> or point to
152 * a Swing window
153 * @param message the message
154 * @param title the message box's title
155 * @param messageType the type of the message
156 * @param buttonType specifies the buttons to be displayed
157 * @return the pressed button
158 */
159 public int show(Window parent, Object message, String title,
160 int messageType, int buttonType)
161 {
162 JOptionPane pane = createOptionPane(parent, message, title,
163 convertMessageType(messageType), convertButtonType(buttonType));
164 JDialog dlg = createDialog(pane, parent, title);
165 Object result = showPane(pane, dlg);
166 if (result == null || !(result instanceof Integer))
167 {
168 return RET_CANCEL;
169 }
170 else
171 {
172 return convertReturnValue(((Integer) result).intValue());
173 }
174 }
175
176 /**
177 * Converts the passed in message type into the corresponding type used by
178 * {@code JOptionPane}.
179 *
180 * @param type the type to be converted
181 * @return the corresponding Swing constant
182 */
183 protected int convertMessageType(int type)
184 {
185 return convert(type, MESSAGE_TYPES, SWING_MESSAGE_TYPES);
186 }
187
188 /**
189 * Converts the passed in button type into the corresponding option type
190 * used by {@code JOptionPane}.
191 *
192 * @param type the type to be converted
193 * @return the corresponding Swing constant
194 */
195 protected int convertButtonType(int type)
196 {
197 return convert(type, BUTTON_TYPES, SWING_BUTTON_TYPES);
198 }
199
200 /**
201 * Converts the passed in return value from the {@code JOptionPane}
202 * to the corresponding {@code RET_XXXX} constant.
203 *
204 * @param value the return value from the option pane
205 * @return the corresponding {@code RET_XXXX} constant
206 */
207 protected int convertReturnValue(int value)
208 {
209 return convert(value, SWING_RETURN_VALUES, RETURN_VALUES);
210 }
211
212 /**
213 * Creates the option pane dialog for displaying the message box.
214 *
215 * @param parent the parent window
216 * @param message the message
217 * @param title the title
218 * @param messageType the message type
219 * @param optionType the option type
220 * @return the option pane
221 */
222 protected JOptionPane createOptionPane(Window parent, Object message,
223 String title, int messageType, int optionType)
224 {
225 return new JOptionPane(processMessage(message), messageType, optionType);
226 }
227
228 /**
229 * Displays the given option pane. This method is called after the pane has
230 * been created and initialized.
231 *
232 * @param pane the pane to display
233 * @param dialog the dialog obtained from the option pane
234 * @return the return value of the pane (indicating the option selected by
235 * the user)
236 */
237 protected Object showPane(JOptionPane pane, JDialog dialog)
238 {
239 dialog.setVisible(true);
240 return pane.getValue();
241 }
242
243 /**
244 * Creates the dialog from the option pane. This is the component that is to
245 * be displayed.
246 *
247 * @param pane the option pane
248 * @param parent the parent component
249 * @param title the dialog's title
250 * @return the dialog to display
251 */
252 protected JDialog createDialog(JOptionPane pane, Window parent, String title)
253 {
254 return pane.createDialog((parent != null) ? (Component) WindowUtils
255 .getPlatformWindow(parent) : null, title);
256 }
257
258 /**
259 * Processes the given message before it is displayed. This method
260 * implements some conversions to ensure that a valid message is displayed
261 * in a visually pleasant way. It does the following changes:
262 * <ul>
263 * <li>If a maximum line length is specified, line wrapping is performed.</li>
264 * <li>If the message string is wrapped in HTML tags, they are removed.</li>
265 * </ul>
266 *
267 * @param message the message
268 * @return the processed message
269 */
270 private Object processMessage(Object message)
271 {
272 if (message == null)
273 {
274 return StringUtils.EMPTY;
275 }
276
277 String msg = removeHtmlTags(message);
278 if (getMaximumLineLength() != NO_LINE_WRAP)
279 {
280 return lineWrap(msg);
281 }
282 return msg;
283 }
284
285 /**
286 * Performs line wrapping for the specified message object. For each line in
287 * the message string the maximum line length is enforced.
288 *
289 * @param message the message
290 * @return the message with line wrapping performed
291 */
292 private String lineWrap(String message)
293 {
294 String[] lines = message.split(String.valueOf(CR));
295 StringBuilder buf = new StringBuilder(BUF_LENGTH);
296 boolean first = true;
297
298 for (String line : lines)
299 {
300 if (first)
301 {
302 first = false;
303 }
304 else
305 {
306 buf.append(CR);
307 }
308 buf.append(WordUtils.wrap(line, getMaximumLineLength(), CR, true));
309 }
310 return buf.toString();
311 }
312
313 /**
314 * Removes leading and trailing html tags from the message string. HTML
315 * formatting is not supported.
316 *
317 * @param message the message
318 * @return the processed message
319 */
320 private static String removeHtmlTags(Object message)
321 {
322 String s = message.toString();
323 return StringUtils.removeEndIgnoreCase(
324 StringUtils.removeStartIgnoreCase(s, HTML_START), HTML_END);
325 }
326
327 /**
328 * Converts a value from a source range into a destination range. If the
329 * conversion fails, an exception is thrown.
330 *
331 * @param value the value
332 * @param src the source values
333 * @param dest the destination values
334 * @return the converted value
335 */
336 private static int convert(int value, int[] src, int[] dest)
337 {
338 for (int i = 0; i < src.length; i++)
339 {
340 if (value == src[i])
341 {
342 return dest[i];
343 }
344 }
345
346 throw new IllegalArgumentException("Unknown parameter " + value);
347 }
348 }