1
2
3
4
5
6
7
8
9
10
11
12
13
14
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
78
79
80
81
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
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
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 }