package sk.stuba.fiit.perconik.core.ui.preferences;
import java.lang.annotation.Annotation;
import java.util.Comparator;
import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.dialogs.MessageDialogWithToggle;
import org.eclipse.jface.layout.TableColumnLayout;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.jface.viewers.CheckStateChangedEvent;
import org.eclipse.jface.viewers.CheckboxTableViewer;
import org.eclipse.jface.viewers.ICheckStateListener;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.ITableLabelProvider;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.window.Window;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Table;
import sk.stuba.fiit.perconik.core.annotations.Version;
import sk.stuba.fiit.perconik.core.persistence.AnnotableRegistration;
import sk.stuba.fiit.perconik.core.persistence.MarkableRegistration;
import sk.stuba.fiit.perconik.core.persistence.RegistrationMarker;
import sk.stuba.fiit.perconik.core.ui.plugin.Activator;
import sk.stuba.fiit.perconik.eclipse.jface.viewers.CollectionContentProvider;
import sk.stuba.fiit.perconik.eclipse.jface.viewers.RegularTableViewer;
import sk.stuba.fiit.perconik.eclipse.jface.viewers.SortingViewerComparator;
import sk.stuba.fiit.perconik.eclipse.swt.widgets.SetTableSorter;
import sk.stuba.fiit.perconik.eclipse.swt.widgets.TableSorter;
import sk.stuba.fiit.perconik.eclipse.swt.widgets.WidgetListener;
import sk.stuba.fiit.perconik.ui.Buttons;
import sk.stuba.fiit.perconik.ui.Labels;
import sk.stuba.fiit.perconik.ui.Tables;
import sk.stuba.fiit.perconik.ui.preferences.AbstractWorkbenchPreferencePage;
import sk.stuba.fiit.perconik.utilities.reflect.annotation.Annotations;
import static java.lang.String.format;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.collect.Lists.newArrayList;
import static com.google.common.collect.Sets.newLinkedHashSet;
import static org.eclipse.jface.dialogs.IDialogConstants.CANCEL_LABEL;
import static org.eclipse.jface.dialogs.IDialogConstants.PROCEED_LABEL;
import static org.eclipse.jface.dialogs.MessageDialog.openError;
import static org.eclipse.jface.dialogs.MessageDialog.openInformation;
import static sk.stuba.fiit.perconik.core.plugin.Activator.loadedServices;
import static sk.stuba.fiit.perconik.utilities.MoreStrings.toUpperCaseFirst;
/**
* TODO
*
* @author Pavol Zbell
* @since 1.0
*/
abstract class AbstractPreferencePage<P, R extends AnnotableRegistration & MarkableRegistration & RegistrationMarker<R>> extends AbstractWorkbenchPreferencePage {
private P preferences;
Set<R> registrations;
AtomicBoolean restoreOptions;
CheckboxTableViewer tableViewer;
AbstractOptionsDialog<P, R> optionsDialog;
Button addButton;
Button removeButton;
Button registerButton;
Button unregisterButton;
Button importButton;
Button exportButton;
Button refreshButton;
Button optionsButton;
Button notesButton;
AbstractPreferencePage() {
this.preferences = null;
this.registrations = null;
this.restoreOptions = new AtomicBoolean(false);
}
abstract String name();
private static String pluralize(final String s) {
return s + "s";
}
abstract Class<R> type();
final R cast(final Object o) {
return this.type().cast(o);
}
@Override
public final void createControl(final Composite parent) {
super.createControl(parent);
this.performRefresh();
}
@Override
protected final Control createContent(final Composite parent) {
Composite composite = new Composite(parent, SWT.NONE);
GridLayout parentLayout = new GridLayout();
parentLayout.numColumns = 2;
parentLayout.marginHeight = 0;
parentLayout.marginWidth = 0;
composite.setLayout(parentLayout);
composite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
Composite innerParent = new Composite(composite, SWT.NONE);
GridLayout innerLayout = new GridLayout();
innerLayout.numColumns = 2;
innerLayout.marginHeight = 0;
innerLayout.marginWidth = 0;
innerParent.setLayout(innerLayout);
GridData innerGrid = new GridData(GridData.FILL_BOTH);
innerGrid.horizontalSpan = 2;
innerParent.setLayoutData(innerGrid);
Composite tableComposite = new Composite(innerParent, SWT.NONE);
TableColumnLayout tableLayout = new TableColumnLayout();
GridData tableGrid = new GridData(GridData.FILL_BOTH);
tableGrid.widthHint = 720;
tableGrid.heightHint = this.convertHeightInCharsToPixels(10);
tableComposite.setLayout(tableLayout);
tableComposite.setLayoutData(tableGrid);
Table table = Tables.create(tableComposite, SWT.CHECK | SWT.BORDER | SWT.MULTI | SWT.FULL_SELECTION | SWT.H_SCROLL | SWT.V_SCROLL);
GC gc = new GC(this.getShell());
gc.setFont(JFaceResources.getDialogFont());
this.createTableColumns(table, tableLayout, gc);
gc.dispose();
this.tableViewer = new RegularTableViewer(table);
this.tableViewer.setContentProvider(new CollectionContentProvider());
this.tableViewer.setLabelProvider(this.createContentProvider());
this.tableViewer.addSelectionChangedListener(new ISelectionChangedListener() {
public void selectionChanged(final SelectionChangedEvent e) {
updateButtons();
}
});
this.tableViewer.addCheckStateListener(new ICheckStateListener() {
public void checkStateChanged(final CheckStateChangedEvent e) {
@SuppressWarnings("unchecked")
R data = (R) e.getElement();
if (data.isProvided()) {
updateData(data, e.getChecked());
updateButtons();
} else {
e.getCheckable().setChecked(data, data.hasRegistredMark());
}
}
});
Composite buttons = new Composite(innerParent, SWT.NONE);
buttons.setLayoutData(new GridData(GridData.VERTICAL_ALIGN_BEGINNING));
parentLayout = new GridLayout();
parentLayout.marginHeight = 0;
parentLayout.marginWidth = 0;
buttons.setLayout(parentLayout);
this.addButton = Buttons.createCentering(buttons, "Add", new WidgetListener() {
public void handleEvent(final Event e) {
performAdd();
}
});
this.removeButton = Buttons.createCentering(buttons, "Remove", new WidgetListener() {
public void handleEvent(final Event e) {
performRemove();
}
});
Labels.createButtonSeparator(buttons);
this.registerButton = Buttons.createCentering(buttons, "Register", new WidgetListener() {
public void handleEvent(final Event e) {
performRegister();
}
});
this.unregisterButton = Buttons.createCentering(buttons, "Unregister", new WidgetListener() {
public void handleEvent(final Event e) {
performUnregister();
}
});
Labels.createButtonSeparator(buttons);
this.importButton = Buttons.createCentering(buttons, "Import", new WidgetListener() {
public void handleEvent(final Event e) {
performImport();
}
});
this.exportButton = Buttons.createCentering(buttons, "Export", new WidgetListener() {
public void handleEvent(final Event e) {
performExport();
}
});
Labels.createButtonSeparator(buttons);
this.refreshButton = Buttons.createCentering(buttons, "Refresh", new WidgetListener() {
public void handleEvent(final Event e) {
performRefresh();
}
});
this.optionsButton = Buttons.createCentering(buttons, "Options", new WidgetListener() {
public void handleEvent(final Event e) {
performOptions();
}
});
this.notesButton = Buttons.createCentering(buttons, "Notes", new WidgetListener() {
public void handleEvent(final Event e) {
performNotes();
}
});
this.optionsDialog = this.createOptionsDialog();
Dialog.applyDialogFont(composite);
innerParent.layout();
return composite;
}
protected abstract AbstractLabelProvider<R> createContentProvider();
protected abstract AbstractOptionsDialog<P, R> createOptionsDialog();
protected abstract SortingViewerComparator createViewerComparator();
protected abstract void createTableColumns(final Table table, final TableColumnLayout layout, final GC gc);
final Set<R> checkedData() {
return Sets.filter(this.registrations, new Predicate<R>() {
public boolean apply(@Nonnull final R registration) {
return registration.hasRegistredMark();
}
});
}
final Set<R> unknownData() {
return Sets.filter(this.registrations, new Predicate<R>() {
public boolean apply(@Nonnull final R registration) {
return !registration.isProvided();
}
});
}
final void updateData(final R registration, final boolean status) {
List<R> registrations = newArrayList(this.registrations);
this.updateData(registrations, registration, status);
this.registrations = newLinkedHashSet(registrations);
this.tableViewer.refresh();
}
final void updateSelectedData(final boolean status) {
IStructuredSelection selection = (IStructuredSelection) this.tableViewer.getSelection();
List<R> registrations = newArrayList(this.registrations);
for (Object item: selection.toList()) {
R registration = this.cast(item);
if (registration.isProvided()) {
this.updateData(registrations, registration, status);
}
}
this.registrations = newLinkedHashSet(registrations);
this.tableViewer.refresh();
}
private void updateData(final List<R> registrations, final R registration, final boolean status) {
registrations.set(registrations.indexOf(registration), registration.markRegistered(status));
this.tableViewer.setChecked(registration, status);
}
final void updatePage() {
this.updateMessage();
this.updateTable();
this.sortTable();
this.updateButtons();
}
final void updateMessage() {
if (loadedServices()) {
this.setErrorMessage(null);
} else {
this.setErrorMessage("Core services not loaded");
}
}
final void updateButtons() {
if (!loadedServices()) {
this.getApplyButton().setEnabled(false);
this.getDefaultsButton().setEnabled(false);
this.addButton.setEnabled(false);
this.removeButton.setEnabled(false);
this.registerButton.setEnabled(false);
this.unregisterButton.setEnabled(false);
this.importButton.setEnabled(false);
this.exportButton.setEnabled(false);
this.refreshButton.setEnabled(true);
this.optionsButton.setEnabled(false);
this.notesButton.setEnabled(false);
return;
}
IStructuredSelection selection = (IStructuredSelection) this.tableViewer.getSelection();
int selectionCount = selection.size();
int itemCount = this.tableViewer.getTable().getItemCount();
boolean registrable = false;
boolean unregistrable = false;
if (selectionCount > 0) {
for (Object item: selection.toList()) {
R registration = this.cast(item);
if (registration.isProvided()) {
boolean registred = registration.hasRegistredMark();
registrable |= !registred;
unregistrable |= registred;
}
}
}
this.getApplyButton().setEnabled(true);
this.getDefaultsButton().setEnabled(true);
this.addButton.setEnabled(true);
this.removeButton.setEnabled(selectionCount > 0 && selectionCount <= itemCount);
this.registerButton.setEnabled(registrable);
this.unregisterButton.setEnabled(unregistrable);
this.importButton.setEnabled(true);
this.exportButton.setEnabled(selectionCount > 0);
this.refreshButton.setEnabled(true);
this.optionsButton.setEnabled(selectionCount == 1 && !this.restoreOptions.get());
this.notesButton.setEnabled(selectionCount == 1);
}
final void updateTable() {
assert this.tableViewer != null;
this.tableViewer.setInput(this.registrations);
this.tableViewer.refresh();
if (this.registrations != null) {
this.tableViewer.setAllChecked(false);
this.tableViewer.setAllGrayed(false);
this.tableViewer.setCheckedElements(this.checkedData().toArray());
this.tableViewer.setGrayedElements(this.unknownData().toArray());
}
}
final void sortTable() {
assert this.tableViewer != null;
Table table = this.tableViewer.getTable();
TableSorter.enable(table, this.registrations != null);
TableSorter.automaticSort(table);
}
final class LocalSetTableSorter extends SetTableSorter<R> {
LocalSetTableSorter(final Table table, @Nullable final Comparator<? super R> comparator) {
super(table, comparator);
}
@Override
protected Set<R> loadSet() {
return AbstractPreferencePage.this.registrations;
}
@Override
protected void updateSet(final Set<R> set) {
AbstractPreferencePage.this.registrations = set;
updateTable();
}
}
static abstract class AbstractLabelProvider<R extends AnnotableRegistration & MarkableRegistration & RegistrationMarker<R>> extends LabelProvider implements ITableLabelProvider {
AbstractLabelProvider() {}
public final String getNotes(final R registration) {
if (!registration.isProvided()) {
return "?";
}
return Annotations.toString(NoteFilter.apply(registration.getAnnotations()));
}
public final String getVersion(final R registration) {
Version version = registration.getAnnotation(Version.class);
return version != null ? version.value() : "?";
}
public Image getColumnImage(final Object element, final int column) {
return null;
}
}
private enum NoteFilter implements Predicate<Annotation> {
INSTANCE;
public static Iterable<Annotation> apply(final Iterable<Annotation> annotations) {
return Iterables.filter(annotations, INSTANCE);
}
public boolean apply(@Nonnull final Annotation annotation) {
return annotation.annotationType() != Version.class;
}
}
void performAdd() {
openInformation(this.getShell(), "Add " + toUpperCaseFirst(this.name()), "Operation not yet supported.");
}
void performRemove() {
openInformation(this.getShell(), "Remove" + toUpperCaseFirst(this.name()), "Operation not yet supported.");
}
void performRegister() {
this.updateSelectedData(true);
this.updateButtons();
}
void performUnregister() {
this.updateSelectedData(false);
this.updateButtons();
}
void performImport() {
openInformation(this.getShell(), "Import " + toUpperCaseFirst(this.name()), "Operation not yet supported.");
}
void performExport() {
openInformation(this.getShell(), "Export " + toUpperCaseFirst(this.name()), "Operation not yet supported.");
}
void performRefresh() {
if (this.registrations == null) {
this.loadInternal(this.sharedPreferences());
}
if (!loadedServices()) {
return;
}
for (R registration: newLinkedHashSet(this.registrations)) {
this.updateData(registration, registration.isRegistered());
}
}
void performOptions() {
IStructuredSelection selection = (IStructuredSelection) this.tableViewer.getSelection();
R registration = this.cast(selection.toList().get(0));
AbstractOptionsDialog<P, R> dialog = this.optionsDialog;
dialog.setTitle("Options for " + ((ITableLabelProvider) this.tableViewer.getLabelProvider()).getColumnText(registration, 0));
dialog.setPreferences(this.getPreferences());
dialog.setRegistration(registration);
dialog.open();
if (dialog.getReturnCode() == Window.OK) {
dialog.configure();
}
}
void performNotes() {
IStructuredSelection selection = (IStructuredSelection) this.tableViewer.getSelection();
R registration = this.cast(selection.toList().get(0));
String name = ((ITableLabelProvider) this.tableViewer.getLabelProvider()).getColumnText(registration, 0);
String message = Annotations.toString(NoteFilter.apply(registration.getAnnotations()));
openInformation(this.getShell(), "Notes for " + name, !message.isEmpty() ? message : "No notes available.");
}
abstract P defaultPreferences();
abstract P sharedPreferences();
abstract Set<R> registrations(P preferences);
@Override
public final boolean performOk() {
this.applyInternal();
this.saveInternal();
return super.performOk();
}
@Override
public final boolean performCancel() {
this.loadInternal(this.sharedPreferences());
return super.performCancel();
}
@Override
protected final void performDefaults() {
String title = format("Restore %s Defaults", toUpperCaseFirst(pluralize(this.name())));
String message = format("PerConIK Core is about to restore default state of %s registrations. Configured options are not restored by default, see options dialog to restore effective options individually.", this.name());
String toggle = format("Restore configured options for all %s", pluralize(this.name()));
MessageDialogWithToggle dialog = new MessageDialogWithToggle(this.getShell(), title, null, message, MessageDialog.WARNING, new String[] {PROCEED_LABEL, CANCEL_LABEL}, 1, toggle, false);
if (dialog.open() == 1) {
return;
}
this.registrations = this.registrations(this.defaultPreferences());
this.restoreOptions.set(dialog.getToggleState());
this.updatePage();
super.performDefaults();
}
@Override
protected final void performApply() {
this.performOk();
}
abstract void apply();
abstract void load(P preferences);
abstract void save();
private void applyInternal() {
if (!loadedServices()) {
return;
}
this.apply();
this.updatePage();
}
private void loadInternal(final P preferences) {
if (loadedServices()) {
this.load(preferences);
}
this.updatePage();
}
private void saveInternal() {
if (!loadedServices()) {
return;
}
try {
this.save();
} catch (RuntimeException failure) {
String title = "Preferences";
String message = "Failed to save preferences.";
openError(this.getShell(), title, message + " See error log for more details.");
Activator.defaultInstance().getConsole().error(failure, message);
}
}
final void setPreferences(final P preferences) {
this.preferences = checkNotNull(preferences);
}
final P getPreferences() {
return this.preferences;
}
@Override
public Control getControl() {
if (this.isContentCreated()) {
if (loadedServices()) {
this.performRefresh();
} else {
this.preferences = null;
this.registrations = null;
}
this.updatePage();
}
return super.getControl();
}
}