/* * * Copyright 2014 http://Bither.net * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * / */ package net.bither.viewsystem.froms; import net.bither.Bither; import net.bither.BitherSetting; import net.bither.bitherj.api.http.BitherUrl; import net.bither.bitherj.core.AddressManager; import net.bither.bitherj.core.HDAccount; import net.bither.bitherj.core.Tx; import net.bither.bitherj.utils.Utils; import net.bither.implbitherj.BlockNotificationCenter; import net.bither.implbitherj.TxNotificationCenter; import net.bither.languages.MessageKey; import net.bither.model.TxTableModel; import net.bither.utils.DateUtils; import net.bither.utils.LocaliserUtils; import net.bither.utils.ViewUtil; import net.bither.viewsystem.action.ShowTransactionDetailsAction; import net.bither.viewsystem.base.*; import net.bither.viewsystem.base.renderer.DecimalAlignRenderer; import net.bither.viewsystem.base.renderer.ImageRenderer; import net.bither.viewsystem.base.renderer.TrailingJustifiedDateRenderer; import net.bither.viewsystem.components.ScrollBarUIDecorator; import net.bither.viewsystem.themes.Themes; import javax.swing.*; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; import javax.swing.table.TableCellRenderer; import javax.swing.table.TableColumn; import javax.swing.table.TableModel; import javax.swing.table.TableRowSorter; import java.awt.*; import java.awt.event.ActionEvent; import java.math.BigDecimal; import java.net.URI; import java.net.URISyntaxException; import java.text.DecimalFormat; import java.text.ParseException; import java.util.ArrayList; import java.util.Comparator; import java.util.Date; import java.util.List; public class ShowTransactionsForm implements Viewable, TxNotificationCenter.ITxListener, BlockNotificationCenter.IBlockListener { private JTable table; private TxTableModel txTableModel; private TableRowSorter<TableModel> rowSorter; private ListSelectionModel listSelectionModel; private int selectedRow = -1; private Action showTransactionDetailsAction; private JButton showTransactionsButton; private JScrollPane scrollPane; private ShowTransactionHeaderForm showTransactionHeaderForm; private JPanel panelMain; private List<Tx> txList = new ArrayList<Tx>(); private JButton btnAddress; public ShowTransactionsForm() { TxNotificationCenter.addTxListener(ShowTransactionsForm.this); BlockNotificationCenter.addBlockChange(ShowTransactionsForm.this); initUI(); panelMain.applyComponentOrientation(ComponentOrientation.getOrientation(LocaliserUtils.getLocale())); refreshTx(); } private void refreshTx() { new Thread(new Runnable() { @Override public void run() { if (Bither.getActionAddress() != null) { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { if (Bither.getActionAddress() instanceof HDAccount) { btnAddress.setVisible(false); } else { btnAddress.setVisible(true); } } }); final List<Tx> actionTxList = Bither.getActionAddress().getTxs(); SwingUtilities.invokeLater(new Runnable() { @Override public void run() { txList.clear(); txList.addAll(actionTxList); txTableModel.fireTableDataChanged(); } }); } } }).start(); } private void initUI() { showTransactionHeaderForm = new ShowTransactionHeaderForm(); JPanel panel = showTransactionHeaderForm.getPanel(); JPanel btnTxPanel = createTxDetailButtonPanel(); panelMain.add(panel, BorderLayout.NORTH); JPanel transactionsPanel = createTransactionsPanel(); panelMain.add(transactionsPanel, BorderLayout.CENTER); panelMain.add(btnTxPanel, BorderLayout.SOUTH); if (AddressManager.getInstance().getAllAddresses().size() == 0 && AddressManager.getInstance().getHdAccount() == null) { showTransactionHeaderForm.setVisible(false); } else { showTransactionHeaderForm.setVisible(true); } } private JPanel createTransactionsPanel() { JPanel transactionsPanel = new JPanel(); transactionsPanel.setMinimumSize(new Dimension(550, 160)); transactionsPanel.setBackground(Themes.currentTheme.detailPanelBackground()); transactionsPanel.setLayout(new GridBagLayout()); transactionsPanel.setOpaque(true); GridBagConstraints constraints = new GridBagConstraints(); txTableModel = new TxTableModel(txList); table = new JTable(txTableModel); table.setOpaque(false); table.setBorder(BorderFactory.createEmptyBorder()); table.setComponentOrientation(ComponentOrientation.getOrientation(LocaliserUtils.getLocale())); table.setRowHeight(Math.max(BitherSetting.MINIMUM_ICON_HEIGHT, panelMain.getFontMetrics(FontSizer.INSTANCE.getAdjustedDefaultFont()).getHeight()) + BitherSetting.HEIGHT_DELTA); // Use status icons. table.getColumnModel().getColumn(0).setCellRenderer(new ImageRenderer(ShowTransactionsForm.this)); table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); table.setRowSelectionAllowed(true); table.setColumnSelectionAllowed(false); table.setBackground(Themes.currentTheme.detailPanelBackground()); // No row is currently selected. selectedRow = -1; // Listener for row selections. listSelectionModel = table.getSelectionModel(); listSelectionModel.addListSelectionListener(new SharedListSelectionHandler(showTransactionDetailsAction)); // Date right justified. table.getColumnModel().getColumn(1).setCellRenderer(new TrailingJustifiedDateRenderer(ShowTransactionsForm.this)); // Justify column headers. justifyColumnHeaders(); // // Description leading justified (set explicitly as it does not seem to work otherwise). // if (ComponentOrientation.getOrientation(LocaliserUtils.getLocale()).isLeftToRight()) { // table.getColumnModel().getColumn(2).setCellRenderer(new LeadingJustifiedRenderer(ShowTransactionsForm.this)); // } else { // table.getColumnModel().getColumn(2).setCellRenderer(new TrailingJustifiedStringRenderer(ShowTransactionsForm.this)); // } // Amount decimal aligned DecimalAlignRenderer decimalAlignRenderer = new DecimalAlignRenderer(ShowTransactionsForm.this); table.getColumnModel().getColumn(2).setCellRenderer(decimalAlignRenderer); FontMetrics fontMetrics = panelMain.getFontMetrics(FontSizer.INSTANCE.getAdjustedDefaultFont()); TableColumn tableColumn = table.getColumnModel().getColumn(0); // status int statusWidth = fontMetrics.stringWidth(LocaliserUtils.getString("walletData.statusText")); tableColumn.setPreferredWidth(statusWidth + BitherSetting.STATUS_WIDTH_DELTA); tableColumn = table.getColumnModel().getColumn(1); // Date. int dateWidth = Math.max(fontMetrics.stringWidth(LocaliserUtils.getString("walletData.dateText")), fontMetrics.stringWidth(DateUtils.getDateTimeString(new Date(DateUtils.nowUtc().getMillis())))); tableColumn.setPreferredWidth(dateWidth); tableColumn = table.getColumnModel().getColumn(2); // Amount (BTC). int amountBTCWidth = Math.max(fontMetrics.stringWidth(LocaliserUtils.getString("sendBitcoinPanel.amountLabel") + " (BTC)"), fontMetrics.stringWidth("00000.000000000")); tableColumn.setPreferredWidth(amountBTCWidth); tableColumn.setMinWidth(amountBTCWidth); // Row sorter. rowSorter = new TableRowSorter<TableModel>(table.getModel()); table.setRowSorter(rowSorter); // Sort by date descending. List<TableRowSorter.SortKey> sortKeys = new ArrayList<TableRowSorter.SortKey>(); sortKeys.add(new TableRowSorter.SortKey(1, SortOrder.DESCENDING)); rowSorter.setSortKeys(sortKeys); rowSorter.setComparator(1, dateComparator); rowSorter.setComparator(2, comparatorNumber); scrollPane = new JScrollPane(table, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED); scrollPaneSetup(); showTransactionDetailsAction.setEnabled(table.getSelectedRow() > -1); constraints.fill = GridBagConstraints.BOTH; constraints.gridx = 0; constraints.gridy = 0; constraints.gridwidth = 1; constraints.weightx = 1; constraints.weighty = 1; transactionsPanel.add(scrollPane, constraints); return transactionsPanel; } private void justifyColumnHeaders() { TableCellRenderer renderer = table.getTableHeader().getDefaultRenderer(); JLabel label = (JLabel) renderer; label.setHorizontalAlignment(JLabel.CENTER); } private JPanel createTxDetailButtonPanel() { JPanel buttonPanel = new JPanel(); buttonPanel.setLayout(new GridBagLayout()); GridBagConstraints constraints = new GridBagConstraints(); buttonPanel.setBorder(BorderFactory.createMatteBorder(1, 0, 0, 0, SystemColor.windowBorder)); buttonPanel.setOpaque(true); buttonPanel.setBackground(Themes.currentTheme.detailPanelBackground()); buttonPanel.setComponentOrientation(ComponentOrientation.getOrientation(LocaliserUtils.getLocale())); showTransactionDetailsAction = new ShowTransactionDetailsAction(this); showTransactionsButton = Buttons.newLaunchBrowserButton(showTransactionDetailsAction , MessageKey.TRANSCATION); showTransactionsButton.setEnabled(false); constraints.fill = GridBagConstraints.NONE; constraints.gridx = 1; constraints.gridy = 0; constraints.weightx = 0.1; constraints.weighty = 1.0; constraints.gridwidth = 1; constraints.gridheight = 1; constraints.anchor = GridBagConstraints.LINE_START; buttonPanel.add(showTransactionsButton, constraints); btnAddress = Buttons.newLaunchBrowserButton(new AbstractAction() { @Override public void actionPerformed(ActionEvent e) { try { String url = BitherUrl.BLOCKCHAIN_INFO_ADDRESS_URL + Bither.getActionAddress().getAddress(); ViewUtil.openURI(new URI(url)); } catch (URISyntaxException ie) { ie.printStackTrace(); } } }, MessageKey.ADDRESS); if (Bither.getActionAddress() == null) { btnAddress.setEnabled(false); } constraints.fill = GridBagConstraints.NONE; constraints.gridx = 2; constraints.gridy = 0; constraints.weightx = 0.1; constraints.weighty = 1.0; constraints.gridwidth = 1; constraints.gridheight = 1; constraints.anchor = GridBagConstraints.LINE_END; buttonPanel.add(btnAddress, constraints); // JPanel fill1 = new JPanel(); // fill1.setOpaque(false); // constraints.fill = GridBagConstraints.HORIZONTAL; // constraints.gridx = 3; // constraints.gridy = 0; // constraints.weightx = 200; // constraints.weighty = 1; // constraints.gridwidth = 1; // constraints.gridheight = 1; // constraints.anchor = GridBagConstraints.CENTER; // buttonPanel.add(fill1, constraints); return buttonPanel; } private void scrollPaneSetup() { scrollPane.setBackground(Themes.currentTheme.detailPanelBackground()); scrollPane.getViewport().setBackground(Themes.currentTheme.detailPanelBackground()); scrollPane.setComponentOrientation(ComponentOrientation.getOrientation(LocaliserUtils.getLocale())); scrollPane.getHorizontalScrollBar().setUnitIncrement(BitherSetting.SCROLL_INCREMENT); scrollPane.getVerticalScrollBar().setUnitIncrement(BitherSetting.SCROLL_INCREMENT); scrollPane.setOpaque(true); scrollPane.getViewport().setOpaque(true); scrollPane.setBorder(BorderFactory.createMatteBorder(1, 1, 0, 1, SystemColor.windowBorder)); ScrollBarUIDecorator.apply(scrollPane, false); } @Override public void displayView(DisplayHint displayHint) { if (AddressManager.getInstance().getAllAddresses().size() == 0 && AddressManager.getInstance().getHdAccount() == null) { showTransactionHeaderForm.setVisible(false); } else { showTransactionHeaderForm.setVisible(true); } justifyColumnHeaders(); scrollPaneSetup(); // Amount decimal aligned DecimalAlignRenderer decimalAlignRenderer = new DecimalAlignRenderer(ShowTransactionsForm.this); table.getColumnModel().getColumn(2).setCellRenderer(decimalAlignRenderer); showTransactionHeaderForm.updateUI(); refreshTx(); if (selectedRow > -1 && selectedRow < table.getRowCount()) { table.setRowSelectionInterval(selectedRow, selectedRow); } } public void setSelectedRow(int row) { selectedRow = row; } public Tx getSelectedRowData() { int row = table.getSelectedRow(); return txTableModel.getRow(rowSorter.convertRowIndexToModel(row)); } public JTable getTable() { return table; } @Override public ViewEnum getViewId() { return ViewEnum.TRANSACTIONS_VIEW; } @Override public void notificatTx(String address, Tx tx, Tx.TxNotificationType txNotificationType, long deltaBalance) { String actionAddress = ""; if (Bither.getActionAddress() != null) { actionAddress = Bither.getActionAddress().getAddress(); } if (Utils.compareString(address, actionAddress)) { displayView(DisplayHint.WALLET_TRANSACTIONS_HAVE_CHANGED); } } @Override public void blockChange() { displayView(DisplayHint.WALLET_TRANSACTIONS_HAVE_CHANGED); } @Override public JPanel getPanel() { return panelMain; } { // GUI initializer generated by IntelliJ IDEA GUI Designer // >>> IMPORTANT!! <<< // DO NOT EDIT OR ADD ANY CODE HERE! $$$setupUI$$$(); } /** * Method generated by IntelliJ IDEA GUI Designer * >>> IMPORTANT!! <<< * DO NOT edit this method OR call it in your code! * * @noinspection ALL */ private void $$$setupUI$$$() { panelMain = new JPanel(); panelMain.setLayout(new BorderLayout(0, 0)); } /** * @noinspection ALL */ public JComponent $$$getRootComponent$$$() { return panelMain; } class SharedListSelectionHandler implements ListSelectionListener { private Action showTransactionDetailsAction; SharedListSelectionHandler(Action action) { this.showTransactionDetailsAction = action; } public void valueChanged(ListSelectionEvent e) { ListSelectionModel lsm = (ListSelectionModel) e.getSource(); if (lsm.isSelectionEmpty()) { showTransactionDetailsAction.setEnabled(false); showTransactionsButton.invalidate(); showTransactionsButton.validate(); showTransactionsButton.repaint(); } else { // Find out which indexes are selected. int minIndex = lsm.getMinSelectionIndex(); int maxIndex = lsm.getMaxSelectionIndex(); for (int i = minIndex; i <= maxIndex; i++) { if (lsm.isSelectedIndex(i)) { showTransactionDetailsAction.setEnabled(true); showTransactionsButton.invalidate(); showTransactionsButton.validate(); showTransactionsButton.repaint(); break; } } } } } Comparator<Date> dateComparator = new Comparator<Date>() { @Override public int compare(Date o1, Date o2) { if (o1 == null) { if (o2 == null) { return 0; } else { return 1; } } else { if (o2 == null) { return -1; } } long n1 = o1.getTime(); long n2 = o2.getTime(); if (n1 == 0) { // Object 1 has missing date. return 1; } if (n2 == 0) { // Object 2 has missing date. return -1; } if (n1 < n2) { return -1; } else if (n1 > n2) { return 1; } else { return 0; } } }; Comparator<String> comparatorNumber = new Comparator<String>() { @Override public int compare(String o1, String o2) { try { if (o1 == null) { if (o2 == null) { return 0; } else { return 1; } } else { if (o2 == null) { return -1; } } DecimalFormat formatter = (DecimalFormat) DecimalFormat.getInstance(LocaliserUtils.getLocale()); formatter.setParseBigDecimal(true); // Convert spaces to non breakable space. o1 = o1.replaceAll(" ", "\u00A0"); o2 = o2.replaceAll(" ", "\u00A0"); BigDecimal parsedO1 = (BigDecimal) formatter.parse(o1); BigDecimal parsedO2 = (BigDecimal) formatter.parse(o2); return parsedO1.compareTo(parsedO2); } catch (NumberFormatException nfe) { return o1.compareTo(o2); } catch (ParseException e) { return o1.compareTo(o2); } } }; }