diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml index b36fac6ea4..0dbcd4785a 100644 --- a/.github/workflows/maven.yml +++ b/.github/workflows/maven.yml @@ -13,7 +13,7 @@ jobs: steps: - uses: actions/checkout@v2 - name: Checkout core - run: git clone --depth=50 --branch=develop https://github.com/informatici/openhospital-core.git openhospital-core + run: git clone --depth=50 --branch=OP-162_ResultSet_Pagination_prototype https://github.com/informatici/openhospital-core.git openhospital-core - name: Install core run: cd openhospital-core && mvn install -DskipTests=true && cd .. - name: Set up JDK 1.8 diff --git a/bundle/language_ar.properties b/bundle/language_ar.properties index 44d881809e..f23591fcb9 100644 --- a/bundle/language_ar.properties +++ b/bundle/language_ar.properties @@ -55,6 +55,8 @@ angal.common.delete = ح angal.common.edit = تعديل angal.common.minutes = الدقائق angal.common.minutesabbr = الدقائق +angal.common.page.range.fmt = صفحة {0} / {1} +angal.common.pagesize.label = مقاس الصفحه: angal.common.patientID = رقم تعريف المريض angal.common.save = حفظ angal.dicom.delete.no = لا diff --git a/bundle/language_en.properties b/bundle/language_en.properties index 733a06ca92..e888e08e88 100644 --- a/bundle/language_en.properties +++ b/bundle/language_en.properties @@ -297,6 +297,9 @@ angal.common.note.title angal.common.note.txt = Note angal.common.ok.btn = OK angal.common.ok.btn.key = O +angal.common.page.range.fmt = Page {0} / {1} +angal.common.page.range.fmt.txt = Page +angal.common.pagesize.label = Page Size: angal.common.patient.txt = Patient angal.common.patientID = Pat. ID angal.common.pleaseinsertacode.msg = Please insert a code. diff --git a/bundle/language_es.properties b/bundle/language_es.properties index c5578cf4ae..9d4cf49370 100644 --- a/bundle/language_es.properties +++ b/bundle/language_es.properties @@ -297,6 +297,8 @@ angal.common.note.title angal.common.note.txt = Notas angal.common.ok.btn = OK angal.common.ok.btn.key = O +angal.common.page.range.fmt = Página {0} / {1} +angal.common.pagesize.label = Paginación angal.common.patient.txt = Paciente angal.common.patientID = ID.Pac. angal.common.pleaseinsertacode.msg = Introduzca un código. diff --git a/bundle/language_fr.properties b/bundle/language_fr.properties index 57d8f9f9e8..5770421b40 100644 --- a/bundle/language_fr.properties +++ b/bundle/language_fr.properties @@ -297,6 +297,8 @@ angal.common.note.title angal.common.note.txt = Remarque angal.common.ok.btn = OK angal.common.ok.btn.key = O +angal.common.page.range.fmt = Page {0} / {1} +angal.common.pagesize.label = Taille: angal.common.patient.txt = Patient angal.common.patientID = ID.Pat angal.common.pleaseinsertacode.msg = Veuillez insérer un code. diff --git a/bundle/language_it.properties b/bundle/language_it.properties index 49df729f50..5dda98c97f 100644 --- a/bundle/language_it.properties +++ b/bundle/language_it.properties @@ -297,6 +297,8 @@ angal.common.note.title angal.common.note.txt = Nota angal.common.ok.btn = OK angal.common.ok.btn.key = O +angal.common.page.range.fmt = Pagina {0} / {1} +angal.common.pagesize.label = Paginazione: angal.common.patient.txt = Paziente angal.common.patientID = ID Paz. angal.common.pleaseinsertacode.msg = Si prega di inserire un codice. diff --git a/bundle/language_pt.properties b/bundle/language_pt.properties index e33b814b3d..f7e643362b 100644 --- a/bundle/language_pt.properties +++ b/bundle/language_pt.properties @@ -57,6 +57,8 @@ angal.common.edit = Ed angal.common.hours = Horas angal.common.minutes = Minutos angal.common.minutesabbr = Min +angal.common.page.range.fmt = Página {0} / {1} +angal.common.pagesize.label = Paginação angal.common.patientID = ID.Pac. angal.common.save = Salvar angal.common.uom.bpm = bpm diff --git a/bundle/language_zh_CN.properties b/bundle/language_zh_CN.properties index 0cd80c4d8d..24147436d0 100644 --- a/bundle/language_zh_CN.properties +++ b/bundle/language_zh_CN.properties @@ -30,3 +30,5 @@ angal.admission.delivery.title = 康复(Delivery) angal.admission.deliverydate.border = 康复(Delivery)日期 angal.admission.deliveryresultype.border = 康复(Delivery)原因及类型 angal.admission.deliverytype.border = 康复(Delivery)类型 +angal.common.page.range.fmt = 页 {0} / {1} +angal.common.pagesize.label = 页面大小: diff --git a/src/main/java/org/isf/opd/gui/OpdBrowser.java b/src/main/java/org/isf/opd/gui/OpdBrowser.java index ff3930370a..1185132ed0 100644 --- a/src/main/java/org/isf/opd/gui/OpdBrowser.java +++ b/src/main/java/org/isf/opd/gui/OpdBrowser.java @@ -33,9 +33,11 @@ import java.awt.event.KeyListener; import java.text.DateFormat; import java.text.SimpleDateFormat; +import java.time.Year; import java.util.ArrayList; import java.util.Calendar; import java.util.GregorianCalendar; +import java.util.List; import java.util.Locale; import javax.swing.AbstractButton; @@ -50,13 +52,11 @@ import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JRadioButton; -import javax.swing.JScrollPane; import javax.swing.JTable; import javax.swing.JTextField; import javax.swing.ListSelectionModel; import javax.swing.SwingConstants; import javax.swing.table.DefaultTableCellRenderer; -import javax.swing.table.DefaultTableModel; import javax.swing.table.TableColumnModel; import javax.swing.text.AttributeSet; import javax.swing.text.BadLocationException; @@ -76,8 +76,10 @@ import org.isf.utils.exception.OHServiceException; import org.isf.utils.jobjects.MessageDialog; import org.isf.utils.jobjects.ModalJFrame; +import org.isf.utils.jobjects.PageableTableModel; +import org.isf.utils.jobjects.PaginatedTableDecorator; +import org.isf.utils.jobjects.PaginationDataProvider; import org.isf.utils.jobjects.VoLimitedTextField; -import org.isf.utils.time.TimeTools; /** * ------------------------------------------ @@ -101,11 +103,13 @@ * - version is now 1.2.2 * ------------------------------------------ */ -public class OpdBrowser extends ModalJFrame implements OpdEdit.SurgeryListener, OpdEditExtended.SurgeryListener { +public class OpdBrowser extends ModalJFrame implements OpdEdit.SurgeryListener, OpdEditExtended.SurgeryListener, PaginationDataProvider { private static final long serialVersionUID = 2372745781159245861L; - private static final SimpleDateFormat dateFormat = new SimpleDateFormat("dd/MM/yy"); + private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("dd/MM/yy"); + private static final int DEFAULT_PAGE_SIZE = 100; + private static final String CURRENT_YEAR = Year.now().toString(); private JPanel jButtonPanel = null; private JPanel jContainPanel = null; @@ -129,7 +133,7 @@ public class OpdBrowser extends ModalJFrame implements OpdEdit.SurgeryListener, private JPanel jAgeToPanel = null; private VoLimitedTextField jAgeToTextField = null; private JPanel jAgePanel = null; - private JComboBox jDiseaseTypeBox; + private JComboBox jDiseaseTypeBox; private JComboBox jDiseaseBox; private JPanel sexPanel = null; private JPanel newPatientPanel = null; @@ -150,7 +154,8 @@ public class OpdBrowser extends ModalJFrame implements OpdEdit.SurgeryListener, MessageBundle.getMessage("angal.opd.diseasetype.col").toUpperCase(), MessageBundle.getMessage("angal.opd.patientstatus.col").toUpperCase() }; - private ArrayList pSur; + private List pSur; + private int pSurSize; private JTable jTable = null; private OpdBrowsingModel model; private int[] pColumnWidth = {50, 50, 70, 70, 150, 30, 30, 195, 195, 50 }; @@ -171,6 +176,15 @@ public class OpdBrowser extends ModalJFrame implements OpdEdit.SurgeryListener, private DiseaseBrowserManager diseaseManager = Context.getApplicationContext().getBean(DiseaseBrowserManager.class); private ArrayList diseases = null; protected AbstractButton searchButton; + + private String disease; + private String diseasetype; + private char sex; + private char newPatient; + private GregorianCalendar dateFrom; + private GregorianCalendar dateTo; + + private PaginatedTableDecorator paginatedDecorator; public JTable getJTable() { if (jTable == null) { @@ -241,9 +255,15 @@ public OpdBrowser(Patient patient) { private JPanel getJButtonPanel() { if (jButtonPanel == null) { jButtonPanel = new JPanel(); - if (MainMenu.checkUserGrants("btnopdnew")) jButtonPanel.add(getJNewButton(), null); - if (MainMenu.checkUserGrants("btnopdedit")) jButtonPanel.add(getJEditButton(), null); - if (MainMenu.checkUserGrants("btnopddel")) jButtonPanel.add(getJDeleteButton(), null); + if (MainMenu.checkUserGrants("btnopdnew")) { + jButtonPanel.add(getJNewButton(), null); + } + if (MainMenu.checkUserGrants("btnopdedit")) { + jButtonPanel.add(getJEditButton(), null); + } + if (MainMenu.checkUserGrants("btnopddel")) { + jButtonPanel.add(getJDeleteButton(), null); + } jButtonPanel.add(getJCloseButton(), null); } return jButtonPanel; @@ -255,13 +275,13 @@ private JPanel getJButtonPanel() { private void initialize() { Toolkit kit = Toolkit.getDefaultToolkit(); Dimension screensize = kit.getScreenSize(); - final int pfrmBase = 20; - final int pfrmWidth = 17; - final int pfrmHeight = 12; - this.setBounds((screensize.width - screensize.width * pfrmWidth / pfrmBase ) / 2, - (screensize.height - screensize.height * pfrmHeight / pfrmBase)/2, - screensize.width * pfrmWidth / pfrmBase+50, - screensize.height * pfrmHeight / pfrmBase+20); + final int pfrmBase = 20; + final int pfrmWidth = 17; + final int pfrmHeight = 12; + this.setBounds((screensize.width - screensize.width * pfrmWidth / pfrmBase) / 2, + (screensize.height - screensize.height * pfrmHeight / pfrmBase) / 2, + screensize.width * pfrmWidth / pfrmBase + 50, + screensize.height * pfrmHeight / pfrmBase + 20); this.setTitle(MessageBundle.getMessage("angal.opd.opdoutpatientdepartment.title")); this.setContentPane(getJContainPanel()); rowCounter.setText(rowCounterText + pSur.size()); @@ -280,7 +300,9 @@ private JPanel getJContainPanel() { jContainPanel.setLayout(new BorderLayout()); jContainPanel.add(getJButtonPanel(), java.awt.BorderLayout.SOUTH); jContainPanel.add(getJSelectionPanel(), java.awt.BorderLayout.WEST); - jContainPanel.add(new JScrollPane(getJTable()), java.awt.BorderLayout.CENTER); + paginatedDecorator = PaginatedTableDecorator.decorate(getJTable(), + OpdBrowser.this, new int[]{5, 10, 20, 50, 75, 100}, DEFAULT_PAGE_SIZE, PaginatedTableDecorator.PAGEBROWSER); + jContainPanel.add(paginatedDecorator.getContentPanel()); validate(); } return jContainPanel; @@ -356,7 +378,7 @@ private JButton getJCloseButton() { } return jCloseButton; } - + /** * This method initializes jDeleteButton * @@ -380,7 +402,7 @@ private JButton getJDeleteButton() { } String message = MessageBundle.formatMessage("angal.opd.deletefollowingopd.fmt.msg", - dateFormat.format(opd.getDate()), + DATE_FORMAT.format(opd.getDate()), opd.getDisease().getDescription() == null ? '[' + MessageBundle.getMessage("angal.opd.notspecified.msg") + ']' : opd.getDisease().getDescription(), @@ -464,10 +486,11 @@ public void focusLost(FocusEvent e) { if (dayFrom.getText().length() != 0) { if (dayFrom.getText().length() == 1) { String typed = dayFrom.getText(); - dayFrom.setText("0" + typed); + dayFrom.setText('0' + typed); } - if (!isValidDay(dayFrom.getText())) + if (!isValidDay(dayFrom.getText())) { dayFrom.setText("1"); + } } } @@ -483,10 +506,11 @@ public void focusLost(FocusEvent e) { if (monthFrom.getText().length() != 0) { if (monthFrom.getText().length() == 1) { String typed = monthFrom.getText(); - monthFrom.setText("0" + typed); + monthFrom.setText('0' + typed); } - if (!isValidMonth(monthFrom.getText())) + if (!isValidMonth(monthFrom.getText())) { monthFrom.setText("1"); + } } } @@ -500,10 +524,12 @@ public void focusGained(FocusEvent e) { @Override public void focusLost(FocusEvent e) { if (yearFrom.getText().length() == 4) { - if (!isValidYear(yearFrom.getText())) - yearFrom.setText("2006"); - } else - yearFrom.setText("2006"); + if (!isValidYear(yearFrom.getText())) { + yearFrom.setText(CURRENT_YEAR); + } + } else { + yearFrom.setText(CURRENT_YEAR); + } } @Override @@ -563,10 +589,11 @@ public void focusLost(FocusEvent e) { if (dayTo.getText().length() != 0) { if (dayTo.getText().length() == 1) { String typed = dayTo.getText(); - dayTo.setText("0" + typed); + dayTo.setText('0' + typed); } - if (!isValidDay(dayTo.getText())) + if (!isValidDay(dayTo.getText())) { dayTo.setText("1"); + } } } @@ -582,10 +609,11 @@ public void focusLost(FocusEvent e) { if (monthTo.getText().length() != 0) { if (monthTo.getText().length() == 1) { String typed = monthTo.getText(); - monthTo.setText("0" + typed); + monthTo.setText('0' + typed); } - if (!isValidMonth(monthTo.getText())) + if (!isValidMonth(monthTo.getText())) { monthTo.setText("1"); + } } } @@ -599,10 +627,12 @@ public void focusGained(FocusEvent e) { @Override public void focusLost(FocusEvent e) { if (yearTo.getText().length() == 4) { - if (!isValidYear(yearTo.getText())) - yearTo.setText("2006"); - } else - yearTo.setText("2006"); + if (!isValidYear(yearTo.getText())) { + yearTo.setText(CURRENT_YEAR); + } + } else { + yearTo.setText(CURRENT_YEAR); + } } @Override @@ -652,15 +682,14 @@ private GregorianCalendar getDateTo() { Integer.parseInt(dayTo.getText())); } - /** * This method initializes jComboBox * * @return javax.swing.JComboBox */ - public JComboBox getDiseaseTypeBox() { + public JComboBox getDiseaseTypeBox() { if (jDiseaseTypeBox == null) { - jDiseaseTypeBox = new JComboBox(); + jDiseaseTypeBox = new JComboBox<>(); jDiseaseTypeBox.setMaximumSize(new Dimension(300,50)); DiseaseTypeBrowserManager diseaseTypeManager = Context.getApplicationContext().getBean(DiseaseTypeBrowserManager.class); @@ -696,7 +725,6 @@ public JComboBox getDiseaseBox() { if (jDiseaseBox == null) { jDiseaseBox = new JComboBox(); jDiseaseBox.setMaximumSize(new Dimension(300, 50)); - } try { if (((DiseaseType)jDiseaseTypeBox.getSelectedItem()).getDescription().equals(MessageBundle.getMessage("angal.common.alltypes.txt"))){ @@ -758,7 +786,6 @@ public JPanel getNewPatientPanel() { return newPatientPanel; } - /** * This method initializes jSelectionDiseasePanel * @@ -799,22 +826,21 @@ public void keyTyped(KeyEvent e) {} searchButton = new JButton(""); searchFieldPanel.add(searchButton); - searchButton.addActionListener(arg0 -> { - jDiseaseBox.removeAllItems(); - jDiseaseBox.addItem(""); - for(Disease disease: - getSearchDiagnosisResults(searchDiseasetextField.getText(), diseases)) { - jDiseaseBox.addItem(disease); - } + searchButton.addActionListener(arg0 -> { + jDiseaseBox.removeAllItems(); + jDiseaseBox.addItem(""); + for (Disease disease : getSearchDiagnosisResults(searchDiseasetextField.getText(), diseases)) { + jDiseaseBox.addItem(disease); + } - if (jDiseaseBox.getItemCount() >= 2){ - jDiseaseBox.setSelectedIndex(1); - } - jDiseaseBox.requestFocus(); - if (jDiseaseBox.getItemCount() > 2){ - jDiseaseBox.showPopup(); - } - }); + if (jDiseaseBox.getItemCount() >= 2) { + jDiseaseBox.setSelectedIndex(1); + } + jDiseaseBox.requestFocus(); + if (jDiseaseBox.getItemCount() > 2) { + jDiseaseBox.showPopup(); + } + }); searchButton.setPreferredSize(new Dimension(20, 20)); searchButton.setIcon(new ImageIcon("rsc/icons/zoom_r_button.png")); return searchFieldPanel; @@ -867,14 +893,15 @@ private JPanel getJAgeFromPanel() { */ private VoLimitedTextField getJAgeFromTextField() { if (jAgeFromTextField == null) { - jAgeFromTextField = new VoLimitedTextField(3,2); + jAgeFromTextField = new VoLimitedTextField(3, 2); jAgeFromTextField.setText("0"); jAgeFromTextField.setMinimumSize(new Dimension(100, 50)); - ageFrom=0; + ageFrom = 0; jAgeFromTextField.addFocusListener(new FocusListener() { + @Override public void focusLost(FocusEvent e) { - try { + try { ageFrom = Integer.parseInt(jAgeFromTextField.getText()); if (ageFrom < 0 || ageFrom > 200) { jAgeFromTextField.setText(""); @@ -884,7 +911,7 @@ public void focusLost(FocusEvent e) { jAgeFromTextField.setText(""); } } - + @Override public void focusGained(FocusEvent e) { } @@ -915,16 +942,17 @@ private JPanel getJAgeToPanel() { */ private VoLimitedTextField getJAgeToTextField() { if (jAgeToTextField == null) { - jAgeToTextField = new VoLimitedTextField(3,2); + jAgeToTextField = new VoLimitedTextField(3, 2); jAgeToTextField.setText("0"); jAgeToTextField.setMaximumSize(new Dimension(100, 50)); - ageTo=0; + ageTo = 0; jAgeToTextField.addFocusListener(new FocusListener() { + @Override public void focusLost(FocusEvent e) { - try { + try { ageTo = Integer.parseInt(jAgeToTextField.getText()); - if (ageTo < 0 || ageTo > 200 ) { + if (ageTo < 0 || ageTo > 200) { jAgeToTextField.setText(""); MessageDialog.error(OpdBrowser.this, "angal.opd.insertavalidage.msg"); } @@ -932,7 +960,7 @@ public void focusLost(FocusEvent e) { jAgeToTextField.setText(""); } } - + @Override public void focusGained(FocusEvent e) { } @@ -956,15 +984,18 @@ private JPanel getJAgePanel() { return jAgePanel; } - class OpdBrowsingModel extends DefaultTableModel { + class OpdBrowsingModel extends PageableTableModel { private static final long serialVersionUID = -9129145534999353730L; public OpdBrowsingModel(String diseaseTypeCode, String diseaseCode, GregorianCalendar dateFrom, GregorianCalendar dateTo, int ageFrom, int ageTo, - char sex, char newPatient) { + char sex, char newPatient, int pageNumber, int pageSize) { + pSur = manager.getOpdPaginated(diseaseTypeCode, diseaseCode, dateFrom, dateTo, ageFrom, ageTo, sex, newPatient, + pageNumber, + pageSize == 0 ? DEFAULT_PAGE_SIZE : pageSize); try { - pSur = manager.getOpd(diseaseTypeCode, diseaseCode, dateFrom, dateTo, ageFrom, ageTo, sex, newPatient); - } catch (OHServiceException ohServiceException) { + pSurSize = manager.getOpd(diseasetype, disease, dateFrom, dateTo, ageFrom, ageTo, sex, newPatient).size(); + } catch (OHServiceException ohServiceException) { MessageDialog.showExceptions(ohServiceException); } } @@ -979,8 +1010,9 @@ public OpdBrowsingModel() { @Override public int getRowCount() { - if (pSur == null) + if (pSur == null) { return 0; + } return pSur.size(); } @@ -995,8 +1027,7 @@ public int getColumnCount() { } @Override - public Object getValueAt(int r, int c) { - Opd opd = pSur.get(pSur.size() - r - 1); + public Object getValueAt(Opd opd, int c) { Patient pat = opd.getPatient(); if (c == -1) { return opd; @@ -1009,7 +1040,7 @@ public Object getValueAt(int r, int c) { if (opd.getVisitDate() == null) { sVisitDate = ""; } else { - sVisitDate = dateFormat.format(opd.getVisitDate().getTime()); + sVisitDate = DATE_FORMAT.format(opd.getVisitDate().getTime()); } return sVisitDate; } else if (c == 3) { @@ -1040,6 +1071,12 @@ public Object getValueAt(int r, int c) { public boolean isCellEditable(int arg0, int arg1) { return false; } + + @Override + public String getFieldName(int column) { + return null; + } + } @Override @@ -1062,73 +1099,81 @@ public void surgeryInserted(AWTEvent e, Opd opd) { } rowCounter.setText(rowCounterText + pSur.size()); } - + private JButton getFilterButton() { if (filterButton == null) { filterButton = new JButton(MessageBundle.getMessage("angal.common.search.btn")); - filterButton.setMnemonic(MessageBundle.getMnemonic("angal.common.search.btn.key")); + filterButton.setMnemonic(MessageBundle.getMnemonic("angal.common.search.btn.key")); filterButton.addActionListener(e -> { Object selectedItem = jDiseaseBox.getSelectedItem(); if (!(selectedItem instanceof Disease)) { MessageDialog.error(OpdBrowser.this, "angal.opd.pleaseselectadisease.msg"); return; } - String disease = ((Disease)selectedItem).getCode(); - String diseasetype = ((DiseaseType)jDiseaseTypeBox.getSelectedItem()).getCode(); + disease = ((Disease) selectedItem).getCode(); + diseasetype = ((DiseaseType) jDiseaseTypeBox.getSelectedItem()).getCode(); - char sex; if (radioa.isSelected()) { - sex='A'; + sex = 'A'; + } else if (radiom.isSelected()) { + sex = 'M'; } else { - if (radiom.isSelected()) { - sex='M'; - } else { - sex='F'; - } + sex = 'F'; } - char newPatient; if (radioAll.isSelected()) { - newPatient='A'; + newPatient = 'A'; + } else if (radioNew.isSelected()) { + newPatient = 'N'; } else { - if (radioNew.isSelected()) { - newPatient='N'; - } else { - newPatient='R'; - } + newPatient = 'R'; } - GregorianCalendar dateFrom = getDateFrom(); - GregorianCalendar dateTo = getDateTo(); + dateFrom = getDateFrom(); + dateTo = getDateTo(); - if (dateFrom.after(dateTo)){ + if (dateFrom.after(dateTo)) { MessageDialog.error(OpdBrowser.this, "angal.opd.datefrommustbebefordateto.msg"); return; } - if (ageFrom>ageTo){ + if (ageFrom > ageTo) { MessageDialog.error(OpdBrowser.this, "angal.opd.agefrommustbelowerthanageto.msg"); jAgeFromTextField.setText(ageTo.toString()); - ageFrom=ageTo; + ageFrom = ageTo; return; } - //TODO: to retrieve resultset size instead of assuming 1 year as limit for the warning - if (TimeTools.getDaysBetweenDates(dateFrom, dateTo, true) >= 360) { + try { + pSurSize = manager.getOpd(diseasetype, disease, dateFrom, dateTo, ageFrom, ageTo, sex, newPatient).size(); + } catch (OHServiceException ohServiceException) { + MessageDialog.showExceptions(ohServiceException); + } + if (getTotalRowCount() > DEFAULT_PAGE_SIZE * 10) { int ok = JOptionPane.showConfirmDialog(OpdBrowser.this, MessageBundle.getMessage("angal.common.thiscouldretrievealargeamountofdataproceed.msg"), MessageBundle.getMessage("angal.messagedialog.question.title"), JOptionPane.OK_CANCEL_OPTION); - if (ok != JOptionPane.OK_OPTION) return; + if (ok != JOptionPane.OK_OPTION) { + return; + } } - - model = new OpdBrowsingModel(diseasetype,disease,getDateFrom(), getDateTo(),ageFrom,ageTo,sex,newPatient); - model.fireTableDataChanged(); - jTable.updateUI(); - rowCounter.setText(rowCounterText + pSur.size()); + rowCounter.setText(rowCounterText + getTotalRowCount()); + paginatedDecorator.paginate(); }); } return filterButton; } - + + @Override + public int getTotalRowCount() { + return pSurSize; + } + + @Override + public List getRows(int pageNumber, int pageSize) { + model = new OpdBrowsingModel(diseasetype, disease, getDateFrom(), getDateTo(), ageFrom, ageTo, sex, newPatient, pageNumber, pageSize); + return pSur; + } + } diff --git a/src/main/java/org/isf/utils/jobjects/PageableTableModel.java b/src/main/java/org/isf/utils/jobjects/PageableTableModel.java new file mode 100644 index 0000000000..b0cadcba93 --- /dev/null +++ b/src/main/java/org/isf/utils/jobjects/PageableTableModel.java @@ -0,0 +1,66 @@ +/* + * Open Hospital (www.open-hospital.org) + * Copyright © 2006-2021 Informatici Senza Frontiere (info@informaticisenzafrontiere.org) + * + * Open Hospital is a free and open source software for healthcare data management. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * https://www.gnu.org/licenses/gpl-3.0-standalone.html + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.isf.utils.jobjects; + +import java.util.ArrayList; +import java.util.List; + +import javax.swing.table.AbstractTableModel; + +public abstract class PageableTableModel extends AbstractTableModel { + private List objectRows = new ArrayList<>(); + + public List getObjectRows() { + return objectRows; + } + + public void setObjectRows(List objectRows) { + this.objectRows = objectRows; + } + + @Override + public int getRowCount() { + return objectRows.size(); + } + + @Override + public Object getValueAt(int rowIndex, int columnIndex) { + T t = objectRows.get(rowIndex); + return getValueAt(t, columnIndex); + } + + @Override + public Class getColumnClass(int columnIndex) { + if (objectRows.isEmpty()) { + return Object.class; + } + Object valueAt = getValueAt(0, columnIndex); + return valueAt!=null? valueAt.getClass(): Object.class; + } + + public abstract Object getValueAt(T t, int columnIndex); + + @Override + public abstract String getColumnName(int column); + + public abstract String getFieldName(int column); +} diff --git a/src/main/java/org/isf/utils/jobjects/PaginatedTableDecorator.java b/src/main/java/org/isf/utils/jobjects/PaginatedTableDecorator.java new file mode 100644 index 0000000000..e8c00b58cd --- /dev/null +++ b/src/main/java/org/isf/utils/jobjects/PaginatedTableDecorator.java @@ -0,0 +1,370 @@ +/* + * Open Hospital (www.open-hospital.org) + * Copyright © 2006-2021 Informatici Senza Frontiere (info@informaticisenzafrontiere.org) + * + * Open Hospital is a free and open source software for healthcare data management. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * https://www.gnu.org/licenses/gpl-3.0-standalone.html + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.isf.utils.jobjects; + +import java.awt.BorderLayout; +import java.awt.Component; +import java.awt.GridLayout; +import java.awt.Insets; +import java.awt.event.ActionEvent; +import java.awt.event.KeyEvent; +import java.util.Arrays; +import java.util.List; + +import javax.swing.Box; +import javax.swing.ButtonGroup; +import javax.swing.JButton; +import javax.swing.JComboBox; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTable; +import javax.swing.JToggleButton; +import javax.swing.SwingConstants; +import javax.swing.event.RowSorterEvent; +import javax.swing.event.TableModelEvent; +import javax.swing.table.TableModel; + +import org.isf.generaldata.MessageBundle; + +/** + * Idea inspired by LogicBig - JTable Lazy Pagination with JPA
+ *
+ * {@link PaginatedTableDecorator} is JTable decorator that shows a page selector at the top of the scrollpane, also giving the ability to choose the page + * size.
+ *
+ * It works with: + *
    + *
  • {@link PageableTableModel}: the table model must be of type PageableTableModel + *
  • {@link PaginationDataProvider}: the container class has to implement the interface PaginationDataProvider + *
