/* * Copyright 2012 Anchialas. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.kenai.redminenb.query; import com.kenai.redminenb.Redmine; import com.kenai.redminenb.RedmineConfig; import com.kenai.redminenb.RedmineConnector; import com.kenai.redminenb.issue.RedmineIssue; import com.kenai.redminenb.query.RedmineQueryParameter.CheckBoxParameter; import com.kenai.redminenb.query.RedmineQueryParameter.ListParameter; import com.kenai.redminenb.query.RedmineQueryParameter.ComboParameter; import com.kenai.redminenb.query.RedmineQueryParameter.TextFieldParameter; import com.kenai.redminenb.repository.RedmineRepository; import com.kenai.redminenb.timetracker.IssueTimeTrackerTopComponent; import com.kenai.redminenb.user.RedmineUser; import com.kenai.redminenb.util.AssigneeWrapper; import com.kenai.redminenb.util.CancelableRunnable; import com.kenai.redminenb.util.CancelableRunnableWrapper; import com.kenai.redminenb.util.NestedProject; import com.kenai.redminenb.util.RedmineUtil; import com.kenai.redminenb.util.RedmineUtil.RedmineUserComparator; import com.kenai.redminenb.util.SafeAutoCloseable; import com.kenai.redminenb.util.TableCellRendererCategory; import com.kenai.redminenb.util.TableCellRendererPriority; import com.kenai.redminenb.util.TableCellRendererProject; import com.kenai.redminenb.util.TableCellRendererTracker; import com.kenai.redminenb.util.TableCellRendererUser; import com.kenai.redminenb.util.TableCellRendererVersion; import com.taskadapter.redmineapi.bean.IssueCategory; import com.taskadapter.redmineapi.bean.IssuePriority; import com.taskadapter.redmineapi.bean.IssueStatus; import com.taskadapter.redmineapi.bean.Project; import com.taskadapter.redmineapi.bean.SavedQuery; import com.taskadapter.redmineapi.bean.Tracker; import com.taskadapter.redmineapi.bean.Version; import java.awt.Component; import java.awt.EventQueue; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.KeyEvent; import java.awt.event.KeyListener; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.beans.PropertyChangeSupport; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.net.MalformedURLException; import java.net.URL; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.MissingResourceException; import java.util.SortedSet; import java.util.TreeSet; import java.util.logging.Level; import java.util.logging.Logger; import javax.swing.JComponent; import javax.swing.JMenuItem; import javax.swing.JPopupMenu; import javax.swing.JScrollPane; import javax.swing.JTable; import javax.swing.RowSorter; import javax.swing.SortOrder; import javax.swing.SwingUtilities; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; import javax.swing.table.DefaultTableColumnModel; import javax.swing.table.TableColumn; import javax.xml.ws.Holder; import org.netbeans.api.progress.ProgressHandle; import org.netbeans.api.progress.ProgressHandleFactory; import org.netbeans.modules.bugtracking.spi.QueryController; import org.openide.DialogDisplayer; import org.openide.NotifyDescriptor; import org.openide.awt.HtmlBrowser; import org.openide.util.Cancellable; import org.openide.util.HelpCtx; import org.openide.util.Mutex; import org.openide.util.NbBundle; import org.openide.util.RequestProcessor; /** * * @author Anchialas <anchialas@gmail.com> */ @NbBundle.Messages({ "MSG_SameName=Query with the same name already exists.", "MSG_NoResults=No Issues found", "# {0} - the issue number", "MSG_NotFound=Issue #{0} not found", "# {0} - the issue number", "MSG_Opening=Opening Issue #{0}...", "MSG_Searching=Searching...", "# {0} - the query name", "MSG_SearchingQuery=Searching {0}...", "# {0} - the query name", "MSG_RemoveQuery=Do you want to remove the query ''{0}''?", "# {0} - the display name of the repository", "MSG_Populating=Reading server data from Issue Tracker ''{0}''...", "CTL_RemoveQuery=Remove", "# {0} - the issue number", "LBL_RetrievingIssue=Retrieved issue #{0}", "LBL_Never=Never", "# {0} - the search hits count", "LBL_MatchingIssues=There {0,choice,0#are no issues|1#is one issue|1<are {0,number,integer} issues} matching this query.", "LBL_SelectKeywords=Select or deselect keywords.", "MNU_OpenIssue=Open Issue", "MNU_OpenIssueForTimeTracking=Open Timetracker with Issue" }) public class RedmineQueryController implements QueryController, ActionListener { private static final Logger LOG = Logger.getLogger(RedmineQueryController.class.getName()); private RedmineQueryPanel queryPanel; private final QueryListModel queryListModel = new QueryListModel(); private JTable issueTable; // private final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); // NOI18N private final RedmineRepository repository; // private final RedmineQuery query; // private List<SavedQuery> savedQueries = Collections.EMPTY_LIST; // private ListParameter versionParameter; private ListParameter trackerParameter; private ListParameter statusParameter; private ListParameter categoryParameter; private ListParameter priorityParameter; private ListParameter assigneeParameter; private ListParameter watcherParameter; private ListParameter projectParameter; private ComboParameter queryParameter; private ComboParameter project2Parameter; private Map<String, RedmineQueryParameter> parameters; // private final Object REFRESH_LOCK = new Object(); private QueryTask refreshTask; public RedmineQueryController(RedmineRepository repository, RedmineQuery query) { this.repository = repository; this.query = query; } private void setListeners() { queryPanel.searchButton.addActionListener(this); queryPanel.refreshCheckBox.addActionListener(this); queryPanel.saveChangesButton.addActionListener(this); queryPanel.cancelChangesButton.addActionListener(this); queryPanel.gotoIssueButton.addActionListener(this); queryPanel.webButton.addActionListener(this); queryPanel.saveButton.addActionListener(this); queryPanel.refreshButton.addActionListener(this); queryPanel.modifyButton.addActionListener(this); queryPanel.removeButton.addActionListener(this); queryPanel.refreshConfigurationButton.addActionListener(this); queryPanel.issueIdTextField.addActionListener(this); queryPanel.queryTextField.addActionListener(this); queryPanel.projectList.addListSelectionListener(new ListSelectionListener() { @Override public void valueChanged(ListSelectionEvent e) { if (!e.getValueIsAdjusting()) { repository.getRequestProcessor().execute(new Runnable() { @Override public void run() { updateProjectValues(); } }); } } }); queryPanel.bySaveQueryProjectCB.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { updateProjectValues2(); } }); } private void updateProjectValues2() { Integer projectId = null; try { projectId = Integer.valueOf(project2Parameter.getValues()[0].getValue()); } catch (NullPointerException | NumberFormatException ex) {} List<ParameterValue> queries = new ArrayList<>(); for(SavedQuery sq: savedQueries) { if(projectId != null && (sq.getProjectId() == null || sq.getProjectId().equals(projectId))) { queries.add(new ParameterValue(sq.getName(), sq.getId())); } } queryParameter.setParameterValues(queries); } private void updateProjectValues() { assert (!SwingUtilities.isEventDispatchThread()) : "Must be called off the EDT"; try (SafeAutoCloseable sac = query.busy()) { ParameterValue pv = Mutex.EVENT.writeAccess(new Mutex.Action<ParameterValue>() { @Override public ParameterValue run() { return (ParameterValue) queryPanel.projectList.getSelectedValue(); } }); NestedProject np = null; Project p = null; if (pv != null) { np = repository.getProjects().get(Integer.valueOf(pv.getValue())); } if (np != null) { p = np.getProject(); } final List<ParameterValue> categoryList = new ArrayList<>(); final List<ParameterValue> versionList = new ArrayList<>(); final List<ParameterValue> watcherList = new ArrayList<>(); watcherList.add(new ParameterValue("(me)", "me")); if (p != null) { categoryList.add(ParameterValue.NONE_PARAMETERVALUE); for (IssueCategory c : repository.getIssueCategories(p)) { categoryList.add(new ParameterValue(c.getName(), c.getId())); } versionList.add(ParameterValue.NONE_PARAMETERVALUE); for (Version v : repository.getVersions(p)) { versionList.add(new ParameterValue(v.getName(), v.getId())); } for (RedmineUser redmineUser : repository.getUsers(p)) { watcherList.add(new ParameterValue(redmineUser.toString(), redmineUser.getId())); } } Mutex.EVENT.writeAccess(new Mutex.Action<Void>() { @Override public Void run() { categoryParameter.setParameterValues(categoryList); versionParameter.setParameterValues(versionList); watcherParameter.setParameterValues(watcherList); return null; } }); } } @Override public HelpCtx getHelpCtx() { return HelpCtx.DEFAULT_HELP; } private void modelToGUI() { assert SwingUtilities.isEventDispatchThread(); Map<String, ParameterValue[]> queryParams = query.getParameters(); // Initalize project first, as other values depend on selected project if (queryParams.containsKey("project_id")) { parameters.get("project_id").setValues(queryParams.get("project_id")); parameters.get("project_id2").setValues(queryParams.get("project_id")); } for (Entry<String, ParameterValue[]> e : query.getParameters().entrySet()) { if ((!"project_id".equals(e.getKey())) && parameters.containsKey(e.getKey())) { parameters.get(e.getKey()).setValues(e.getValue()); } } if(queryParams.containsKey("query_id")) { queryPanel.queryTypeCombo.setSelectedIndex(1); } else { queryPanel.queryTypeCombo.setSelectedIndex(0); } queryPanel.setTitle(query.getDisplayName()); queryPanel.cancelChangesButton.setVisible(query.getDisplayName() != null); queryPanel.setLastRefresh(getLastRefresh()); } private void guiToModel() { Map<String, ParameterValue[]> parameters = new HashMap<>(); if(queryPanel.queryTypeCombo.getSelectedIndex() == 1) { parameters.put("query_id", this.parameters.get("query_id").getValues()); parameters.put("project_id", this.parameters.get("project_id2").getValues()); } else { for (Entry<String,RedmineQueryParameter> e: this.parameters.entrySet()) { if("query_id".equals(e.getKey()) || "project_id2".equals(e.getKey())) { continue; } RedmineQueryParameter rqp = e.getValue(); parameters.put(rqp.getParameter(), rqp.getValues()); } } query.setParameters(parameters); } @Override public void actionPerformed(ActionEvent e) { if (e.getSource() == queryPanel.searchButton) { guiToModel(); refresh(); } else if (e.getSource() == queryPanel.gotoIssueButton) { onGotoIssue(); } else if (e.getSource() == queryPanel.saveChangesButton) { guiToModel(); onSave(true); // refresh } else if (e.getSource() == queryPanel.cancelChangesButton) { discardUnsavedChanges(); } else if (e.getSource() == queryPanel.webButton) { onWeb(); } else if (e.getSource() == queryPanel.saveButton) { guiToModel(); onSave(false); // do not refresh } else if (e.getSource() == queryPanel.refreshButton) { refresh(); } else if (e.getSource() == queryPanel.modifyButton) { onModify(); } else if (e.getSource() == queryPanel.removeButton) { onRemove(); } else if (e.getSource() == queryPanel.refreshCheckBox) { onAutoRefresh(); } else if (e.getSource() == queryPanel.refreshConfigurationButton) { refreshConfiguration(); } else if (e.getSource() == queryPanel.issueIdTextField) { if (!queryPanel.issueIdTextField.getText().trim().equals("")) { // NOI18N onGotoIssue(); } } else if (e.getSource() == queryPanel.issueIdTextField || e.getSource() == queryPanel.queryTextField) { refresh(); } } ///////////////////////////////////////////////////////////////////////////// private void onSave(final boolean refresh) { query.getRepository().getRequestProcessor().post(new Runnable() { @Override public void run() { Redmine.LOG.fine("on save start"); String name = query.getDisplayName(); if (query.getDisplayName() == null || query.getDisplayName().isEmpty()) { name = getSaveName(); if (name == null) { return; } } assert name != null; saveChanges(name); Redmine.LOG.fine("on save finnish"); if (refresh) { refresh(); } } }); } private String getSaveName() { NotifyDescriptor.InputLine nd = new NotifyDescriptor.InputLine( "Name", "Save query"); DialogDisplayer.getDefault().notify(nd); if (nd.getValue() == NotifyDescriptor.OK_OPTION) { return nd.getInputText(); } else { return null; } } private void setAsSaved(boolean showModify) { queryPanel.setModifyVisible(showModify); queryPanel.cancelChangesButton.setVisible(!showModify); queryPanel.refreshCheckBox.setVisible(!showModify); } private String getLastRefresh() throws MissingResourceException { long l = query.getLastRefresh(); return l > 0 ? dateFormat.format(new Date(l)) : Bundle.LBL_Never(); } private void onGotoIssue() { final Long issueId = (Long) queryPanel.issueIdTextField.getValue(); if (issueId == null) { return; } CancelableRunnableWrapper c = new CancelableRunnableWrapper(); final ProgressHandle handle = ProgressHandleFactory.createHandle(Bundle.MSG_Opening(issueId), c); // NOI18N Runnable r = new Runnable() { @Override public void run() { handle.start(); try { openIssue(repository.getIssue(String.valueOf(issueId))); } finally { handle.finish(); } } }; c.setBackingRunnable(r); query.getRepository().getRequestProcessor().submit(c); } protected void openIssue(RedmineIssue issue) { if (issue != null) { RedmineUtil.openIssue(issue); } else { // XXX nice message? } } private static class UrlOpener implements Runnable { private final String urlString; public UrlOpener(String urlString) { this.urlString = urlString; } @Override public void run() { URL url; try { url = new URL(urlString); } catch (NullPointerException | MalformedURLException ex) { Redmine.LOG.log(Level.SEVERE, null, ex); return; } HtmlBrowser.URLDisplayer displayer = HtmlBrowser.URLDisplayer.getDefault(); if (displayer != null) { displayer.showURL(url); } else { // XXX nice error message? Redmine.LOG.warning("No URLDisplayer found."); // NOI18N } } } private void onWeb() { query.getRepository().getRequestProcessor().post(new UrlOpener(repository.getUrl())); } public void autoRefresh() { refresh(true); } private void onAutoRefresh() { final boolean autoRefresh = queryPanel.refreshCheckBox.isSelected(); RedmineConfig.getInstance().setQueryAutoRefresh(query.getDisplayName(), autoRefresh); logAutoRefreshEvent(autoRefresh); if (autoRefresh) { scheduleForRefresh(); } else { repository.stopRefreshing(query); } } public void refresh() { refresh(false); } private void refresh(final boolean auto) { synchronized (REFRESH_LOCK) { if (refreshTask == null) { refreshTask = new QueryTask(); } else { refreshTask.cancel(); } refreshTask.post(auto); } } private void onModify() { queryPanel.setModifyVisible(true); } private void onRemove() { NotifyDescriptor nd = new NotifyDescriptor.Confirmation(Bundle.MSG_RemoveQuery(query.getDisplayName()), Bundle.CTL_RemoveQuery(), NotifyDescriptor.OK_CANCEL_OPTION); if (DialogDisplayer.getDefault().notify(nd) == NotifyDescriptor.OK_OPTION) { query.getRepository().getRequestProcessor().post(new Runnable() { @Override public void run() { remove(); } }); } } protected void scheduleForRefresh() { if (query.isSaved()) { repository.scheduleForRefresh(query); } } protected void logAutoRefreshEvent(boolean autoRefresh) { LOG.fine(String.format("AutoRefresh '%s-%s', Autorefresh: %b", RedmineConnector.NAME, query.getDisplayName(), autoRefresh )); } private void refreshConfiguration() { postPopulate(); } protected final void postPopulate() { final Holder<ProgressHandle> handleValue = new Holder<>(); final String msgPopulating = Bundle.MSG_Populating(repository.getDisplayName()); CancelableRunnable cr = new CancelableRunnable() { @Override public void guardedRun() { Redmine.LOG.log(Level.FINE, "Starting populate query controller (saved: {0}, name: {1})", new Object[]{query.isSaved(), query.getDisplayName()}); try (SafeAutoCloseable sac = query.busy()) { Mutex.EVENT.writeAccess(new Mutex.Action<Void>() { @Override public Void run() { queryPanel.showRetrievingProgress(true, msgPopulating, !query.isSaved()); handleValue.value.start(); return null; } }); savedQueries = repository.getServersideQueries(); final List<ParameterValue> trackerList = new ArrayList<>(); for (Tracker t : repository.getTrackers()) { trackerList.add(new ParameterValue(t.getName(), t.getId())); } final List<ParameterValue> statusList = new ArrayList<>(); for (IssueStatus s : repository.getStatuses()) { statusList.add(new ParameterValue(s.getName(), s.getId())); } final List<ParameterValue> priorityList = new ArrayList<>(); for (IssuePriority ip : repository.getIssuePriorities()) { priorityList.add(new ParameterValue(ip.getName(), ip.getId())); } SortedSet<RedmineUser> userList = new TreeSet<>(RedmineUserComparator.SINGLETON); SortedSet<AssigneeWrapper> assigneeWrapperList = new TreeSet<>(); for (Entry<Integer, NestedProject> entry : repository.getProjects().entrySet()) { userList.addAll(repository.getUsers(entry.getValue().getProject())); assigneeWrapperList.addAll(repository.getAssigneeWrappers(entry.getValue().getProject())); } final List<ParameterValue> assigneeList = new ArrayList<>(); assigneeList.add(ParameterValue.NONE_PARAMETERVALUE); for (AssigneeWrapper assigneeWrapper : assigneeWrapperList) { assigneeList.add(new ParameterValue(assigneeWrapper.getName(), assigneeWrapper.getId())); } List<NestedProject> projectList = new ArrayList<>(repository.getProjects().values()); Collections.sort(projectList); final List<ParameterValue> projectValueList = new ArrayList<>(); for (NestedProject np : projectList) { projectValueList.add(new ParameterValue(np.toString(), np.getProject().getId())); } Mutex.EVENT.writeAccess(new Mutex.Action<Void>() { @Override public Void run() { trackerParameter.setParameterValues(trackerList); categoryParameter.setParameterValues(Collections.EMPTY_LIST); versionParameter.setParameterValues(Collections.EMPTY_LIST); watcherParameter.setParameterValues(Collections.EMPTY_LIST); statusParameter.setParameterValues(statusList); priorityParameter.setParameterValues(priorityList); assigneeParameter.setParameterValues(assigneeList); projectParameter.setParameterValues(projectValueList); project2Parameter.setParameterValues(projectValueList); if (query.isSaved()) { boolean autoRefresh = RedmineConfig.getInstance().getQueryAutoRefresh(query.getDisplayName()); queryPanel.refreshCheckBox.setSelected(autoRefresh); } updateProjectValues2(); return null; } }); updateProjectValues(); Mutex.EVENT.writeAccess(new Runnable() { @Override public void run() { handleValue.value.finish(); modelToGUI(); queryPanel.showRetrievingProgress(false, null, !query.isSaved()); } }); Redmine.LOG.log(Level.FINE, "Finnished populate query controller (saved: {0}, name: {1})", new Object[]{query.isSaved(), query.getDisplayName()}); } } }; handleValue.value = ProgressHandleFactory.createHandle(msgPopulating); repository.getRequestProcessor().execute(cr); } private <T extends RedmineQueryParameter> T registerQueryParameter(Class<T> clazz, Component c, String parameterName, String internalParamName) { try { Constructor<T> constructor = clazz.getConstructor(c.getClass(), String.class); T t = constructor.newInstance(c, parameterName); if(internalParamName == null) { parameters.put(parameterName, t); } else { parameters.put(internalParamName, t); } return t; } catch (NoSuchMethodException | SecurityException | InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) { Redmine.LOG.log(Level.SEVERE, parameterName, ex); } return null; } private <T extends RedmineQueryParameter> T registerQueryParameter(Class<T> clazz, Component c, String parameterName) { return registerQueryParameter(clazz, c, parameterName, parameterName); } protected void enableFields(boolean bl) { // set all non parameter fields queryPanel.enableFields(bl); // set the parameter fields for (Map.Entry<String, RedmineQueryParameter> e : parameters.entrySet()) { RedmineQueryParameter pv = parameters.get(e.getKey()); pv.setEnabled(bl && !pv.isEmpty()); } } private void remove() { if (refreshTask != null) { refreshTask.cancel(); } query.remove(); } private void setIssueCount(final int count) { EventQueue.invokeLater(new Runnable() { @Override public void run() { queryPanel.tableSummaryLabel.setText(Bundle.LBL_MatchingIssues(count)); } }); } @Override public boolean providesMode(QueryMode qm) { return qm == QueryMode.EDIT || qm == QueryMode.VIEW; } @Override public JComponent getComponent(QueryMode qm) { if (queryPanel == null) { DefaultTableColumnModel tcm = new DefaultTableColumnModel(); TableColumn tce; tce = new TableColumn(0); tce.setHeaderValue("ID"); tce.setMinWidth(0); tce.setPreferredWidth(40); tce.setMaxWidth(40); tcm.addColumn(tce); tce = new TableColumn(1); tce.setHeaderValue("Summary"); tce.setPreferredWidth(250); tcm.addColumn(tce); tce = new TableColumn(8); tce.setHeaderValue("Project"); tce.setCellRenderer(new TableCellRendererProject()); tce.setMinWidth(0); tce.setPreferredWidth(80); tce.setMaxWidth(80); tcm.addColumn(tce); tce = new TableColumn(2); tce.setHeaderValue("Tracker"); tce.setCellRenderer(new TableCellRendererTracker()); tce.setMinWidth(0); tce.setPreferredWidth(80); tce.setMaxWidth(80); tcm.addColumn(tce); tce = new TableColumn(3); tce.setHeaderValue("Priority"); tce.setCellRenderer(new TableCellRendererPriority()); tce.setMinWidth(0); tce.setPreferredWidth(80); tce.setMaxWidth(80); tcm.addColumn(tce); tce = new TableColumn(4); tce.setHeaderValue("Status"); tce.setMinWidth(0); tce.setPreferredWidth(80); tce.setMaxWidth(80); tcm.addColumn(tce); tce = new TableColumn(5); tce.setHeaderValue("Assigned to"); tce.setCellRenderer(new TableCellRendererUser()); tce.setMinWidth(0); tce.setPreferredWidth(80); tce.setMaxWidth(80); tcm.addColumn(tce); tce = new TableColumn(6); tce.setHeaderValue("Category"); tce.setCellRenderer(new TableCellRendererCategory()); tce.setMinWidth(0); tce.setPreferredWidth(80); tce.setMaxWidth(80); tcm.addColumn(tce); tce = new TableColumn(7); tce.setHeaderValue("Version"); tce.setCellRenderer(new TableCellRendererVersion()); tce.setMinWidth(0); tce.setPreferredWidth(80); tce.setMaxWidth(80); tcm.addColumn(tce); issueTable = new JTable(); issueTable.setAutoCreateRowSorter(true); issueTable.setModel(queryListModel); issueTable.setColumnModel(tcm); issueTable.getRowSorter().setSortKeys(Collections.singletonList(new RowSorter.SortKey(0, SortOrder.ASCENDING))); issueTable.getTableHeader().setReorderingAllowed(false); issueTable.doLayout(); issueTable.addMouseListener(issueTableIssueOpener); issueTable.addKeyListener(issueTableIssueOpener); queryPanel = new RedmineQueryPanel(new JScrollPane(issueTable), this); parameters = new LinkedHashMap<>(); // set parameters trackerParameter = registerQueryParameter(ListParameter.class, queryPanel.trackerList, "tracker_id"); categoryParameter = registerQueryParameter(ListParameter.class, queryPanel.categoryList, "category_id"); versionParameter = registerQueryParameter(ListParameter.class, queryPanel.versionList, "fixed_version_id"); statusParameter = registerQueryParameter(ListParameter.class, queryPanel.statusList, "status_id"); priorityParameter = registerQueryParameter(ListParameter.class, queryPanel.priorityList, "priority_id"); assigneeParameter = registerQueryParameter(ListParameter.class, queryPanel.assigneeList, "assigned_to_id"); watcherParameter = registerQueryParameter(ListParameter.class, queryPanel.watcherList, "watcher_id"); projectParameter = registerQueryParameter(ListParameter.class, queryPanel.projectList, "project_id"); project2Parameter = registerQueryParameter(ComboParameter.class, queryPanel.bySaveQueryProjectCB, "project_id", "project_id2"); queryParameter = registerQueryParameter(ComboParameter.class, queryPanel.bySaveQueryQueryCB, "query_id"); registerQueryParameter(TextFieldParameter.class, queryPanel.queryTextField, "query"); registerQueryParameter(CheckBoxParameter.class, queryPanel.qSubjectCheckBox, "is_subject"); registerQueryParameter(CheckBoxParameter.class, queryPanel.qDescriptionCheckBox, "is_description"); query.addPropertyChangeListener(new PropertyChangeListener() { @Override public void propertyChange(PropertyChangeEvent evt) { if ("busy".equals(evt.getPropertyName())) { enableFields(!((boolean) evt.getNewValue())); } } }); setListeners(); postPopulate(); } if (qm == QueryMode.VIEW) { setAsSaved(false); } else { setAsSaved(true); } return queryPanel; } @Override public void opened() { modelToGUI(); } @Override public void closed() { } @Override public boolean saveChanges(String name) { Redmine.LOG.log(Level.FINE, "saving query '{0}'", new Object[]{name}); query.setName(name); repository.saveQuery(query); query.setSaved(true); // XXX setAsSaved(false); if (!query.wasRun()) { Redmine.LOG.log(Level.FINE, "refreshing query '{0}' after save", new Object[]{name}); refresh(); } Redmine.LOG.log(Level.FINE, "query '{0}' saved", new Object[]{name}); return true; } @Override public boolean discardUnsavedChanges() { RedmineConfig.getInstance().reloadQuery(query); modelToGUI(); setAsSaved(false); return true; } @Override public boolean isChanged() { return query.isSaved(); } private final PropertyChangeSupport support = new PropertyChangeSupport(this); @Override public void addPropertyChangeListener(PropertyChangeListener pl) { support.addPropertyChangeListener(pl); } @Override public void removePropertyChangeListener(PropertyChangeListener pl) { support.removePropertyChangeListener(pl); } private class QueryTask implements Runnable, Cancellable, QueryNotifyListener { private RequestProcessor.Task task; private int counter; private boolean autoRefresh; public QueryTask() { query.addNotifyListener(this); } private void startQuery() { if (queryPanel != null) { EventQueue.invokeLater(new Runnable() { @Override public void run() { queryPanel.showSearchingProgress(true, Bundle.MSG_Searching()); } }); } } private synchronized void finnishQuery() { task = null; if (queryPanel != null) { EventQueue.invokeLater(new Runnable() { @Override public void run() { queryPanel.setQueryRunning(false); queryPanel.setLastRefresh(getLastRefresh()); queryPanel.showNoContentPanel(false); } }); } } public void executeQuery() { try(SafeAutoCloseable sac = query.busy()) { startQuery(); query.refresh(autoRefresh); } finally { setQueryRunning(false); // XXX do we need this? its called in finishQuery anyway task = null; } } private void setQueryRunning(final boolean running) { if (queryPanel != null) { EventQueue.invokeLater(new Runnable() { @Override public void run() { queryPanel.setQueryRunning(running); } }); } } @Override public void run() { startQuery(); try { executeQuery(); } finally { finnishQuery(); } } RequestProcessor.Task post(boolean autoRefresh) { if (task != null) { task.cancel(); } task = query.getRepository().getRequestProcessor().create(this); this.autoRefresh = autoRefresh; task.schedule(0); return task; } @Override public boolean cancel() { if (task != null) { task.cancel(); finnishQuery(); } return true; } @Override public void notifyData(RedmineIssue issue) { counter++; if (queryPanel != null) { setIssueCount(counter); if (counter == 1) { EventQueue.invokeLater(new Runnable() { @Override public void run() { queryPanel.showNoContentPanel(false); } }); } } } @Override public void started() { counter = 0; if (queryPanel != null) { setIssueCount(counter); } } @Override public void finished() { queryListModel.setIssues(query.getIssues()); } } private class IssueTableIssueOpener implements MouseListener, KeyListener { @Override public void mouseClicked(MouseEvent e) { int mouseRow = issueTable.rowAtPoint(e.getPoint()); if ((mouseRow != -1) && (!issueTable.isRowSelected(mouseRow))) { issueTable.setRowSelectionInterval(mouseRow, mouseRow); } if (e.getButton() == MouseEvent.BUTTON1 && e.getClickCount() == 2) { int viewRow = issueTable.getSelectedRow(); if (viewRow == -1) { return; } int modelRow = issueTable.convertRowIndexToModel(viewRow); RedmineIssue mi = queryListModel.getIssue(modelRow); Redmine.getInstance().getSupport().openIssue( mi.getRepository(), mi); } else if (e.getButton() == MouseEvent.BUTTON3) { final RedmineIssue issue; int viewRow = issueTable.getSelectedRow(); if (viewRow != -1) { int modelRow = issueTable.convertRowIndexToModel(viewRow); issue = queryListModel.getIssue(modelRow); } else { issue = null; } JPopupMenu menu = new JPopupMenu(); JMenuItem openItem = new JMenuItem(Bundle.MNU_OpenIssue()); openItem.setEnabled(issue != null); openItem.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { Redmine.getInstance().getSupport().openIssue( issue.getRepository(), issue); } }); JMenuItem trackItem = new JMenuItem(Bundle.MNU_OpenIssueForTimeTracking()); trackItem.setEnabled(issue != null); trackItem.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { IssueTimeTrackerTopComponent.getInstance().open(); IssueTimeTrackerTopComponent.getInstance().setIssue(issue); } }); menu.add(openItem); menu.add(trackItem); menu.show(e.getComponent(), e.getX(), e.getY()); } } @Override public void mousePressed(MouseEvent e) { } @Override public void mouseReleased(MouseEvent e) { } @Override public void mouseEntered(MouseEvent e) { } @Override public void mouseExited(MouseEvent e) { } @Override public void keyTyped(KeyEvent e) { } @Override public void keyPressed(KeyEvent e) { if (e.getKeyCode() == KeyEvent.VK_ENTER) { e.consume(); } } @Override public void keyReleased(KeyEvent e) { if (e.getKeyCode() == KeyEvent.VK_ENTER) { int viewRow = issueTable.getSelectedRow(); if (viewRow == -1) { return; } int modelRow = issueTable.convertRowIndexToModel(viewRow); RedmineIssue mi = queryListModel.getIssue(modelRow); Redmine.getInstance().getSupport().openIssue( mi.getRepository(), mi); e.consume(); } } } IssueTableIssueOpener issueTableIssueOpener = new IssueTableIssueOpener(); }