
org.bidib.wizard.mvc.pomupdate.view.PomUpdateView Maven / Gradle / Ivy
package org.bidib.wizard.mvc.pomupdate.view;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Frame;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import javax.swing.AbstractAction;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JMenu;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.KeyStroke;
import javax.swing.ListModel;
import javax.swing.SwingUtilities;
import javax.swing.filechooser.FileFilter;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.StringUtils;
import org.bidib.jbidibc.core.message.CommandStationPomMessage;
import org.bidib.jbidibc.pomupdate.DecoderInformation;
import org.bidib.jbidibc.pomupdate.DecoderPomUpdate;
import org.bidib.wizard.dialog.CustomDialog;
import org.bidib.wizard.dialog.FileDialog;
import org.bidib.wizard.locale.Resources;
import org.bidib.wizard.mvc.common.view.DockKeys;
import org.bidib.wizard.mvc.common.view.button.JSplitButton;
import org.bidib.wizard.mvc.common.view.button.listener.SplitButtonActionListener;
import org.bidib.wizard.mvc.common.view.table.ProgressCellRender;
import org.bidib.wizard.mvc.main.view.menu.BasicPopupMenu;
import org.bidib.wizard.mvc.main.view.table.AbstractEmptyTable;
import org.bidib.wizard.mvc.pomupdate.model.Decoder;
import org.bidib.wizard.mvc.pomupdate.model.PomUpdateModel;
import org.bidib.wizard.mvc.pomupdate.view.listener.DecoderInfoStatusListener;
import org.bidib.wizard.mvc.pomupdate.view.listener.PomUpdatePerformStatusListener;
import org.bidib.wizard.mvc.pomupdate.view.listener.PomUpdateStatusListener;
import org.bidib.wizard.mvc.pomupdate.view.listener.PomUpdateViewListener;
import org.bidib.wizard.mvc.script.view.NodeScriptView;
import org.jdesktop.swingx.decorator.Highlighter;
import org.jdesktop.swingx.decorator.HighlighterFactory;
import org.jdesktop.swingx.prompt.PromptSupport;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.jgoodies.binding.adapter.BasicComponentFactory;
import com.jgoodies.binding.adapter.SingleListSelectionAdapter;
import com.jgoodies.binding.beans.PropertyAdapter;
import com.jgoodies.binding.beans.PropertyConnector;
import com.jgoodies.binding.list.SelectionInList;
import com.jgoodies.binding.value.ValueModel;
import com.jgoodies.forms.builder.ButtonBarBuilder;
import com.jgoodies.forms.builder.DefaultFormBuilder;
import com.jgoodies.forms.debug.FormDebugPanel;
import com.jgoodies.forms.factories.Borders;
import com.jgoodies.forms.layout.FormLayout;
import com.vlsolutions.swing.docking.DockKey;
import com.vlsolutions.swing.docking.Dockable;
import com.vlsolutions.swing.docking.DockableState;
import com.vlsolutions.swing.docking.DockingDesktop;
import com.vlsolutions.swing.docking.event.DockableStateChangeEvent;
import com.vlsolutions.swing.docking.event.DockableStateChangeListener;
public class PomUpdateView implements Dockable {
private static final Logger LOGGER = LoggerFactory.getLogger(PomUpdateView.class);
private static final String SUFFIX_DECODERFILE = "hex";
private static final String ENCODED_DIALOG_COLUMN_SPECS = "pref, 3dlu, fill:50dlu:grow, 3dlu, pref, 3dlu, pref";
public static final KeyStroke KEYSTROKE_ADD_DECODER = KeyStroke
.getKeyStroke(KeyEvent.VK_ADD, ActionEvent.CTRL_MASK);
private final DockableStateChangeListener dockableStateChangeListener;
private final DockingDesktop desktop;
private final JPanel contentPanel;
private ValueModel decoderFileValueModel;
private final PomUpdateModel pomUpdateModel;
private SelectionInList decoderSelection;
private final PomUpdateTableModel tableModel;
private final JButton selectDecoderFileButton = new JButton(Resources.getString(getClass(), "select-decoderfile"));
private final JButton showDecoderFileInfoButton = new JButton(Resources.getString(getClass(),
"show-decoderfile-info"));
private final JSplitButton prepareUpdateButton =
new JSplitButton(Resources.getString(getClass(), "prepare-update"));
private final JPopupMenu popupMenuSplit;
private final JButton performUpdateButton = new JButton(Resources.getString(getClass(), "perform-update"));
private DecoderPomUpdate decoderPomUpdate;
private final JPopupMenu popupMenu;
private JMenuItem addAddressItem;
private JMenuItem removeAddressItem;
private JMenuItem loadDecoderInfoItem;
private JMenuItem forcePrepareItem;
private final AbstractEmptyTable decoderTable;
private DecoderInformation decoderInformation;
private final Collection listeners = new LinkedList<>();
private final FileFilter ff = new FileFilter() {
@Override
public boolean accept(File file) {
boolean result = false;
if (file != null) {
if (file.isDirectory()) {
result = true;
}
else if (FilenameUtils.wildcardMatch(file.getName(), "*." + SUFFIX_DECODERFILE)) {
result = true;
}
}
return result;
}
@Override
public String getDescription() {
return Resources.getString(NodeScriptView.class, "filter") + " (*." + SUFFIX_DECODERFILE + ")";
}
};
public PomUpdateView(final DockingDesktop desktop, final PomUpdateModel pomUpdateModel) {
this.desktop = desktop;
DockKeys.DOCKKEY_POM_UPDATE_VIEW.setName(Resources.getString(getClass(), "title"));
DockKeys.DOCKKEY_POM_UPDATE_VIEW.setFloatEnabled(true);
DockKeys.DOCKKEY_POM_UPDATE_VIEW.setAutoHideEnabled(false);
LOGGER.info("Create new PomUpdateView");
dockableStateChangeListener = new DockableStateChangeListener() {
@Override
public void dockableStateChanged(DockableStateChangeEvent event) {
LOGGER.info("The state has changed, newState: {}, prevState: {}", event.getNewState(),
event.getPreviousState());
DockableState newState = event.getNewState();
if (newState.getDockable().equals(PomUpdateView.this) && newState.isClosed()) {
LOGGER.info("The DebugInterfaceView is closed.");
// we are closed
desktop.removeDockableStateChangeListener(dockableStateChangeListener);
fireClose();
}
}
};
desktop.addDockableStateChangeListener(dockableStateChangeListener);
this.pomUpdateModel = pomUpdateModel;
decoderPomUpdate = new DecoderPomUpdate();
decoderSelection = new SelectionInList((ListModel) pomUpdateModel.getDecoderListModel());
tableModel = new PomUpdateTableModel(decoderSelection);
popupMenu = new BasicPopupMenu() {
private static final long serialVersionUID = 1L;
};
prepareMenuItems(popupMenu);
// prepare the popup menu for the split button
popupMenuSplit = new BasicPopupMenu() {
private static final long serialVersionUID = 1L;
};
forcePrepareItem = new JMenuItem(Resources.getString(getClass(), "prepare-force-update"));
forcePrepareItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
prepareUpdate(true);
}
});
addMenuItem(popupMenuSplit, forcePrepareItem);
// add the popup menu to the split button
prepareUpdateButton.setPopupMenu(popupMenuSplit);
// create a decoder table
decoderTable = new AbstractEmptyTable(
tableModel, Resources.getString(getClass(), "empty_table")) {
private static final long serialVersionUID = 1L;
@Override
public boolean isSkipPackColumn() {
return true;
}
};
decoderTable.setSelectionModel(new SingleListSelectionAdapter(decoderSelection.getSelectionIndexHolder()));
decoderTable.getColumn(PomUpdateTableModel.COLUMN_PERFORM_PROGRESS).setCellRenderer(
new ProgressCellRender(true));
decoderTable.getColumn(PomUpdateTableModel.COLUMN_PREPARE_PROGRESS).setCellRenderer(
new ProgressCellRender(true));
Highlighter simpleStriping = HighlighterFactory.createSimpleStriping();
decoderTable.setHighlighters(simpleStriping);
decoderTable
.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(KEYSTROKE_ADD_DECODER, "addDecoder");
decoderTable.getActionMap().put("addDecoder", new AbstractAction() {
private static final long serialVersionUID = 1L;
@Override
public void actionPerformed(ActionEvent e) {
fireAddAddress();
}
});
decoderTable.putClientProperty("terminateEditOnFocusLost", Boolean.TRUE);
// create form builder
DefaultFormBuilder dialogBuilder = null;
boolean debugDialog = false;
if (debugDialog) {
JPanel panel = new FormDebugPanel();
dialogBuilder = new DefaultFormBuilder(new FormLayout(ENCODED_DIALOG_COLUMN_SPECS), panel);
}
else {
JPanel panel = new JPanel(new BorderLayout());
dialogBuilder = new DefaultFormBuilder(new FormLayout(ENCODED_DIALOG_COLUMN_SPECS), panel);
}
dialogBuilder.border(Borders.DIALOG);
// add some components
decoderFileValueModel =
new PropertyAdapter(pomUpdateModel, PomUpdateModel.PROPERTY_DECODER_FILE, true);
JTextField selectedFileText = BasicComponentFactory.createTextField(decoderFileValueModel, true);
selectedFileText.setEditable(false);
PromptSupport.init(Resources.getString(getClass(), "select-decoderfile.prompt"), null, null, selectedFileText);
dialogBuilder.append(Resources.getString(getClass(), "decoderFile"), selectedFileText);
// prepare the select and info button
JPanel firmwareActionButtons =
new ButtonBarBuilder()
.addButton(selectDecoderFileButton).addGlue().addButton(showDecoderFileInfoButton).build();
dialogBuilder.append(firmwareActionButtons);
// add bindings for enable/disable the show decoder info button
PropertyConnector.connect(pomUpdateModel, PomUpdateModel.PROPERTY_HAS_FIRMWARE_AVAILABLE,
showDecoderFileInfoButton, "enabled");
selectDecoderFileButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
fireSelectDecoderFile();
}
});
showDecoderFileInfoButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
showDecoderFileInfo();
}
});
showDecoderFileInfoButton.setEnabled(false);
dialogBuilder.appendRow("3dlu");
dialogBuilder.appendRow("fill:p:grow");
dialogBuilder.nextLine(2);
final JScrollPane scrollTable = new JScrollPane(decoderTable);
dialogBuilder.append(scrollTable, 7);
dialogBuilder.appendRow("3dlu");
dialogBuilder.appendRow("pref");
dialogBuilder.nextLine(2);
// prepare the prepare and perform update button
JPanel pomUpdateActionButtons =
new ButtonBarBuilder()
.addButton(prepareUpdateButton).addRelatedGap().addButton(performUpdateButton).build();
dialogBuilder.append(pomUpdateActionButtons, 7);
prepareUpdateButton.addSplitButtonActionListener(new SplitButtonActionListener() {
@Override
public void buttonClicked(ActionEvent e) {
LOGGER.info("Button clicked!");
prepareUpdate(false);
}
@Override
public void splitButtonClicked(ActionEvent e) {
LOGGER.info("Split button clicked!");
// do nothing here ... the user must click the menu item
}
});
performUpdateButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
performUpdate();
}
});
prepareUpdateButton.setEnabled(false);
performUpdateButton.setEnabled(false);
// add bindings for enable/disable the prepare and perform update button
PropertyConnector.connect(pomUpdateModel, PomUpdateModel.PROPERTY_READY_FOR_PREPARE_UPDATE,
prepareUpdateButton, "enabled");
PropertyConnector.connect(pomUpdateModel, PomUpdateModel.PROPERTY_READY_FOR_PERFORM_UPDATE,
performUpdateButton, "enabled");
contentPanel = dialogBuilder.build();
decoderTable.addMouseListener(new MouseAdapter() {
@Override
public void mousePressed(MouseEvent e) {
if (e.getClickCount() == 1 && e.isPopupTrigger()) {
LOGGER.debug("Show the popup menu.");
e.consume();
// prepare the popup menu items
if (!pomUpdateModel.isUpdateInProgress()) {
addAddressItem.setEnabled(true);
}
else {
addAddressItem.setEnabled(false);
}
if (decoderTable.getSelectedRowCount() > 0 && !pomUpdateModel.isUpdateInProgress()) {
removeAddressItem.setEnabled(true);
loadDecoderInfoItem.setEnabled(true);
}
else {
removeAddressItem.setEnabled(false);
loadDecoderInfoItem.setEnabled(false);
}
popupMenu.show(decoderTable, e.getX(), e.getY());
}
}
public void mouseReleased(MouseEvent e) {
LOGGER.debug("Mouse released.");
if (e.isPopupTrigger()) {
mousePressed(e);
}
}
});
pomUpdateModel.addPropertyChangeListener(new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt) {
LOGGER.info("Property changed: {}", evt);
if (evt.getPropertyName() == null) {
// multiple change
LOGGER.info("Update table data.");
tableModel.fireTableDataChanged();
}
}
});
}
protected void prepareUpdate(boolean forceUpdate) {
LOGGER.info("Prepare udpate, forceUpdate: {}", forceUpdate);
if (CollectionUtils.isEmpty(pomUpdateModel.getDecoderListModel())) {
LOGGER.warn("No decoders to update specified!");
}
final Map> prepareUpdateMap = new LinkedHashMap<>();
DecoderInformation updateDecoderInformation = decoderInformation;
// support for force the update
if (forceUpdate) {
LOGGER.info("Force the firmware update!");
updateDecoderInformation = new DecoderInformation(decoderInformation);
updateDecoderInformation.forceFirmwareUpdate();
}
for (Decoder decoder : pomUpdateModel.getDecoderListModel()) {
LOGGER.info("Prepare update list for decoder: {}", decoder);
List prepareUpdateList =
decoderPomUpdate.prepareDecoderInfoPomMessages(updateDecoderInformation, decoder.getAddress());
prepareUpdateMap.put(decoder, prepareUpdateList);
}
// let the controller send the prepare update packets to the decoders
firePrepareUpdate(prepareUpdateMap);
}
private void firePrepareUpdate(final Map> prepareUpdateMap) {
final PomUpdateStatusListener statusListener = new PomUpdateStatusListener() {
@Override
public void updateStatus(final Decoder decoder, final int progress) {
LOGGER.info("Update the prepare status for decoder: {}, progress: {}", decoder, progress);
if (SwingUtilities.isEventDispatchThread()) {
tableModel.updatePrepareStatus(decoder, progress);
if (decoder.isPrepareUpdateDone()) {
pomUpdateModel.checkPendingPrepare();
}
}
else {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
tableModel.updatePrepareStatus(decoder, progress);
if (decoder.isPrepareUpdateDone()) {
pomUpdateModel.checkPendingPrepare();
}
}
});
}
}
};
for (PomUpdateViewListener l : listeners) {
l.prepareUpdate(prepareUpdateMap, statusListener);
}
}
protected void performUpdate() {
// Prepare the prepareDecoderUpdatePomMessages
List updateMessages =
decoderPomUpdate.prepareDecoderUpdatePomMessages(pomUpdateModel.getFirmwareContent(), decoderInformation);
firePerformUpdate(updateMessages);
}
private void firePerformUpdate(final List updateMessages) {
pomUpdateModel.setUpdateInProgress(true);
final int totalPackets = updateMessages.size();
LOGGER.info("Perform update with total number of POM packets: {}", totalPackets);
final PomUpdatePerformStatusListener statusListener = new PomUpdatePerformStatusListener() {
@Override
public void updateStatus(final int progress) {
LOGGER.info("Update the perform status, progress: {}", progress);
if (SwingUtilities.isEventDispatchThread()) {
tableModel.updatePerformStatus(null, progress);
}
else {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
tableModel.updatePerformStatus(null, progress);
}
});
}
}
@Override
public void finished() {
LOGGER.info("Perform update has finished.");
pomUpdateModel.setUpdateInProgress(false);
}
};
for (PomUpdateViewListener l : listeners) {
l.performUpdate(updateMessages, statusListener);
}
}
private void prepareMenuItems(JPopupMenu menu) {
addAddressItem = new JMenuItem(Resources.getString(getClass(), "addAddress") + " ...");
addAddressItem.setAccelerator(KEYSTROKE_ADD_DECODER);
addAddressItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
fireAddAddress();
}
});
addMenuItem(menu, addAddressItem);
removeAddressItem = new JMenuItem(Resources.getString(getClass(), "removeAddress") + " ...");
removeAddressItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
fireRemoveAddress();
}
});
addMenuItem(menu, removeAddressItem);
loadDecoderInfoItem = new JMenuItem(Resources.getString(getClass(), "loadDecoderInfo") + " ...");
loadDecoderInfoItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
fireLoadDecoderInfo();
}
});
addMenuItem(menu, loadDecoderInfoItem);
}
private void addMenuItem(Object menu, JMenuItem menuItem) {
if (menu instanceof JMenu) {
((JMenu) menu).add(menuItem);
}
else if (menu instanceof JPopupMenu) {
((JPopupMenu) menu).add(menuItem);
}
}
private void fireClose() {
for (PomUpdateViewListener l : listeners) {
l.close();
}
desktop.addDockableStateChangeListener(dockableStateChangeListener);
}
private void fireAddAddress() {
Frame frame = JOptionPane.getFrameForComponent(contentPanel);
// add new address
CustomDialog customDialog =
new CustomDialog(frame, Resources.getString(getClass(), "enterDecoderAddress.title"), Resources.getString(
getClass(), "enterDecoderAddress.message"), Resources.getString(getClass(),
"enterDecoderAddress.buttonAdd"), Resources.getString(getClass(), "enterDecoderAddress.buttonCancel"));
customDialog.pack();
customDialog.setLocationRelativeTo(contentPanel);
customDialog.setVisible(true);
String decoderAddress = customDialog.getValidatedText();
if (StringUtils.isNotBlank(decoderAddress)) {
LOGGER.info("Adding new decoder address: {}", decoderAddress);
pomUpdateModel.addDecoder(new Decoder(Integer.parseInt(decoderAddress)));
}
}
private void fireRemoveAddress() {
List decodersToRemove = new ArrayList<>();
int[] selectedRows = decoderTable.getSelectedRows();
for (int row : selectedRows) {
row = decoderTable.convertRowIndexToModel(row);
Decoder rowValue = tableModel.getRow(row);
decodersToRemove.add(rowValue);
}
for (Decoder decoderAddress : decodersToRemove) {
LOGGER.debug("Remove decoder address: {}", decoderAddress);
pomUpdateModel.removeDecoder(decoderAddress);
}
}
private void fireLoadDecoderInfo() {
List decodersToLoadInfo = new ArrayList<>();
int[] selectedRows = decoderTable.getSelectedRows();
for (int row : selectedRows) {
row = decoderTable.convertRowIndexToModel(row);
Decoder rowValue = tableModel.getRow(row);
decodersToLoadInfo.add(rowValue);
}
LOGGER.debug("Load decoder info for decoders: {}", decodersToLoadInfo);
final DecoderInfoStatusListener statusListener = new DecoderInfoStatusListener() {
@Override
public void updateStatus(int progress) {
LOGGER.info("Update status: {}", progress);
}
@Override
public void finished() {
LOGGER.info("Get decoder info has finished.");
}
};
for (PomUpdateViewListener l : listeners) {
l.performLoadDecoderInfo(decodersToLoadInfo, statusListener);
}
}
protected void fireSelectDecoderFile() {
final FileDialog dialog = new FileDialog(
contentPanel, FileDialog.OPEN, null, ff) {
@Override
public void approve(String selectedFile) {
File file = new File(selectedFile);
selectedFile = file.getName();
try {
pomUpdateModel.setDecoderFile(null);
decoderInformation = null;
loadScript(file);
pomUpdateModel.setDecoderFile(file.toString());
}
catch (IOException ex) {
LOGGER.warn("Load decoder firmware from file failed.", ex);
pomUpdateModel.setFirmwareContent(null);
JOptionPane.showMessageDialog(contentPanel,
Resources.getString(PomUpdateView.class, "load-firmware-failed"));
}
catch (IllegalArgumentException ex) {
LOGGER.warn("Load firmware from file failed.", ex);
pomUpdateModel.setFirmwareContent(null);
JOptionPane.showMessageDialog(contentPanel,
Resources.getString(PomUpdateView.class, "invalid-firmware-file"));
}
}
};
dialog.showDialog();
}
private void loadScript(File decoderFirmare) throws IOException {
LOGGER.info("Load decoder firmware from file: {}", decoderFirmare);
List firmwareContent = decoderPomUpdate.loadFirmwareFile(decoderFirmare);
// this will throw an IllegalArgumentException if the content does not contain the security line
decoderInformation = decoderPomUpdate.findDecoderInformation(firmwareContent);
pomUpdateModel.setFirmwareContent(firmwareContent);
}
protected void showDecoderFileInfo() {
LOGGER.info("Loaded decoder information: {}", decoderInformation);
// TODO show nice dialog
DecoderFirmwareInfoDialog infoDialog = new DecoderFirmwareInfoDialog(null, null, true);
infoDialog.setDecoderInfo(decoderInformation);
infoDialog.showDialog(contentPanel);
}
@Override
public DockKey getDockKey() {
return DockKeys.DOCKKEY_POM_UPDATE_VIEW;
}
@Override
public Component getComponent() {
return contentPanel;
}
public void addPomUpdateViewListener(PomUpdateViewListener l) {
listeners.add(l);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy