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.github.andrewlalis.running_every_day.data.DataSource;
|
||||
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;
|
||||
|
||||
/**
|
||||
* The main application entrypoint.
|
||||
*/
|
||||
public class RecorderApp {
|
||||
public static void main(String[] args) throws SQLException {
|
||||
DataSource dataSource = new DataSource("jdbc:sqlite:runs.db");
|
||||
public static void main(String[] args) {
|
||||
FlatLightLaf.setup();
|
||||
var window = new RecorderAppWindow(dataSource);
|
||||
window.setVisible(true);
|
||||
try {
|
||||
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.List;
|
||||
|
||||
/**
|
||||
* A single object that serves as the application's data source.
|
||||
*/
|
||||
public class DataSource {
|
||||
private final Connection conn;
|
||||
|
||||
|
|
|
@ -35,6 +35,13 @@ public record RunRecord(
|
|||
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() {
|
||||
return weightKg.multiply(BigDecimal.valueOf(1000));
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ import java.util.List;
|
|||
|
||||
public record RunRecordRepository(Connection conn) {
|
||||
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());
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
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 javax.swing.table.AbstractTableModel;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class RunRecordTableModel extends AbstractTableModel {
|
||||
private final DataSource dataSource;
|
||||
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.currentPage = new Pagination(0, 20);
|
||||
}
|
||||
|
||||
public void setPage(Page<RunRecord> page) {
|
||||
this.records = page.items();
|
||||
this.fireTableDataChanged();
|
||||
public void setPageSize(int size) {
|
||||
this.currentPage = new Pagination(0, size, currentPage.sorts());
|
||||
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
|
||||
|
@ -38,7 +100,7 @@ public class RunRecordTableModel extends AbstractTableModel {
|
|||
case 1 -> r.date().toString();
|
||||
case 2 -> r.startTime().toString();
|
||||
case 3 -> r.distanceKm().toPlainString();
|
||||
case 4 -> r.duration().toString();
|
||||
case 4 -> r.durationFormatted();
|
||||
case 5 -> r.weightKg().toPlainString();
|
||||
case 6 -> r.comment();
|
||||
default -> null;
|
||||
|
|
|
@ -1,81 +1,76 @@
|
|||
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.Pagination;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.sql.SQLException;
|
||||
|
||||
/**
|
||||
* A panel for displaying a table view of run records, and various controls for
|
||||
* navigating the data.
|
||||
*/
|
||||
public class RunRecordsPanel extends JPanel {
|
||||
private final DataSource dataSource;
|
||||
private final RunRecordTableModel tableModel = new RunRecordTableModel();
|
||||
private final RunRecordTableModel tableModel;
|
||||
private final JTextField currentPageField;
|
||||
private final JComboBox<Integer> pageSizeSelector;
|
||||
private final JButton firstPageButton;
|
||||
private final JButton previousPageButton;
|
||||
private final JButton nextPageButton;
|
||||
private final JButton lastPageButton;
|
||||
|
||||
private Pagination currentPage;
|
||||
public RunRecordsPanel(DataSource dataSource) {
|
||||
super(new BorderLayout());
|
||||
this.dataSource = dataSource;
|
||||
this.currentPage = new Pagination(0, 20);
|
||||
this.tableModel = new RunRecordTableModel(dataSource);
|
||||
|
||||
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);
|
||||
this.add(scrollPane, BorderLayout.CENTER);
|
||||
|
||||
JPanel paginationPanel = new JPanel(new GridLayout(1, 6));
|
||||
firstPageButton = new JButton("First Page");
|
||||
firstPageButton.addActionListener(e -> {
|
||||
currentPage = new Pagination(0, currentPage.size(), currentPage.sorts());
|
||||
loadPage();
|
||||
tableModel.firstPage();
|
||||
updateButtonStates();
|
||||
});
|
||||
previousPageButton = new JButton("Previous Page");
|
||||
previousPageButton.addActionListener(e -> {
|
||||
currentPage = currentPage.previousPage();
|
||||
loadPage();
|
||||
tableModel.previousPage();
|
||||
updateButtonStates();
|
||||
});
|
||||
nextPageButton = new JButton("Next Page");
|
||||
nextPageButton.addActionListener(e -> {
|
||||
currentPage = currentPage.nextPage();
|
||||
loadPage();
|
||||
tableModel.nextPage();
|
||||
updateButtonStates();
|
||||
});
|
||||
lastPageButton = new JButton("Last Page");
|
||||
lastPageButton.addActionListener(e -> {
|
||||
try {
|
||||
long pageCount = dataSource.runRecords().pageCount(currentPage.size());
|
||||
if (pageCount <= 0) pageCount = 1;
|
||||
currentPage = new Pagination((int) (pageCount - 1), currentPage.size(), currentPage.sorts());
|
||||
loadPage();
|
||||
} catch (SQLException ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
tableModel.lastPage();
|
||||
updateButtonStates();
|
||||
});
|
||||
|
||||
JPanel currentPagePanel = new JPanel();
|
||||
currentPagePanel.add(new JLabel("Current Page: "));
|
||||
this.currentPageField = new JTextField("1", 3);
|
||||
// TODO: Add document change listener.
|
||||
currentPagePanel.add(this.currentPageField);
|
||||
|
||||
JPanel pageSizePanel = new JPanel();
|
||||
pageSizePanel.add(new JLabel("Page Size: "));
|
||||
pageSizeSelector = new JComboBox<>(new Integer[]{5, 10, 20, 50, 100, 500});
|
||||
pageSizeSelector.setSelectedItem(this.currentPage.size());
|
||||
JComboBox<Integer> pageSizeSelector = new JComboBox<>(new Integer[]{5, 10, 20, 50, 100, 500});
|
||||
pageSizeSelector.setSelectedItem(tableModel.getPagination().size());
|
||||
pageSizeSelector.addItemListener(e -> {
|
||||
currentPage = new Pagination(0, (Integer) e.getItem(), currentPage.sorts());
|
||||
loadPage();
|
||||
int size = (int) e.getItem();
|
||||
tableModel.setPageSize(size);
|
||||
updateButtonStates();
|
||||
});
|
||||
pageSizePanel.add(pageSizeSelector);
|
||||
|
||||
|
||||
paginationPanel.add(firstPageButton);
|
||||
paginationPanel.add(previousPageButton);
|
||||
paginationPanel.add(currentPagePanel);
|
||||
|
@ -84,21 +79,22 @@ public class RunRecordsPanel extends JPanel {
|
|||
paginationPanel.add(lastPageButton);
|
||||
|
||||
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() {
|
||||
try {
|
||||
this.tableModel.setPage(dataSource.runRecords().findAll(this.currentPage));
|
||||
long pageCount = dataSource.runRecords().pageCount(this.currentPage.size());
|
||||
this.firstPageButton.setEnabled(this.currentPage.page() != 0);
|
||||
this.previousPageButton.setEnabled(this.currentPage.page() > 0);
|
||||
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();
|
||||
}
|
||||
private void updateButtonStates() {
|
||||
firstPageButton.setEnabled(tableModel.canGoToFirstPage());
|
||||
nextPageButton.setEnabled(tableModel.canGoToNextPage());
|
||||
previousPageButton.setEnabled(tableModel.canGoToPreviousPage());
|
||||
lastPageButton.setEnabled(tableModel.canGoToLastPage());
|
||||
currentPageField.setText(String.valueOf(tableModel.getPagination().page() + 1));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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