+ * + * It offers three different layout: + *
    + *
  • {@link PAGELINK}: clickable page numbers with auto-layout + *
  • {@link PAGEBROWSER}: standard page selector with arrows for first, previous, next and last page + *
  • {@link PAGEBROWSER_PAGELINK}: both the previous ones + *
+ * + * @author Nanni + * + * @param + * - The model to be paginated + */ +public class PaginatedTableDecorator { + + /** PAGELINK: clickable page numbers with auto-layout */ + public static final int PAGELINK = 0; + /** PAGEBROWSER: standard page selector with arrows for first, previous, next and last page */ + public static final int PAGEBROWSER = 1; + /** PAGEBROWSER_PAGELINK: standard page selector with arrows and clickable page numbers with auto-layout */ + public static final int PAGEBROWSER_PAGELINK = 2; + protected int paginatorType; + + private JTable table; + private PaginationDataProvider dataProvider; + private int[] pageSizes; + private JPanel contentPanel; + private int currentPageSize; + private int currentPage = 1; + private int lastPage = 1; + private JPanel pageLinkPanel; + private PageableTableModel objectTableModel; + private JButton jFirstPageButton; + private JButton jPreviousPageButton; + private JButton jNextPageButton; + private JButton jLastPageButton; + private JLabel currentPageLabel; + private static final int MAX_PAGING_COMP_TO_SHOW = 9; + private static final String ELLIPSES = "..."; + + private PaginatedTableDecorator(JTable table, PaginationDataProvider dataProvider, int[] pageSizes, int defaultPageSize, int paginatorType) { + this.table = table; + this.dataProvider = dataProvider; + this.pageSizes = pageSizes; + this.currentPageSize = defaultPageSize; + this.paginatorType = paginatorType; + } + + /** + * Decorate a JTable with a page selector at the top of the scrollpane, also giving the ability to choose the page size.
+ * + * @param + * - the Object to be paginated + * @param table + * - the JTable to be paginated + * @param dataProvider + * - the class responsible to provide the List and its size separately + * @param pageSizes + * - an array with page sizes (e.g. new int[]{5, 10, 20, 50, 75, 100}) + * @param defaultPageSize + * - the initial page size + * @param paginatorType + * - the layout type PAGELINK, PAGEBROWSER, PAGEBROWSER_PAGELINK + * @return {@code PaginatedTableDecorator} object + */ + public static PaginatedTableDecorator decorate(JTable table, PaginationDataProvider dataProvider, int[] pageSizes, int defaultPageSize, + int paginatorType) { + PaginatedTableDecorator decorator = new PaginatedTableDecorator<>(table, dataProvider, pageSizes, defaultPageSize, paginatorType); + decorator.init(); + return decorator; + } + + /** + * Returns the JPanel with all set (JScrollPane, JTable and PageSelectors) + * @return JPanel + */ + public JPanel getContentPanel() { + return contentPanel; + } + + private void init() { + initDataModel(); + initPaginationComponents(); + initListeners(); + paginate(); + } + + private void initListeners() { + objectTableModel.addTableModelListener(this::refreshPageButtonPanel); + if (table.getRowSorter() != null) { + table.getRowSorter().addRowSorterListener(e -> { + if (e.getType() == RowSorterEvent.Type.SORT_ORDER_CHANGED) { + currentPage = 1; + paginate(); + } + }); + } + } + + private void initDataModel() { + TableModel model = table.getModel(); + if (!(model instanceof PageableTableModel)) { + throw new IllegalArgumentException("TableModel must be a subclass of PageTableModel"); + } + objectTableModel = (PageableTableModel) model; + } + + private void initPaginationComponents() { + contentPanel = new JPanel(new BorderLayout()); + JPanel paginationPanel = createPaginationPanel(); + contentPanel.add(paginationPanel, BorderLayout.NORTH); + contentPanel.add(new JScrollPane(table)); + } + + private JPanel createPaginationPanel() { + JPanel paginationPanel = new JPanel(); + + if (paginatorType == PAGEBROWSER || paginatorType == PAGEBROWSER_PAGELINK) { + + paginationPanel.add(getJFirstPageButton()); + paginationPanel.add(getJPreviousPageButton()); + paginationPanel.add(getJLabelCurrentPage()); + paginationPanel.add(getJNextPageButton()); + paginationPanel.add(getJLastPageButton()); + paginationPanel.add(Box.createHorizontalStrut(15)); + } + + if (paginatorType == PAGELINK || paginatorType == PAGEBROWSER_PAGELINK) { + + pageLinkPanel = new JPanel(new GridLayout(1, MAX_PAGING_COMP_TO_SHOW, 3, 3)); + paginationPanel.add(pageLinkPanel); + } + + if (pageSizes != null) { + JComboBox pageComboBox = new JComboBox<>(Arrays.stream(pageSizes).boxed().toArray(Integer[]::new)); + pageComboBox.addActionListener((ActionEvent e) -> { + // to preserve current rows position + int currentPageStartRow = ((currentPage - 1) * currentPageSize) + 1; + currentPageSize = (Integer) pageComboBox.getSelectedItem(); + currentPage = ((currentPageStartRow - 1) / currentPageSize) + 1; + paginate(); + }); + paginationPanel.add(Box.createHorizontalStrut(15)); + paginationPanel.add(new JLabel(MessageBundle.getMessage("angal.common.pagesize.label"))); + paginationPanel.add(pageComboBox); + pageComboBox.setSelectedItem(currentPageSize); + } + return paginationPanel; + } + + private void refreshPageButtonPanel(TableModelEvent tme) { + + if (paginatorType == PAGEBROWSER || paginatorType == PAGEBROWSER_PAGELINK) { + + getJLabelCurrentPage(); + togglePreviousPageButton(); + toggleNextPageButton(); + } + + if (paginatorType == PAGELINK || paginatorType == PAGEBROWSER_PAGELINK) { + + pageLinkPanel.removeAll(); + int totalRows = dataProvider.getTotalRowCount(); + int pages = (int) Math.ceil((double) totalRows / currentPageSize); + ButtonGroup buttonGroup = new ButtonGroup(); + if (pages > MAX_PAGING_COMP_TO_SHOW) { + addPageButton(pageLinkPanel, buttonGroup, 1); + if (currentPage > (pages - ((MAX_PAGING_COMP_TO_SHOW + 1) / 2))) { + // case: 1 ... n->lastPage + pageLinkPanel.add(createEllipsesComponent()); + addPageButtonRange(pageLinkPanel, buttonGroup, pages - MAX_PAGING_COMP_TO_SHOW + 3, pages); + } else if (currentPage <= (MAX_PAGING_COMP_TO_SHOW + 1) / 2) { + // case: 1->n ...lastPage + addPageButtonRange(pageLinkPanel, buttonGroup, 2, MAX_PAGING_COMP_TO_SHOW - 2); + pageLinkPanel.add(createEllipsesComponent()); + addPageButton(pageLinkPanel, buttonGroup, pages); + } else {// case: 1 .. x->n .. lastPage + pageLinkPanel.add(createEllipsesComponent());// first ellipses + // currentPage is approx mid point among total max-4 center links + int start = currentPage - (MAX_PAGING_COMP_TO_SHOW - 4) / 2; + int end = start + MAX_PAGING_COMP_TO_SHOW - 5; + addPageButtonRange(pageLinkPanel, buttonGroup, start, end); + pageLinkPanel.add(createEllipsesComponent());// last ellipsis + addPageButton(pageLinkPanel, buttonGroup, pages);// last page link + } + } else { + addPageButtonRange(pageLinkPanel, buttonGroup, 1, pages); + } + pageLinkPanel.getParent().validate(); + pageLinkPanel.getParent().repaint(); + } + } + + private Component createEllipsesComponent() { + return new JLabel(ELLIPSES, SwingConstants.CENTER); + } + + private void addPageButtonRange(JPanel parentPanel, ButtonGroup buttonGroup, int start, int end) { + for (; start <= end; start++) { + addPageButton(parentPanel, buttonGroup, start); + } + } + + private void addPageButton(JPanel parentPanel, ButtonGroup buttonGroup, int pageNumber) { + JToggleButton toggleButton = new JToggleButton(Integer.toString(pageNumber)); + toggleButton.setMargin(new Insets(1, 3, 1, 3)); + buttonGroup.add(toggleButton); + parentPanel.add(toggleButton); + if (pageNumber == currentPage) { + toggleButton.setSelected(true); + } + toggleButton.addActionListener(ae -> { + currentPage = Integer.parseInt(ae.getActionCommand()); + paginate(); + }); + } + + public void paginate() { + List rows = dataProvider.getRows(currentPage - 1, currentPageSize); + lastPage = (dataProvider.getTotalRowCount() / currentPageSize) + 1; + objectTableModel.setObjectRows(rows); + objectTableModel.fireTableDataChanged(); + } + + private JLabel getJLabelCurrentPage() { + if (currentPageLabel == null) { + currentPageLabel = new JLabel(); + } + currentPageLabel.setText(MessageBundle.formatMessage("angal.common.page.range.fmt", currentPage, lastPage)); + return currentPageLabel; + } + + private JButton getJFirstPageButton() { + if (jFirstPageButton == null) { + jFirstPageButton = new JButton(); + jFirstPageButton.setText("<<"); + jFirstPageButton.setMnemonic(KeyEvent.VK_HOME); + jFirstPageButton.addActionListener(event -> { + currentPage = 1; + paginate(); + }); + jFirstPageButton.setEnabled(false); + } + return jFirstPageButton; + } + + private JButton getJPreviousPageButton() { + if (jPreviousPageButton == null) { + jPreviousPageButton = new JButton(); + jPreviousPageButton.setText("<"); + jPreviousPageButton.setMnemonic(KeyEvent.VK_PAGE_UP); + jPreviousPageButton.addActionListener(event -> { + if (currentPage > 1) { + currentPage--; + } + paginate(); + }); + jPreviousPageButton.setEnabled(false); + } + return jPreviousPageButton; + } + + private JButton getJNextPageButton() { + if (jNextPageButton == null) { + jNextPageButton = new JButton(); + jNextPageButton.setText(">"); + jNextPageButton.setMnemonic(KeyEvent.VK_PAGE_DOWN); + jNextPageButton.addActionListener(event -> { + if (currentPage < lastPage) { + currentPage++; + } + paginate(); + }); + } + return jNextPageButton; + } + + private JButton getJLastPageButton() { + if (jLastPageButton == null) { + jLastPageButton = new JButton(); + jLastPageButton.setText(">>"); + jLastPageButton.setMnemonic(KeyEvent.VK_END); + jLastPageButton.addActionListener(event -> { + currentPage = lastPage; + paginate(); + }); + jLastPageButton.setEnabled(false); + } + return jLastPageButton; + } + + private void togglePreviousPageButton() { + if (currentPage == 1) { + jPreviousPageButton.setEnabled(false); + jFirstPageButton.setEnabled(false); + } else { + jPreviousPageButton.setEnabled(true); + jFirstPageButton.setEnabled(true); + } + } + + private void toggleNextPageButton() { + if (dataProvider.getTotalRowCount() < currentPageSize || currentPage == lastPage) { + jNextPageButton.setEnabled(false); + jLastPageButton.setEnabled(false); + } else { + jNextPageButton.setEnabled(true); + jLastPageButton.setEnabled(true); + } + } +} diff --git a/src/main/java/org/isf/utils/jobjects/PaginationDataProvider.java b/src/main/java/org/isf/utils/jobjects/PaginationDataProvider.java new file mode 100644 index 0000000000..2c5c859aad --- /dev/null +++ b/src/main/java/org/isf/utils/jobjects/PaginationDataProvider.java @@ -0,0 +1,31 @@ +/* + * Open Hospital (www.open-hospital.org) + * Copyright © 2006-2021 Informatici Senza Frontiere (info@informaticisenzafrontiere.org) + * + * Open Hospital is a free and open source software for healthcare data management. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * https://www.gnu.org/licenses/gpl-3.0-standalone.html + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.isf.utils.jobjects; + +import java.util.List; + +public interface PaginationDataProvider { + + int getTotalRowCount(); + + List getRows(int startPage, int pageSize); +}