More updates.
This commit is contained in:
parent
830ca054d8
commit
9f4e3813a0
|
@ -3,17 +3,25 @@ package com.github.andrewlalis.running_every_day;
|
||||||
import com.formdev.flatlaf.FlatLightLaf;
|
import com.formdev.flatlaf.FlatLightLaf;
|
||||||
import com.github.andrewlalis.running_every_day.data.DataSource;
|
import com.github.andrewlalis.running_every_day.data.DataSource;
|
||||||
import com.github.andrewlalis.running_every_day.view.RecorderAppWindow;
|
import com.github.andrewlalis.running_every_day.view.RecorderAppWindow;
|
||||||
|
import com.github.andrewlalis.running_every_day.view.WindowDataSourceCloser;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
import java.sql.SQLException;
|
import java.sql.SQLException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The main application entrypoint.
|
* The main application entrypoint.
|
||||||
*/
|
*/
|
||||||
public class RecorderApp {
|
public class RecorderApp {
|
||||||
public static void main(String[] args) throws SQLException {
|
public static void main(String[] args) {
|
||||||
DataSource dataSource = new DataSource("jdbc:sqlite:runs.db");
|
|
||||||
FlatLightLaf.setup();
|
FlatLightLaf.setup();
|
||||||
var window = new RecorderAppWindow(dataSource);
|
try {
|
||||||
window.setVisible(true);
|
DataSource dataSource = new DataSource("jdbc:sqlite:runs.db");
|
||||||
|
var window = new RecorderAppWindow(dataSource);
|
||||||
|
window.addWindowListener(new WindowDataSourceCloser(dataSource));
|
||||||
|
window.setVisible(true);
|
||||||
|
} catch (SQLException e) {
|
||||||
|
JOptionPane.showMessageDialog(null, "Failed to open database: " + e.getMessage(), "Error", JOptionPane.ERROR_MESSAGE);
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,9 @@ import java.sql.SQLException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A single object that serves as the application's data source.
|
||||||
|
*/
|
||||||
public class DataSource {
|
public class DataSource {
|
||||||
private final Connection conn;
|
private final Connection conn;
|
||||||
|
|
||||||
|
|
|
@ -35,6 +35,13 @@ public record RunRecord(
|
||||||
return (int) duration.getSeconds();
|
return (int) duration.getSeconds();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String durationFormatted() {
|
||||||
|
if (duration.toHoursPart() > 0) {
|
||||||
|
return String.format("%d:%02d:%02d", duration.toHoursPart(), duration.toMinutesPart(), duration.toSecondsPart());
|
||||||
|
}
|
||||||
|
return String.format("%02d:%02d", duration.toMinutesPart(), duration.toSecondsPart());
|
||||||
|
}
|
||||||
|
|
||||||
public BigDecimal weightGrams() {
|
public BigDecimal weightGrams() {
|
||||||
return weightKg.multiply(BigDecimal.valueOf(1000));
|
return weightKg.multiply(BigDecimal.valueOf(1000));
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@ import java.util.List;
|
||||||
|
|
||||||
public record RunRecordRepository(Connection conn) {
|
public record RunRecordRepository(Connection conn) {
|
||||||
public Page<RunRecord> findAll(Pagination pagination) throws SQLException {
|
public Page<RunRecord> findAll(Pagination pagination) throws SQLException {
|
||||||
String query = "SELECT id, date, start_time, distance, duration, weight, comment FROM run";
|
String query = "SELECT * FROM run ORDER BY date ASC";
|
||||||
return pagination.execute(conn, query, new RunRecord.Mapper());
|
return pagination.execute(conn, query, new RunRecord.Mapper());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,78 @@
|
||||||
|
package com.github.andrewlalis.running_every_day.view;
|
||||||
|
|
||||||
|
import com.github.andrewlalis.running_every_day.data.RunRecord;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class RunRecordRowSorter extends RowSorter<RunRecord> {
|
||||||
|
@Override
|
||||||
|
public RunRecord getModel() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void toggleSortOrder(int column) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int convertRowIndexToModel(int index) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int convertRowIndexToView(int index) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setSortKeys(List<? extends SortKey> keys) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<? extends SortKey> getSortKeys() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getViewRowCount() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getModelRowCount() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void modelStructureChanged() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void allRowsChanged() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void rowsInserted(int firstRow, int endRow) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void rowsDeleted(int firstRow, int endRow) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void rowsUpdated(int firstRow, int endRow) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void rowsUpdated(int firstRow, int endRow, int column) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,22 +1,84 @@
|
||||||
package com.github.andrewlalis.running_every_day.view;
|
package com.github.andrewlalis.running_every_day.view;
|
||||||
|
|
||||||
import com.github.andrewlalis.running_every_day.data.Page;
|
import com.github.andrewlalis.running_every_day.data.DataSource;
|
||||||
|
import com.github.andrewlalis.running_every_day.data.Pagination;
|
||||||
import com.github.andrewlalis.running_every_day.data.RunRecord;
|
import com.github.andrewlalis.running_every_day.data.RunRecord;
|
||||||
|
|
||||||
import javax.swing.table.AbstractTableModel;
|
import javax.swing.table.AbstractTableModel;
|
||||||
|
import java.sql.SQLException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class RunRecordTableModel extends AbstractTableModel {
|
public class RunRecordTableModel extends AbstractTableModel {
|
||||||
|
private final DataSource dataSource;
|
||||||
private List<RunRecord> records;
|
private List<RunRecord> records;
|
||||||
|
private int cachedPageCount;
|
||||||
|
private Pagination currentPage;
|
||||||
|
|
||||||
public RunRecordTableModel() {
|
public RunRecordTableModel(DataSource dataSource) {
|
||||||
|
this.dataSource = dataSource;
|
||||||
|
this.cachedPageCount = 0;
|
||||||
this.records = new ArrayList<>(0);
|
this.records = new ArrayList<>(0);
|
||||||
|
this.currentPage = new Pagination(0, 20);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setPage(Page<RunRecord> page) {
|
public void setPageSize(int size) {
|
||||||
this.records = page.items();
|
this.currentPage = new Pagination(0, size, currentPage.sorts());
|
||||||
this.fireTableDataChanged();
|
loadPage();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void firstPage() {
|
||||||
|
this.currentPage = new Pagination(0, currentPage.size(), currentPage.sorts());
|
||||||
|
loadPage();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean canGoToFirstPage() {
|
||||||
|
return currentPage.page() != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void lastPage() {
|
||||||
|
int pageIdx = cachedPageCount > 0 ? cachedPageCount - 1 : 0;
|
||||||
|
this.currentPage = new Pagination(pageIdx, currentPage.size(), currentPage.sorts());
|
||||||
|
loadPage();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean canGoToLastPage() {
|
||||||
|
return currentPage.page() < cachedPageCount - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void nextPage() {
|
||||||
|
this.currentPage = currentPage.nextPage();
|
||||||
|
loadPage();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean canGoToNextPage() {
|
||||||
|
return currentPage.page() < cachedPageCount - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void previousPage() {
|
||||||
|
this.currentPage = currentPage.previousPage();
|
||||||
|
loadPage();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean canGoToPreviousPage() {
|
||||||
|
return currentPage.page() > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Pagination getPagination() {
|
||||||
|
return currentPage;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadPage() {
|
||||||
|
try {
|
||||||
|
var page = dataSource.runRecords().findAll(currentPage);
|
||||||
|
cachedPageCount = (int) dataSource.runRecords().pageCount(currentPage.size());
|
||||||
|
records = page.items();
|
||||||
|
this.fireTableDataChanged();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
records = new ArrayList<>();
|
||||||
|
currentPage = new Pagination(0, 20);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -38,7 +100,7 @@ public class RunRecordTableModel extends AbstractTableModel {
|
||||||
case 1 -> r.date().toString();
|
case 1 -> r.date().toString();
|
||||||
case 2 -> r.startTime().toString();
|
case 2 -> r.startTime().toString();
|
||||||
case 3 -> r.distanceKm().toPlainString();
|
case 3 -> r.distanceKm().toPlainString();
|
||||||
case 4 -> r.duration().toString();
|
case 4 -> r.durationFormatted();
|
||||||
case 5 -> r.weightKg().toPlainString();
|
case 5 -> r.weightKg().toPlainString();
|
||||||
case 6 -> r.comment();
|
case 6 -> r.comment();
|
||||||
default -> null;
|
default -> null;
|
||||||
|
|
|
@ -1,81 +1,76 @@
|
||||||
package com.github.andrewlalis.running_every_day.view;
|
package com.github.andrewlalis.running_every_day.view;
|
||||||
|
|
||||||
import com.github.andrewlalis.running_every_day.data.DataSource;
|
import com.github.andrewlalis.running_every_day.data.DataSource;
|
||||||
import com.github.andrewlalis.running_every_day.data.Pagination;
|
|
||||||
|
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.sql.SQLException;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A panel for displaying a table view of run records, and various controls for
|
* A panel for displaying a table view of run records, and various controls for
|
||||||
* navigating the data.
|
* navigating the data.
|
||||||
*/
|
*/
|
||||||
public class RunRecordsPanel extends JPanel {
|
public class RunRecordsPanel extends JPanel {
|
||||||
private final DataSource dataSource;
|
private final RunRecordTableModel tableModel;
|
||||||
private final RunRecordTableModel tableModel = new RunRecordTableModel();
|
|
||||||
private final JTextField currentPageField;
|
private final JTextField currentPageField;
|
||||||
private final JComboBox<Integer> pageSizeSelector;
|
|
||||||
private final JButton firstPageButton;
|
private final JButton firstPageButton;
|
||||||
private final JButton previousPageButton;
|
private final JButton previousPageButton;
|
||||||
private final JButton nextPageButton;
|
private final JButton nextPageButton;
|
||||||
private final JButton lastPageButton;
|
private final JButton lastPageButton;
|
||||||
|
|
||||||
private Pagination currentPage;
|
|
||||||
public RunRecordsPanel(DataSource dataSource) {
|
public RunRecordsPanel(DataSource dataSource) {
|
||||||
super(new BorderLayout());
|
super(new BorderLayout());
|
||||||
this.dataSource = dataSource;
|
this.tableModel = new RunRecordTableModel(dataSource);
|
||||||
this.currentPage = new Pagination(0, 20);
|
|
||||||
|
|
||||||
var table = new JTable(tableModel);
|
var table = new JTable(tableModel);
|
||||||
|
table.getTableHeader().setReorderingAllowed(false);
|
||||||
|
table.setAutoResizeMode(JTable.AUTO_RESIZE_LAST_COLUMN);
|
||||||
|
table.getColumnModel().getColumn(0).setMaxWidth(40);
|
||||||
|
table.getColumnModel().getColumn(1).setMaxWidth(80);
|
||||||
|
table.getColumnModel().getColumn(2).setMaxWidth(80);
|
||||||
|
table.getColumnModel().getColumn(3).setMaxWidth(100);
|
||||||
|
table.getColumnModel().getColumn(4).setMaxWidth(80);
|
||||||
|
table.getColumnModel().getColumn(5).setMaxWidth(80);
|
||||||
var scrollPane = new JScrollPane(table, ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);
|
var scrollPane = new JScrollPane(table, ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);
|
||||||
this.add(scrollPane, BorderLayout.CENTER);
|
this.add(scrollPane, BorderLayout.CENTER);
|
||||||
|
|
||||||
JPanel paginationPanel = new JPanel(new GridLayout(1, 6));
|
JPanel paginationPanel = new JPanel(new GridLayout(1, 6));
|
||||||
firstPageButton = new JButton("First Page");
|
firstPageButton = new JButton("First Page");
|
||||||
firstPageButton.addActionListener(e -> {
|
firstPageButton.addActionListener(e -> {
|
||||||
currentPage = new Pagination(0, currentPage.size(), currentPage.sorts());
|
tableModel.firstPage();
|
||||||
loadPage();
|
updateButtonStates();
|
||||||
});
|
});
|
||||||
previousPageButton = new JButton("Previous Page");
|
previousPageButton = new JButton("Previous Page");
|
||||||
previousPageButton.addActionListener(e -> {
|
previousPageButton.addActionListener(e -> {
|
||||||
currentPage = currentPage.previousPage();
|
tableModel.previousPage();
|
||||||
loadPage();
|
updateButtonStates();
|
||||||
});
|
});
|
||||||
nextPageButton = new JButton("Next Page");
|
nextPageButton = new JButton("Next Page");
|
||||||
nextPageButton.addActionListener(e -> {
|
nextPageButton.addActionListener(e -> {
|
||||||
currentPage = currentPage.nextPage();
|
tableModel.nextPage();
|
||||||
loadPage();
|
updateButtonStates();
|
||||||
});
|
});
|
||||||
lastPageButton = new JButton("Last Page");
|
lastPageButton = new JButton("Last Page");
|
||||||
lastPageButton.addActionListener(e -> {
|
lastPageButton.addActionListener(e -> {
|
||||||
try {
|
tableModel.lastPage();
|
||||||
long pageCount = dataSource.runRecords().pageCount(currentPage.size());
|
updateButtonStates();
|
||||||
if (pageCount <= 0) pageCount = 1;
|
|
||||||
currentPage = new Pagination((int) (pageCount - 1), currentPage.size(), currentPage.sorts());
|
|
||||||
loadPage();
|
|
||||||
} catch (SQLException ex) {
|
|
||||||
ex.printStackTrace();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
JPanel currentPagePanel = new JPanel();
|
JPanel currentPagePanel = new JPanel();
|
||||||
currentPagePanel.add(new JLabel("Current Page: "));
|
currentPagePanel.add(new JLabel("Current Page: "));
|
||||||
this.currentPageField = new JTextField("1", 3);
|
this.currentPageField = new JTextField("1", 3);
|
||||||
// TODO: Add document change listener.
|
|
||||||
currentPagePanel.add(this.currentPageField);
|
currentPagePanel.add(this.currentPageField);
|
||||||
|
|
||||||
JPanel pageSizePanel = new JPanel();
|
JPanel pageSizePanel = new JPanel();
|
||||||
pageSizePanel.add(new JLabel("Page Size: "));
|
pageSizePanel.add(new JLabel("Page Size: "));
|
||||||
pageSizeSelector = new JComboBox<>(new Integer[]{5, 10, 20, 50, 100, 500});
|
JComboBox<Integer> pageSizeSelector = new JComboBox<>(new Integer[]{5, 10, 20, 50, 100, 500});
|
||||||
pageSizeSelector.setSelectedItem(this.currentPage.size());
|
pageSizeSelector.setSelectedItem(tableModel.getPagination().size());
|
||||||
pageSizeSelector.addItemListener(e -> {
|
pageSizeSelector.addItemListener(e -> {
|
||||||
currentPage = new Pagination(0, (Integer) e.getItem(), currentPage.sorts());
|
int size = (int) e.getItem();
|
||||||
loadPage();
|
tableModel.setPageSize(size);
|
||||||
|
updateButtonStates();
|
||||||
});
|
});
|
||||||
pageSizePanel.add(pageSizeSelector);
|
pageSizePanel.add(pageSizeSelector);
|
||||||
|
|
||||||
|
|
||||||
paginationPanel.add(firstPageButton);
|
paginationPanel.add(firstPageButton);
|
||||||
paginationPanel.add(previousPageButton);
|
paginationPanel.add(previousPageButton);
|
||||||
paginationPanel.add(currentPagePanel);
|
paginationPanel.add(currentPagePanel);
|
||||||
|
@ -84,21 +79,22 @@ public class RunRecordsPanel extends JPanel {
|
||||||
paginationPanel.add(lastPageButton);
|
paginationPanel.add(lastPageButton);
|
||||||
|
|
||||||
this.add(paginationPanel, BorderLayout.SOUTH);
|
this.add(paginationPanel, BorderLayout.SOUTH);
|
||||||
|
SwingUtilities.invokeLater(() -> {
|
||||||
|
tableModel.firstPage();
|
||||||
|
updateButtonStates();
|
||||||
|
});
|
||||||
|
|
||||||
SwingUtilities.invokeLater(this::loadPage);
|
JPanel actionsPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
|
||||||
|
JButton addActionButton = new JButton("Add a Record");
|
||||||
|
actionsPanel.add(addActionButton);
|
||||||
|
this.add(actionsPanel, BorderLayout.NORTH);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadPage() {
|
private void updateButtonStates() {
|
||||||
try {
|
firstPageButton.setEnabled(tableModel.canGoToFirstPage());
|
||||||
this.tableModel.setPage(dataSource.runRecords().findAll(this.currentPage));
|
nextPageButton.setEnabled(tableModel.canGoToNextPage());
|
||||||
long pageCount = dataSource.runRecords().pageCount(this.currentPage.size());
|
previousPageButton.setEnabled(tableModel.canGoToPreviousPage());
|
||||||
this.firstPageButton.setEnabled(this.currentPage.page() != 0);
|
lastPageButton.setEnabled(tableModel.canGoToLastPage());
|
||||||
this.previousPageButton.setEnabled(this.currentPage.page() > 0);
|
currentPageField.setText(String.valueOf(tableModel.getPagination().page() + 1));
|
||||||
this.nextPageButton.setEnabled(this.currentPage.page() < pageCount - 1);
|
|
||||||
this.lastPageButton.setEnabled(this.currentPage.page() != pageCount - 1);
|
|
||||||
this.currentPageField.setText(String.valueOf(this.currentPage.page() + 1));
|
|
||||||
} catch (SQLException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
package com.github.andrewlalis.running_every_day.view;
|
||||||
|
|
||||||
|
import com.github.andrewlalis.running_every_day.data.DataSource;
|
||||||
|
|
||||||
|
import java.awt.event.WindowAdapter;
|
||||||
|
import java.awt.event.WindowEvent;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simple listener that closes the given datasource when the window closes.
|
||||||
|
*/
|
||||||
|
public class WindowDataSourceCloser extends WindowAdapter {
|
||||||
|
private final DataSource dataSource;
|
||||||
|
|
||||||
|
public WindowDataSourceCloser(DataSource dataSource) {
|
||||||
|
this.dataSource = dataSource;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void windowClosing(WindowEvent e) {
|
||||||
|
try {
|
||||||
|
dataSource.close();
|
||||||
|
} catch (SQLException ex) {
|
||||||
|
ex.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue