/*******************************************************************************
* Copyright (c) 2011 Subgraph.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Subgraph - initial API and implementation
******************************************************************************/
package com.subgraph.vega.ui.hexeditor;
import org.eclipse.jface.layout.TableColumnLayout;
import org.eclipse.jface.viewers.CellNavigationStrategy;
import org.eclipse.jface.viewers.ColumnPixelData;
import org.eclipse.jface.viewers.ColumnViewer;
import org.eclipse.jface.viewers.ColumnViewerEditor;
import org.eclipse.jface.viewers.ColumnViewerEditorActivationEvent;
import org.eclipse.jface.viewers.ColumnViewerEditorActivationListener;
import org.eclipse.jface.viewers.ColumnViewerEditorActivationStrategy;
import org.eclipse.jface.viewers.ColumnViewerEditorDeactivationEvent;
import org.eclipse.jface.viewers.ColumnWeightData;
import org.eclipse.jface.viewers.EditingSupport;
import org.eclipse.jface.viewers.FocusCellOwnerDrawHighlighter;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.jface.viewers.TableViewerColumn;
import org.eclipse.jface.viewers.TableViewerEditor;
import org.eclipse.jface.viewers.TableViewerFocusCellManager;
import org.eclipse.jface.viewers.ViewerCell;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.ControlAdapter;
import org.eclipse.swt.events.ControlEvent;
import org.eclipse.swt.events.ControlListener;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;
public class HexEditControl extends Composite {
private final static int WORD_SIZE = 4;
private final static int MINIMUM_DATA_COLUMNS = 4;
private final HexEditFonts fonts;
private TableViewer tableViewer;
private HexEditModel model;
private int currentDataColumnCount;
private boolean editable = true;
private volatile boolean enablePreserveSelection = true;
public HexEditControl(Composite parent) {
super(parent, SWT.NONE);
this.fonts = new HexEditFonts(this);
currentDataColumnCount = calculateDataColumnCount(getClientArea().width);
tableViewer = createTableViewer();
tableViewer.setContentProvider(new HexEditContentProvider(tableViewer));
createColumns(currentDataColumnCount);
addControlListener(createControlListener());
}
private TableViewer createTableViewer() {
final TableViewer tv = new TableViewer(this, SWT.H_SCROLL | SWT.V_SCROLL | SWT.BORDER | SWT.VIRTUAL) {
@Override
protected void preservingSelection(Runnable updateCode) {
if(enablePreserveSelection)
super.preservingSelection(updateCode);
}
};
tv.getTable().setHeaderVisible(true);
tv.setUseHashlookup(true);
if(editable)
addEditorSupport(tv);
return tv;
}
/*
* Q: teh fuck is this?
* A: http://bingjava.appspot.com/snippet.jsp?id=2213
*/
private void addEditorSupport(TableViewer tv) {
final CellNavigationStrategy cellNavigation = createCellNavigationStrategy(tv);
final TableViewerFocusCellManager focusCellManager = new TableViewerFocusCellManager(tv, new FocusCellOwnerDrawHighlighter(tv), cellNavigation);
final ColumnViewerEditorActivationStrategy activationStrategy = createEditorActivationStrategy(tv);
TableViewerEditor.create(tv, focusCellManager, activationStrategy,
ColumnViewerEditor.TABBING_HORIZONTAL
| ColumnViewerEditor.TABBING_MOVE_TO_ROW_NEIGHBOR
| ColumnViewerEditor.TABBING_VERTICAL
| ColumnViewerEditor.KEYBOARD_ACTIVATION);
tv.getColumnViewerEditor().addEditorActivationListener(createEditorActivationListener(tv));
}
private CellNavigationStrategy createCellNavigationStrategy(TableViewer tv) {
final Table t = tv.getTable();
return new CellNavigationStrategy() {
@Override
public ViewerCell findSelectedCell(ColumnViewer viewer,
ViewerCell currentSelectedCell, Event event) {
final ViewerCell cell = super.findSelectedCell(viewer, currentSelectedCell, event);
if(cell != null) {
t.showColumn(t.getColumn(cell.getColumnIndex()));
}
return cell;
}
};
}
private ColumnViewerEditorActivationStrategy createEditorActivationStrategy(TableViewer tv) {
return new ColumnViewerEditorActivationStrategy(tv) {
protected boolean isEditorActivationEvent(ColumnViewerEditorActivationEvent event) {
return event.eventType == ColumnViewerEditorActivationEvent.TRAVERSAL
|| event.eventType == ColumnViewerEditorActivationEvent.MOUSE_DOUBLE_CLICK_SELECTION
|| (event.eventType == ColumnViewerEditorActivationEvent.KEY_PRESSED && event.keyCode == SWT.CR);
}
};
}
private ColumnViewerEditorActivationListener createEditorActivationListener(TableViewer tv) {
final Table t = tv.getTable();
return new ColumnViewerEditorActivationListener() {
@Override
public void beforeEditorDeactivated(
ColumnViewerEditorDeactivationEvent event) {
}
@Override
public void beforeEditorActivated(ColumnViewerEditorActivationEvent event) {
ViewerCell cell = (ViewerCell) event.getSource();
t.showColumn(t.getColumn(cell.getColumnIndex()));
}
@Override
public void afterEditorDeactivated(ColumnViewerEditorDeactivationEvent event) {
}
@Override
public void afterEditorActivated(ColumnViewerEditorActivationEvent event) {
}
};
}
public void setInput(byte[] binaryData) {
if(currentDataColumnCount == 0) {
model = new HexEditModel(binaryData);
} else {
model = new HexEditModel(binaryData, currentDataColumnCount);
}
changeModel(model);
}
public boolean isContentDirty() {
if(model != null) {
return model.isDirty();
} else {
return false;
}
}
public byte[] getContent() {
return model.getContent();
}
private void changeModel(final HexEditModel model) {
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=200214
enablePreserveSelection = false;
tableViewer.setInput(model);
enablePreserveSelection = true;
}
private void createColumns(int dataColumnCount) {
tableViewer.getTable().dispose();
tableViewer = createTableViewer();
tableViewer.setContentProvider(new HexEditContentProvider(tableViewer));
final TableColumnLayout layout = new TableColumnLayout();
createOffsetColumn(layout);
for(int i = 0; i < dataColumnCount; i++)
createDataColumn(layout, i);
createAsciiColumn(layout);
setLayout(layout);
tableViewer.setLabelProvider(new HexEditLabelProvider(fonts));
}
private void createOffsetColumn(TableColumnLayout layout) {
final TableColumn c = createColumn("Offset", SWT.CENTER);
c.setAlignment(SWT.RIGHT);
layout.setColumnData(c, new ColumnPixelData(fonts.getOffsetColumnWidth(), false, false));
}
private void createAsciiColumn(TableColumnLayout layout) {
final TableColumn c = createColumn("", SWT.CENTER);
layout.setColumnData(c, new ColumnWeightData(100));
}
private void createDataColumn(TableColumnLayout layout, int index) {
final EditingSupport editor = (editable) ? (new HexEditTableEditor(tableViewer, index)) : (null);
final TableColumn c = createColumn(String.format("%02X", index), SWT.CENTER, editor);
layout.setColumnData(c, new ColumnPixelData(fonts.getDataColumnWidth(), false, false));
}
private TableColumn createColumn(String headerText, int align) {
return createColumn(headerText, align, null);
}
private TableColumn createColumn(String headerText, int align, EditingSupport editor) {
final TableViewerColumn tvc = new TableViewerColumn(tableViewer, align);
final TableColumn tc = tvc.getColumn();
tc.setMoveable(false);
tc.setResizable(false);
tc.setText(headerText);
if(editor != null)
tvc.setEditingSupport(editor);
return tc;
}
private ControlListener createControlListener() {
return new ControlAdapter() {
@Override
public void controlResized(ControlEvent e) {
doResize();
}
};
}
private void doResize() {
final int width = getClientArea().width;
final int cc = calculateDataColumnCount(width);
if(cc != currentDataColumnCount) {
changeModel(null);
createColumns(cc);
if(model != null) {
// Remembering old offset does not work on Linux. Maybe because of these bugs:
//
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=74739
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=202392
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=295666
final HexEditModel newModel = model.getModelForRowLength(cc);
final int topIndex = tableViewer.getTable().getTopIndex();
final int topOffset = model.getOffsetForLine(topIndex);
final int newTopIndex = newModel.getLineForOffset(topOffset);
changeModel(newModel);
model = newModel;
if(newTopIndex != 0) {
tableViewer.getTable().setTopIndex(newTopIndex);
}
} else {
changeModel(model);
}
currentDataColumnCount = cc;
}
}
private int calculateDataColumnCount(int pixelWidth) {
/*
* w = width
* o = offsetColumnWidth
* d = dataColumnWidth
* a = asciiColumnWordWidth
* n = words
*
* w = o + 4nd + na
* w - o = n(4d + a)
* n = (w - o) / (4d + a)
*/
int space = (pixelWidth - fonts.getOffsetColumnWidth()) - 30;
int perWord = WORD_SIZE * fonts.getDataColumnWidth() + fonts.getAsciiColumnWordWidth();
int words = space / perWord;
int columns = words * WORD_SIZE;
return (columns < MINIMUM_DATA_COLUMNS) ? (MINIMUM_DATA_COLUMNS) : (columns);
}
}