/* * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. * * For information about the authors of this project Have a look * at the AUTHORS file in the root of this project. */ package net.sourceforge.fullsync.ui; import java.util.HashMap; import java.util.Map; import net.sourceforge.fullsync.Action; import net.sourceforge.fullsync.ActionType; import net.sourceforge.fullsync.ExceptionHandler; import net.sourceforge.fullsync.Location; import net.sourceforge.fullsync.Profile; import net.sourceforge.fullsync.Task; import net.sourceforge.fullsync.TaskTree; import net.sourceforge.fullsync.fs.File; import org.eclipse.swt.SWT; import org.eclipse.swt.events.MouseAdapter; import org.eclipse.swt.events.MouseEvent; import org.eclipse.swt.graphics.GC; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.graphics.ImageData; import org.eclipse.swt.graphics.PaletteData; import org.eclipse.swt.graphics.RGB; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Listener; import org.eclipse.swt.widgets.Menu; import org.eclipse.swt.widgets.MenuItem; import org.eclipse.swt.widgets.Table; import org.eclipse.swt.widgets.TableColumn; import org.eclipse.swt.widgets.TableItem; public class TaskDecisionList extends Composite { private TableColumn tableColumnExplanation; private TableColumn tableColumnFilename; private TableColumn tableColumnAction; private TableColumn tableColumnSourceSize; private Table tableLogLines; private int tableLogLinesFillIndex; private int tableLogLinesFillCount; private final Map<ActionType, Image> actionImages; private final Map<Integer, Image> taskImages; private Image locationSource; private Image locationDestination; private Image locationBoth; private Image nodeFile; private Image nodeDirectory; private Image nodeUndefined; private TaskTree taskTree; private final Map<Task, TableItem> taskItemMap; private boolean onlyChanges; private boolean changeAllowed; public TaskDecisionList(Composite parent, int style) { super(parent, style); taskItemMap = new HashMap<Task, TableItem>(); actionImages = new HashMap<ActionType, Image>(); taskImages = new HashMap<Integer, Image>(); try { this.setSize(550, 500); tableLogLines = new Table(this, SWT.FULL_SELECTION | SWT.MULTI | SWT.BORDER); GridData tableLogLinesLData = new GridData(); tableLogLinesLData.verticalAlignment = SWT.FILL; tableLogLinesLData.horizontalAlignment = SWT.FILL; tableLogLinesLData.horizontalIndent = 0; tableLogLinesLData.horizontalSpan = 3; tableLogLinesLData.grabExcessHorizontalSpace = true; tableLogLinesLData.grabExcessVerticalSpace = true; tableLogLines.setLayoutData(tableLogLinesLData); tableLogLines.setHeaderVisible(true); tableLogLines.setLinesVisible(true); tableColumnFilename = new TableColumn(tableLogLines, SWT.NONE); tableColumnFilename.setText(Messages.getString("TaskDecisionList.Filename")); //$NON-NLS-1$ tableColumnFilename.setWidth(240); tableColumnSourceSize = new TableColumn(tableLogLines, SWT.NONE); tableColumnSourceSize.setText(Messages.getString("TaskDecisionList.Size")); //$NON-NLS-1$ tableColumnSourceSize.setAlignment(SWT.RIGHT); tableColumnSourceSize.setWidth(90); tableColumnAction = new TableColumn(tableLogLines, SWT.NONE); tableColumnAction.setResizable(false); tableColumnAction.setText(Messages.getString("TaskDecisionList.Action")); //$NON-NLS-1$ tableColumnAction.setWidth(70); tableColumnExplanation = new TableColumn(tableLogLines, SWT.NONE); tableColumnExplanation.setText(Messages.getString("TaskDecisionList.Explanation")); //$NON-NLS-1$ tableColumnExplanation.setWidth(170); tableLogLines.addMouseListener(new MouseAdapter() { @Override public void mouseUp(MouseEvent evt) { tableLogLinesMouseUp(evt); } }); GridLayout thisLayout = new GridLayout(); this.setLayout(thisLayout); this.layout(); } catch (Exception e) { ExceptionHandler.reportException(e); } initializeImages(); onlyChanges = true; changeAllowed = true; } public static void show(final GuiController guiController, final Profile profile, final TaskTree task, final boolean interactive) { final Display display = Display.getDefault(); display.asyncExec(() -> { try { final TaskDecisionPage dialog = new TaskDecisionPage(guiController.getMainShell(), guiController, task); if (!interactive) { dialog.addWizardDialogListener(() -> dialog.performActions()); } dialog.show(); } catch (Exception ex) { ExceptionHandler.reportException(ex); } }); } public void setTaskTree(TaskTree task) { this.taskTree = task; } public void initializeImages() { GuiController gui = GuiController.getInstance(); nodeFile = gui.getImage("Node_File.png"); //$NON-NLS-1$ nodeDirectory = gui.getImage("Node_Directory.png"); //$NON-NLS-1$ nodeUndefined = gui.getImage("Node_Undefined.png"); //$NON-NLS-1$ locationSource = gui.getImage("Location_Source.png"); //$NON-NLS-1$ locationDestination = gui.getImage("Location_Destination.png"); //$NON-NLS-1$ locationBoth = gui.getImage("Location_Both.png"); //$NON-NLS-1$ for (ActionType action : ActionType.values()) { actionImages.put(action, gui.getImage("Action_" + action.name() + ".png")); //$NON-NLS-1$ //$NON-NLS-2$ } } @Override public void dispose() { nodeFile.dispose(); nodeDirectory.dispose(); nodeUndefined.dispose(); locationSource.dispose(); locationDestination.dispose(); locationBoth.dispose(); for (Image i : actionImages.values()) { i.dispose(); } for (Image i : taskImages.values()) { i.dispose(); } super.dispose(); } protected void drawSide(GC g, Task t, Action a, Location location) { File n; if (t == null) { n = null; } else if (location == Location.Source) { n = t.getSource(); } else { n = t.getDestination(); } int x = location == Location.Source ? 2 : (2 * 16) + 2; if (n == null) { g.drawImage(nodeUndefined, x, 0); } else if (n.exists()) { if (n.isDirectory()) { g.drawImage(nodeDirectory, x, 0); } else { g.drawImage(nodeFile, x, 0); } } // TODO draw some not-existing image ? if ((a.getLocation() == location) || (a.getLocation() == Location.Both)) { Image actionImage = actionImages.get(a.getType()); if (actionImage != null) { g.drawImage(actionImage, x, 0); } if (location == Location.Source) { g.drawImage(locationSource, x + 16, 0); } else { g.drawImage(locationDestination, x - 16, 0); } } } protected void drawLocation(GC g, Action a) { switch (a.getLocation()) { case Source: g.drawImage(locationSource, 16 + 2, 0); break; case Destination: g.drawImage(locationDestination, 16 + 2, 0); break; case Both: g.drawImage(locationBoth, 16 + 2, 0); break; case Buffer: break; case None: break; } } protected Integer calcTaskImageHash(Task t, Action a) { int hash = 0; // using 5 bits for files if (t == null) { hash |= 1; } else { File src = t.getSource(); File dst = t.getDestination(); if (src.exists()) { hash |= 2; if (src.isDirectory()) { hash |= 4; } } if (dst.exists()) { hash |= 8; if (dst.isDirectory()) { hash |= 16; } } } // using 2+ bits for action hash |= a.getLocation().ordinal() << 6; hash |= a.getType().ordinal() << 8; return Integer.valueOf(hash); } protected Image buildTaskImage(Task t, Action a) { ImageData data = new ImageData((16 * 3) + 2, 16, 8, new PaletteData(255, 255, 255)); data.transparentPixel = data.palette.getPixel(new RGB(0, 0, 0)); Image image = new Image(null, data); GC g = new GC(image); drawSide(g, t, a, Location.Source); drawSide(g, t, a, Location.Destination); drawLocation(g, a); g.dispose(); return image; } protected Image getTaskImage(Task t, Action a) { Image image; Integer key = calcTaskImageHash(t, a); image = taskImages.get(key); if (image == null) { image = buildTaskImage(t, a); taskImages.put(key, image); } return image; } protected Image getTaskImage(Action a) { return getTaskImage(null, a); } protected Image getTaskImage(Task t) { return getTaskImage(t, t.getCurrentAction()); } protected void addTaskChildren(Task task) { for (Task t : task.getChildren()) { addTask(t); } } protected void addTask(Task t) { if (!onlyChanges || (t.getCurrentAction().getType() != ActionType.Nothing)) { Image image = getTaskImage(t); TableItem item; if (tableLogLinesFillIndex < tableLogLinesFillCount) { item = tableLogLines.getItem(tableLogLinesFillIndex); } else { item = new TableItem(tableLogLines, SWT.NULL); ++tableLogLinesFillCount; } ++tableLogLinesFillIndex; item.setImage(2, image); item.setText(new String[] { t.getSource().getPath(), formatSize(t), "", //$NON-NLS-1$ t.getCurrentAction().getExplanation() }); item.setData(t); taskItemMap.put(t, item); } addTaskChildren(t); } private String formatSize(final Task t) { long size = -1; final Action action = t.getCurrentAction(); if ((action.getType() == ActionType.Add) || (action.getType() == ActionType.Update)) { switch (action.getLocation()) { case Source: size = t.getDestination().getSize(); break; case Destination: size = t.getSource().getSize(); break; default: break; } } return UISettings.formatSize(size); } protected void updateTask(TableItem item) { Task t = (Task) item.getData(); Image image = getTaskImage(t); item.setImage(2, image); item.setText(3, t.getCurrentAction().getExplanation()); } public void rebuildActionList() { tableLogLinesFillIndex = 0; tableLogLinesFillCount = tableLogLines.getItemCount(); setRedraw(false); addTaskChildren(taskTree.getRoot()); setRedraw(true); // index is always pointing at the next free slot if (tableLogLinesFillIndex < tableLogLinesFillCount) { tableLogLines.setItemCount(tableLogLinesFillIndex); tableLogLinesFillCount = tableLogLines.getItemCount(); } } protected void showPopup(int x, int y) { final TableItem[] tableItemList = tableLogLines.getSelection(); // TODO impl some kind of ActionList supporting "containsAction" // and "indexOfAction" using own comparison rules if (tableItemList.length == 0) { return; } Listener selListener = e -> { Action targetAction = (Action) e.widget.getData(); for (TableItem item : tableItemList) { Task task = (Task) item.getData(); Action[] actions = task.getActions(); for (int iAction = 0; iAction < actions.length; iAction++) { Action a = actions[iAction]; if ((a.getType() == targetAction.getType()) && (a.getLocation() == targetAction.getLocation()) && a.getExplanation().equals(targetAction.getExplanation())) { task.setCurrentAction(iAction); break; } } updateTask(item); } }; Task[] taskList = new Task[tableItemList.length]; for (int i = 0; i < tableItemList.length; i++) { taskList[i] = (Task) tableItemList[i].getData(); } Menu m = new Menu(this); MenuItem mi; // load initial actions of first task Action[] possibleActions = taskList[0].getActions().clone(); for (int iTask = 1; iTask < taskList.length; iTask++) { // invalidate all possible actions we dont find in this actionlist Action[] actions = taskList[iTask].getActions(); for (int iPosAction = 0; iPosAction < possibleActions.length; iPosAction++) { Action action = possibleActions[iPosAction]; boolean found = false; if (action == null) { continue; } // check whether action is also supported by this task for (Action a : actions) { if ((a.getType() == action.getType()) && (a.getLocation() == action.getLocation()) && a.getExplanation().equals(action.getExplanation())) { // the action exists found = true; break; } } if (!found) { // invalidate action that is not supported by all selected tasks possibleActions[iPosAction] = null; } } } Task referenceTask = taskList.length == 1 ? taskList[0] : null; for (Action action : possibleActions) { if (action == null) { continue; } Image image = getTaskImage(referenceTask, action); mi = new MenuItem(m, SWT.NULL); mi.setImage(image); mi.setText(action.getType().toString() + " - " + action.getExplanation()); //$NON-NLS-1$ mi.setData(action); mi.addListener(SWT.Selection, selListener); } m.setLocation(tableLogLines.toDisplay(x, y)); m.setVisible(true); } public void setOnlyChanges(boolean onlyChanges) { this.onlyChanges = onlyChanges; } public boolean isChangeAllowed() { return changeAllowed; } public void setChangeAllowed(boolean changeAllowed) { this.changeAllowed = changeAllowed; } public void showItem(TableItem item) { tableLogLines.showItem(item); } protected void tableLogLinesMouseUp(MouseEvent evt) { if (changeAllowed && (evt.button == 3)) { showPopup(evt.x, evt.y); } } public TableItem getTableItemForTask(Task task) { return taskItemMap.get(task); } }