diff --git a/README.md b/README.md index 8fa8686..7c70eee 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ This plugin provides support for [backlog](http://backlogtool.com/?lang=1). ## Requirements -- NetBeans 8.0+ +- NetBeans 8.1+ ## Features @@ -18,6 +18,7 @@ This plugin provides support for [backlog](http://backlogtool.com/?lang=1). - Create queries - Find issues - Schedules +- Notification (only comment) ## Add Backlog repository @@ -46,7 +47,6 @@ If you don't want to use thses, you can disable these in Options (Tools > Option ## Resources - [Backlog4j](https://github.com/nulab/backlog4j) -- [yenta](https://bitbucket.org/jglick/yenta) ## License diff --git a/README_ja.md b/README_ja.md index a63b704..33a6119 100644 --- a/README_ja.md +++ b/README_ja.md @@ -8,7 +8,7 @@ ## 必要条件 -- NetBeans 8.0+ +- NetBeans 8.1+ ## 機能 @@ -17,6 +17,7 @@ - 問合せの作成 - 課題の検索 - スケジュール +- お知らせ (コメントのみ) ## Backlogリポジトリの追加 @@ -33,6 +34,7 @@ - 担当 - 登録 +- お知らせ 完了以外の状態の課題が表示されます。 もしこれらの問合せを使いたくなければ、オプションで無効に設定できます。(ツール > オプション > チーム > Backlog) @@ -44,7 +46,6 @@ ## リソース - [Backlog4j](https://github.com/nulab/backlog4j) -- [yenta](https://bitbucket.org/jglick/yenta) ## ライセンス diff --git a/pom.xml b/pom.xml index 05960be..b975dad 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 com.junichi11 netbeans-backlog-plugin - 0.3.5 + 0.4.0 nbm @@ -54,18 +54,23 @@ org.netbeans.api org-netbeans-api-annotations-common - RELEASE80 + ${netbeans.api.version} org.netbeans.api org-netbeans-modules-bugtracking - RELEASE80 + ${netbeans.api.version} jar org.netbeans.api org-openide-util - RELEASE80 + ${netbeans.api.version} + + + org.netbeans.api + org-openide-util-ui + ${netbeans.api.version} org.netbeans.external @@ -75,43 +80,53 @@ org.netbeans.modules org-netbeans-modules-bugtracking-commons - RELEASE80 + ${netbeans.api.version} org.netbeans.api org-netbeans-api-progress - RELEASE80 + ${netbeans.api.version} + + + org.netbeans.api + org-netbeans-api-progress-nb + ${netbeans.api.version} org.netbeans.api org-openide-filesystems - RELEASE80 + ${netbeans.api.version} + + + org.netbeans.api + org-openide-filesystems-nb + ${netbeans.api.version} org.netbeans.api org-openide-dialogs - RELEASE80 + ${netbeans.api.version} org.netbeans.api org-openide-awt - RELEASE80 + ${netbeans.api.version} org.netbeans.api org-openide-nodes - RELEASE80 + ${netbeans.api.version} org.netbeans.modules org-netbeans-modules-team-commons - RELEASE80 + ${netbeans.api.version} jar org.netbeans.external org-apache-commons-io - RELEASE80 + ${netbeans.api.version} com.nulab-inc @@ -121,17 +136,17 @@ org.netbeans.api org-openide-modules - RELEASE80 + ${netbeans.api.version} org.netbeans.api org-openide-util-lookup - RELEASE80 + ${netbeans.api.version} org.netbeans.api org-netbeans-modules-options-api - RELEASE80 + ${netbeans.api.version} junit @@ -142,6 +157,7 @@ UTF-8 + RELEASE81-BETA Backlog This plugin provides support for <a href ="http://backlogtool.com/?lang=1">backlog</a>. @@ -159,4 +175,4 @@ <li><a href="https://github.com/junichi11/netbeans-backlog-plugin">https://github.com/junichi11/netbeans-backlog-plugin</a></li> <li><a href="https://github.com/nulab/backlog4j">Backlog4j</a></li> </ul> - \ No newline at end of file + diff --git a/src/main/java/com/junichi11/netbeans/modules/backlog/BacklogConfig.java b/src/main/java/com/junichi11/netbeans/modules/backlog/BacklogConfig.java index f3cb43d..4310dce 100644 --- a/src/main/java/com/junichi11/netbeans/modules/backlog/BacklogConfig.java +++ b/src/main/java/com/junichi11/netbeans/modules/backlog/BacklogConfig.java @@ -41,12 +41,16 @@ */ package com.junichi11.netbeans.modules.backlog; +import com.junichi11.netbeans.modules.backlog.issue.BacklogIssue; import com.junichi11.netbeans.modules.backlog.query.BacklogQuery; import com.junichi11.netbeans.modules.backlog.repository.BacklogRepository; import com.junichi11.netbeans.modules.backlog.utils.StringUtils; +import java.util.ArrayList; import java.util.prefs.BackingStoreException; import java.util.prefs.Preferences; +import org.netbeans.modules.bugtracking.spi.IssueStatusProvider.Status; import org.openide.util.Exceptions; +import org.openide.util.NbBundle; import org.openide.util.NbPreferences; /** @@ -56,8 +60,21 @@ public final class BacklogConfig { private static final BacklogConfig INSTANCE = new BacklogConfig(); + + // query private static final String QUERY = "query"; // NOI18N private static final String QUERY_PARAMS = "query.params"; // NOI18N + + // status + private static final String STATUS = "status"; // NOI18N + // [status]::[last updated] + private static final String STATUS_FORMAT = "%s::%s"; // NOI18N + private static final String STATUS_DELIMITER = "::"; // NOI18N + + // template + private static final String TEMPLATE = "template"; // NOI18N + private static final String DEFAULT_TEMPLATE_NAME = "default"; // NOI18N + private static final String QUERY_MAX_ISSUE_COUNT = "query.max.issue.count"; // NOI18N private BacklogConfig() { @@ -67,6 +84,43 @@ public static BacklogConfig getInstance() { return INSTANCE; } + public Status getStatus(BacklogIssue issue) { + BacklogRepository repository = issue.getRepository(); + Preferences preferences = getPreferences().node(repository.getID()).node(STATUS); + String statusTime = preferences.get(issue.getKeyId(), null); + if (statusTime == null) { + return Status.INCOMING_NEW; + } + + String[] split = statusTime.split(STATUS_DELIMITER); + if (split.length != 2) { + return Status.INCOMING_NEW; + } + + // TODO CONFLICT, OUTGOING_NEW, OUTGOING_MODIFIED + Status status = Status.valueOf(split[0]); + long lastUpdated = Long.parseLong(split[1]); + if (status == Status.SEEN) { + long lastUpdatedTime = issue.getLastUpdatedTime(); + if (lastUpdatedTime != -1L) { + if (lastUpdated < lastUpdatedTime) { + setStatus(issue, Status.INCOMING_MODIFIED); + return Status.INCOMING_MODIFIED; + } + } + } + return status; + } + + public void setStatus(BacklogIssue issue, Status status) { + long lastUpdatedTime = issue.getLastUpdatedTime(); + if (lastUpdatedTime != -1L) { + BacklogRepository repository = issue.getRepository(); + Preferences preferences = getPreferences().node(repository.getID()).node(STATUS); + preferences.put(issue.getKeyId(), String.format(STATUS_FORMAT, status.name(), lastUpdatedTime)); + } + } + /** * Return saved query names. * @@ -151,6 +205,73 @@ public void setMaxIssueCount(BacklogRepository repository, BacklogQuery query) { preferences.putInt(QUERY_MAX_ISSUE_COUNT, query.getMaxIssueCount()); } + /** + * Get the template for specified name. + * + * @param name the template name + * @return the template + */ + @NbBundle.Messages("BacklogConfig.default.template=#### Overview description\n" + + "\n" + + "#### Steps to reproduce\n" + + "\n" + + "1. \n" + + "2. \n" + + "3. \n" + + "\n" + + "#### Actual results\n" + + "\n" + + "#### Expected results\n") + public String getTemplate(String name) { + return getPreferences().node(TEMPLATE).get(name, Bundle.BacklogConfig_default_template()); + } + + /** + * Set template. + * + * @param name the template name + * @param template the template + */ + public void setTemplate(String name, String template) { + getPreferences().node(TEMPLATE).put(name, template); + } + + /** + * Remove a template. NOTE: Can't remove the default template. But + * default template will be initialized. + * + * @param name the template name + */ + public void removeTemplate(String name) { + getPreferences().node(TEMPLATE).remove(name); + } + + /** + * Get all template names. + * + * @return all template names + */ + public String[] getTemplateNames() { + ArrayList names = new ArrayList<>(); + names.add(DEFAULT_TEMPLATE_NAME); + Preferences preferences = getPreferences().node(TEMPLATE); + try { + // contains the default template if it was edited + String[] childrenNames = preferences.keys(); + int count = 1; // default template + for (String childName : childrenNames) { + if (!childName.equals(DEFAULT_TEMPLATE_NAME)) { + names.add(childName); + count++; + } + } + return names.toArray(new String[count]); + } catch (BackingStoreException ex) { + Exceptions.printStackTrace(ex); + } + return names.toArray(new String[1]); + } + private Preferences getPreferences() { return NbPreferences.forModule(BacklogConfig.class); } diff --git a/src/main/java/com/junichi11/netbeans/modules/backlog/BacklogData.java b/src/main/java/com/junichi11/netbeans/modules/backlog/BacklogData.java index 171487f..f35bf14 100644 --- a/src/main/java/com/junichi11/netbeans/modules/backlog/BacklogData.java +++ b/src/main/java/com/junichi11/netbeans/modules/backlog/BacklogData.java @@ -159,12 +159,12 @@ public List getIssueTypes(boolean isForce) { } /** - * Get Users. + * Get project Users. * * @return Users */ - public List getUsers() { - return getUsers(false); + public List getProjectUsers() { + return getProjectUsers(false); } /** @@ -175,6 +175,9 @@ public List getUsers() { */ @CheckForNull public Icon getUserIcon(User user) { + if (user == null) { + return null; + } Icon icon = userIcons.get(user); if (icon != null) { return icon; @@ -203,13 +206,13 @@ public Icon getUserIcon(User user) { } /** - * Get Users. + * Get project Users. * * @param isForce {@code true} if you want to reload data, {@code false} if * you want to use cache data * @return Users */ - public List getUsers(boolean isForce) { + public List getProjectUsers(boolean isForce) { BacklogClient backlogClient = repository.createBacklogClient(); if (backlogClient == null) { return Collections.emptyList(); diff --git a/src/main/java/com/junichi11/netbeans/modules/backlog/Installer.java b/src/main/java/com/junichi11/netbeans/modules/backlog/Installer.java deleted file mode 100644 index abc90dd..0000000 --- a/src/main/java/com/junichi11/netbeans/modules/backlog/Installer.java +++ /dev/null @@ -1,188 +0,0 @@ -/* - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. - * - * Copyright 2014 Oracle and/or its affiliates. All rights reserved. - * - * Oracle and Java are registered trademarks of Oracle and/or its affiliates. - * Other names may be trademarks of their respective owners. - * - * The contents of this file are subject to the terms of either the GNU - * General Public License Version 2 only ("GPL") or the Common - * Development and Distribution License("CDDL") (collectively, the - * "License"). You may not use this file except in compliance with the - * License. You can obtain a copy of the License at - * http://www.netbeans.org/cddl-gplv2.html - * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the - * specific language governing permissions and limitations under the - * License. When distributing the software, include this License Header - * Notice in each file and include the License file at - * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the GPL Version 2 section of the License file that - * accompanied this code. If applicable, add the following below the - * License Header, with the fields enclosed by brackets [] replaced by - * your own identifying information: - * "Portions Copyrighted [year] [name of copyright owner]" - * - * If you wish your version of this file to be governed by only the CDDL - * or only the GPL Version 2, indicate your decision by adding - * "[Contributor] elects to include this software in this distribution - * under the [CDDL or GPL Version 2] license." If you do not indicate a - * single choice of license, a recipient has the option to distribute - * your version of this file under either the CDDL, the GPL Version 2 or - * to extend the choice of license to its licensees as provided above. - * However, if you add GPL Version 2 code and therefore, elected the GPL - * Version 2 license, then the option applies only if the new code is - * made subject to such option by the copyright holder. - * - * Contributor(s): - * - * Portions Copyrighted 2014 Sun Microsystems, Inc. - */ -package com.junichi11.netbeans.modules.backlog; - -import java.lang.reflect.Field; -import java.lang.reflect.Method; -import java.util.Collections; -import java.util.HashSet; -import java.util.Set; -import org.openide.modules.Dependency; -import org.openide.modules.ModuleInfo; -import org.openide.modules.ModuleInstall; -import org.openide.modules.Modules; - -/** - * Fixes up module system dependencies. - *

- * First add dependencies on whatever API modules you want to use, even if you - * are not (yet) their “friend”. Setting - * {@code warn} allows you to still compile - * against them and declare a regular specification version dependency, but be - * careful since this also disables checks about transitive dependency usage. - *

- * You can also add dependencies on modules with no exported packages; declare a - * specification version dependency and use the same flag to suppress checks. - * The same applies to modules exporting friend packages from which you - * also/instead want to use implementation packages. In either case be very - * careful and preferably catch {@link LinkageError} to be defensive. - *

- * Then add a module installer and extend this class rather than - * {@link ModuleInstall} directly. Override {@link #friends} and/or - * {@link #siblings}. When {@link #validate} is called, module system errors - * will be suppressed. - * - * Original source code : https://bitbucket.org/jglick/yenta - */ -public class Installer extends ModuleInstall { - - private static final long serialVersionUID = 8470550717876369223L; - - /** - * Specifies the modules with whom you would like to be friends. These - * modules must be among your declared dependencies and they must export - * friend packages. For each such module, if you are not already a friend, - * you will be treated as one, so you will be able to access friend (but not - * private) packages. - * - * @return a set of module code name bases (default implementation is empty) - */ - protected Set friends() { - Set set = new HashSet<>(); - set.add("org.netbeans.modules.bugtracking.commons"); // NOI18N - set.add("org.netbeans.modules.team.commons"); // NOI18N - return set; - } - - /** - * Specifies the modules from whom you need complete access. These modules - * must be among your declared dependencies. For each such module, you will - * be able to access all packages, as with an implementation dependency. Be - * careful to defend against unexpected signature changes! - * - * @return a set of module code name bases (default implementation is empty) - */ - protected Set siblings() { - return Collections.emptySet(); - } - - /** - * @inheritDoc - * @throws IllegalStateException if {@link #friends} and {@link #siblings} are misconfigured or if the module system cannot be manipulated - */ - @Override - public void validate() throws IllegalStateException { - Set friends = friends(); - Set siblings = siblings(); - if (friends.isEmpty() && siblings.isEmpty()) { - throw new IllegalStateException("Must specify some friends and/or siblings"); - } - ModuleInfo me = Modules.getDefault().ownerOf(getClass()); - if (me == null) { - throw new IllegalStateException("No apparent module owning " + getClass()); - } - try { - Object manager = me.getClass().getMethod("getManager").invoke(me); - for (String m : friends) { - if (siblings.contains(m)) { - throw new IllegalStateException("Cannot specify the same module " + m + " in both friends and siblings"); - } - Object data = data(findDependency(manager, m)); - Field friendNamesF = Class.forName("org.netbeans.ModuleData", true, data.getClass().getClassLoader()).getDeclaredField("friendNames"); - friendNamesF.setAccessible(true); - Set names = (Set) friendNamesF.get(data); - Set newNames = new HashSet<>(names); - newNames.add(me.getCodeNameBase()); - friendNamesF.set(data, newNames); - } - for (String m : siblings) { - ModuleInfo dep = findDependency(manager, m); - String implVersion = dep.getImplementationVersion(); - if (implVersion == null) { - throw new IllegalStateException("No implementation version found in " + m); - } - Object data = data(me); - Field dependenciesF = Class.forName("org.netbeans.ModuleData", true, data.getClass().getClassLoader()).getDeclaredField("dependencies"); - dependenciesF.setAccessible(true); - Dependency[] dependencies = (Dependency[]) dependenciesF.get(data); - boolean found = false; - for (int i = 0; i < dependencies.length; i++) { - if (dependencies[i].getName().replaceFirst("/.+$", "").equals(m)) { - Set nue = Dependency.create(Dependency.TYPE_MODULE, dependencies[i].getName() + " = " + implVersion); - if (nue.size() != 1) { - throw new IllegalStateException("Could not recreate dependency from " + dependencies[i] + " based on " + implVersion); - } - dependencies[i] = nue.iterator().next(); - found = true; - } - } - if (!found) { - throw new IllegalStateException("Did not find dependency on " + m); - } - // StandardModule.classLoaderUp skips adding a parent if the dep seemed to offer us nothing, and this has already been called. - Object[] publicPackages = (Object[]) dep.getClass().getMethod("getPublicPackages").invoke(dep); - if (publicPackages != null && publicPackages.length == 0) { - me.getClassLoader().getClass().getMethod("append", ClassLoader[].class).invoke(me.getClassLoader(), (Object) new ClassLoader[]{dep.getClassLoader()}); - } - } - } catch (IllegalStateException x) { - throw x; - } catch (Exception x) { - throw new IllegalStateException(x); - } - } - - private ModuleInfo findDependency(/*ModuleManager*/Object manager, String m) throws Exception { - Object dep = manager.getClass().getMethod("get", String.class).invoke(manager, m); - if (dep == null) { - throw new IllegalStateException("No such dependency " + m); - } - return (ModuleInfo) dep; - } - - private Object data(ModuleInfo module) throws Exception { - Method dataM = Class.forName("org.netbeans.Module", true, module.getClass().getClassLoader()).getDeclaredMethod("data"); - dataM.setAccessible(true); - return dataM.invoke(module); - } - -} diff --git a/src/main/java/com/junichi11/netbeans/modules/backlog/issue/BacklogIssue.java b/src/main/java/com/junichi11/netbeans/modules/backlog/issue/BacklogIssue.java index 078fd86..008d708 100644 --- a/src/main/java/com/junichi11/netbeans/modules/backlog/issue/BacklogIssue.java +++ b/src/main/java/com/junichi11/netbeans/modules/backlog/issue/BacklogIssue.java @@ -41,6 +41,7 @@ */ package com.junichi11.netbeans.modules.backlog.issue; +import com.junichi11.netbeans.modules.backlog.BacklogConfig; import com.junichi11.netbeans.modules.backlog.BacklogConnector; import com.junichi11.netbeans.modules.backlog.repository.BacklogRepository; import static com.junichi11.netbeans.modules.backlog.utils.BacklogUtils.DEFAULT_DATE_FORMAT; @@ -55,6 +56,8 @@ import com.nulabinc.backlog4j.Resolution; import com.nulabinc.backlog4j.ResponseList; import com.nulabinc.backlog4j.User; +import com.nulabinc.backlog4j.api.option.AddIssueCommentNotificationParams; +import com.nulabinc.backlog4j.api.option.AddIssueCommentParams; import com.nulabinc.backlog4j.api.option.CreateIssueParams; import com.nulabinc.backlog4j.api.option.UpdateIssueCommentParams; import com.nulabinc.backlog4j.api.option.UpdateIssueParams; @@ -74,6 +77,7 @@ import java.util.logging.Logger; import javax.swing.JTable; import org.netbeans.api.annotations.common.CheckForNull; +import org.netbeans.api.annotations.common.NonNull; import org.netbeans.modules.bugtracking.api.Repository; import org.netbeans.modules.bugtracking.api.RepositoryManager; import org.netbeans.modules.bugtracking.api.Util; @@ -100,6 +104,7 @@ public final class BacklogIssue { private IssueNode node; private String subtaskParentIssueKey; private IssueScheduleInfo scheduleInfo; + private final List comments = Collections.synchronizedList(new ArrayList()); private final BacklogRepository repository; private final PropertyChangeSupport changeSupport = new PropertyChangeSupport(this); @@ -120,6 +125,7 @@ public final class BacklogIssue { public static final String PROP_COMMENT_DELETED = "backlog.comment.deleted"; // NOI18N public static final String PROP_COMMENT_QUOTE = "backlog.comment.quote"; // NOI18N public static final String PROP_COMMENT_EDITED = "backlog.comment.edited"; // NOI18N + public static final String PROP_COMMENT_NOTIFY = "backlog.comment.notify"; // NOI18N private static final Logger LOGGER = Logger.getLogger(BacklogIssue.class.getName()); public BacklogIssue(BacklogRepository repository) { @@ -425,10 +431,15 @@ public void refresh() { return; } try { - issue = backlogClient.getIssue(id); + setIssue(backlogClient.getIssue(id)); } catch (BacklogAPIException ex) { LOGGER.log(Level.WARNING, ex.getMessage()); } + fireStatusChange(); + } + + public void refreshIssue(Issue issue) { + setIssue(issue); } /** @@ -437,8 +448,53 @@ public void refresh() { * @return status */ public Status getStatus() { - // TODO - return Status.SEEN; + return BacklogConfig.getInstance().getStatus(this); + } + + public void setStatus(Status status) { + BacklogConfig.getInstance().setStatus(this, status); + fireStatusChange(); + } + + public List getIssueComments() { + if (issue == null) { + return Collections.emptyList(); + } + return comments; + } + + private void refreshIssueComments() { + if (issue == null) { + return; + } + comments.clear(); + BacklogClient backlogClient = repository.createBacklogClient(); + if (backlogClient != null) { + try { + ResponseList issueComments = backlogClient.getIssueComments(issue.getId()); + comments.addAll(issueComments); + } catch (BacklogAPIException ex) { + LOGGER.log(Level.WARNING, ex.getMessage()); + } + } + } + + public long getLastUpdatedTime() { + Date updated = this.getUpdated(); + if (updated != null) { + long time = updated.getTime(); + for (IssueComment issueComment : getIssueComments()) { + Date commentUpdated = issueComment.getUpdated(); + if (commentUpdated != null) { + long commentTime = commentUpdated.getTime(); + if (time < commentTime) { + time = commentTime; + } + } + } + return time; + } + return -1L; } /** @@ -522,7 +578,7 @@ public Issue addIssue(CreateIssueParams issueParams) { fireChange(); fireDataChange(); fireScheduleChange(); -// fireStatusChange(); + fireStatusChange(); ((BacklogIssueController) getController()).setChanged(false); } subtaskParentIssueKey = null; @@ -618,12 +674,59 @@ public Attachment deleteIssueAttachment(long attachmentId) { return attachment; } + /** + * Add IssueComment. + * + * @param content the added issue comment + * @param userIds user identifers + * @return IssueComment if adding is successful, otherwise {@code null} + */ + @CheckForNull + public IssueComment addIssueComment(String content, List userIds) { + BacklogClient backlogClient = repository.createBacklogClient(); + if (backlogClient == null) { + return null; + } + IssueComment issueComment = null; + try { + AddIssueCommentParams addIssueCommentParams = new AddIssueCommentParams(issue.getId(), content); + addIssueCommentParams.notifiedUserIds(userIds); + issueComment = backlogClient.addIssueComment(addIssueCommentParams); + } catch (BacklogAPIException ex) { + LOGGER.log(Level.WARNING, ex.getMessage()); + } + return issueComment; + } + + /** + * Add an issue comment notification. + * + * @param comment the issue comment + * @param userIds user identifers + * @return IssueComment if adding is successful, otherwise {@code null} + */ + @CheckForNull + public IssueComment addIssueCommentNotification(@NonNull IssueComment comment, List userIds) { + BacklogClient backlogClient = repository.createBacklogClient(); + if (backlogClient == null) { + return null; + } + IssueComment issueComment = null; + try { + AddIssueCommentNotificationParams params = new AddIssueCommentNotificationParams(issue.getId(), comment.getId(), userIds); + issueComment = backlogClient.addIssueCommentNotification(params); + } catch (BacklogAPIException ex) { + LOGGER.log(Level.WARNING, ex.getMessage()); + } + return issueComment; + } + /** * Update IssueComment. * * @param comment IssueComment * @param content new content - * @return Updated IssueComment if update is successful, otherwise + * @return Updated IssueComment if updating is successful, otherwise * {@code null} */ @CheckForNull @@ -659,6 +762,9 @@ public List getSubissueIds() { private void setIssue(Issue issue) { this.issue = issue; this.summary = issue.getSummary(); + // XXX many requests may be posted for getting comments + // Use notification? +// refreshIssueComments(); } /** @@ -767,10 +873,8 @@ private IssueScheduleInfo createScheduleInfo() { int interval = (int) ((due - start) / (1000 * 60 * 60 * 24)); return new IssueScheduleInfo(startDate, interval); } - } else { - if (dueDate != null) { - return new IssueScheduleInfo(dueDate, 1); - } + } else if (dueDate != null) { + return new IssueScheduleInfo(dueDate, 1); } return null; } diff --git a/src/main/java/com/junichi11/netbeans/modules/backlog/issue/BacklogIssueStatusProvider.java b/src/main/java/com/junichi11/netbeans/modules/backlog/issue/BacklogIssueStatusProvider.java index b828674..3518094 100644 --- a/src/main/java/com/junichi11/netbeans/modules/backlog/issue/BacklogIssueStatusProvider.java +++ b/src/main/java/com/junichi11/netbeans/modules/backlog/issue/BacklogIssueStatusProvider.java @@ -59,7 +59,15 @@ public Status getStatus(BacklogIssue issue) { } @Override - public void setSeenIncoming(BacklogIssue i, boolean bln) { + public void setSeenIncoming(BacklogIssue issue, boolean seen) { + Status status = getStatus(issue); + if (!seen) { + issue.setStatus(Status.INCOMING_NEW); + return; + } + if (status != Status.SEEN && seen) { + issue.setStatus(Status.SEEN); + } } @Override diff --git a/src/main/java/com/junichi11/netbeans/modules/backlog/issue/ui/BacklogIssuePanel.form b/src/main/java/com/junichi11/netbeans/modules/backlog/issue/ui/BacklogIssuePanel.form index 34db3e0..4b8bd31 100644 --- a/src/main/java/com/junichi11/netbeans/modules/backlog/issue/ui/BacklogIssuePanel.form +++ b/src/main/java/com/junichi11/netbeans/modules/backlog/issue/ui/BacklogIssuePanel.form @@ -93,7 +93,11 @@ - + + + + + @@ -112,11 +116,11 @@ - + - + @@ -130,16 +134,18 @@ - - - - - - - + + + + + + + + + - + @@ -149,6 +155,8 @@ + + @@ -212,6 +220,20 @@ + + + + + + + + + + + + + + @@ -299,39 +321,25 @@ - + - - - - - - - - - - - - + + + + - - - - + - - - - - - - - + + + + + @@ -346,7 +354,7 @@ - + @@ -387,7 +395,7 @@ - + @@ -397,23 +405,22 @@ - + + + + - + - - - - - - - - - + + + + + @@ -429,7 +436,14 @@ - + + + + + + + + @@ -506,11 +520,17 @@ - - + + + + + + + + + + - - @@ -898,6 +918,64 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/java/com/junichi11/netbeans/modules/backlog/issue/ui/BacklogIssuePanel.java b/src/main/java/com/junichi11/netbeans/modules/backlog/issue/ui/BacklogIssuePanel.java index 026653e..5421718 100644 --- a/src/main/java/com/junichi11/netbeans/modules/backlog/issue/ui/BacklogIssuePanel.java +++ b/src/main/java/com/junichi11/netbeans/modules/backlog/issue/ui/BacklogIssuePanel.java @@ -41,6 +41,7 @@ */ package com.junichi11.netbeans.modules.backlog.issue.ui; +import com.junichi11.netbeans.modules.backlog.BacklogConfig; import com.junichi11.netbeans.modules.backlog.BacklogData; import com.junichi11.netbeans.modules.backlog.issue.BacklogAttachment; import com.junichi11.netbeans.modules.backlog.issue.BacklogIssue; @@ -53,6 +54,7 @@ import static com.junichi11.netbeans.modules.backlog.utils.BacklogUtils.DEFAULT_DATE_FORMAT; import static com.junichi11.netbeans.modules.backlog.utils.BacklogUtils.DEFAULT_DATE_FORMAT_WITH_TIME; import com.junichi11.netbeans.modules.backlog.utils.StringUtils; +import com.junichi11.netbeans.modules.backlog.utils.UiUtils; import com.nulabinc.backlog4j.Attachment; import com.nulabinc.backlog4j.BacklogAPIException; import com.nulabinc.backlog4j.BacklogClient; @@ -62,6 +64,7 @@ import com.nulabinc.backlog4j.IssueComment; import com.nulabinc.backlog4j.IssueType; import com.nulabinc.backlog4j.Milestone; +import com.nulabinc.backlog4j.Notification; import com.nulabinc.backlog4j.Priority; import com.nulabinc.backlog4j.Project; import com.nulabinc.backlog4j.Resolution; @@ -79,7 +82,10 @@ import com.nulabinc.backlog4j.internal.json.UserJSONImpl; import com.nulabinc.backlog4j.internal.json.VersionJSONImpl; import java.awt.Component; +import java.awt.EventQueue; import java.awt.Font; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.io.File; @@ -88,6 +94,8 @@ import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; import java.util.Date; import java.util.List; import java.util.logging.Level; @@ -106,11 +114,14 @@ import javax.swing.event.ChangeListener; import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; +import javax.swing.text.BadLocationException; +import javax.swing.text.Document; import org.netbeans.api.annotations.common.CheckForNull; import org.netbeans.api.progress.ProgressHandle; import org.netbeans.api.progress.ProgressHandleFactory; import org.netbeans.modules.bugtracking.issuetable.IssueTable; import org.netbeans.modules.bugtracking.issuetable.QueryTableCellRenderer; +import org.openide.DialogDescriptor; import org.openide.DialogDisplayer; import org.openide.NotifyDescriptor; import org.openide.awt.HtmlBrowser; @@ -155,12 +166,20 @@ public class BacklogIssuePanel extends javax.swing.JPanel implements PropertyCha private final DefaultListModel categoryListModel = new DefaultListModel<>(); private final DefaultListModel versionListModel = new DefaultListModel<>(); private final DefaultListModel milestoneListModel = new DefaultListModel<>(); + private final DefaultListModel notificationUserListModel = new DefaultListModel<>(); private final ChangeSupport changeSupport = new ChangeSupport(this); // icon private static final Icon ERROR_ICON = BacklogImage.ERROR_16.getIcon(); private static final Icon ICON = BacklogImage.ICON_32.getIcon(); + // manage templates options + private static final String TEMPLATES_ADD_OPTION = Bundle.BacklogIssuePanel_manage_templates_add_option(); + private static final String TEMPLATES_EDIT_OPTION = Bundle.BacklogIssuePanel_manage_templates_edit_option(); + private static final String TEMPLATES_DUPLICATE_OPTION = Bundle.BacklogIssuePanel_manage_templates_duplicate_option(); + private static final String TEMPLATES_REMOVE_OPTION = Bundle.BacklogIssuePanel_manage_templates_remove_option(); + private static final String TEMPLATES_CLOSE_OPTION = Bundle.BacklogIssuePanel_manage_templates_close_option(); + private static final int MAX_COMMENT_COUNT = 100; /** @@ -214,6 +233,7 @@ private void init() { versionList.setCellRenderer(new AttributesListCellRenderer(versionList.getCellRenderer())); categoryList.setCellRenderer(new AttributesListCellRenderer(categoryList.getCellRenderer())); milestoneList.setCellRenderer(new AttributesListCellRenderer(milestoneList.getCellRenderer())); + notificationUserList.setCellRenderer(new AttributesListCellRenderer(notificationUserList.getCellRenderer(), repositoryId)); resolutionComboBox.setRenderer(new AttributesListCellRenderer(resolutionComboBox.getRenderer())); // attachments @@ -338,6 +358,7 @@ private void setHeader() { Issue existingIssue = issue.getIssue(); setHeaderIssueKey(existingIssue.getIssueKey() + " " + existingIssue.getSummary()); setDateLabel(headerCreatedDateLabel, existingIssue.getCreated(), true); + setDateLabel(headerUpdatedDateLabel, existingIssue.getUpdated(), true); setDateLabel(headerStartDateViewLabel, existingIssue.getStartDate(), false); setDateLabel(headerDueDateViewLabel, existingIssue.getDueDate(), false); User createdUser = existingIssue.getCreatedUser(); @@ -665,13 +686,21 @@ private void setCategories(BacklogData data, boolean force) { } private void setUsers(BacklogData data) { - List users = data.getUsers(); + // asssignee, notification user + List users = data.getProjectUsers(); assigneeComboBoxModel.removeAllElements(); assigneeComboBoxModel.addElement(new UserJSONImpl()); + notificationUserListModel.removeAllElements(); + notificationUserListModel.addElement(new UserJSONImpl()); + User myself = data.getMyself(); for (User user : users) { assigneeComboBoxModel.addElement(user); + if (!user.equals(myself)) { + notificationUserListModel.addElement(user); + } } assigneeComboBox.setModel(assigneeComboBoxModel); + notificationUserList.setModel(notificationUserListModel); } private void setVersions(BacklogData data, boolean force) { @@ -828,6 +857,24 @@ public String getComment() { return commentTextArea.getText(); } + public List getNotificationUsers() { + return notificationUserList.getSelectedValuesList(); + } + + public List getNotificationUserIds() { + List users = getNotificationUsers(); + if (users.size() == 1 && StringUtils.isEmpty(users.get(0).getName())) { + return Collections.emptyList(); + } + List ids = new ArrayList<>(); + for (User user : users) { + if (!StringUtils.isEmpty(user.getName())) { + ids.add(user.getId()); + } + } + return ids; + } + void fireChange() { changeSupport.fireChange(); } @@ -866,6 +913,8 @@ private void initComponents() { headerIssueKeyLabel = new javax.swing.JLabel(); headerCreatedLabel = new javax.swing.JLabel(); headerCreatedDateLabel = new javax.swing.JLabel(); + headerUpdatedLabel = new javax.swing.JLabel(); + headerUpdatedDateLabel = new javax.swing.JLabel(); headerStartDateLabel = new javax.swing.JLabel(); headerStartDateViewLabel = new javax.swing.JLabel(); headerDueDateLabel = new javax.swing.JLabel(); @@ -878,21 +927,21 @@ private void initComponents() { jSeparator3 = new javax.swing.JSeparator(); mainScrollPane = new javax.swing.JScrollPane(); mainPanel = new javax.swing.JPanel(); - priorityComboBox = new javax.swing.JComboBox(); + priorityComboBox = new javax.swing.JComboBox<>(); descriptionScrollPane = new javax.swing.JScrollPane(); descriptionEditorPane = new javax.swing.JEditorPane(); estimatedHoursLabel = new javax.swing.JLabel(); priorityLabel = new javax.swing.JLabel(); - resolutionComboBox = new javax.swing.JComboBox(); + resolutionComboBox = new javax.swing.JComboBox<>(); jSeparator2 = new javax.swing.JSeparator(); versionLabel = new javax.swing.JLabel(); milestoneLabel = new javax.swing.JLabel(); statusLabel = new javax.swing.JLabel(); categoryScrollPane = new javax.swing.JScrollPane(); - categoryList = new javax.swing.JList(); + categoryList = new javax.swing.JList<>(); resolutionLabel = new javax.swing.JLabel(); assigneeLabel = new javax.swing.JLabel(); - assigneeComboBox = new javax.swing.JComboBox(); + assigneeComboBox = new javax.swing.JComboBox<>(); acturalHoursLabel = new javax.swing.JLabel(); estimatedHoursTextField = new javax.swing.JTextField(); actualHoursTextField = new javax.swing.JTextField(); @@ -905,11 +954,11 @@ private void initComponents() { attributeLabel = new javax.swing.JLabel(); descriptionLabel = new javax.swing.JLabel(); versionScrollPane = new javax.swing.JScrollPane(); - versionList = new javax.swing.JList(); + versionList = new javax.swing.JList<>(); categoryLabel = new javax.swing.JLabel(); milestoneScrollPane = new javax.swing.JScrollPane(); - milestoneList = new javax.swing.JList(); - issueTypeComboBox = new javax.swing.JComboBox(); + milestoneList = new javax.swing.JList<>(); + issueTypeComboBox = new javax.swing.JComboBox<>(); commentLabel = new javax.swing.JLabel(); commentScrollPane = new javax.swing.JScrollPane(); commentTextArea = new javax.swing.JTextArea(); @@ -920,11 +969,16 @@ private void initComponents() { addVersionButton = new javax.swing.JButton(); addMilestoneButton = new javax.swing.JButton(); assignToMyselfLinkButton = new org.netbeans.modules.bugtracking.commons.LinkButton(); - statusComboBox = new javax.swing.JComboBox(); + statusComboBox = new javax.swing.JComboBox<>(); hoursEstimatedLabel = new javax.swing.JLabel(); hoursActualLabel = new javax.swing.JLabel(); attachmentsCollapsibleSectionPanel = new org.netbeans.modules.bugtracking.commons.CollapsibleSectionPanel(); subtaskingCollapsibleSectionPanel = new org.netbeans.modules.bugtracking.commons.CollapsibleSectionPanel(); + notificationLabel = new javax.swing.JLabel(); + notificationUserScrollPane = new javax.swing.JScrollPane(); + notificationUserList = new javax.swing.JList<>(); + insertTemplateButton = new javax.swing.JButton(); + manageTemplatesButton = new javax.swing.JButton(); dummyMainCommentsPanel.setLayout(new java.awt.BorderLayout()); @@ -978,6 +1032,10 @@ public void actionPerformed(java.awt.event.ActionEvent evt) { org.openide.awt.Mnemonics.setLocalizedText(headerCreatedDateLabel, org.openide.util.NbBundle.getMessage(BacklogIssuePanel.class, "BacklogIssuePanel.headerCreatedDateLabel.text")); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(headerUpdatedLabel, org.openide.util.NbBundle.getMessage(BacklogIssuePanel.class, "BacklogIssuePanel.headerUpdatedLabel.text")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(headerUpdatedDateLabel, org.openide.util.NbBundle.getMessage(BacklogIssuePanel.class, "BacklogIssuePanel.headerUpdatedDateLabel.text")); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(headerStartDateLabel, org.openide.util.NbBundle.getMessage(BacklogIssuePanel.class, "BacklogIssuePanel.headerStartDateLabel.text")); // NOI18N org.openide.awt.Mnemonics.setLocalizedText(headerStartDateViewLabel, org.openide.util.NbBundle.getMessage(BacklogIssuePanel.class, "BacklogIssuePanel.headerStartDateViewLabel.text")); // NOI18N @@ -1019,6 +1077,10 @@ public void actionPerformed(java.awt.event.ActionEvent evt) { .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(headerCreatedDateLabel) .addGap(18, 18, 18) + .addComponent(headerUpdatedLabel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(headerUpdatedDateLabel) + .addGap(18, 18, 18) .addComponent(headerStartDateLabel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(headerStartDateViewLabel) @@ -1036,11 +1098,11 @@ public void actionPerformed(java.awt.event.ActionEvent evt) { .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) .addComponent(addSubtaskLinkButton, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(jSeparator3, javax.swing.GroupLayout.PREFERRED_SIZE, 6, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(jSeparator3, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(refreshLinkButton, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(jSeparator5, javax.swing.GroupLayout.PREFERRED_SIZE, 6, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(jSeparator5, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(showOnBrowserLinkButton, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))) .addContainerGap()) @@ -1050,14 +1112,15 @@ public void actionPerformed(java.awt.event.ActionEvent evt) { .addGroup(headerPanelLayout.createSequentialGroup() .addContainerGap() .addGroup(headerPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(headerPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(showOnBrowserLinkButton, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addComponent(refreshLinkButton, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addComponent(jSeparator5) - .addComponent(jSeparator3) - .addComponent(addSubtaskLinkButton, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addComponent(headerIssueKeyLabel)) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(headerIssueKeyLabel) + .addGroup(headerPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) + .addGroup(headerPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) + .addComponent(jSeparator5) + .addComponent(jSeparator3) + .addComponent(addSubtaskLinkButton, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(refreshLinkButton, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addComponent(showOnBrowserLinkButton, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) .addGroup(headerPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .addComponent(headerCreatedLabel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addComponent(headerCreatedDateLabel) @@ -1066,7 +1129,9 @@ public void actionPerformed(java.awt.event.ActionEvent evt) { .addComponent(headerCreatedByLabel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addComponent(headerCreatedUserLinkButton, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addComponent(headerStartDateLabel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(headerStartDateViewLabel)) + .addComponent(headerStartDateViewLabel) + .addComponent(headerUpdatedLabel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(headerUpdatedDateLabel)) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addGroup(headerPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .addComponent(errorHeaderLabel) @@ -1175,6 +1240,28 @@ public void actionPerformed(java.awt.event.ActionEvent evt) { subtaskingCollapsibleSectionPanel.setExpanded(false); subtaskingCollapsibleSectionPanel.setLabel(org.openide.util.NbBundle.getMessage(BacklogIssuePanel.class, "BacklogIssuePanel.subtaskingCollapsibleSectionPanel.label")); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(notificationLabel, org.openide.util.NbBundle.getMessage(BacklogIssuePanel.class, "BacklogIssuePanel.notificationLabel.text")); // NOI18N + + notificationUserScrollPane.setViewportView(notificationUserList); + + insertTemplateButton.setIcon(new javax.swing.ImageIcon("/home/junichi11/NetBeansProjects/netbeans-backlog-plugin/src/main/resources/com/junichi11/netbeans/modules/backlog/resources/template_16.png")); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(insertTemplateButton, org.openide.util.NbBundle.getMessage(BacklogIssuePanel.class, "BacklogIssuePanel.insertTemplateButton.text")); // NOI18N + insertTemplateButton.setToolTipText(org.openide.util.NbBundle.getMessage(BacklogIssuePanel.class, "BacklogIssuePanel.insertTemplateButton.toolTipText")); // NOI18N + insertTemplateButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + insertTemplateButtonActionPerformed(evt); + } + }); + + manageTemplatesButton.setIcon(new javax.swing.ImageIcon("/home/junichi11/NetBeansProjects/netbeans-backlog-plugin/src/main/resources/com/junichi11/netbeans/modules/backlog/resources/manage_template_16.png")); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(manageTemplatesButton, org.openide.util.NbBundle.getMessage(BacklogIssuePanel.class, "BacklogIssuePanel.manageTemplatesButton.text")); // NOI18N + manageTemplatesButton.setToolTipText(org.openide.util.NbBundle.getMessage(BacklogIssuePanel.class, "BacklogIssuePanel.manageTemplatesButton.toolTipText")); // NOI18N + manageTemplatesButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + manageTemplatesButtonActionPerformed(evt); + } + }); + javax.swing.GroupLayout mainPanelLayout = new javax.swing.GroupLayout(mainPanel); mainPanel.setLayout(mainPanelLayout); mainPanelLayout.setHorizontalGroup( @@ -1182,29 +1269,20 @@ public void actionPerformed(java.awt.event.ActionEvent evt) { .addGroup(mainPanelLayout.createSequentialGroup() .addContainerGap() .addGroup(mainPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(mainPanelLayout.createSequentialGroup() - .addComponent(subtaskingCollapsibleSectionPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addContainerGap()) - .addGroup(mainPanelLayout.createSequentialGroup() - .addComponent(attachmentsCollapsibleSectionPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addContainerGap()) - .addGroup(mainPanelLayout.createSequentialGroup() - .addComponent(jSeparator1) - .addContainerGap()) + .addComponent(subtaskingCollapsibleSectionPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(jSeparator1) .addGroup(mainPanelLayout.createSequentialGroup() .addGroup(mainPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(summaryLabel) + .addComponent(descriptionLabel) .addGroup(mainPanelLayout.createSequentialGroup() - .addGroup(mainPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(summaryLabel) - .addComponent(descriptionLabel)) + .addComponent(insertTemplateButton) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addGroup(mainPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(descriptionScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addComponent(summaryTextField))) - .addGroup(mainPanelLayout.createSequentialGroup() - .addComponent(attributeLabel) - .addGap(0, 0, Short.MAX_VALUE))) - .addContainerGap()) + .addComponent(manageTemplatesButton))) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(mainPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(descriptionScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(summaryTextField))) .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, mainPanelLayout.createSequentialGroup() .addGap(12, 12, 12) .addGroup(mainPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) @@ -1217,7 +1295,7 @@ public void actionPerformed(java.awt.event.ActionEvent evt) { .addComponent(typeLabel)) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addGroup(mainPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(dueDatePicker, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(dueDatePicker, javax.swing.GroupLayout.DEFAULT_SIZE, 197, Short.MAX_VALUE) .addComponent(startDatePicker, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) .addComponent(milestoneScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 0, Short.MAX_VALUE) .addComponent(versionScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 0, Short.MAX_VALUE) @@ -1252,23 +1330,24 @@ public void actionPerformed(java.awt.event.ActionEvent evt) { .addComponent(assigneeComboBox, javax.swing.GroupLayout.Alignment.TRAILING, 0, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) .addGroup(mainPanelLayout.createSequentialGroup() .addComponent(assignToMyselfLinkButton, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addGap(0, 0, Short.MAX_VALUE)) + .addGap(0, 87, Short.MAX_VALUE)) .addGroup(mainPanelLayout.createSequentialGroup() .addComponent(actualHoursTextField) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(hoursActualLabel))))) - .addContainerGap()) + .addComponent(hoursActualLabel)))))) + .addComponent(commentsCollapsibleSectionPanel, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(jSeparator2, javax.swing.GroupLayout.Alignment.TRAILING) + .addComponent(attachmentsCollapsibleSectionPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(attributeLabel) .addGroup(mainPanelLayout.createSequentialGroup() .addComponent(commentLabel) .addGap(18, 18, 18) .addComponent(commentScrollPane) - .addGap(12, 12, 12)) - .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, mainPanelLayout.createSequentialGroup() - .addComponent(commentsCollapsibleSectionPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addContainerGap()) - .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, mainPanelLayout.createSequentialGroup() - .addComponent(jSeparator2) - .addContainerGap()))) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(notificationLabel) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(notificationUserScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 0, Short.MAX_VALUE))) + .addContainerGap()) ); mainPanelLayout.setVerticalGroup( mainPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) @@ -1281,7 +1360,12 @@ public void actionPerformed(java.awt.event.ActionEvent evt) { .addComponent(summaryLabel)) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addGroup(mainPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(descriptionLabel) + .addGroup(mainPanelLayout.createSequentialGroup() + .addComponent(descriptionLabel) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(mainPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(insertTemplateButton) + .addComponent(manageTemplatesButton))) .addComponent(descriptionScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 130, javax.swing.GroupLayout.PREFERRED_SIZE)) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(jSeparator1, javax.swing.GroupLayout.PREFERRED_SIZE, 10, javax.swing.GroupLayout.PREFERRED_SIZE) @@ -1344,10 +1428,14 @@ public void actionPerformed(java.awt.event.ActionEvent evt) { .addComponent(attachmentsCollapsibleSectionPanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addGroup(mainPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(commentLabel) - .addComponent(commentScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addGap(18, 18, 18) - .addComponent(commentsCollapsibleSectionPanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGroup(mainPanelLayout.createSequentialGroup() + .addGroup(mainPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) + .addComponent(commentLabel) + .addComponent(commentScrollPane) + .addComponent(notificationUserScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 0, Short.MAX_VALUE)) + .addGap(18, 18, 18) + .addComponent(commentsCollapsibleSectionPanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addComponent(notificationLabel)) .addContainerGap()) ); @@ -1474,6 +1562,69 @@ public void run() { }); }//GEN-LAST:event_addSubtaskLinkButtonActionPerformed + @NbBundle.Messages("BacklogIssuePanel.insert.template.title=Insert Template") + private void insertTemplateButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_insertTemplateButtonActionPerformed + assert EventQueue.isDispatchThread(); + String[] templateNames = BacklogConfig.getInstance().getTemplateNames(); + InsertTemplatePanel insertTemplatePanel = new InsertTemplatePanel(); + insertTemplatePanel.setTemplates(templateNames); + NotifyDescriptor.Confirmation message = new NotifyDescriptor.Confirmation( + insertTemplatePanel, + Bundle.BacklogIssuePanel_insert_template_title(), + NotifyDescriptor.OK_CANCEL_OPTION, + NotifyDescriptor.PLAIN_MESSAGE); + if (DialogDisplayer.getDefault().notify(message) == NotifyDescriptor.OK_OPTION) { + String selectedTemplateName = insertTemplatePanel.getSelectedTemplateName(); + String template = BacklogConfig.getInstance().getTemplate(selectedTemplateName); + if (template == null || template.isEmpty()) { + return; + } + + // insert a template to a caret position + int caretPosition = descriptionEditorPane.getCaretPosition(); + Document document = descriptionEditorPane.getDocument(); + try { + document.insertString(caretPosition, template, null); + } catch (BadLocationException ex) { + LOGGER.log(Level.WARNING, "Can''t insert a template to " + caretPosition, ex); // NOI18N + } + } + + }//GEN-LAST:event_insertTemplateButtonActionPerformed + + @NbBundle.Messages({ + "BacklogIssuePanel.manage.templates.title=Manage Templates", + "BacklogIssuePanel.manage.templates.add.option=Add", + "BacklogIssuePanel.manage.templates.remove.option=Remove", + "BacklogIssuePanel.manage.templates.edit.option=Edit", + "BacklogIssuePanel.manage.templates.duplicate.option=Duplicate", + "BacklogIssuePanel.manage.templates.close.option=Close" + }) + private void manageTemplatesButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_manageTemplatesButtonActionPerformed + assert EventQueue.isDispatchThread(); + final ManageTemplatesPanel manageTemplatesPanel = new ManageTemplatesPanel(); + final DialogDescriptor descriptor = new DialogDescriptor( + manageTemplatesPanel, // message + Bundle.BacklogIssuePanel_manage_templates_title(), // title + true, // modal + null, // options + null, // initial value + DialogDescriptor.RIGHT_ALIGN, + null, // help + null // action listener + ); + descriptor.setOptions(new String[]{ + TEMPLATES_ADD_OPTION, + TEMPLATES_EDIT_OPTION, + TEMPLATES_DUPLICATE_OPTION, + TEMPLATES_REMOVE_OPTION, + TEMPLATES_CLOSE_OPTION + }); + descriptor.setClosingOptions(new String[]{TEMPLATES_CLOSE_OPTION}); + descriptor.setButtonListener(new ManageTemplateButtonListener(descriptor, manageTemplatesPanel)); + DialogDisplayer.getDefault().notify(descriptor); + }//GEN-LAST:event_manageTemplatesButtonActionPerformed + @NbBundle.Messages({ "BacklogIssuePanel.label.select.file=Select File", "BacklogIssuePanel.message.uploading.attachments=Uploading files" @@ -1501,11 +1652,11 @@ public void run() { ProgressHandle handle = ProgressHandleFactory.createHandle( Bundle.BacklogIssuePanel_message_uploading_attachments(), new Cancellable() { - @Override - public boolean cancel() { - return true; - } - }); + @Override + public boolean cancel() { + return true; + } + }); try { handle.start(attachments.length); int progressCount = 0; @@ -1600,13 +1751,18 @@ public void run() { }) private void updateIssue() { UpdateIssueParams issueParams = createUpdateIssueParams(); - final Issue updateIssue = issue.updateIssue(issueParams); - final boolean hasComment = isCommentUpdated(); + // TODO check changes + final Issue updatedIssue = issue.updateIssue(issueParams); + + final boolean hasComment = hasComment(); + if (hasComment) { + IssueComment addedComment = issue.addIssueComment(getComment(), getNotificationUserIds()); + } SwingUtilities.invokeLater(new Runnable() { @Override public void run() { - if (updateIssue != null) { + if (updatedIssue != null) { unsubmittedAttachmentsPanel.removeAllAttachments(); StatusDisplayer.getDefault().setStatusText(Bundle.BacklogIssuePanel_message_update_issue_success()); } else { @@ -1741,11 +1897,11 @@ private UpdateIssueParams createUpdateIssueParams() { } // comment - String comment = getComment(); - if (!StringUtils.isEmpty(comment)) { - issueParams = issueParams.comment(comment); - } - + // sepalate +// String comment = getComment(); +// if (!StringUtils.isEmpty(comment)) { +// issueParams = issueParams.comment(comment); +// } // check status // Can't change to the same status Issue existingIssue = issue.getIssue(); @@ -1790,7 +1946,7 @@ private UpdateIssueParams createUpdateIssueParams() { return issueParams; } - private boolean isCommentUpdated() { + private boolean hasComment() { String comment = getComment(); return !StringUtils.isEmpty(comment); } @@ -1859,8 +2015,11 @@ private static String getBacklogIssueUrlFormat(String backlogDomain) { private javax.swing.JPanel headerPanel; private javax.swing.JLabel headerStartDateLabel; private javax.swing.JLabel headerStartDateViewLabel; + private javax.swing.JLabel headerUpdatedDateLabel; + private javax.swing.JLabel headerUpdatedLabel; private javax.swing.JLabel hoursActualLabel; private javax.swing.JLabel hoursEstimatedLabel; + private javax.swing.JButton insertTemplateButton; private javax.swing.JComboBox issueTypeComboBox; private javax.swing.JSeparator jSeparator1; private javax.swing.JSeparator jSeparator2; @@ -1871,9 +2030,13 @@ private static String getBacklogIssueUrlFormat(String backlogDomain) { private javax.swing.JPanel mainPanel; private javax.swing.JScrollPane mainScrollPane; private javax.swing.JPanel mainSubtaskTablePanel; + private javax.swing.JButton manageTemplatesButton; private javax.swing.JLabel milestoneLabel; private javax.swing.JList milestoneList; private javax.swing.JScrollPane milestoneScrollPane; + private javax.swing.JLabel notificationLabel; + private javax.swing.JList notificationUserList; + private javax.swing.JScrollPane notificationUserScrollPane; private javax.swing.JComboBox priorityComboBox; private javax.swing.JLabel priorityLabel; private org.netbeans.modules.bugtracking.commons.LinkButton refreshLinkButton; @@ -1907,6 +2070,9 @@ public void propertyChange(PropertyChangeEvent event) { case BacklogIssue.PROP_COMMENT_EDITED: editComment(commentsPanel.getEditedComment()); break; + case BacklogIssue.PROP_COMMENT_NOTIFY: + notifyComment(commentsPanel.getNotifyComment()); + break; case AttachmentPanel.PROP_ATTACHMENT_DELETED: Attachment attachment = (Attachment) event.getOldValue(); deleteAttachment(attachment); @@ -1958,6 +2124,48 @@ public void run() { }); } + @NbBundle.Messages("BacklogIssuePanel.no.notification.user=There are no users that can be notified.") + private void notifyComment(final IssueComment comment) { + SwingUtilities.invokeLater(new Runnable() { + + @Override + public void run() { + // users + List allUsers = getAllNotificationUsers(comment); + if (allUsers.isEmpty()) { + UiUtils.showPlainDialog(Bundle.BacklogIssuePanel_no_notification_user()); + return; + } + + // show dialog + List userIds = NotifyCommentPanel.showDialog(allUsers, issue.getRepository()); + if (userIds.isEmpty()) { + return; + } + IssueComment updatedIssueComment = issue.addIssueCommentNotification(comment, userIds); + if (updatedIssueComment != null) { + update(true); + } + } + }); + } + + private List getAllNotificationUsers(IssueComment comment) { + BacklogRepository repository = issue.getRepository(); + BacklogData data = BacklogData.create(repository); + User myself = data.getMyself(); + List notifications = comment.getNotifications(); + List allUsers = new ArrayList<>(data.getProjectUsers()); + allUsers.remove(myself); + for (Notification notification : notifications) { + User user = notification.getUser(); + if (user != null) { + allUsers.remove(user); + } + } + return allUsers; + } + private void deleteAttachment(Attachment attachment) { Attachment a = issue.deleteIssueAttachment(attachment.getId()); if (a != null) { @@ -1996,4 +2204,149 @@ private void processUpdate() { fireChange(); } } + + //~ inner class + private static class ManageTemplateButtonListener implements ActionListener { + + private final DialogDescriptor descriptor; + private final ManageTemplatesPanel manageTemplatesPanel; + + public ManageTemplateButtonListener(DialogDescriptor descriptor, ManageTemplatesPanel manageTemplatesPanel) { + this.descriptor = descriptor; + this.manageTemplatesPanel = manageTemplatesPanel; + } + + @Override + public void actionPerformed(ActionEvent e) { + Object value = descriptor.getValue(); + if (value == TEMPLATES_ADD_OPTION) { + add(); + } else if (value == TEMPLATES_EDIT_OPTION) { + edit(); + } else if (value == TEMPLATES_DUPLICATE_OPTION) { + duplicate(); + } else if (value == TEMPLATES_REMOVE_OPTION) { + remove(); + } + } + + @NbBundle.Messages("ManageTemplateButtonListener.add.title=Add Template") + private void add() { + showDialog(TEMPLATES_ADD_OPTION, Bundle.ManageTemplateButtonListener_add_title()); + } + + @NbBundle.Messages("ManageTemplateButtonListener.edit.title=Edit Template") + private void edit() { + showDialog(TEMPLATES_EDIT_OPTION, Bundle.ManageTemplateButtonListener_edit_title()); + } + + @NbBundle.Messages("ManageTemplateButtonListener.duplicate.title=Duplicate Template") + private void duplicate() { + showDialog(TEMPLATES_DUPLICATE_OPTION, Bundle.ManageTemplateButtonListener_duplicate_title()); + } + + @NbBundle.Messages({ + "# {0} - name", + "ManageTemplateButtonListener.remove.message=Do you really want to remove {0}? \n(In case of default, it is just initialized without removing.)" + }) + private void remove() { + String selectedTemplateName = manageTemplatesPanel.getSelectedTemplateName(); + if (selectedTemplateName == null || selectedTemplateName.isEmpty()) { + return; + } + if (UiUtils.showQuestionDialog(Bundle.ManageTemplateButtonListener_remove_message(selectedTemplateName))) { + BacklogConfig.getInstance().removeTemplate(selectedTemplateName); + manageTemplatesPanel.resetTemplateNameList(); + } + } + + private void showDialog(String option, String title) { + if (!option.equals(TEMPLATES_ADD_OPTION) + && !option.equals(TEMPLATES_EDIT_OPTION) + && !option.equals(TEMPLATES_DUPLICATE_OPTION)) { + return; + } + + // create panel + final TemplatePanel templatePanel = new TemplatePanel(); + String selectedTemplateName = manageTemplatesPanel.getSelectedTemplateName(); + if (!option.equals(TEMPLATES_ADD_OPTION)) { + if (selectedTemplateName == null || selectedTemplateName.isEmpty()) { + return; + } + templatePanel.setTemplateNameEditable(!option.equals(TEMPLATES_EDIT_OPTION)); + templatePanel.setTemplateName(selectedTemplateName); + templatePanel.setTemplate(BacklogConfig.getInstance().getTemplate(selectedTemplateName)); + } + final NotifyDescriptor.Confirmation notify = new NotifyDescriptor.Confirmation( + templatePanel, + title, + NotifyDescriptor.OK_CANCEL_OPTION, + NotifyDescriptor.PLAIN_MESSAGE); + + // add listener + ChangeListener listener = null; + if (option.equals(TEMPLATES_ADD_OPTION) || option.equals(TEMPLATES_DUPLICATE_OPTION)) { + final List existingNames = new ArrayList<>(Arrays.asList(BacklogConfig.getInstance().getTemplateNames())); + listener = new TemplatePanelChangeListener(templatePanel, notify, existingNames); + templatePanel.addChangeListener(listener); + templatePanel.fireChange(); + } + + // show dialog + if (DialogDisplayer.getDefault().notify(notify) == NotifyDescriptor.OK_OPTION) { + String templateName = templatePanel.getTemplateName(); + if (templateName != null && !templateName.isEmpty()) { + String template = templatePanel.getTemplate(); + BacklogConfig.getInstance().setTemplate(templateName, template); + if (option.equals(TEMPLATES_EDIT_OPTION)) { + manageTemplatesPanel.setSelectedTemplateName(selectedTemplateName); + } else { + manageTemplatesPanel.resetTemplateNameList(); + } + } + } + + if (listener != null) { + templatePanel.removeChangeListener(listener); + } + } + } + + private static class TemplatePanelChangeListener implements ChangeListener { + + private final TemplatePanel templatePanel; + private final NotifyDescriptor.Confirmation notify; + private final List existingNames; + + public TemplatePanelChangeListener(TemplatePanel templatePanel, NotifyDescriptor.Confirmation notify, List existingNames) { + this.templatePanel = templatePanel; + this.notify = notify; + this.existingNames = existingNames; + } + + @Override + @NbBundle.Messages({ + "TemplatePanelChangeListener.invalid.empty=Name must be set.", + "TemplatePanelChangeListener.invalid.existing=It already exisits." + }) + public void stateChanged(ChangeEvent e) { + // validate + String templateName = templatePanel.getTemplateName(); + if ((templateName == null || templateName.isEmpty())) { + notify.setValid(false); + templatePanel.setErrorMessage(Bundle.TemplatePanelChangeListener_invalid_empty()); + return; + } + if (existingNames.contains(templateName)) { + notify.setValid(false); + templatePanel.setErrorMessage(Bundle.TemplatePanelChangeListener_invalid_existing()); + return; + } + + // everything ok + notify.setValid(true); + templatePanel.setErrorMessage(" "); // NOI18N + } + } } diff --git a/src/main/java/com/junichi11/netbeans/modules/backlog/issue/ui/Bundle.properties b/src/main/java/com/junichi11/netbeans/modules/backlog/issue/ui/Bundle.properties index acf4f9e..f3a187e 100644 --- a/src/main/java/com/junichi11/netbeans/modules/backlog/issue/ui/Bundle.properties +++ b/src/main/java/com/junichi11/netbeans/modules/backlog/issue/ui/Bundle.properties @@ -28,6 +28,8 @@ BacklogIssuePanel.headerCreatedByLabel.text=Created by: BacklogIssuePanel.headerIssueKeyLabel.text=ISSUE_KEY BacklogIssuePanel.headerCreatedLabel.text=Created: BacklogIssuePanel.headerCreatedDateLabel.text=- +BacklogIssuePanel.headerUpdatedLabel.text=Updated: +BacklogIssuePanel.headerUpdatedDateLabel.text=- BacklogIssuePanel.headerCreatedUserLinkButton.text=- SubmitPanel.errorLabel.text=ERROR SubmitPanel.submitButton.text=Submit @@ -59,3 +61,14 @@ CommentPanel.quoteLinkButton.text=Quote CommentPanel.deleteLinkButton.text=Delete BacklogIssuePanel.subtaskingCollapsibleSectionPanel.label=Subtasking BacklogIssuePanel.addSubtaskLinkButton.text=Add subtask +BacklogIssuePanel.notificationLabel.text=Notify: +CommentPanel.notificationSentToLabel.text=Notification sent to: +CommentPanel.notifyLinkButton.text=Notify +BacklogIssuePanel.insertTemplateButton.toolTipText=Insert Template +BacklogIssuePanel.insertTemplateButton.text= +BacklogIssuePanel.manageTemplatesButton.toolTipText=Manage Templates +BacklogIssuePanel.manageTemplatesButton.text= +InsertTemplatePanel.templatesComboBox.toolTipText= +TemplatePanel.nameLabel.text=Name: +TemplatePanel.nameTextField.text= +TemplatePanel.errorLabel.text=ERROR diff --git a/src/main/java/com/junichi11/netbeans/modules/backlog/issue/ui/CommentPanel.form b/src/main/java/com/junichi11/netbeans/modules/backlog/issue/ui/CommentPanel.form index 704e64d..99aba36 100644 --- a/src/main/java/com/junichi11/netbeans/modules/backlog/issue/ui/CommentPanel.form +++ b/src/main/java/com/junichi11/netbeans/modules/backlog/issue/ui/CommentPanel.form @@ -16,13 +16,13 @@ - - - - + + + + - + @@ -31,6 +31,8 @@ + + @@ -38,8 +40,13 @@ + + + + + - + @@ -56,12 +63,17 @@ + - + + + + + @@ -144,5 +156,28 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/java/com/junichi11/netbeans/modules/backlog/issue/ui/CommentPanel.java b/src/main/java/com/junichi11/netbeans/modules/backlog/issue/ui/CommentPanel.java index 5538a8b..914ae7c 100644 --- a/src/main/java/com/junichi11/netbeans/modules/backlog/issue/ui/CommentPanel.java +++ b/src/main/java/com/junichi11/netbeans/modules/backlog/issue/ui/CommentPanel.java @@ -47,9 +47,12 @@ import com.junichi11.netbeans.modules.backlog.utils.BacklogUtils; import com.junichi11.netbeans.modules.backlog.utils.UiUtils; import com.nulabinc.backlog4j.IssueComment; +import com.nulabinc.backlog4j.Notification; import com.nulabinc.backlog4j.User; import java.util.Date; +import java.util.List; import javax.swing.Icon; +import javax.swing.JLabel; import org.openide.util.NbBundle; /** @@ -58,11 +61,17 @@ */ public class CommentPanel extends javax.swing.JPanel { + public enum Status { + Quote, + Edited, + Deleted, + Notify, + None + } + private static final long serialVersionUID = 4522741935313865234L; private IssueComment comment; - private boolean isQuote; - private boolean isEdited; - private boolean isDeleted; + private Status status = Status.None; /** * Creates new form CommentPanel @@ -74,12 +83,23 @@ private CommentPanel() { public CommentPanel(BacklogRepository repository, IssueComment comment) { this.comment = comment; initComponents(); + setUser(repository, comment.getCreatedUser()); setCreatedDate(comment.getCreated()); setUpdatedDate(comment.getUpdated()); setContent(comment.getContent()); - // XXX delete comment is still not supported by api v2 + setNotifications(comment, repository); + + // disable + BacklogData cache = BacklogData.create(repository); + User myself = cache.getMyself(); + if (!comment.getCreatedUser().equals(myself)) { + notifyLinkButton.setEnabled(false); + editLinkButton.setEnabled(false); + } + + // TODO delete comment is still not supported by api v2 deleteLinkButton.setEnabled(false); } @@ -108,12 +128,26 @@ private void setUpdatedDate(Date date) { private void setContent(String content) { if (content == null) { - contentTextPane.setText(""); + contentTextPane.setText(""); // NOI18N } else if (content.isEmpty()) { // TODO show change log? contentTextPane.setText(""); // NOI18N } else { - contentTextPane.setText(content); // NOI18N + contentTextPane.setText(content); + } + } + + private void setNotifications(IssueComment comment, BacklogRepository repository) { + List notifications = comment.getNotifications(); + BacklogData data = BacklogData.create(repository); + for (Notification notification : notifications) { + User user = notification.getUser(); + Icon userIcon = data.getUserIcon(user); + if (userIcon != null) { + JLabel userLabel = new JLabel(userIcon); + userLabel.setToolTipText(user.getName()); + notificationUsersPanel.add(userLabel); + } } } @@ -125,22 +159,12 @@ public String getSelectedText() { return contentTextPane.getSelectedText(); } - public boolean isQuote() { - return isQuote; - } - - public boolean isEdited() { - return isEdited; - } - - public boolean isDeleted() { - return isDeleted; + public Status getStatus() { + return status; } void resetProperties() { - isQuote = false; - isEdited = false; - isDeleted = false; + status = Status.None; } /** @@ -162,6 +186,9 @@ private void initComponents() { contentTextPane = new javax.swing.JTextPane(); quoteLinkButton = new org.netbeans.modules.bugtracking.commons.LinkButton(); deleteLinkButton = new org.netbeans.modules.bugtracking.commons.LinkButton(); + notifyLinkButton = new org.netbeans.modules.bugtracking.commons.LinkButton(); + notificationSentToLabel = new javax.swing.JLabel(); + notificationUsersPanel = new javax.swing.JPanel(); org.openide.awt.Mnemonics.setLocalizedText(createdLabel, org.openide.util.NbBundle.getMessage(CommentPanel.class, "CommentPanel.createdLabel.text")); // NOI18N @@ -197,17 +224,28 @@ public void actionPerformed(java.awt.event.ActionEvent evt) { } }); + org.openide.awt.Mnemonics.setLocalizedText(notifyLinkButton, org.openide.util.NbBundle.getMessage(CommentPanel.class, "CommentPanel.notifyLinkButton.text")); // NOI18N + notifyLinkButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + notifyLinkButtonActionPerformed(evt); + } + }); + + org.openide.awt.Mnemonics.setLocalizedText(notificationSentToLabel, org.openide.util.NbBundle.getMessage(CommentPanel.class, "CommentPanel.notificationSentToLabel.text")); // NOI18N + + notificationUsersPanel.setLayout(new java.awt.FlowLayout(java.awt.FlowLayout.RIGHT)); + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); this.setLayout(layout); layout.setHorizontalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() + .addGroup(layout.createSequentialGroup() .addContainerGap() - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) - .addComponent(contentTextPane) - .addGroup(javax.swing.GroupLayout.Alignment.LEADING, layout.createSequentialGroup() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(contentTextPane, javax.swing.GroupLayout.Alignment.TRAILING) + .addGroup(layout.createSequentialGroup() .addComponent(userLinkButton, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 139, Short.MAX_VALUE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 89, Short.MAX_VALUE) .addComponent(createdLabel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(createdDateLabel) @@ -216,12 +254,18 @@ public void actionPerformed(java.awt.event.ActionEvent evt) { .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(updatedDateLabel) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(notifyLinkButton, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(quoteLinkButton, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(editLinkButton, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(deleteLinkButton, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addComponent(jSeparator1, javax.swing.GroupLayout.Alignment.LEADING)) + .addComponent(jSeparator1) + .addComponent(notificationUsersPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() + .addGap(0, 0, Short.MAX_VALUE) + .addComponent(notificationSentToLabel))) .addContainerGap()) ); layout.setVerticalGroup( @@ -236,17 +280,22 @@ public void actionPerformed(java.awt.event.ActionEvent evt) { .addComponent(userLinkButton, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addComponent(editLinkButton, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addComponent(quoteLinkButton, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(deleteLinkButton, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addComponent(deleteLinkButton, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(notifyLinkButton, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(jSeparator1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(contentTextPane, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(notificationSentToLabel) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(notificationUsersPanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addContainerGap(24, Short.MAX_VALUE)) ); }// //GEN-END:initComponents private void quoteLinkButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_quoteLinkButtonActionPerformed - isQuote = true; + status = Status.Quote; firePropertyChange(BacklogIssue.PROP_COMMENT_QUOTE, null, null); }//GEN-LAST:event_quoteLinkButtonActionPerformed @@ -257,15 +306,20 @@ private void deleteLinkButtonActionPerformed(java.awt.event.ActionEvent evt) {// if (!UiUtils.showQuestionDialog(Bundle.CommentPanel_message_delete_issue())) { return; } - isDeleted = true; + status = Status.Deleted; firePropertyChange(BacklogIssue.PROP_COMMENT_DELETED, null, null); }//GEN-LAST:event_deleteLinkButtonActionPerformed private void editLinkButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_editLinkButtonActionPerformed - isEdited = true; + status = Status.Edited; firePropertyChange(BacklogIssue.PROP_COMMENT_EDITED, null, null); }//GEN-LAST:event_editLinkButtonActionPerformed + private void notifyLinkButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_notifyLinkButtonActionPerformed + status = Status.Notify; + firePropertyChange(BacklogIssue.PROP_COMMENT_NOTIFY, null, null); + }//GEN-LAST:event_notifyLinkButtonActionPerformed + // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JTextPane contentTextPane; private javax.swing.JLabel createdDateLabel; @@ -273,6 +327,9 @@ private void editLinkButtonActionPerformed(java.awt.event.ActionEvent evt) {//GE private org.netbeans.modules.bugtracking.commons.LinkButton deleteLinkButton; private org.netbeans.modules.bugtracking.commons.LinkButton editLinkButton; private javax.swing.JSeparator jSeparator1; + private javax.swing.JLabel notificationSentToLabel; + private javax.swing.JPanel notificationUsersPanel; + private org.netbeans.modules.bugtracking.commons.LinkButton notifyLinkButton; private org.netbeans.modules.bugtracking.commons.LinkButton quoteLinkButton; private javax.swing.JLabel updatedDateLabel; private javax.swing.JLabel updatedLabel; diff --git a/src/main/java/com/junichi11/netbeans/modules/backlog/issue/ui/CommentsPanel.java b/src/main/java/com/junichi11/netbeans/modules/backlog/issue/ui/CommentsPanel.java index f73da1c..20b5bff 100644 --- a/src/main/java/com/junichi11/netbeans/modules/backlog/issue/ui/CommentsPanel.java +++ b/src/main/java/com/junichi11/netbeans/modules/backlog/issue/ui/CommentsPanel.java @@ -63,6 +63,7 @@ public class CommentsPanel extends javax.swing.JPanel implements PropertyChangeL private CommentPanel quoteCommentPanel; private CommentPanel deletedCommentPanel; private CommentPanel editedCommentPanel; + private CommentPanel notifyCommentPanel; /** * Creates new form UnsubmittedAttachmentsPanel @@ -127,10 +128,15 @@ public IssueComment getEditedComment() { return editedCommentPanel == null ? null : editedCommentPanel.getComment(); } + public IssueComment getNotifyComment() { + return notifyCommentPanel == null ? null : notifyCommentPanel.getComment(); + } + public void resetChangedPanels() { quoteCommentPanel = null; deletedCommentPanel = null; editedCommentPanel = null; + notifyCommentPanel = null; } /** @@ -159,6 +165,9 @@ public void propertyChange(PropertyChangeEvent event) { case BacklogIssue.PROP_COMMENT_EDITED: fireEditedPropertyChanged(); break; + case BacklogIssue.PROP_COMMENT_NOTIFY: + fireNotifyPropertyChanged(); + break; default: break; } @@ -168,7 +177,7 @@ private void fireQuotePropertyChanged() { synchronized (commentPanels) { quoteCommentPanel = null; for (CommentPanel comment : commentPanels) { - if (comment.isQuote()) { + if (comment.getStatus() == CommentPanel.Status.Quote) { quoteCommentPanel = comment; comment.resetProperties(); firePropertyChange(BacklogIssue.PROP_COMMENT_QUOTE, null, null); @@ -182,7 +191,7 @@ private void fireDeletedPropertyChanged() { synchronized (commentPanels) { deletedCommentPanel = null; for (CommentPanel comment : commentPanels) { - if (comment.isDeleted()) { + if (comment.getStatus() == CommentPanel.Status.Deleted) { deletedCommentPanel = comment; comment.resetProperties(); firePropertyChange(BacklogIssue.PROP_COMMENT_DELETED, null, null); @@ -196,7 +205,7 @@ private void fireEditedPropertyChanged() { synchronized (commentPanels) { editedCommentPanel = null; for (CommentPanel comment : commentPanels) { - if (comment.isEdited()) { + if (comment.getStatus() == CommentPanel.Status.Edited) { editedCommentPanel = comment; comment.resetProperties(); firePropertyChange(BacklogIssue.PROP_COMMENT_EDITED, null, null); @@ -206,4 +215,18 @@ private void fireEditedPropertyChanged() { } } + private void fireNotifyPropertyChanged() { + synchronized (commentPanels) { + notifyCommentPanel = null; + for (CommentPanel comment : commentPanels) { + if (comment.getStatus() == CommentPanel.Status.Notify) { + notifyCommentPanel = comment; + comment.resetProperties(); + firePropertyChange(BacklogIssue.PROP_COMMENT_NOTIFY, null, null); + break; + } + } + } + } + } diff --git a/src/main/java/com/junichi11/netbeans/modules/backlog/issue/ui/InsertTemplatePanel.form b/src/main/java/com/junichi11/netbeans/modules/backlog/issue/ui/InsertTemplatePanel.form new file mode 100644 index 0000000..262db5e --- /dev/null +++ b/src/main/java/com/junichi11/netbeans/modules/backlog/issue/ui/InsertTemplatePanel.form @@ -0,0 +1,46 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/src/main/java/com/junichi11/netbeans/modules/backlog/issue/ui/InsertTemplatePanel.java b/src/main/java/com/junichi11/netbeans/modules/backlog/issue/ui/InsertTemplatePanel.java new file mode 100644 index 0000000..735a3ed --- /dev/null +++ b/src/main/java/com/junichi11/netbeans/modules/backlog/issue/ui/InsertTemplatePanel.java @@ -0,0 +1,98 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2015 Oracle and/or its affiliates. All rights reserved. + * + * Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2015 Sun Microsystems, Inc. + */ +package com.junichi11.netbeans.modules.backlog.issue.ui; + +/** + * + * @author junichi11 + */ +public class InsertTemplatePanel extends javax.swing.JPanel { + + private static final long serialVersionUID = -5815122403054262852L; + + /** + * Creates new form InsertTemplatePanel + */ + public InsertTemplatePanel() { + initComponents(); + } + + public void setTemplates(String[] templates) { + for (String template : templates) { + templatesComboBox.addItem(template); + } + } + + public String getSelectedTemplateName() { + return (String) templatesComboBox.getSelectedItem(); + } + + /** + * This method is called from within the constructor to initialize the form. + * WARNING: Do NOT modify this code. The content of this method is always + * regenerated by the Form Editor. + */ + @SuppressWarnings("unchecked") + // //GEN-BEGIN:initComponents + private void initComponents() { + + templatesComboBox = new javax.swing.JComboBox(); + + templatesComboBox.setToolTipText(org.openide.util.NbBundle.getMessage(InsertTemplatePanel.class, "InsertTemplatePanel.templatesComboBox.toolTipText")); // NOI18N + templatesComboBox.setPreferredSize(new java.awt.Dimension(200, 27)); + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); + this.setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(templatesComboBox, 0, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(templatesComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + ); + }// //GEN-END:initComponents + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JComboBox templatesComboBox; + // End of variables declaration//GEN-END:variables +} diff --git a/src/main/java/com/junichi11/netbeans/modules/backlog/issue/ui/ManageTemplatesPanel.form b/src/main/java/com/junichi11/netbeans/modules/backlog/issue/ui/ManageTemplatesPanel.form new file mode 100644 index 0000000..1f8c9fb --- /dev/null +++ b/src/main/java/com/junichi11/netbeans/modules/backlog/issue/ui/ManageTemplatesPanel.form @@ -0,0 +1,76 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/src/main/java/com/junichi11/netbeans/modules/backlog/issue/ui/ManageTemplatesPanel.java b/src/main/java/com/junichi11/netbeans/modules/backlog/issue/ui/ManageTemplatesPanel.java new file mode 100644 index 0000000..9988370 --- /dev/null +++ b/src/main/java/com/junichi11/netbeans/modules/backlog/issue/ui/ManageTemplatesPanel.java @@ -0,0 +1,155 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2015 Oracle and/or its affiliates. All rights reserved. + * + * Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2015 Sun Microsystems, Inc. + */ +package com.junichi11.netbeans.modules.backlog.issue.ui; + +import com.junichi11.netbeans.modules.backlog.BacklogConfig; +import javax.swing.DefaultListModel; +import javax.swing.JPanel; +import javax.swing.event.ListSelectionEvent; +import javax.swing.event.ListSelectionListener; + +/** + * + * @author junichi11 + */ +public class ManageTemplatesPanel extends JPanel { + + private static final long serialVersionUID = -8286266826160163754L; + + private final DefaultListModel listModel = new DefaultListModel<>(); + + /** + * Creates new form ManageTemplatesPanel + */ + public ManageTemplatesPanel() { + initComponents(); + init(); + } + + private void init() { + resetTemplateNameList(); + templateNameList.setModel(listModel); + templateNameList.addListSelectionListener(new ListSelectionListener() { + + @Override + public void valueChanged(ListSelectionEvent e) { + String selectedValue = templateNameList.getSelectedValue(); + if (selectedValue != null) { + // show template + setTemplate(selectedValue); + } + } + }); + } + + public void resetTemplateNameList() { + listModel.clear(); + templateEditorPane.setText(""); // NOI18N + BacklogConfig config = BacklogConfig.getInstance(); + String[] templateNames = config.getTemplateNames(); + for (String templateName : templateNames) { + listModel.addElement(templateName); + } + } + + public String getSelectedTemplateName() { + return templateNameList.getSelectedValue(); + } + + public void setSelectedTemplateName(String name) { + templateNameList.setSelectedValue(name, true); + setTemplate(name); + } + + private void setTemplate(String templateName) { + String template = BacklogConfig.getInstance().getTemplate(templateName); + templateEditorPane.setText(template); + } + + /** + * This method is called from within the constructor to initialize the form. + * WARNING: Do NOT modify this code. The content of this method is always + * regenerated by the Form Editor. + */ + @SuppressWarnings("unchecked") + // //GEN-BEGIN:initComponents + private void initComponents() { + + templateScrollPane = new javax.swing.JScrollPane(); + templateEditorPane = new javax.swing.JEditorPane(); + templateNameScrollPane = new javax.swing.JScrollPane(); + templateNameList = new javax.swing.JList(); + + templateEditorPane.setEditable(false); + templateScrollPane.setViewportView(templateEditorPane); + + templateNameScrollPane.setViewportView(templateNameList); + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); + this.setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addContainerGap() + .addComponent(templateNameScrollPane) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(templateScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 400, Short.MAX_VALUE) + .addContainerGap()) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addContainerGap() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(templateScrollPane) + .addComponent(templateNameScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 276, Short.MAX_VALUE)) + .addContainerGap()) + ); + }// //GEN-END:initComponents + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JEditorPane templateEditorPane; + private javax.swing.JList templateNameList; + private javax.swing.JScrollPane templateNameScrollPane; + private javax.swing.JScrollPane templateScrollPane; + // End of variables declaration//GEN-END:variables +} diff --git a/src/main/java/com/junichi11/netbeans/modules/backlog/issue/ui/NotifyCommentPanel.form b/src/main/java/com/junichi11/netbeans/modules/backlog/issue/ui/NotifyCommentPanel.form new file mode 100644 index 0000000..c8ec442 --- /dev/null +++ b/src/main/java/com/junichi11/netbeans/modules/backlog/issue/ui/NotifyCommentPanel.form @@ -0,0 +1,50 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/src/main/java/com/junichi11/netbeans/modules/backlog/issue/ui/NotifyCommentPanel.java b/src/main/java/com/junichi11/netbeans/modules/backlog/issue/ui/NotifyCommentPanel.java new file mode 100644 index 0000000..30c54ba --- /dev/null +++ b/src/main/java/com/junichi11/netbeans/modules/backlog/issue/ui/NotifyCommentPanel.java @@ -0,0 +1,136 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2015 Oracle and/or its affiliates. All rights reserved. + * + * Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2015 Sun Microsystems, Inc. + */ +package com.junichi11.netbeans.modules.backlog.issue.ui; + +import com.junichi11.netbeans.modules.backlog.repository.BacklogRepository; +import com.junichi11.netbeans.modules.backlog.ui.AttributesListCellRenderer; +import com.junichi11.netbeans.modules.backlog.utils.StringUtils; +import com.nulabinc.backlog4j.User; +import com.nulabinc.backlog4j.internal.json.UserJSONImpl; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import javax.swing.DefaultListModel; +import org.openide.DialogDisplayer; +import org.openide.NotifyDescriptor; +import org.openide.util.NbBundle; + +/** + * + * @author junichi11 + */ +public class NotifyCommentPanel extends javax.swing.JPanel { + + private final DefaultListModel notificationUserListModel = new DefaultListModel<>(); + + /** + * Creates new form NotifyCommentPanel + */ + private NotifyCommentPanel(List users, BacklogRepository repository) { + initComponents(); + // set notificatoin user list + notificationUserList.setCellRenderer(new AttributesListCellRenderer(notificationUserList.getCellRenderer(), repository.getID())); + + notificationUserListModel.addElement(new UserJSONImpl()); + for (User user : users) { + notificationUserListModel.addElement(user); + } + notificationUserList.setModel(notificationUserListModel); + } + + @NbBundle.Messages("NotifyCommentPanel.dialog.title=Notify comment to") + public static List showDialog(List users, BacklogRepository repository) { + NotifyCommentPanel panel = new NotifyCommentPanel(users, repository); + NotifyDescriptor.Confirmation confirmation = new NotifyDescriptor.Confirmation( + panel, + Bundle.NotifyCommentPanel_dialog_title(), + NotifyDescriptor.OK_CANCEL_OPTION, + NotifyDescriptor.PLAIN_MESSAGE + ); + if (DialogDisplayer.getDefault().notify(confirmation) == NotifyDescriptor.OK_OPTION) { + return panel.getNotificationUserIds(); + } + return Collections.emptyList(); + } + + List getNotificationUserIds() { + List selectedUsers = notificationUserList.getSelectedValuesList(); + List userIds = new ArrayList<>(); + for (User selectedUser : selectedUsers) { + if (!StringUtils.isEmpty(selectedUser.getName())) { + userIds.add(selectedUser.getId()); + } + } + return userIds; + } + + /** + * This method is called from within the constructor to initialize the form. + * WARNING: Do NOT modify this code. The content of this method is always + * regenerated by the Form Editor. + */ + @SuppressWarnings("unchecked") + // //GEN-BEGIN:initComponents + private void initComponents() { + + notificationUserScrollPane = new javax.swing.JScrollPane(); + notificationUserList = new javax.swing.JList(); + + notificationUserScrollPane.setViewportView(notificationUserList); + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); + this.setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(notificationUserScrollPane) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(notificationUserScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + ); + }// //GEN-END:initComponents + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JList notificationUserList; + private javax.swing.JScrollPane notificationUserScrollPane; + // End of variables declaration//GEN-END:variables +} diff --git a/src/main/java/com/junichi11/netbeans/modules/backlog/issue/ui/TemplatePanel.form b/src/main/java/com/junichi11/netbeans/modules/backlog/issue/ui/TemplatePanel.form new file mode 100644 index 0000000..adae4d8 --- /dev/null +++ b/src/main/java/com/junichi11/netbeans/modules/backlog/issue/ui/TemplatePanel.form @@ -0,0 +1,88 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/src/main/java/com/junichi11/netbeans/modules/backlog/issue/ui/TemplatePanel.java b/src/main/java/com/junichi11/netbeans/modules/backlog/issue/ui/TemplatePanel.java new file mode 100644 index 0000000..0f81421 --- /dev/null +++ b/src/main/java/com/junichi11/netbeans/modules/backlog/issue/ui/TemplatePanel.java @@ -0,0 +1,207 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2015 Oracle and/or its affiliates. All rights reserved. + * + * Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2015 Sun Microsystems, Inc. + */ +package com.junichi11.netbeans.modules.backlog.issue.ui; + +import com.junichi11.netbeans.modules.backlog.utils.BacklogImage; +import javax.swing.UIManager; +import javax.swing.event.ChangeListener; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; +import org.openide.util.ChangeSupport; + +/** + * + * @author junichi11 + */ +public class TemplatePanel extends javax.swing.JPanel { + + private static final long serialVersionUID = 3434944709638210871L; + + private final ChangeSupport changeSupport = new ChangeSupport(this); + + /** + * Creates new form TemplatePanel + */ + public TemplatePanel() { + initComponents(); + init(); + } + + private void init() { + // add listener + nameTextField.getDocument().addDocumentListener(new DefaultDocumentListener()); + + // error + errorLabel.setForeground(UIManager.getColor("nb.errorForeground")); // NOI18N + setErrorMessage(""); // NOI18N + } + + public void setTemplateNameEditable(boolean isEditable) { + nameTextField.setEditable(isEditable); + } + + public void setTemplateName(String name) { + nameTextField.setText(name); + } + + public String getTemplateName() { + return nameTextField.getText().trim(); + } + + public void setTemplate(String template) { + templateEditorPane.setText(template); + } + + public String getTemplate() { + return templateEditorPane.getText(); + } + + public void setErrorMessage(String errorMessage) { + if (errorMessage == null || errorMessage.trim().isEmpty()) { + errorMessage = ""; // NOI18N + errorLabel.setIcon(null); + } else { + errorLabel.setIcon(BacklogImage.ERROR_16.getIcon()); + } + errorLabel.setText(errorMessage); + } + + public void addChangeListener(ChangeListener listener) { + changeSupport.addChangeListener(listener); + } + + public void removeChangeListener(ChangeListener listener) { + changeSupport.removeChangeListener(listener); + } + + void fireChange() { + changeSupport.fireChange(); + } + + /** + * This method is called from within the constructor to initialize the form. + * WARNING: Do NOT modify this code. The content of this method is always + * regenerated by the Form Editor. + */ + @SuppressWarnings("unchecked") + // //GEN-BEGIN:initComponents + private void initComponents() { + + nameLabel = new javax.swing.JLabel(); + nameTextField = new javax.swing.JTextField(); + templateScrollPane = new javax.swing.JScrollPane(); + templateEditorPane = new javax.swing.JEditorPane(); + errorLabel = new javax.swing.JLabel(); + + org.openide.awt.Mnemonics.setLocalizedText(nameLabel, org.openide.util.NbBundle.getMessage(TemplatePanel.class, "TemplatePanel.nameLabel.text")); // NOI18N + + nameTextField.setText(org.openide.util.NbBundle.getMessage(TemplatePanel.class, "TemplatePanel.nameTextField.text")); // NOI18N + + templateScrollPane.setViewportView(templateEditorPane); + + org.openide.awt.Mnemonics.setLocalizedText(errorLabel, org.openide.util.NbBundle.getMessage(TemplatePanel.class, "TemplatePanel.errorLabel.text")); // NOI18N + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); + this.setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addContainerGap() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(templateScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 426, Short.MAX_VALUE) + .addGroup(layout.createSequentialGroup() + .addComponent(nameLabel) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(nameTextField)) + .addGroup(layout.createSequentialGroup() + .addComponent(errorLabel) + .addGap(0, 0, Short.MAX_VALUE))) + .addContainerGap()) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addContainerGap() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(nameLabel) + .addComponent(nameTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(templateScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 220, Short.MAX_VALUE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(errorLabel) + .addContainerGap()) + ); + }// //GEN-END:initComponents + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JLabel errorLabel; + private javax.swing.JLabel nameLabel; + private javax.swing.JTextField nameTextField; + private javax.swing.JEditorPane templateEditorPane; + private javax.swing.JScrollPane templateScrollPane; + // End of variables declaration//GEN-END:variables + + private class DefaultDocumentListener implements DocumentListener { + + public DefaultDocumentListener() { + } + + @Override + public void insertUpdate(DocumentEvent e) { + processUpdate(); + } + + @Override + public void removeUpdate(DocumentEvent e) { + processUpdate(); + } + + @Override + public void changedUpdate(DocumentEvent e) { + processUpdate(); + } + + private void processUpdate() { + fireChange(); + } + } +} diff --git a/src/main/java/com/junichi11/netbeans/modules/backlog/options/BacklogOptions.java b/src/main/java/com/junichi11/netbeans/modules/backlog/options/BacklogOptions.java index 62416d4..40b0e5b 100644 --- a/src/main/java/com/junichi11/netbeans/modules/backlog/options/BacklogOptions.java +++ b/src/main/java/com/junichi11/netbeans/modules/backlog/options/BacklogOptions.java @@ -55,6 +55,7 @@ public final class BacklogOptions { private static final String ASSIGNED_TO_ME = "assigned.to.me"; // NOI18N private static final String CREATED_BY_ME = "created.by.me"; // NOI18N private static final String QUERY_MAX_ISSUE_COUNT = "query.max.issue.count"; // NOI18N + private static final String NOTIFICATIONS = "notifications"; // NOI18N private static final BacklogOptions INSTANCE = new BacklogOptions(); private BacklogOptions() { @@ -88,6 +89,14 @@ public void setMaxIssueCountForDefaultQuery(int value) { getPreferences().putInt(QUERY_MAX_ISSUE_COUNT, value); } + public void setNotificationsQuery(boolean isEnabled) { + getPreferences().putBoolean(NOTIFICATIONS, isEnabled); + } + + public boolean isNotificationsQuery() { + return getPreferences().getBoolean(NOTIFICATIONS, false); + } + private Preferences getPreferences() { return NbPreferences.forModule(BacklogOptions.class).node(PREFERENCES_PATH); } diff --git a/src/main/java/com/junichi11/netbeans/modules/backlog/options/BacklogOptionsPanel.form b/src/main/java/com/junichi11/netbeans/modules/backlog/options/BacklogOptionsPanel.form index 3fadc40..a9ccd32 100644 --- a/src/main/java/com/junichi11/netbeans/modules/backlog/options/BacklogOptionsPanel.form +++ b/src/main/java/com/junichi11/netbeans/modules/backlog/options/BacklogOptionsPanel.form @@ -29,6 +29,7 @@ + @@ -48,6 +49,8 @@ + + @@ -75,6 +78,13 @@ + + + + + + + diff --git a/src/main/java/com/junichi11/netbeans/modules/backlog/options/BacklogOptionsPanel.java b/src/main/java/com/junichi11/netbeans/modules/backlog/options/BacklogOptionsPanel.java index ca3288b..c1438ea 100644 --- a/src/main/java/com/junichi11/netbeans/modules/backlog/options/BacklogOptionsPanel.java +++ b/src/main/java/com/junichi11/netbeans/modules/backlog/options/BacklogOptionsPanel.java @@ -55,6 +55,9 @@ final class BacklogOptionsPanel extends javax.swing.JPanel { initComponents(); maxIssueSpinnerNumberModel = new SpinnerNumberModel(20, 20, 500, 10); maxIssueCountSpinner.setModel(maxIssueSpinnerNumberModel); + + // XXX another way should be used + notificationsCheckBox.setVisible(false); } /** @@ -69,6 +72,7 @@ private void initComponents() { assignedToMeCheckBox = new javax.swing.JCheckBox(); createdByMeCheckBox = new javax.swing.JCheckBox(); maxIssueCountSpinner = new javax.swing.JSpinner(); + notificationsCheckBox = new javax.swing.JCheckBox(); org.openide.awt.Mnemonics.setLocalizedText(defaultQueriesLabel, org.openide.util.NbBundle.getMessage(BacklogOptionsPanel.class, "BacklogOptionsPanel.defaultQueriesLabel.text")); // NOI18N @@ -77,6 +81,7 @@ private void initComponents() { org.openide.awt.Mnemonics.setLocalizedText(createdByMeCheckBox, org.openide.util.NbBundle.getMessage(BacklogOptionsPanel.class, "BacklogOptionsPanel.createdByMeCheckBox.text")); // NOI18N maxIssueCountSpinner.setToolTipText(org.openide.util.NbBundle.getMessage(BacklogOptionsPanel.class, "BacklogOptionsPanel.maxIssueCountSpinner.toolTipText")); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(notificationsCheckBox, org.openide.util.NbBundle.getMessage(BacklogOptionsPanel.class, "BacklogOptionsPanel.notificationsCheckBox.text")); // NOI18N javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); this.setLayout(layout); @@ -93,7 +98,8 @@ private void initComponents() { .addGap(12, 12, 12) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addComponent(createdByMeCheckBox) - .addComponent(assignedToMeCheckBox)))) + .addComponent(assignedToMeCheckBox) + .addComponent(notificationsCheckBox)))) .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) ); layout.setVerticalGroup( @@ -107,6 +113,8 @@ private void initComponents() { .addComponent(assignedToMeCheckBox) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(createdByMeCheckBox) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(notificationsCheckBox) .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) ); }// //GEN-END:initComponents @@ -116,6 +124,7 @@ void load() { assignedToMeCheckBox.setSelected(options.isAssignedToMeQuery()); createdByMeCheckBox.setSelected(options.isCreatedByMeQuery()); setMaxIssueCount(options.getMaxIssueCountForDefaultQuery()); + notificationsCheckBox.setSelected(options.isNotificationsQuery()); } void store() { @@ -123,6 +132,7 @@ void store() { options.setAssignedToMeQuery(isAssignedToMeQuery()); options.setCreatedByMeQuery(isCreatedByMeQuery()); options.setMaxIssueCountForDefaultQuery(getMaxIssueCount()); + options.setNotificationsQuery(isNotificationsQuery()); } boolean valid() { @@ -146,10 +156,15 @@ private void setMaxIssueCount(int count) { maxIssueSpinnerNumberModel.setValue(count); } + private boolean isNotificationsQuery() { + return notificationsCheckBox.isSelected(); + } + // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JCheckBox assignedToMeCheckBox; private javax.swing.JCheckBox createdByMeCheckBox; private javax.swing.JLabel defaultQueriesLabel; private javax.swing.JSpinner maxIssueCountSpinner; + private javax.swing.JCheckBox notificationsCheckBox; // End of variables declaration//GEN-END:variables } diff --git a/src/main/java/com/junichi11/netbeans/modules/backlog/query/BacklogQuery.java b/src/main/java/com/junichi11/netbeans/modules/backlog/query/BacklogQuery.java index d6c07f3..6636296 100644 --- a/src/main/java/com/junichi11/netbeans/modules/backlog/query/BacklogQuery.java +++ b/src/main/java/com/junichi11/netbeans/modules/backlog/query/BacklogQuery.java @@ -135,14 +135,16 @@ public void setSaved(boolean isSaved) { /** * Get all BacklogIssues for this query. * + * @param isRefresh {@code true} if clear an issue cache, otherwise + * {@code false} * @return BacklogIssues */ - public Collection getAllIssues() { + public Collection getAllIssues(boolean isRefresh) { GetIssuesParams issuesParams = createGetIssuesParams(); if (issuesParams == null) { return Collections.emptyList(); } - return getAllIssues(getGetIssuesParams(issuesParams), getMaxIssueCount()); + return getAllIssues(getGetIssuesParams(issuesParams), getMaxIssueCount(), isRefresh); } /** @@ -151,18 +153,20 @@ public Collection getAllIssues() { * @param issuesParams GetIssuesParams * @return BacklogIssues */ - public Collection getAllIssues(GetIssuesParams issuesParams, int maxIssueCount) { - return repository.getIssues(issuesParams, maxIssueCount, true); + public Collection getAllIssues(GetIssuesParams issuesParams, int maxIssueCount, boolean isRefresh) { + return repository.getIssues(issuesParams, maxIssueCount, true, isRefresh); } /** * Get BacklogIssues for GetIssuesParams. * * @param issuesParams GetIssuesParams + * @param isRefresh {@code true} if clear an issue cache, otherwise + * {@code false} * @return BacklogIssues */ - public Collection getIssues(GetIssuesParams issuesParams) { - return repository.getIssues(issuesParams, 100, false); + public Collection getIssues(GetIssuesParams issuesParams, boolean isRefresh) { + return repository.getIssues(issuesParams, 100, false, isRefresh); } /** @@ -547,7 +551,7 @@ public void refresh() { if (issueContainer != null) { issueContainer.refreshingStarted(); issueContainer.clear(); - for (BacklogIssue issue : getAllIssues()) { + for (BacklogIssue issue : getAllIssues(true)) { issueContainer.add(issue); } } diff --git a/src/main/java/com/junichi11/netbeans/modules/backlog/query/BacklogQueryController.java b/src/main/java/com/junichi11/netbeans/modules/backlog/query/BacklogQueryController.java index dcb1157..1d6d025 100644 --- a/src/main/java/com/junichi11/netbeans/modules/backlog/query/BacklogQueryController.java +++ b/src/main/java/com/junichi11/netbeans/modules/backlog/query/BacklogQueryController.java @@ -211,11 +211,11 @@ public void run() { ProgressHandle handle = ProgressHandleFactory.createHandle( Bundle.BacklogQueryController_label_searching_issues(), new Cancellable() { - @Override - public boolean cancel() { - return isCancel.getAndSet(true); - } - } + @Override + public boolean cancel() { + return isCancel.getAndSet(true); + } + } ); final int maxIssueCount = getPanel().getMaxIssueCount(); @@ -235,7 +235,7 @@ public boolean cancel() { GetIssuesParams clonedParams = support.newGetIssuesParams() .count(count) .offset(i * count); - issues.addAll(query.getIssues(clonedParams)); + issues.addAll(query.getIssues(clonedParams, false)); handle.progress(i + 1); } diff --git a/src/main/java/com/junichi11/netbeans/modules/backlog/query/NotificationsQuery.java b/src/main/java/com/junichi11/netbeans/modules/backlog/query/NotificationsQuery.java new file mode 100644 index 0000000..b96974e --- /dev/null +++ b/src/main/java/com/junichi11/netbeans/modules/backlog/query/NotificationsQuery.java @@ -0,0 +1,234 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2015 Oracle and/or its affiliates. All rights reserved. + * + * Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2015 Sun Microsystems, Inc. + */ +package com.junichi11.netbeans.modules.backlog.query; + +import com.junichi11.netbeans.modules.backlog.BacklogConnector; +import com.junichi11.netbeans.modules.backlog.BacklogData; +import com.junichi11.netbeans.modules.backlog.issue.BacklogIssue; +import com.junichi11.netbeans.modules.backlog.query.ui.NotificationPanel; +import com.junichi11.netbeans.modules.backlog.repository.BacklogRepository; +import com.junichi11.netbeans.modules.backlog.utils.UiUtils; +import com.nulabinc.backlog4j.Issue; +import com.nulabinc.backlog4j.IssueComment; +import com.nulabinc.backlog4j.Notification; +import com.nulabinc.backlog4j.Project; +import com.nulabinc.backlog4j.User; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import javax.swing.Icon; +import javax.swing.JLabel; +import javax.swing.SwingUtilities; +import org.netbeans.modules.bugtracking.api.Repository; +import org.netbeans.modules.bugtracking.api.RepositoryManager; +import org.netbeans.modules.bugtracking.api.Util; +import org.netbeans.modules.bugtracking.spi.IssueStatusProvider.Status; +import org.openide.DialogDisplayer; +import org.openide.NotifyDescriptor; +import org.openide.awt.NotificationDisplayer; +import org.openide.awt.NotificationDisplayer.Priority; +import org.openide.util.NbBundle; + +/** + * + * @author junichi11 + */ +public final class NotificationsQuery extends BacklogQuery implements DefaultQuery { + + private final List notifications = Collections.synchronizedList(new ArrayList()); + private final Set alreadyNotifiedIds = Collections.synchronizedSet(new HashSet()); + + public NotificationsQuery(BacklogRepository repository) { + super(repository); + } + + @NbBundle.Messages("NotificationsQuery.displayName=Notifications") + @Override + public String getDisplayName() { + return Bundle.NotificationsQuery_displayName(); + } + + @Override + public String getTooltip() { + return getDisplayName(); + } + + @Override + @NbBundle.Messages({ + "# {0} - content", + "NotificationsQuery.notification.comment=Comment: {0}", + "NotificationsQuery.notification.marked.all.as.read=Marked all notifications as read." + }) + public Collection getAllIssues(boolean isRefresh) { + final BacklogRepository repository = getRepository(); + List issues = new ArrayList<>(); + if (isRefresh) { + notifications.clear(); + notifications.addAll(repository.getNotifications()); + } + for (final Notification notification : notifications) { + if (notification.isAlreadyRead() || notification.isResourceAlreadyRead()) { + continue; + } + final BacklogIssue issue = repository.getIssue(notification, isRefresh); + if (!issues.contains(issue)) { + Status status = issue.getStatus(); + if (status == Status.SEEN) { + issue.setStatus(Status.INCOMING_MODIFIED); + } + issues.add(issue); + } + // icon + BacklogData data = BacklogData.create(repository); + Icon senderIcon = data.getUserIcon(notification.getSender()); + + IssueComment comment = notification.getComment(); + String id = String.valueOf(notification.getId()); + if (!alreadyNotifiedIds.contains(id)) { + // show notification + alreadyNotifiedIds.add(id); + NotificationPanel notificationPanel = new NotificationPanel(comment.getContent(), notification.getId()); + notificationPanel.addPropertyChangeListener(new PropertyChangeListener() { + @Override + public void propertyChange(PropertyChangeEvent evt) { + if (evt.getPropertyName().equals(NotificationPanel.PROPERTY_MARK_AS_READ)) { + Repository repo = RepositoryManager.getInstance().getRepository(BacklogConnector.ID, repository.getID()); + Util.openIssue(repo, issue.getKeyId()); + repository.markAsReadNotification(notification.getId()); + } else if (evt.getPropertyName().equals(NotificationPanel.PROPERTY_MARK_ALL_AS_READ)) { + repository.resetNotificationCount(); + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + DialogDisplayer.getDefault().notify(new NotifyDescriptor.Message(Bundle.NotificationsQuery_notification_marked_all_as_read())); + } + }); + } + } + }); + NotificationDisplayer.getDefault().notify( + getTitle(notification), + senderIcon, + new JLabel(getTitle(notification)), + notificationPanel, + Priority.NORMAL, + NotificationDisplayer.Category.INFO + ); + } + } + return issues; + } + + @NbBundle.Messages({ + "# {0} - title", + "NotificationsQuery.notification.title=Backlog: {0}", + "NotificationsQuery.reason.assigned=Assigned", + "NotificationsQuery.reason.commented=Commented", + "NotificationsQuery.reason.fileAttached=File Attached", + "NotificationsQuery.reason.issueCreated=Issue Created", + "NotificationsQuery.reason.issueUpdated=Issue Updated ", + "NotificationsQuery.reason.projectUserAdded=Project User Added", + "NotificationsQuery.reason.other=Other", + "# {0} - sender", + "NotificationsQuery.notification.sender=Sender:{0}" + }) + private static String getTitle(Notification notification) { + StringBuilder sb = new StringBuilder(); + Project project = notification.getProject(); + Issue issue = notification.getIssue(); + sb.append(project.getProjectKey()).append("-").append(issue.getKeyId()).append(" "); // NOI18N + Notification.Reason reason = notification.getReason(); + switch (reason) { + case Assigned: + sb.append(Bundle.NotificationsQuery_reason_assigned()); + break; + case Commented: + sb.append(Bundle.NotificationsQuery_reason_commented()); + break; + case FileAttached: + sb.append(Bundle.NotificationsQuery_reason_fileAttached()); + break; + case IssueCreated: + sb.append(Bundle.NotificationsQuery_reason_issueCreated()); + break; + case IssueUpdated: + sb.append(Bundle.NotificationsQuery_reason_issueUpdated()); + break; + case ProjectUserAdded: + sb.append(Bundle.NotificationsQuery_reason_projectUserAdded()); + break; + case Other: + sb.append(Bundle.NotificationsQuery_reason_other()); + break; + default: + throw new AssertionError(); + } + User sender = notification.getSender(); + if (sender != null) { + sb.append(" ").append(Bundle.NotificationsQuery_notification_sender(sender.getName())); // NOI18N + } + return Bundle.NotificationsQuery_notification_title(sb.toString()); + } + + @Override + public boolean canRename() { + return false; + } + + @Override + public boolean canRemove() { + // XXX delete action is not set to disable + return false; + } + + @Override + public void remove() { + // XXX delete action is not set to disable + UiUtils.showOptions(); + } + +} diff --git a/src/main/java/com/junichi11/netbeans/modules/backlog/query/ui/Bundle.properties b/src/main/java/com/junichi11/netbeans/modules/backlog/query/ui/Bundle.properties index a40f1b5..f133498 100644 --- a/src/main/java/com/junichi11/netbeans/modules/backlog/query/ui/Bundle.properties +++ b/src/main/java/com/junichi11/netbeans/modules/backlog/query/ui/Bundle.properties @@ -30,3 +30,6 @@ DatePanel.startDateLabel.text=Start date DatePanel.startDateHyphenLabel.text=- BacklogQueryPanel.issueCountLabel.text=ISSUE COUNT BacklogQueryPanel.maxIssueCountSpinner.toolTipText=Maximum count +NotificationPanel.commentLabel.text=Comment +NotificationPanel.markAllAsReadButton.text=Mark All As Read +NotificationPanel.markAsReadButton.text=Mark As Read & Open diff --git a/src/main/java/com/junichi11/netbeans/modules/backlog/query/ui/GeneralPanel.java b/src/main/java/com/junichi11/netbeans/modules/backlog/query/ui/GeneralPanel.java index 7468c24..6689cba 100644 --- a/src/main/java/com/junichi11/netbeans/modules/backlog/query/ui/GeneralPanel.java +++ b/src/main/java/com/junichi11/netbeans/modules/backlog/query/ui/GeneralPanel.java @@ -170,8 +170,8 @@ private void setAttributes() { setCategory(data.getCategories()); setVersion(data.getVersions()); setMilestone(data.getVersions()); - setAssignee(data.getUsers()); - setResisteredBy(data.getUsers()); + setAssignee(data.getProjectUsers()); + setResisteredBy(data.getProjectUsers()); setResolution(data.getResolutions()); setFile(); } diff --git a/src/main/java/com/junichi11/netbeans/modules/backlog/query/ui/NotificationPanel.form b/src/main/java/com/junichi11/netbeans/modules/backlog/query/ui/NotificationPanel.form new file mode 100644 index 0000000..a900052 --- /dev/null +++ b/src/main/java/com/junichi11/netbeans/modules/backlog/query/ui/NotificationPanel.form @@ -0,0 +1,89 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/src/main/java/com/junichi11/netbeans/modules/backlog/query/ui/NotificationPanel.java b/src/main/java/com/junichi11/netbeans/modules/backlog/query/ui/NotificationPanel.java new file mode 100644 index 0000000..55e47d6 --- /dev/null +++ b/src/main/java/com/junichi11/netbeans/modules/backlog/query/ui/NotificationPanel.java @@ -0,0 +1,164 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2015 Oracle and/or its affiliates. All rights reserved. + * + * Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2015 Sun Microsystems, Inc. + */ +package com.junichi11.netbeans.modules.backlog.query.ui; + +/** + * + * @author junichi11 + */ +public class NotificationPanel extends javax.swing.JPanel { + + public static final String PROPERTY_MARK_AS_READ = "notification.mark.as.read"; // NOI18N + public static final String PROPERTY_MARK_ALL_AS_READ = "notification.mark.all.as.read"; // NOI18N + private final String comment; + private final long id; + + /** + * Creates new form NotificationPanel + */ + public NotificationPanel() { + this("", -1); // NOI18N + } + + public NotificationPanel(String comment, long id) { + this.comment = comment; + this.id = id; + initComponents(); + setComment(comment); + } + + public long getNotificationId() { + return id; + } + + public String getComment() { + return comment; + } + + private void setComment(String comment) { + commentTextPane.setText(comment); + } + + void markAsReadPressed() { + firePropertyChange(PROPERTY_MARK_AS_READ, null, null); + } + + void markAllAsReadPressed() { + firePropertyChange(PROPERTY_MARK_ALL_AS_READ, null, null); + } + + /** + * This method is called from within the constructor to initialize the form. + * WARNING: Do NOT modify this code. The content of this method is always + * regenerated by the Form Editor. + */ + @SuppressWarnings("unchecked") + // //GEN-BEGIN:initComponents + private void initComponents() { + + markAsReadButton = new javax.swing.JButton(); + markAllAsReadButton = new javax.swing.JButton(); + commentLabel = new javax.swing.JLabel(); + commentScrollPane = new javax.swing.JScrollPane(); + commentTextPane = new javax.swing.JTextPane(); + + setOpaque(false); + + org.openide.awt.Mnemonics.setLocalizedText(markAsReadButton, org.openide.util.NbBundle.getMessage(NotificationPanel.class, "NotificationPanel.markAsReadButton.text")); // NOI18N + markAsReadButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + markAsReadButtonActionPerformed(evt); + } + }); + + org.openide.awt.Mnemonics.setLocalizedText(markAllAsReadButton, org.openide.util.NbBundle.getMessage(NotificationPanel.class, "NotificationPanel.markAllAsReadButton.text")); // NOI18N + markAllAsReadButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + markAllAsReadButtonActionPerformed(evt); + } + }); + + org.openide.awt.Mnemonics.setLocalizedText(commentLabel, org.openide.util.NbBundle.getMessage(NotificationPanel.class, "NotificationPanel.commentLabel.text")); // NOI18N + + commentTextPane.setEditable(false); + commentScrollPane.setViewportView(commentTextPane); + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); + this.setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addComponent(markAsReadButton) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(markAllAsReadButton)) + .addComponent(commentLabel) + .addComponent(commentScrollPane) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addComponent(commentLabel) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(commentScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 51, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(markAsReadButton) + .addComponent(markAllAsReadButton))) + ); + }// //GEN-END:initComponents + + private void markAsReadButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_markAsReadButtonActionPerformed + markAsReadPressed(); + }//GEN-LAST:event_markAsReadButtonActionPerformed + + private void markAllAsReadButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_markAllAsReadButtonActionPerformed + markAllAsReadPressed(); + }//GEN-LAST:event_markAllAsReadButtonActionPerformed + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JLabel commentLabel; + private javax.swing.JScrollPane commentScrollPane; + private javax.swing.JTextPane commentTextPane; + private javax.swing.JButton markAllAsReadButton; + private javax.swing.JButton markAsReadButton; + // End of variables declaration//GEN-END:variables +} diff --git a/src/main/java/com/junichi11/netbeans/modules/backlog/repository/BacklogRepository.java b/src/main/java/com/junichi11/netbeans/modules/backlog/repository/BacklogRepository.java index 573ebf1..6bba926 100644 --- a/src/main/java/com/junichi11/netbeans/modules/backlog/repository/BacklogRepository.java +++ b/src/main/java/com/junichi11/netbeans/modules/backlog/repository/BacklogRepository.java @@ -49,6 +49,7 @@ import com.junichi11.netbeans.modules.backlog.query.BacklogQuery; import com.junichi11.netbeans.modules.backlog.query.CreatedByMeQuery; import com.junichi11.netbeans.modules.backlog.query.DefaultQuery; +import com.junichi11.netbeans.modules.backlog.query.NotificationsQuery; import com.junichi11.netbeans.modules.backlog.query.GetIssuesParamsSupport; import com.junichi11.netbeans.modules.backlog.utils.BacklogImage; import com.junichi11.netbeans.modules.backlog.utils.BacklogUtils; @@ -57,6 +58,7 @@ import com.nulabinc.backlog4j.BacklogClient; import com.nulabinc.backlog4j.BacklogClientFactory; import com.nulabinc.backlog4j.Issue; +import com.nulabinc.backlog4j.Notification; import com.nulabinc.backlog4j.Project; import com.nulabinc.backlog4j.ResponseList; import com.nulabinc.backlog4j.api.option.GetIssuesCountParams; @@ -80,6 +82,7 @@ import java.util.logging.Logger; import javax.swing.SwingUtilities; import org.netbeans.api.annotations.common.CheckForNull; +import org.netbeans.api.annotations.common.NonNull; import org.netbeans.modules.bugtracking.api.Repository; import org.netbeans.modules.bugtracking.api.RepositoryManager; import org.netbeans.modules.bugtracking.api.Util; @@ -114,7 +117,9 @@ public final class BacklogRepository { // default queries private BacklogQuery assignedToMeQuery; private BacklogQuery createdByMeQuery; + private final BacklogQuery notificationQuery = new NotificationsQuery(this); + // key id, issue private final Map issueCache = Collections.synchronizedMap(new HashMap()); // XXX for subtask private BacklogIssue subtaskParentIssue; @@ -233,11 +238,15 @@ public BacklogIssue createIssue(String summary, String description) { * @param issue an issue * @return backlog issue */ - public synchronized BacklogIssue createIssue(Issue issue) { + public synchronized BacklogIssue createIssue(Issue issue, boolean isRefresh) { // use cache String keyId = String.valueOf(issue.getKeyId()); BacklogIssue backlogIssue = issueCache.get(keyId); if (backlogIssue != null) { + // #27 + if (isRefresh) { + backlogIssue.refreshIssue(issue); + } return backlogIssue; } backlogIssue = new BacklogIssue(this, issue); @@ -295,7 +304,7 @@ public List getIssues(String... keyIds) { try { Issue issue = client.getIssue(issueKey); if (issue != null) { - backlogIssue = createIssue(issue); + backlogIssue = createIssue(issue, false); backlogIssues.add(backlogIssue); } } catch (BacklogAPIException ex) { @@ -309,10 +318,12 @@ public List getIssues(String... keyIds) { * Get BacklogIssues. * * @param issuesParams GetIssuesParams + * @param isRefresh {@code true} if clear an issue cache, otherwise + * {@code false} * @return BacklogIssues */ - public Collection getIssues(GetIssuesParams issuesParams) { - return getIssues(issuesParams, 100, false); + public Collection getIssues(GetIssuesParams issuesParams, boolean isRefresh) { + return getIssues(issuesParams, 100, false, isRefresh); } /** @@ -321,8 +332,8 @@ public Collection getIssues(GetIssuesParams issuesParams) { * @param issuesParams GetIssuesParams * @return BacklogIssues */ - public Collection getAllIssues(GetIssuesParams issuesParams, int maxCount) { - return getIssues(issuesParams, maxCount, true); + public Collection getAllIssues(GetIssuesParams issuesParams, int maxCount, boolean isRefresh) { + return getIssues(issuesParams, maxCount, true, isRefresh); } /** @@ -331,7 +342,7 @@ public Collection getAllIssues(GetIssuesParams issuesParams, int m * @param issuesParams GetIssuesParams * @return BacklogIssues */ - public Collection getIssues(GetIssuesParams issuesParams, int maxCount, boolean isAll) { + public Collection getIssues(GetIssuesParams issuesParams, int maxCount, boolean isAll, boolean isRefresh) { Project p = getProject(); if (p == null || issuesParams == null) { return Collections.emptyList(); @@ -356,7 +367,7 @@ public Collection getIssues(GetIssuesParams issuesParams, int maxC } ResponseList issues = backlogClient.getIssues(issuesParams); for (Issue issue : issues) { - backlogIssues.add(createIssue(issue)); + backlogIssues.add(createIssue(issue, isRefresh)); if (++total == maxCount) { break; } @@ -372,6 +383,83 @@ public Collection getIssues(GetIssuesParams issuesParams, int maxC return backlogIssues; } + /** + * Get Notifications. + * + * @return Notifications + */ + public Collection getNotifications() { + Project p = getProject(); + if (p == null) { + return Collections.emptyList(); + } + BacklogClient backlogClient = createBacklogClient(); + if (backlogClient == null) { + return Collections.emptyList(); + } + List notifications = new ArrayList<>(); + try { + ResponseList responses = backlogClient.getNotifications(); + notifications.addAll(responses); + } catch (BacklogAPIException ex) { + LOGGER.log(Level.INFO, ex.getMessage()); + } + return notifications; + } + + /** + * Mark as read a notification. + * + * @param id notification identifer + */ + public void markAsReadNotification(long id) { + Project p = getProject(); + if (p == null) { + return; + } + BacklogClient backlogClient = createBacklogClient(); + if (backlogClient == null) { + return; + } + try { + backlogClient.markAsReadNotification(id); + } catch (BacklogAPIException ex) { + LOGGER.log(Level.INFO, ex.getMessage()); + } + } + + /** + * Reset notification count. + * + */ + public void resetNotificationCount() { + Project p = getProject(); + if (p == null) { + return; + } + BacklogClient backlogClient = createBacklogClient(); + if (backlogClient == null) { + return; + } + try { + backlogClient.resetNotificationCount(); + } catch (BacklogAPIException ex) { + LOGGER.log(Level.INFO, ex.getMessage()); + } + } + + /** + * Get BacklogIssue for Notification. + * + * @param notification Notification + * @param isRefresh + * @return BacklogIssue + */ + public BacklogIssue getIssue(@NonNull Notification notification, boolean isRefresh) { + Issue issue = notification.getIssue(); + return createIssue(issue, isRefresh); + } + /** * Get an issue count. * @@ -421,7 +509,7 @@ public BacklogIssue getIssue(String issueKey) { try { Issue issue = backlogClient.getIssue(issueKey); if (issue != null) { - return createIssue(issue); + return createIssue(issue, false); } } catch (BacklogAPIException ex) { LOGGER.log(Level.INFO, ex.getMessage()); @@ -444,7 +532,7 @@ public BacklogIssue getIssue(long issueId) { try { Issue issue = backlogClient.getIssue(issueId); if (issue != null) { - return createIssue(issue); + return createIssue(issue, false); } } catch (BacklogAPIException ex) { LOGGER.log(Level.INFO, ex.getMessage()); @@ -503,7 +591,7 @@ public List getBacklogSubissues(BacklogIssue parentIssue) { List subissues = getSubissues(parentIssue); ArrayList backlogSubissues = new ArrayList<>(subissues.size()); for (Issue subissue : subissues) { - backlogSubissues.add(createIssue(subissue)); + backlogSubissues.add(createIssue(subissue, false)); } return backlogSubissues; } @@ -564,6 +652,9 @@ public Collection getQueries() { if (options.isCreatedByMeQuery()) { addQuery(getCreatedByMeQuery()); } + if (options.isNotificationsQuery()) { + addQuery(getNotificationQuery()); + } // add user queries String[] queryNames = BacklogConfig.getInstance().getQueryNames(this); @@ -628,6 +719,7 @@ public void optionsChanged() { BacklogOptions options = BacklogOptions.getInstance(); setDefaultQuery(getAssignedToMeQuery(), options.isAssignedToMeQuery()); setDefaultQuery(getCreatedByMeQuery(), options.isCreatedByMeQuery()); + setDefaultQuery(getNotificationQuery(), options.isNotificationsQuery()); fireQueryListChanged(); } @@ -636,10 +728,8 @@ private void setDefaultQuery(BacklogQuery query, boolean isEnabled) { if (!getQueries().contains(query)) { getQueries().add(query); } - } else { - if (getQueries().contains(query)) { - getQueries().remove(query); - } + } else if (getQueries().contains(query)) { + getQueries().remove(query); } } @@ -704,7 +794,7 @@ public Collection simpleSearch(String criteria) { GetIssuesParams issuesParams; issuesParams = new GetIssuesParams(Collections.singletonList(p.getId())) .keyword(criteria); - issues.addAll(getIssues(issuesParams)); + issues.addAll(getIssues(issuesParams, false)); return issues; } @@ -857,6 +947,15 @@ private BacklogQuery getCreatedByMeQuery() { return createdByMeQuery; } + /** + * Get NotificationQuery. + * + * @return CreatedByMeQuery + */ + private BacklogQuery getNotificationQuery() { + return notificationQuery; + } + public void addPropertyChangeListener(PropertyChangeListener listener) { propertyChangeSupport.addPropertyChangeListener(listener); } diff --git a/src/main/java/com/junichi11/netbeans/modules/backlog/repository/ui/BacklogRepositoryPanel.form b/src/main/java/com/junichi11/netbeans/modules/backlog/repository/ui/BacklogRepositoryPanel.form index 7ba35bd..001c0c8 100644 --- a/src/main/java/com/junichi11/netbeans/modules/backlog/repository/ui/BacklogRepositoryPanel.form +++ b/src/main/java/com/junichi11/netbeans/modules/backlog/repository/ui/BacklogRepositoryPanel.form @@ -16,44 +16,37 @@ - + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + - - @@ -83,7 +76,6 @@ - diff --git a/src/main/java/com/junichi11/netbeans/modules/backlog/repository/ui/BacklogRepositoryPanel.java b/src/main/java/com/junichi11/netbeans/modules/backlog/repository/ui/BacklogRepositoryPanel.java index 22d8b45..f4a5e54 100644 --- a/src/main/java/com/junichi11/netbeans/modules/backlog/repository/ui/BacklogRepositoryPanel.java +++ b/src/main/java/com/junichi11/netbeans/modules/backlog/repository/ui/BacklogRepositoryPanel.java @@ -274,36 +274,31 @@ public void actionPerformed(java.awt.event.ActionEvent evt) { this.setLayout(layout); layout.setHorizontalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() + .addGap(0, 0, Short.MAX_VALUE) + .addComponent(setDisplayNameButton) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(connectButton)) .addGroup(layout.createSequentialGroup() - .addContainerGap() .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() - .addGap(0, 0, Short.MAX_VALUE) - .addComponent(setDisplayNameButton) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(connectButton)) + .addComponent(spaceIdLabel) + .addComponent(apiKeyLabel) + .addComponent(nameLabel) + .addComponent(projectLabel) + .addComponent(backlogLabel)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(spaceIdLabel) - .addComponent(apiKeyLabel) - .addComponent(nameLabel) - .addComponent(projectLabel) - .addComponent(backlogLabel)) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(layout.createSequentialGroup() - .addComponent(backlogComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addGap(0, 0, Short.MAX_VALUE)) - .addComponent(spaceIdTextField, javax.swing.GroupLayout.Alignment.TRAILING) - .addComponent(apiKeyTextField) - .addComponent(nameTextField) - .addComponent(projectComboBox, 0, 452, Short.MAX_VALUE)))) - .addContainerGap()) + .addComponent(backlogComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(0, 0, Short.MAX_VALUE)) + .addComponent(spaceIdTextField, javax.swing.GroupLayout.Alignment.TRAILING) + .addComponent(apiKeyTextField) + .addComponent(nameTextField) + .addComponent(projectComboBox, 0, 476, Short.MAX_VALUE))) ); layout.setVerticalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() - .addContainerGap() .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .addComponent(backlogLabel) .addComponent(backlogComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) @@ -326,8 +321,7 @@ public void actionPerformed(java.awt.event.ActionEvent evt) { .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .addComponent(nameLabel) - .addComponent(nameTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addComponent(nameTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))) ); }// //GEN-END:initComponents diff --git a/src/main/nbm/manifest.mf b/src/main/nbm/manifest.mf index ec33a6f..046f258 100644 --- a/src/main/nbm/manifest.mf +++ b/src/main/nbm/manifest.mf @@ -1,3 +1,2 @@ Manifest-Version: 1.0 -OpenIDE-Module-Install: com/junichi11/netbeans/modules/backlog/Installer.class OpenIDE-Module-Localizing-Bundle: com/junichi11/netbeans/modules/backlog/Bundle.properties diff --git a/src/main/resources/com/junichi11/netbeans/modules/backlog/Bundle_ja_JP.properties b/src/main/resources/com/junichi11/netbeans/modules/backlog/Bundle_ja_JP.properties index f0a3efa..20b708f 100644 --- a/src/main/resources/com/junichi11/netbeans/modules/backlog/Bundle_ja_JP.properties +++ b/src/main/resources/com/junichi11/netbeans/modules/backlog/Bundle_ja_JP.properties @@ -43,3 +43,6 @@ #OpenIDE-Module-Short-Description= #OpenIDE-Module-Long-Description= OpenIDE-Module-Display-Category=\u30d9\u30fc\u30b9IDE + +# BacklogConfig +BacklogConfig.default.template=#### \u6982\u8981\u8aac\u660e\n\n#### \u518d\u73fe\u624b\u9806\n\n1. \n2. \n3. \n\n#### \u5b9f\u969b\u306e\u7d50\u679c\n\n#### \u671f\u5f85\u3055\u308c\u308b\u7d50\u679c\n diff --git a/src/main/resources/com/junichi11/netbeans/modules/backlog/issue/ui/Bundle_ja_JP.properties b/src/main/resources/com/junichi11/netbeans/modules/backlog/issue/ui/Bundle_ja_JP.properties index 1c55da0..1f19ede 100644 --- a/src/main/resources/com/junichi11/netbeans/modules/backlog/issue/ui/Bundle_ja_JP.properties +++ b/src/main/resources/com/junichi11/netbeans/modules/backlog/issue/ui/Bundle_ja_JP.properties @@ -70,6 +70,8 @@ BacklogIssuePanel.header.new.subtask={0}\u306e\u65b0\u898f\u5b50\u8ab2\u984c BacklogIssuePanel.headerCreatedByLabel.text=\u767b\u9332\u8005: BacklogIssuePanel.headerCreatedDateLabel.text=- BacklogIssuePanel.headerCreatedLabel.text=\u767b\u9332\u65e5: +BacklogIssuePanel.headerUpdatedDateLabel.text=- +BacklogIssuePanel.headerUpdatedLabel.text=\u66f4\u65b0\u65e5: BacklogIssuePanel.headerCreatedUserLinkButton.text=- BacklogIssuePanel.headerDueDateLabel.text=\u671f\u9650\u65e5: BacklogIssuePanel.headerDueDateViewLabel.text=- @@ -89,6 +91,8 @@ BacklogIssuePanel.message.update.issue.fail=\u8ab2\u984c\u3092\u66f4\u65b0\u3067 BacklogIssuePanel.message.update.issue.success=\u8ab2\u984c\u3092\u66f4\u65b0\u3057\u307e\u3057\u305f BacklogIssuePanel.message.uploading.attachments=\u30d5\u30a1\u30a4\u30eb\u3092\u30a2\u30c3\u30d7\u30ed\u30fc\u30c9\u4e2d... BacklogIssuePanel.milestoneLabel.text=\u30de\u30a4\u30eb\u30b9\u30c8\u30fc\u30f3: +BacklogIssuePanel.no.notification.user=\u304a\u77e5\u3089\u305b\u3067\u304d\u308b\u30e6\u30fc\u30b6\u306f\u3044\u307e\u305b\u3093\u3002 +BacklogIssuePanel.notificationLabel.text=\u304a\u77e5\u3089\u305b: BacklogIssuePanel.priorityLabel.text=\u512a\u5148\u5ea6*: BacklogIssuePanel.refreshLinkButton.text=\u30ea\u30d5\u30ec\u30c3\u30b7\u30e5 BacklogIssuePanel.resolutionLabel.text=\u5b8c\u4e86\u7406\u7531: @@ -104,6 +108,28 @@ BacklogIssuePanel.summaryLabel.text=\u4ef6\u540d*: BacklogIssuePanel.summaryTextField.text= BacklogIssuePanel.typeLabel.text=\u7a2e\u5225*: BacklogIssuePanel.versionLabel.text=\u767a\u751f\u30d0\u30fc\u30b8\u30e7\u30f3: +BacklogIssuePanel.insertTemplateButton.toolTipText=\u30c6\u30f3\u30d7\u30ec\u30fc\u30c8\u3092\u633f\u5165 +BacklogIssuePanel.insertTemplateButton.text= +BacklogIssuePanel.insert.template.title=\u30c6\u30f3\u30d7\u30ec\u30fc\u30c8\u3092\u633f\u5165 +BacklogIssuePanel.manageTemplatesButton.toolTipText=\u30c6\u30f3\u30d7\u30ec\u30fc\u30c8\u3092\u7ba1\u7406 +BacklogIssuePanel.manageTemplatesButton.text= +BacklogIssuePanel.manage.templates.title=\u30c6\u30f3\u30d7\u30ec\u30fc\u30c8\u3092\u7ba1\u7406 +BacklogIssuePanel.manage.templates.add.option=\u8ffd\u52a0 +BacklogIssuePanel.manage.templates.remove.option=\u524a\u9664 +BacklogIssuePanel.manage.templates.edit.option=\u7de8\u96c6 +BacklogIssuePanel.manage.templates.duplicate.option=\u8907\u88fd +BacklogIssuePanel.manage.templates.close.option=\u9589\u3058\u308b +InsertTemplatePanel.templatesComboBox.toolTipText= +TemplatePanel.nameLabel.text=\u30c6\u30f3\u30d7\u30ec\u30fc\u30c8\u540d: +TemplatePanel.nameTextField.text= +TemplatePanel.errorLabel.text=ERROR + +ManageTemplateButtonListener.add.title=\u30c6\u30f3\u30d7\u30ec\u30fc\u30c8\u3092\u8ffd\u52a0 +ManageTemplateButtonListener.edit.title=\u30c6\u30f3\u30d7\u30ec\u30fc\u30c8\u3092\u7de8\u96c6 +ManageTemplateButtonListener.duplicate.title=\u30c6\u30f3\u30d7\u30ec\u30fc\u30c8\u3092\u8907\u88fd +ManageTemplateButtonListener.remove.message={0}\u3092\u672c\u5f53\u306b\u524a\u9664\u3057\u307e\u3059\u304b?\n(default\u306e\u5834\u5408\u306f\u524a\u9664\u3055\u308c\u305a\u306b\u521d\u671f\u5316\u3055\u308c\u307e\u3059\u3002) +TemplatePanelChangeListener.invalid.empty=\u30c6\u30f3\u30d7\u30ec\u30fc\u30c8\u540d\u3092\u5165\u529b\u3057\u3066\u304f\u3060\u3055\u3044\u3002 +TemplatePanelChangeListener.invalid.existing=\u305d\u306e\u30c6\u30f3\u30d7\u30ec\u30fc\u30c8\u540d\u306f\u65e2\u306b\u5b58\u5728\u3057\u307e\u3059\u3002 CommentPanel.contentTextPane.text= CommentPanel.createdDateLabel.text=- @@ -111,6 +137,8 @@ CommentPanel.createdLabel.text=\u767b\u9332\u65e5: CommentPanel.deleteLinkButton.text=\u524a\u9664 CommentPanel.editLinkButton.text=\u7de8\u96c6 CommentPanel.message.delete.issue=\u672c\u5f53\u306b\u30b3\u30e1\u30f3\u30c8\u3092\u524a\u9664\u3057\u307e\u3059\u304b? +CommentPanel.notificationSentToLabel.text=\u304a\u77e5\u3089\u305b\u3057\u305f\u4eba: +CommentPanel.notifyLinkButton.text=\u304a\u77e5\u3089\u305b CommentPanel.quoteLinkButton.text=\u5f15\u7528 CommentPanel.updatedDateLabel.text=- CommentPanel.updatedLabel.text=\u66f4\u65b0\u65e5: @@ -118,5 +146,7 @@ CommentPanel.userLinkButton.text=USER EditIssuePanel.dialg.title=\u30b3\u30e1\u30f3\u30c8\u3092\u7de8\u96c6 +NotifyCommentPanel.dialog.title=\u30b3\u30e1\u30f3\u30c8\u3092\u304a\u77e5\u3089\u305b\u3057\u305f\u3044\u30e6\u30fc\u30b6 + SubmitPanel.errorLabel.text=ERROR SubmitPanel.submitButton.text=\u767b\u9332 diff --git a/src/main/resources/com/junichi11/netbeans/modules/backlog/options/Bundle.properties b/src/main/resources/com/junichi11/netbeans/modules/backlog/options/Bundle.properties index 461ac87..e3c0bb6 100644 --- a/src/main/resources/com/junichi11/netbeans/modules/backlog/options/Bundle.properties +++ b/src/main/resources/com/junichi11/netbeans/modules/backlog/options/Bundle.properties @@ -42,3 +42,4 @@ BacklogOptionsPanel.assignedToMeCheckBox.text=Assigned To Me BacklogOptionsPanel.createdByMeCheckBox.text=Created By Me BacklogOptionsPanel.defaultQueriesLabel.text=Default queries: BacklogOptionsPanel.maxIssueCountSpinner.toolTipText=Maximum count +BacklogOptionsPanel.notificationsCheckBox.text=Notifications diff --git a/src/main/resources/com/junichi11/netbeans/modules/backlog/options/Bundle_ja_JP.properties b/src/main/resources/com/junichi11/netbeans/modules/backlog/options/Bundle_ja_JP.properties index 1d6268f..6d03d40 100644 --- a/src/main/resources/com/junichi11/netbeans/modules/backlog/options/Bundle_ja_JP.properties +++ b/src/main/resources/com/junichi11/netbeans/modules/backlog/options/Bundle_ja_JP.properties @@ -40,5 +40,6 @@ BacklogOptionsPanel.assignedToMeCheckBox.text=\u62c5\u5f53 BacklogOptionsPanel.createdByMeCheckBox.text=\u767b\u9332 +BacklogOptionsPanel.notificationsCheckBox.text=\u304a\u77e5\u3089\u305b BacklogOptionsPanel.defaultQueriesLabel.text=\u30c7\u30d5\u30a9\u30eb\u30c8\u306e\u554f\u5408\u305b: BacklogOptionsPanel.maxIssueCountSpinner.toolTipText=\u6700\u5927\u4ef6\u6570 diff --git a/src/main/resources/com/junichi11/netbeans/modules/backlog/query/Bundle_ja_JP.properties b/src/main/resources/com/junichi11/netbeans/modules/backlog/query/Bundle_ja_JP.properties index 08c86b4..8db4231 100644 --- a/src/main/resources/com/junichi11/netbeans/modules/backlog/query/Bundle_ja_JP.properties +++ b/src/main/resources/com/junichi11/netbeans/modules/backlog/query/Bundle_ja_JP.properties @@ -48,3 +48,16 @@ BacklogQueryController.message.error.already.exists=\u3059\u3067\u306b\u5b58\u57 BacklogQueryController.message.error.empty.name=\u554f\u5408\u305b\u540d\u3092\u8a2d\u5b9a\u3057\u3066\u304f\u3060\u3055\u3044 BacklogQueryController.message.saved=\u554f\u5408\u305b\u3092\u4fdd\u5b58\u3057\u307e\u3057\u305f CreatedByMeQuery.displayName=\u767b\u9332 + +NotificationsQuery.displayName=\u304a\u77e5\u3089\u305b +NotificationsQuery.notification.comment=\u30b3\u30e1\u30f3\u30c8: {0} +NotificationsQuery.notification.marked.all.as.read=\u5168\u3066\u65e2\u8aad\u306b\u3057\u307e\u3057\u305f\u3002 +NotificationsQuery.notification.title=Backlog: {0} +NotificationsQuery.reason.assigned=\u62c5\u5f53\u306b\u3055\u308c\u307e\u3057\u305f +NotificationsQuery.reason.commented=\u30b3\u30e1\u30f3\u30c8\u3055\u308c\u307e\u3057\u305f +NotificationsQuery.reason.fileAttached=\u30d5\u30a1\u30a4\u30eb\u304c\u6dfb\u4ed8\u3055\u308c\u307e\u3057\u305f +NotificationsQuery.reason.issueCreated=\u8ab2\u984c\u304c\u4f5c\u6210\u3055\u308c\u307e\u3057\u305f +NotificationsQuery.reason.issueUpdated=\u8ab2\u984c\u304c\u66f4\u65b0\u3055\u308c\u307e\u3057\u305f +NotificationsQuery.reason.projectUserAdded=\u30e6\u30fc\u30b6\u304c\u8ffd\u52a0\u3055\u308c\u307e\u3057\u305f +NotificationsQuery.reason.other=\u305d\u306e\u4ed6 + diff --git a/src/main/resources/com/junichi11/netbeans/modules/backlog/query/ui/Bundle_ja_JP.properties b/src/main/resources/com/junichi11/netbeans/modules/backlog/query/ui/Bundle_ja_JP.properties index 99df1bc..860f834 100644 --- a/src/main/resources/com/junichi11/netbeans/modules/backlog/query/ui/Bundle_ja_JP.properties +++ b/src/main/resources/com/junichi11/netbeans/modules/backlog/query/ui/Bundle_ja_JP.properties @@ -70,6 +70,9 @@ GeneralPanel.registeredByMeLinkButton.text=\u79c1\u3092\u9078\u629e GeneralPanel.resolutionLabel.text=\u5b8c\u4e86\u7406\u7531 GeneralPanel.statusLabel.text=\u72b6\u614b GeneralPanel.versionLabel.text=\u767a\u751f\u30d0\u30fc\u30b8\u30e7\u30f3 +NotificationPanel.commentLabel.text=\u30b3\u30e1\u30f3\u30c8 +NotificationPanel.markAllAsReadButton.text=\u5168\u3066\u65e2\u8aad\u306b\u3059\u308b +NotificationPanel.markAsReadButton.text=\u65e2\u8aad\u306b\u3057\u3066\u958b\u304f UnassignedUser.name=\u672a\u8a2d\u5b9a NoCategory.name=\u672a\u8a2d\u5b9a diff --git a/src/main/resources/com/junichi11/netbeans/modules/backlog/resources/manage_template_16.png b/src/main/resources/com/junichi11/netbeans/modules/backlog/resources/manage_template_16.png new file mode 100644 index 0000000..2e85b04 Binary files /dev/null and b/src/main/resources/com/junichi11/netbeans/modules/backlog/resources/manage_template_16.png differ diff --git a/src/main/resources/com/junichi11/netbeans/modules/backlog/resources/template_16.png b/src/main/resources/com/junichi11/netbeans/modules/backlog/resources/template_16.png new file mode 100644 index 0000000..7d1d8c6 Binary files /dev/null and b/src/main/resources/com/junichi11/netbeans/modules/backlog/resources/template_16.png differ