// Copyright (c) 2021, Fr.Terrot. All rights reserved. // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. package io.gitea.mylyn.ui; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import org.apache.commons.lang.StringUtils; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.jface.dialogs.ErrorDialog; import org.eclipse.jface.layout.GridDataFactory; import org.eclipse.jface.layout.GridLayoutFactory; import org.eclipse.jface.operation.IRunnableContext; import org.eclipse.jface.viewers.ArrayContentProvider; import org.eclipse.jface.viewers.CheckboxTableViewer; import org.eclipse.jface.viewers.ISelectionChangedListener; import org.eclipse.jface.viewers.LabelProvider; import org.eclipse.jface.viewers.SelectionChangedEvent; import org.eclipse.jface.wizard.IWizardPage; import org.eclipse.mylyn.commons.core.ICoreRunnable; import org.eclipse.mylyn.commons.net.Policy; import org.eclipse.mylyn.commons.ui.CommonUiUtil; import org.eclipse.mylyn.tasks.core.IRepositoryQuery; import org.eclipse.mylyn.tasks.core.TaskRepository; import org.eclipse.mylyn.tasks.ui.TasksUiImages; import org.eclipse.mylyn.tasks.ui.wizards.AbstractRepositoryQueryPage; import org.eclipse.swt.SWT; import org.eclipse.swt.events.DisposeEvent; import org.eclipse.swt.events.DisposeListener; import org.eclipse.swt.events.ModifyEvent; import org.eclipse.swt.events.ModifyListener; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.events.SelectionListener; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.widgets.Button; import org.eclipse.swt.widgets.Combo; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Group; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Text; import org.eclipse.swt.widgets.ToolBar; import org.eclipse.swt.widgets.ToolItem; import org.eclipse.ui.PlatformUI; import io.gitea.ApiException; import io.gitea.model.GiteaUser; import io.gitea.model.IssueState; import io.gitea.model.Milestone; import io.gitea.mylyn.core.ConnectionManager; import io.gitea.mylyn.core.GiteaConnection; public class GiteaQueryPage extends AbstractRepositoryQueryPage implements IWizardPage { private SelectionListener completeListener = new SelectionAdapter() { public void widgetSelected(SelectionEvent e) { setPageComplete(isPageComplete()); } }; /** * @param pageName * @param taskRepository * @param query */ public GiteaQueryPage(String pageName, TaskRepository taskRepository, IRepositoryQuery query) { super(pageName, taskRepository, query); setDescription("Specify your query"); setPageComplete(false); } // --------------------------------------------------------------------- // DEFINE QUERY TITLE AREA // --------------------------------------------------------------------- private Text titleText; /** * Create Query title area . Query title is 2 columns based (not same size) * * @implNote allocate {@titleText} private Text widget. */ private void createQueryTitleArea(Composite parent) { Composite titleArea = new Composite(parent, SWT.NONE); GridLayoutFactory.fillDefaults().numColumns(2).applyTo(titleArea); GridDataFactory.fillDefaults().grab(true, false).span(2, 1).applyTo(titleArea); new Label(titleArea, SWT.NONE).setText(Labels.QUERY_TITLE); titleText = new Text(titleArea, SWT.SINGLE | SWT.BORDER); GridDataFactory.fillDefaults().grab(true, false).applyTo(titleText); titleText.addModifyListener(new ModifyListener() { public void modifyText(ModifyEvent e) { setPageComplete(isPageComplete()); } }); } // --------------------------------------------------------------------- // FILTER BY ISSUE STATE AREA // --------------------------------------------------------------------- private Button openButton; private Button closedButton; /** * Create issue state filter area. * * @param parent Composite the state filter area is attached to. * @implNote allocate {@openButton} and @{closedButton} private buttons. */ private void createIssueStateArea(Composite parent) { Composite statusArea = new Composite(parent, SWT.NONE); GridLayoutFactory.fillDefaults().numColumns(4).equalWidth(false).applyTo(statusArea); GridDataFactory.fillDefaults().grab(true, false).span(2, 1).applyTo(statusArea); new Label(statusArea, SWT.NONE).setText(Labels.QUERY_STATE); openButton = new Button(statusArea, SWT.CHECK); openButton.setSelection(true); openButton.setText(IssueState.STATE_OPENED.toString()); openButton.addSelectionListener(completeListener); closedButton = new Button(statusArea, SWT.CHECK); closedButton.setSelection(true); closedButton.setText(IssueState.STATE_CLOSED.toString()); closedButton.addSelectionListener(completeListener); createRefreshButton(statusArea); } // --------------------------------------------------------------------- // REFRESH BUTTON // --------------------------------------------------------------------- private void createRefreshButton(Composite parent) { ToolBar toolbar = new ToolBar(parent, SWT.FLAT); ToolItem updateItem = new ToolItem(toolbar, SWT.PUSH); final Image updateImage = TasksUiImages.REPOSITORY_UPDATE_CONFIGURATION.createImage(); toolbar.addDisposeListener(new DisposeListener() { @Override public void widgetDisposed(DisposeEvent e) { updateImage.dispose(); } }); updateItem.setImage(updateImage); updateItem.setToolTipText(Messages.MylynGiteaUIQueryPage_TooltipUpdateRepository); GridDataFactory.fillDefaults().align(SWT.END, SWT.FILL).grab(true, false).applyTo(toolbar); updateItem.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { refreshRepository(); } }); } // --------------------------------------------------------------------- // FILTER BY MILESTONE AREA // --------------------------------------------------------------------- private Combo milestoneCombo; /** * Create milestones selection area. * * @param parent Composite the milestone selection area is attached to. * @implNote Allocate {@literal milestoneCombo} private Combo. */ private void createFilterByMilestoneArea(Composite parent) { Composite milestonesArea = new Composite(parent, SWT.NONE); GridLayoutFactory.fillDefaults().numColumns(4).equalWidth(false).applyTo(milestonesArea); GridDataFactory.fillDefaults().grab(true, false).span(2, 1).applyTo(milestonesArea); Label milestonesLabel = new Label(milestonesArea, SWT.NONE); milestonesLabel.setText(Labels.QUERY_MILESTONE); milestoneCombo = new Combo(milestonesArea, SWT.DROP_DOWN | SWT.READ_ONLY); GridDataFactory.fillDefaults().grab(true, false).applyTo(milestoneCombo); updateMilestones(); } // --------------------------------------------------------------------- // FILTER BY ASSIGNED MEMBER AREA // --------------------------------------------------------------------- private Text assigneeText; private CheckboxTableViewer assignedViewer; private void refreshAssignedText() { if ((assigneeText != null) && (assignedViewer != null)) { List checkedItemsList = new ArrayList(); for (Object o : assignedViewer.getCheckedElements()) { checkedItemsList.add(((String) o)); } assigneeText.setText(StringUtils.join(checkedItemsList, ",")); //$NON-NLS-1$ } } /** * Create filter by assigned member area. * * @param parent Composite the filter by assigned member area is attached to. * @implNote Allocate {@literal assignedViewer} private attribute. */ private void createFilterByAssignedMembersArea(Composite parent) { Group assignedArea = new Group(parent, SWT.NONE); assignedArea.setText(Labels.QUERY_ASSIGNEE); GridDataFactory.fillDefaults().grab(true, true).applyTo(assignedArea); GridLayoutFactory.swtDefaults().applyTo(assignedArea); assigneeText = new Text(assignedArea, SWT.BORDER | SWT.SINGLE); GridDataFactory.fillDefaults().grab(true, false).applyTo(assigneeText); assignedViewer = CheckboxTableViewer.newCheckList(assignedArea, SWT.BORDER | SWT.V_SCROLL | SWT.H_SCROLL); GridDataFactory.fillDefaults().grab(true, true).hint(100, 80).applyTo(assignedViewer.getControl()); assignedViewer.setContentProvider(ArrayContentProvider.getInstance()); assignedViewer.addSelectionChangedListener(new ISelectionChangedListener() { @Override public void selectionChanged(SelectionChangedEvent event) { setPageComplete(isPageComplete()); refreshAssignedText(); } }); Composite buttonsArea = new Composite(assignedArea, SWT.BORDER | SWT.NONE); GridDataFactory.fillDefaults().grab(true, false).applyTo(buttonsArea); GridLayoutFactory.swtDefaults().numColumns(2).equalWidth(true).applyTo(buttonsArea); final Button selectAllButton = new Button(buttonsArea, SWT.BORDER); selectAllButton.setText(Labels.QUERY_SELECT_ALL_BUTTON); GridDataFactory.fillDefaults().grab(true, false).applyTo(selectAllButton); selectAllButton.addSelectionListener(new SelectionListener() { @Override public void widgetSelected(SelectionEvent e) { assignedViewer.setAllChecked(true); refreshAssignedText(); } @Override public void widgetDefaultSelected(SelectionEvent e) { } }); Button selectNoneButton = new Button(buttonsArea, SWT.BORDER); selectNoneButton.setText(Labels.QUERY_SELECT_NONE_BUTTON); GridDataFactory.fillDefaults().grab(true, false).applyTo(selectNoneButton); selectNoneButton.addSelectionListener(new SelectionListener() { @Override public void widgetSelected(SelectionEvent e) { assignedViewer.setAllChecked(false); refreshAssignedText(); } @Override public void widgetDefaultSelected(SelectionEvent e) { } }); try { updateAssigned(); } catch (ApiException e) { // TODO Auto-generated catch block e.printStackTrace(); } } // --------------------------------------------------------------------- // FILTER BY LABELS AREA // --------------------------------------------------------------------- // private TableViewer labelsViewer; private Text labelsText; private CheckboxTableViewer labelsViewer; // not issue type, not priority private void refreshLabelsText() { if ((labelsText != null) && (labelsViewer != null)) { List checkedItemsList = new ArrayList(); for (Object o : labelsViewer.getCheckedElements()) { checkedItemsList.add(((String) o)); } labelsText.setText(StringUtils.join(checkedItemsList, ",")); //$NON-NLS-1$ } } // --private CheckboxTableViewer prioritiesViewer; // 'priority' labels // --private CheckboxTableViewer issueTypesViewer;// 'issue' type labels private void createFilterByLabelsArea(Composite parent) { // Group labelsArea = new Group(parent, SWT.NONE); // labelsArea.setText(Labels.QUERY_GROUP_LABELS); // GridDataFactory.fillDefaults().grab(true, true).applyTo(labelsArea); // GridLayoutFactory.swtDefaults().applyTo(labelsArea); /** * -- Group issuesArea = new Group(parent, SWT.NONE); * issuesArea.setText(Labels.QUERY_GROUP_LABELS_ISSUE_TYPES); * GridDataFactory.fillDefaults().grab(true, true).applyTo(issuesArea); * GridLayoutFactory.swtDefaults().applyTo(issuesArea); * * Group prioritiesArea = new Group(parent, SWT.NONE); * prioritiesArea.setText(Labels.QUERY_GROUP_LABELS_PRIORITIES); * GridDataFactory.fillDefaults().grab(true, true).applyTo(prioritiesArea); * GridLayoutFactory.swtDefaults().applyTo(prioritiesArea); -- */ Group labelsArea = new Group(parent, SWT.READ_ONLY); labelsArea.setText(Labels.QUERY_GROUP_LABELS); GridDataFactory.fillDefaults().grab(true, true).applyTo(labelsArea); GridLayoutFactory.swtDefaults().applyTo(labelsArea); labelsText = new Text(labelsArea, SWT.BORDER); GridDataFactory.fillDefaults().grab(true, false).applyTo(labelsText); labelsViewer = CheckboxTableViewer.newCheckList(labelsArea, SWT.BORDER | SWT.V_SCROLL | SWT.H_SCROLL); GridDataFactory.fillDefaults().grab(true, true).hint(100, 80).applyTo(labelsViewer.getControl()); labelsViewer.setContentProvider(ArrayContentProvider.getInstance()); labelsViewer.setLabelProvider(new LabelProvider() { public Image getImage(Object element) { return GiteaImages.LABEL; } }); labelsViewer.addSelectionChangedListener(new ISelectionChangedListener() { @Override public void selectionChanged(SelectionChangedEvent event) { setPageComplete(isPageComplete()); refreshLabelsText(); } }); /* * -- TODO: filter labels by priority prioritiesViewer = * CheckboxTableViewer.newCheckList(prioritiesArea, SWT.BORDER | SWT.V_SCROLL | * SWT.H_SCROLL); * * GridDataFactory.fillDefaults().grab(true, true).hint(100, * 80).applyTo(prioritiesViewer.getControl()); * prioritiesViewer.setContentProvider(ArrayContentProvider.getInstance()); * prioritiesViewer.setLabelProvider(new LabelProvider() { public Image * getImage(Object element) { return GiteaImages.LABEL; } }); * prioritiesViewer.addSelectionChangedListener(new ISelectionChangedListener() * { * * @Override public void selectionChanged(SelectionChangedEvent event) { * setPageComplete(isPageComplete()); } }); -- */ /* * -- TODO: filter labels by issue type issueTypesViewer = * CheckboxTableViewer.newCheckList(issuesArea, SWT.BORDER | SWT.V_SCROLL | * SWT.H_SCROLL); * * GridDataFactory.fillDefaults().grab(true, true).hint(100, * 80).applyTo(issueTypesViewer.getControl()); * issueTypesViewer.setContentProvider(ArrayContentProvider.getInstance()); * issueTypesViewer.setLabelProvider(new LabelProvider() { public Image * getImage(Object element) { return GiteaImages.LABEL; } }); * issueTypesViewer.addSelectionChangedListener(new ISelectionChangedListener() * { * * @Override public void selectionChanged(SelectionChangedEvent event) { * setPageComplete(isPageComplete()); } }); -- */ Composite buttonsArea = new Composite(labelsArea, SWT.BORDER | SWT.NONE); GridDataFactory.fillDefaults().grab(true, false).applyTo(buttonsArea); GridLayoutFactory.swtDefaults().numColumns(2).equalWidth(true).applyTo(buttonsArea); final Button selectAllButton = new Button(buttonsArea, SWT.BORDER); selectAllButton.setText(Labels.QUERY_SELECT_ALL_BUTTON); GridDataFactory.fillDefaults().grab(true, false).applyTo(selectAllButton); selectAllButton.addSelectionListener(new SelectionListener() { @Override public void widgetSelected(SelectionEvent e) { labelsViewer.setAllChecked(true); refreshLabelsText(); } @Override public void widgetDefaultSelected(SelectionEvent e) { } }); Button selectNoneButton = new Button(buttonsArea, SWT.BORDER); selectNoneButton.setText(Labels.QUERY_SELECT_NONE_BUTTON); GridDataFactory.fillDefaults().grab(true, false).applyTo(selectNoneButton); selectNoneButton.addSelectionListener(new SelectionListener() { @Override public void widgetSelected(SelectionEvent e) { labelsViewer.setAllChecked(false); refreshLabelsText(); } @Override public void widgetDefaultSelected(SelectionEvent e) { } }); /* * newLabel.addFocusListener(new FocusListener() { * * @Override public void focusLost(FocusEvent e) { * getShell().setDefaultButton(null); setPageComplete(isPageComplete()); } * * @Override public void focusGained(FocusEvent e) { * getShell().setDefaultButton(btnAdd); } }); * labelsViewer.getTable().addKeyListener(new KeyListener() { * * @Override public void keyReleased(KeyEvent e) { if (e.keyCode == SWT.DEL) { * removeSelection(); } } * * @Override public void keyPressed(KeyEvent e) { } }); */ updateLabels(); } private void createLeftColumnArea(Composite parent) { Composite leftArea = new Composite(parent, SWT.NONE); GridLayoutFactory.fillDefaults().numColumns(2).applyTo(leftArea); GridDataFactory.fillDefaults().grab(true, true).applyTo(leftArea); createIssueStateArea(leftArea); createFilterByAssignedMembersArea(leftArea); } private void createRightColumnArea(Composite parent) { Composite rightArea = new Composite(parent, SWT.NONE); GridLayoutFactory.fillDefaults().numColumns(2).applyTo(rightArea); GridDataFactory.fillDefaults().grab(true, true).applyTo(rightArea); createFilterByMilestoneArea(rightArea); createFilterByLabelsArea(rightArea); } /** * Create Composite displayArea (2 columns based). * |---------------------------------------| | * | | | * | |---------------------------------------| */ public void createControl(Composite parent) { Composite displayArea = new Composite(parent, SWT.NONE); GridLayoutFactory.fillDefaults().numColumns(2).equalWidth(true).applyTo(displayArea); GridDataFactory.fillDefaults().grab(true, true).applyTo(displayArea); if (!inSearchContainer()) { createQueryTitleArea(displayArea); } createLeftColumnArea(displayArea); createRightColumnArea(displayArea); initialize(); setControl(displayArea); } private void initialize() { IRepositoryQuery query = getQuery(); if (query == null) { return; } titleText.setText(query.getSummary()); // assigneeText.setText(query.getAttribute("assignee")); milestoneCombo.setText(query.getAttribute("milestone")); openButton.setSelection(Boolean.parseBoolean(query.getAttribute("opened"))); closedButton.setSelection(Boolean.parseBoolean(query.getAttribute("closed"))); try { updateAssigned(); } catch (ApiException e) { // TODO Auto-generated catch block e.printStackTrace(); } updateLabels(); /* * for (String label : query.getAttribute("labels").split(",")) { if * (label.trim().length() > 0) { labelsViewer.add(label.trim()); } } */ } public boolean isPageComplete() { boolean complete = inSearchContainer() ? true : super.isPageComplete(); if (complete) { String message = null; if (!openButton.getSelection() && !closedButton.getSelection()) { message = "Select either closed, opened or both issue states"; } setErrorMessage(message); complete = message == null; } return complete; } public String getQueryTitle() { return titleText != null ? titleText.getText() : null; } public void applyTo(IRepositoryQuery query) { query.setSummary(titleText.getText()); query.setAttribute("assignee", labelsText.getText()); query.setAttribute("milestone", milestoneCombo.getText()); query.setAttribute("opened", "" + openButton.getSelection()); query.setAttribute("closed", "" + closedButton.getSelection()); ArrayList labels = new ArrayList(); /* * TODO: filter labels by issue type or priorities {{{ for (Object o : * issueTypesViewer.getCheckedElements()) { labels.add(((String)o)); } for * (Object o : prioritiesViewer.getCheckedElements()) { labels.add(((String)o)); * } }}} */ for (Object o : labelsViewer.getCheckedElements()) { labels.add(((String) o)); } query.setAttribute("labels", StringUtils.join(labels, ",")); } private boolean updateLabels() { if (labelsViewer == null) return false; if (labelsViewer.getControl().isDisposed()) return false; boolean hasLabels = false; GiteaConnection connection = ConnectionManager.getSafe(getTaskRepository()); if (connection != null) { List labels = connection.getLabels(); // Collections.sort(labels, Comparator.comparing( // io.gitea.model.Label::getName, String.CASE_INSENSITIVE_ORDER)); hasLabels = !labels.isEmpty(); List allLabelsNames = new ArrayList(); labels.forEach((label) -> allLabelsNames.add(label.getName())); labelsViewer.setInput(allLabelsNames); /* * TODO: filter labels by issue types and priorities {{{ List * issueTypesNames = IssueType.getAllIssueType(allLabelsNames); List * prioritiesNames = GiteaPriorityLevel.getAllPriorityLabels(allLabelsNames); * List otherLabelsNames = new ArrayList(); * allLabelsNames.forEach((label) -> { if ((!prioritiesNames.contains(label)) * &&(!issueTypesNames.contains(label))) otherLabelsNames.add(label); }); * * labelsViewer.setInput(otherLabelsNames); * prioritiesViewer.setInput(prioritiesNames); * issueTypesViewer.setInput(issueTypesNames); }}} */ // When coming from existing query, update the dialog to reflect query ... IRepositoryQuery query = getQuery(); if (query != null) { List labelsInQuery = new ArrayList(); Arrays.asList(query.getAttribute("labels").split(",")).forEach((label) -> { if (label.trim().length() > 0) labelsInQuery.add(label.trim()); }); List labelsToCheck = new ArrayList(); /* * TODO: filter labels by issue types and priority {{{ List * priorityToCheck = new ArrayList(); List issueTypeToCheck = * new ArrayList(); }}} */ List labelToGreyed = new ArrayList(); labelsInQuery.forEach((label) -> { if (allLabelsNames.contains(label)) labelsToCheck.add(label); /* * TODO: filter labels by issue types or priorities {{{ if * (prioritiesNames.contains(label)) priorityToCheck.add(label); else if * (otherLabelsNames.contains(label)) labelsToCheck.add(label); else * labelToGreyed.add(label); }}} */ }); labelsViewer.setCheckedElements(labelsToCheck.toArray()); refreshLabelsText(); /* * TODO: filter labels by issue types or priorities {{{ * prioritiesViewer.setCheckedElements(labelsToCheck.toArray()); }}} */ // labelsViewer.setGrayedElements(labelsToGreyed.toArray()); } } return hasLabels; } private boolean updateMilestones() { if (milestoneCombo.isDisposed()) return false; boolean hasMilestones = false; GiteaConnection connection = ConnectionManager.getSafe(getTaskRepository()); if (connection != null) { List milestones = connection.getMilestones(); hasMilestones = !milestones.isEmpty(); milestoneCombo.removeAll(); // Collections.sort(milestones, Comparator.comparing( // Milestone::getTitle, String.CASE_INSENSITIVE_ORDER)); milestoneCombo.add(""); for (Milestone s : milestones) { milestoneCombo.add(s.getTitle()); } milestoneCombo.select(0); } return hasMilestones; } private boolean updateAssigned() throws ApiException { boolean hasAssigned = false; if (labelsViewer == null) return false; if (labelsViewer.getControl().isDisposed()) return false; GiteaConnection connection = ConnectionManager.getSafe(getTaskRepository()); if (connection != null) { IRepositoryQuery query = getQuery(); List usersNames = new ArrayList(); connection.repoListAllCollaborators().forEach((user) -> usersNames.add(GiteaUser.getName(user))); List assignedInQuery = new ArrayList(); Arrays.asList(query.getAttribute("assignee").split(",")).forEach((assignee) -> { if (assignee.trim().length() > 0) assignedInQuery.add(assignee.trim()); }); List assignedList = new ArrayList(); usersNames.forEach((assignee) -> { if (assignedInQuery.contains(assignee)) assignedList.add(assignee); }); assignedViewer.setInput(usersNames); assignedViewer.setCheckedElements(assignedList.toArray()); } return hasAssigned; } private void refreshRepository() { try { ICoreRunnable runnable = new ICoreRunnable() { @Override public void run(IProgressMonitor monitor) throws CoreException { Policy.monitorFor(monitor); monitor.beginTask("", 3); //$NON-NLS-1$ GiteaConnection connection = ConnectionManager.getSafe(getTaskRepository()); monitor.setTaskName(Messages.MylynGiteaUIQueryPage_LoadingLabels); connection.refreshLabels(); monitor.worked(1); monitor.setTaskName(Messages.MylynGiteaUIQueryPage_LoadingMembers); connection.refreshMembers(); monitor.worked(1); monitor.setTaskName(Messages.MylynGiteaUIQueryPage_LoadingMilestones); connection.refreshMilestones(); monitor.done(); PlatformUI.getWorkbench().getDisplay().asyncExec(new Runnable() { @Override public void run() { updateLabels(); updateMilestones(); initialize(); } }); } }; IRunnableContext context = getContainer(); if (context == null) if (inSearchContainer()) context = getSearchContainer().getRunnableContext(); else context = PlatformUI.getWorkbench().getProgressService(); CommonUiUtil.run(context, runnable); } catch (CoreException e) { IStatus status = e.getStatus(); ErrorDialog.openError(getShell(), Messages.MylynGiteaUIQueryPage_ErrorLoading, e.getLocalizedMessage(), status); } } }