View Javadoc

1   /**
2    * Copyright (C) 2008 Atlassian
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  
17  package com.atlassian.theplugin.idea.jira;
18  
19  import com.atlassian.theplugin.commons.cfg.JiraServerCfg;
20  import com.atlassian.theplugin.idea.Constants;
21  import com.atlassian.theplugin.idea.HelpUrl;
22  import com.atlassian.theplugin.idea.IdeaHelper;
23  import com.atlassian.theplugin.jira.JIRAServer;
24  import com.atlassian.theplugin.jira.JIRAServerFacade;
25  import com.atlassian.theplugin.jira.api.JIRAAction;
26  import com.atlassian.theplugin.jira.api.JIRAActionField;
27  import com.atlassian.theplugin.jira.api.JIRAException;
28  import com.intellij.ide.BrowserUtil;
29  import com.intellij.openapi.project.Project;
30  import com.intellij.openapi.ui.DialogWrapper;
31  import com.intellij.openapi.ui.Messages;
32  import com.intellij.ui.HyperlinkLabel;
33  import com.intellij.uiDesigner.core.GridConstraints;
34  import com.intellij.uiDesigner.core.GridLayoutManager;
35  import net.sf.nachocalendar.components.CalendarPanel;
36  import net.sf.nachocalendar.model.DateSelectionModel;
37  
38  import javax.management.timer.Timer;
39  import javax.swing.*;
40  import javax.swing.event.*;
41  import java.awt.*;
42  import java.awt.event.ActionEvent;
43  import java.awt.event.ActionListener;
44  import java.text.DateFormat;
45  import java.util.Calendar;
46  import java.util.Date;
47  import java.util.List;
48  import java.util.regex.Matcher;
49  import java.util.regex.Pattern;
50  
51  
52  public class WorkLogCreate extends DialogWrapper {
53  	private JPanel contentPane;
54  	private JTextArea comment;
55  	private JButton endDateChange;
56  	private JLabel endDateLabel;
57  	private HyperlinkLabel helpLabel;
58  	private JCheckBox stopProgress;
59  	private JLabel stopProgressLabel;
60  	private JTextField timeSpentField;
61  	private JRadioButton btnLeaveUnchanged;
62  	private JRadioButton btnAutoUpdate;
63  	private JRadioButton btnUpdateManually;
64  	private JTextField remainingEstimateField;
65  	private JLabel remainingEstimateLabel;
66  	private boolean haveIssueStopProgressInfo = false;
67  	private JIRAAction inProgressAction;
68  	JIRAServerFacade facade;
69  	private Date endTime;
70  	private final Calendar now = Calendar.getInstance();
71  
72  	WdhmInputListener timeSpentListener;
73  	WdhmInputListener remainingEstimateListener;
74  	private static final int STOP_PROGRESS_ACTION_ID = 301;
75  
76  	/**
77  	 * Method generated by IntelliJ IDEA GUI Designer
78  	 * >>> IMPORTANT!! <<<
79  	 * DO NOT edit this method OR call it in your code!
80  	 *
81  	 * @noinspection ALL
82  	 */
83  	private void $$$setupUI$$$() {
84  		createUIComponents();
85  		contentPane = new JPanel();
86  		contentPane.setLayout(new GridBagLayout());
87  		contentPane.setMinimumSize(new Dimension(700, 300));
88  		contentPane.setPreferredSize(new Dimension(700, 300));
89  		final JPanel panel1 = new JPanel();
90  		panel1.setLayout(new GridBagLayout());
91  		panel1.setMinimumSize(new Dimension(600, 200));
92  		panel1.setPreferredSize(new Dimension(600, 200));
93  		panel1.setRequestFocusEnabled(true);
94  		GridBagConstraints gbc;
95  		gbc = new GridBagConstraints();
96  		gbc.gridx = 8;
97  		gbc.gridy = 0;
98  		gbc.gridheight = 2;
99  		gbc.weightx = 1.0;
100 		gbc.weighty = 1.0;
101 		gbc.fill = GridBagConstraints.BOTH;
102 		gbc.insets = new Insets(12, 12, 12, 12);
103 		contentPane.add(panel1, gbc);
104 		timeSpentField = new JTextField();
105 		timeSpentField.setMinimumSize(new Dimension(100, 28));
106 		timeSpentField.setPreferredSize(new Dimension(150, 28));
107 		gbc = new GridBagConstraints();
108 		gbc.gridx = 1;
109 		gbc.gridy = 0;
110 		gbc.anchor = GridBagConstraints.WEST;
111 		gbc.fill = GridBagConstraints.HORIZONTAL;
112 		panel1.add(timeSpentField, gbc);
113 		final JLabel label1 = new JLabel();
114 		label1.setText("End time:");
115 		gbc = new GridBagConstraints();
116 		gbc.gridx = 0;
117 		gbc.gridy = 1;
118 		gbc.anchor = GridBagConstraints.WEST;
119 		panel1.add(label1, gbc);
120 		final JLabel label2 = new JLabel();
121 		label2.setText("Time spent:");
122 		gbc = new GridBagConstraints();
123 		gbc.gridx = 0;
124 		gbc.gridy = 0;
125 		gbc.anchor = GridBagConstraints.WEST;
126 		panel1.add(label2, gbc);
127 		stopProgressLabel = new JLabel();
128 		stopProgressLabel.setEnabled(false);
129 		stopProgressLabel.setText("Stop progress:");
130 		gbc = new GridBagConstraints();
131 		gbc.gridx = 0;
132 		gbc.gridy = 6;
133 		gbc.anchor = GridBagConstraints.WEST;
134 		gbc.insets = new Insets(0, 0, 12, 12);
135 		panel1.add(stopProgressLabel, gbc);
136 		stopProgress = new JCheckBox();
137 		stopProgress.setEnabled(false);
138 		stopProgress.setHorizontalAlignment(2);
139 		stopProgress.setHorizontalTextPosition(10);
140 		stopProgress.setText("");
141 		gbc = new GridBagConstraints();
142 		gbc.gridx = 1;
143 		gbc.gridy = 6;
144 		gbc.anchor = GridBagConstraints.WEST;
145 		gbc.insets = new Insets(0, 0, 12, 0);
146 		panel1.add(stopProgress, gbc);
147 		final JLabel label3 = new JLabel();
148 		label3.setText("Comment:");
149 		gbc = new GridBagConstraints();
150 		gbc.gridx = 0;
151 		gbc.gridy = 7;
152 		gbc.weighty = 1.0;
153 		gbc.anchor = GridBagConstraints.NORTHWEST;
154 		panel1.add(label3, gbc);
155 		final JScrollPane scrollPane1 = new JScrollPane();
156 		gbc = new GridBagConstraints();
157 		gbc.gridx = 1;
158 		gbc.gridy = 7;
159 		gbc.gridwidth = 8;
160 		gbc.weightx = 1.0;
161 		gbc.weighty = 1.0;
162 		gbc.fill = GridBagConstraints.BOTH;
163 		panel1.add(scrollPane1, gbc);
164 		comment = new JTextArea();
165 		scrollPane1.setViewportView(comment);
166 		final JPanel panel2 = new JPanel();
167 		panel2.setLayout(new FlowLayout(FlowLayout.LEFT, 5, 5));
168 		gbc = new GridBagConstraints();
169 		gbc.gridx = 2;
170 		gbc.gridy = 0;
171 		gbc.gridwidth = 3;
172 		gbc.fill = GridBagConstraints.BOTH;
173 		gbc.insets = new Insets(0, 12, 0, 0);
174 		panel1.add(panel2, gbc);
175 		final JLabel label4 = new JLabel();
176 		label4.setFont(new Font(label4.getFont().getName(), label4.getFont().getStyle(), label4.getFont().getSize()));
177 		label4.setText("Enter time spent in a format of: ");
178 		panel2.add(label4);
179 		final JLabel label5 = new JLabel();
180 		label5.setFont(new Font(label5.getFont().getName(), label5.getFont().getStyle(), label5.getFont().getSize()));
181 		label5.setForeground(Color.blue);
182 		label5.setText("*w*d*h*m");
183 		panel2.add(label5);
184 		final JLabel label6 = new JLabel();
185 		label6.setText("Remaining Estimate:");
186 		gbc = new GridBagConstraints();
187 		gbc.gridx = 0;
188 		gbc.gridy = 3;
189 		gbc.anchor = GridBagConstraints.WEST;
190 		gbc.insets = new Insets(0, 0, 0, 12);
191 		panel1.add(label6, gbc);
192 		btnLeaveUnchanged = new JRadioButton();
193 		btnLeaveUnchanged.setText("Leave Unchanged");
194 		gbc = new GridBagConstraints();
195 		gbc.gridx = 1;
196 		gbc.gridy = 3;
197 		gbc.anchor = GridBagConstraints.WEST;
198 		panel1.add(btnLeaveUnchanged, gbc);
199 		btnUpdateManually = new JRadioButton();
200 		btnUpdateManually.setEnabled(true);
201 		btnUpdateManually.setText("Update Manually");
202 		gbc = new GridBagConstraints();
203 		gbc.gridx = 1;
204 		gbc.gridy = 4;
205 		gbc.anchor = GridBagConstraints.WEST;
206 		panel1.add(btnUpdateManually, gbc);
207 		remainingEstimateLabel = new JLabel();
208 		remainingEstimateLabel.setEnabled(false);
209 		remainingEstimateLabel.setText("New Remaining Estimate:");
210 		gbc = new GridBagConstraints();
211 		gbc.gridx = 1;
212 		gbc.gridy = 5;
213 		gbc.anchor = GridBagConstraints.WEST;
214 		gbc.insets = new Insets(0, 24, 0, 12);
215 		panel1.add(remainingEstimateLabel, gbc);
216 		final JPanel panel3 = new JPanel();
217 		panel3.setLayout(new GridLayoutManager(1, 1, new Insets(0, 0, 0, 0), -1, -1));
218 		gbc = new GridBagConstraints();
219 		gbc.gridx = 2;
220 		gbc.gridy = 5;
221 		gbc.fill = GridBagConstraints.BOTH;
222 		panel1.add(panel3, gbc);
223 		remainingEstimateField = new JTextField();
224 		remainingEstimateField.setEnabled(false);
225 		remainingEstimateField.setMinimumSize(new Dimension(100, 28));
226 		remainingEstimateField.setPreferredSize(new Dimension(150, 28));
227 		panel3.add(remainingEstimateField, new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED, null, new Dimension(150, -1), null, 0, false));
228 		final JPanel panel4 = new JPanel();
229 		panel4.setLayout(new GridLayoutManager(1, 2, new Insets(0, 0, 0, 0), -1, -1));
230 		gbc = new GridBagConstraints();
231 		gbc.gridx = 1;
232 		gbc.gridy = 1;
233 		gbc.fill = GridBagConstraints.BOTH;
234 		gbc.insets = new Insets(12, 0, 12, 0);
235 		panel1.add(panel4, gbc);
236 		endDateLabel = new JLabel();
237 		endDateLabel.setText("1/01/08 12:00");
238 		panel4.add(endDateLabel, new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, new Dimension(109, 16), null, 0, false));
239 		endDateChange = new JButton();
240 		endDateChange.setText("Change");
241 		panel4.add(endDateChange, new GridConstraints(0, 1, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false));
242 		btnAutoUpdate = new JRadioButton();
243 		btnAutoUpdate.setSelected(true);
244 		btnAutoUpdate.setText("Auto Update");
245 		gbc = new GridBagConstraints();
246 		gbc.gridx = 1;
247 		gbc.gridy = 2;
248 		gbc.anchor = GridBagConstraints.WEST;
249 		panel1.add(btnAutoUpdate, gbc);
250 		gbc = new GridBagConstraints();
251 		gbc.gridx = 8;
252 		gbc.gridy = 2;
253 		gbc.anchor = GridBagConstraints.EAST;
254 		gbc.insets = new Insets(0, 0, 0, 12);
255 		contentPane.add(helpLabel, gbc);
256 		ButtonGroup buttonGroup;
257 		buttonGroup = new ButtonGroup();
258 		buttonGroup.add(btnUpdateManually);
259 		buttonGroup.add(btnAutoUpdate);
260 		buttonGroup.add(btnLeaveUnchanged);
261 	}
262 
263 	/**
264 	 * @noinspection ALL
265 	 */
266 	public JComponent $$$getRootComponent$$$() {
267 		return contentPane;
268 	}
269 
270 	private class WdhmInputListener implements DocumentListener {
271 
272 		private static final String REGEX = "^\\s*(\\d+w)?\\s*(\\d+d)?\\s*(\\d+h)?\\s*(\\d+m)?\\s*$";
273 
274 		private JTextField field;
275 		boolean matchFound;
276 
277 		public WdhmInputListener(JTextField field) {
278 			this.field = field;
279 		}
280 
281 		private class Period {
282 			public Period(String r) {
283 				regex = r;
284 				interval = 0;
285 			}
286 
287 			public long interval;
288 			public String regex;
289 
290 			public void findAndSet(String txt) {
291 				Pattern p = Pattern.compile("(\\d+)" + regex);
292 				Matcher m = p.matcher(txt);
293 				if (m.matches()) {
294 					String subs = txt.substring(m.start(1), m.end(1));
295 					interval = Long.valueOf(subs);
296 				}
297 			}
298 		}
299 
300 		private Period weeks = new Period("w");
301 		private Period days = new Period("d");
302 		private Period hours = new Period("h");
303 		private Period minutes = new Period("m");
304 
305 		public void insertUpdate(DocumentEvent e) {
306 			stateChanged();
307 		}
308 
309 		public void removeUpdate(DocumentEvent e) {
310 			stateChanged();
311 		}
312 
313 		public void changedUpdate(DocumentEvent e) {
314 			stateChanged();
315 		}
316 
317 		public void stateChanged() {
318 			if (!field.isEnabled()) {
319 				return;
320 			}
321 
322 			String text = field.getText();
323 
324 			Pattern p = Pattern.compile(REGEX);
325 			Matcher m = p.matcher(text);
326 			Color c;
327 
328 			matchFound = m.matches() && text.length() > 0;
329 			c = matchFound ? Color.BLACK : Color.RED;
330 			field.setForeground(c);
331 			updateOKAction();
332 
333 			if (matchFound) {
334 				weeks.findAndSet(text);
335 				days.findAndSet(text);
336 				hours.findAndSet(text);
337 				minutes.findAndSet(text);
338 			}
339 		}
340 
341 		public long getWeeks() {
342 			return weeks.interval;
343 		}
344 
345 		public long getDays() {
346 			return days.interval;
347 		}
348 
349 		public long getHours() {
350 			return hours.interval;
351 		}
352 
353 		public long getMinutes() {
354 			return minutes.interval;
355 		}
356 
357 		public boolean isOk() {
358 			return matchFound;
359 		}
360 	}
361 
362 	private void createUIComponents() {
363 		helpLabel = new HyperlinkLabel("Help");
364 		final String helpUrl = HelpUrl.getHelpUrl(Constants.HELP_JIRA_WORKLOG);
365 
366 		helpLabel.addHyperlinkListener(new HyperlinkListener() {
367 			public void hyperlinkUpdate(HyperlinkEvent e) {
368 				BrowserUtil.launchBrowser(helpUrl);
369 			}
370 		});
371 	}
372 
373 	private void updateOKAction() {
374 		boolean enable = haveIssueStopProgressInfo && timeSpentListener.isOk();
375 		if (remainingEstimateField.isEnabled() && enable) {
376 			enable = remainingEstimateListener.isOk();
377 		}
378 		setOKActionEnabled(enable);
379 	}
380 
381 	public WorkLogCreate(final JIRAServerFacade jiraFacade, final JiraIssueAdapter adapter, Project project) {
382 		super(false);
383 
384 		this.facade = jiraFacade;
385 
386 		$$$setupUI$$$();
387 		init();
388 		setTitle("Add Worklog for " + adapter.getKey());
389 		setOKActionEnabled(false);
390 		getOKAction().putValue(Action.NAME, "Add Worklog");
391 
392 		timeSpentListener = new WdhmInputListener(timeSpentField);
393 		remainingEstimateListener = new WdhmInputListener(remainingEstimateField);
394 
395 		timeSpentField.getDocument().addDocumentListener(timeSpentListener);
396 		remainingEstimateField.getDocument().addDocumentListener(remainingEstimateListener);
397 
398 		endTime = now.getTime();
399 
400 		endDateLabel.setText(DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT).format(now.getTime()));
401 
402 		endDateChange.addActionListener(new ActionListener() {
403 			public void actionPerformed(ActionEvent e) {
404 				TimeDatePicker tdp = new TimeDatePicker(endTime);
405 				if (tdp.isOK()) {
406 					endTime = tdp.getSelectedTime();
407 					String s = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT).format(endTime);
408 					endDateLabel.setText(s);
409 				}
410 			}
411 		});
412 
413 		btnUpdateManually.addChangeListener(new ChangeListener() {
414 			public void stateChanged(ChangeEvent e) {
415 				boolean b = btnUpdateManually.isSelected();
416 				remainingEstimateField.setEnabled(b);
417 				remainingEstimateLabel.setEnabled(b);
418 				updateOKAction();
419 			}
420 		});
421 
422         final JIRAServer jiraServer = IdeaHelper.getCurrentJIRAServer(project);
423         if (jiraServer == null) {
424             Messages.showErrorDialog(project, "There is no selected JIRA Server", "Error");
425             return;
426         }
427         final JiraServerCfg server = jiraServer.getServer();
428 
429         new Thread(new Runnable() {
430 			public void run() {
431 				try {
432 					List<JIRAAction> actions = facade.getAvailableActions(server, adapter.getIssue());
433 					for (JIRAAction a : actions) {
434 						if (a.getId() == STOP_PROGRESS_ACTION_ID) {
435 							List<JIRAActionField> fields = facade.getFieldsForAction(server, adapter.getIssue(), a);
436 							if (fields.isEmpty()) {
437 								stopProgress.setEnabled(true);
438 								stopProgressLabel.setEnabled(true);
439 								inProgressAction = a;
440 							}
441 							break;
442 						}
443 					}
444 				} catch (JIRAException e) {
445 					// well, let's ignore, this is an optional functionality anyway...
446 				}
447 				haveIssueStopProgressInfo = true;
448 				timeSpentListener.stateChanged();
449 			}
450 		}).start();
451 	}
452 
453 	public String getTimeSpentString() {
454 		return timeSpentField.getText();
455 	}
456 
457 	public String getRemainingEstimateString() {
458 		return remainingEstimateField.getText();
459 	}
460 
461 	public Date getEndDate() {
462 		return (Date) endTime.clone();
463 	}
464 
465 	public Date getStartDate() {
466 		Date d = endTime;
467 		long t = d.getTime()
468 				- (timeSpentListener.getWeeks() * Timer.ONE_WEEK)
469 				- (timeSpentListener.getDays() * Timer.ONE_DAY)
470 				- (timeSpentListener.getHours() * Timer.ONE_HOUR)
471 				- (timeSpentListener.getMinutes() * Timer.ONE_MINUTE);
472 		d.setTime(t);
473 		return d;
474 	}
475 
476 	public String getComment() {
477 		return comment.getText();
478 	}
479 
480 	public boolean isStopProgressSelected() {
481 		return stopProgress.isSelected();
482 	}
483 
484 	public JIRAAction getInProgressAction() {
485 		return inProgressAction;
486 	}
487 
488 	public boolean getAutoUpdateRemaining() {
489 		return btnAutoUpdate.isSelected();
490 	}
491 
492 	public boolean getLeaveRemainingUnchanged() {
493 		return btnLeaveUnchanged.isSelected();
494 	}
495 
496 	public boolean getUpdateRemainingManually() {
497 		return btnUpdateManually.isSelected();
498 	}
499 
500 	protected JComponent createCenterPanel() {
501 		return contentPane;
502 	}
503 
504 	private class TimeDatePicker extends DialogWrapper {
505 
506 		private JPanel panel = new JPanel();
507 
508 		private CalendarPanel calendar;
509 		private JSpinner hour = new JSpinner();
510 		private JSpinner minute = new JSpinner();
511 		private SpinnerNumberModel hourModel;
512 		private SpinnerNumberModel minuteModel;
513 
514 
515 		TimeDatePicker(Date now) {
516 			super(false);
517 			init();
518 
519 			calendar = new CalendarPanel(1);
520 			calendar.setSelectionMode(DateSelectionModel.SINGLE_SELECTION);
521 
522             Calendar nowcal = Calendar.getInstance();
523             nowcal.setTime(now);
524             nowcal.set(Calendar.HOUR_OF_DAY, 0);
525             nowcal.set(Calendar.MINUTE, 0);
526             nowcal.set(Calendar.SECOND, 0);
527             nowcal.set(Calendar.MILLISECOND, 0);
528 
529             Date nowZeroZero = nowcal.getTime();
530 
531 			calendar.setDate(nowZeroZero);
532 			calendar.setValue(nowZeroZero);
533 
534 			Calendar cal = Calendar.getInstance();
535 			cal.setTime(now);
536 			hourModel = new SpinnerNumberModel(cal.get(Calendar.HOUR_OF_DAY), 0, 24, 1);
537 			minuteModel = new SpinnerNumberModel(cal.get(Calendar.MINUTE), 0, 60, 1);
538 			hour.setModel(hourModel);
539 			minute.setModel(minuteModel);
540 
541 			setTitle("Set End Time");
542 
543 			panel.setLayout(new GridBagLayout());
544 			GridBagConstraints gbc = new GridBagConstraints();
545 
546 			gbc.anchor = GridBagConstraints.CENTER;
547 			gbc.fill = GridBagConstraints.HORIZONTAL;
548 			gbc.gridx = 0;
549 			gbc.gridy = 0;
550 			gbc.gridwidth = 2;
551 			gbc.weightx = 1;
552 			gbc.insets = new Insets(Constants.DIALOG_MARGIN, Constants.DIALOG_MARGIN, 0, Constants.DIALOG_MARGIN);
553 			panel.add(new JLabel("Day", SwingConstants.CENTER), gbc);
554 			gbc.gridy = 1;
555 			panel.add(calendar, gbc);
556 
557 			gbc.gridx = 0;
558 			gbc.gridy = 2;
559 			gbc.gridwidth = 1;
560 			gbc.weightx = 0.5;
561 			panel.add(new JLabel("hour", SwingConstants.CENTER), gbc);
562 			gbc.gridy = 3;
563 			panel.add(hour, gbc);
564 
565 			gbc.gridx = 1;
566 			gbc.gridy = 2;
567 			panel.add(new JLabel("minute", SwingConstants.CENTER), gbc);
568 			gbc.gridy = 3;
569 			panel.add(minute, gbc);
570 			show();
571 		}
572 
573 		protected JComponent createCenterPanel() {
574 			return panel;
575 		}
576 
577 		public Date getSelectedTime() {
578 			Date d = (Date) calendar.getValue();
579 			long newTime = d.getTime();
580 			newTime += hourModel.getNumber().intValue() * Timer.ONE_HOUR;
581 			newTime += minuteModel.getNumber().intValue() * Timer.ONE_MINUTE;
582 			d.setTime(newTime);
583 			return d;
584 		}
585 	}
586 
587 }