More updates.

This commit is contained in:
Andrew Lalis 2023-04-19 17:31:49 +02:00
parent 830ca054d8
commit 9f4e3813a0
8 changed files with 233 additions and 52 deletions

View File

@ -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();
}
} }
} }

View File

@ -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;

View File

@ -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));
} }

View File

@ -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());
} }

View File

@ -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) {
}
}

View File

@ -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;

View File

@ -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();
}
} }
} }

View File

@ -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();
}
}
}