diff --git a/base/uk.ac.stfc.isis.ibex.journal.tests/src/uk/ac/stfc/isis/ibex/journal/tests/JournalCategoriserTest.java b/base/uk.ac.stfc.isis.ibex.journal.tests/src/uk/ac/stfc/isis/ibex/journal/tests/JournalCategoriserTest.java new file mode 100644 index 0000000000..5a1038f6f2 --- /dev/null +++ b/base/uk.ac.stfc.isis.ibex.journal.tests/src/uk/ac/stfc/isis/ibex/journal/tests/JournalCategoriserTest.java @@ -0,0 +1,91 @@ + +/* + * This file is part of the ISIS IBEX application. Copyright (C) 2012-2019 + * Science & Technology Facilities Council. All rights reserved. + * + * This program is distributed in the hope that it will be useful. This program + * and the accompanying materials are made available under the terms of the + * Eclipse Public License v1.0 which accompanies this distribution. EXCEPT AS + * EXPRESSLY SET FORTH IN THE ECLIPSE PUBLIC LICENSE V1.0, THE PROGRAM AND + * ACCOMPANYING MATERIALS ARE PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES + * OR CONDITIONS OF ANY KIND. See the Eclipse Public License v1.0 for more + * details. + * + * You should have received a copy of the Eclipse Public License v1.0 along with + * this program; if not, you can obtain a copy from + * https://www.eclipse.org/org/documents/epl-v10.php or + * http://opensource.org/licenses/eclipse-1.0.php + */ + +package uk.ac.stfc.isis.ibex.journal.tests; + +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.assertNotNull; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import org.junit.Test; + +import uk.ac.stfc.isis.ibex.journal.JournalField; +import uk.ac.stfc.isis.ibex.journal.JournalFieldCategoriser; +import uk.ac.stfc.isis.ibex.journal.JournalFieldCategoriser.JournalFieldCategory; + +/** + * Test the Categorisers for JournalFields. + */ +@SuppressWarnings("checkstyle:methodname") +public class JournalCategoriserTest { + + @Test + public void test_all_journal_fields_get_categorised_in_map() { + Map> catMap = JournalFieldCategoriser.calculateCategoryMap(); + + for (final JournalField field : JournalField.values()) { + boolean exists = catMap.values().stream().anyMatch(list -> list.contains(field)); + assertTrue(exists); + } + } + + @Test + public void test_all_categories_have_title() { + for (final JournalFieldCategory category : JournalFieldCategory.values()) { + final String title = category.getCategoryTitle(); + + assertNotNull(title); + assertTrue(title.length() > 0); + } + } + + + @Test + public void test_all_categories_have_no_more_than_n_percent_deviation_from_IQR_mean_of_fields_per_category() { + Map> catMap = JournalFieldCategoriser.calculateCategoryMap(); + + final List lengths = new ArrayList<>(0); + final int numCategories = catMap.size(); + + for (final List fieldsList : catMap.values()) { + lengths.add(fieldsList.size()); + } + + final double iqr_mean_fields_per_category = lengths.subList(numCategories / 4, (3 * numCategories) / 4).stream() + .mapToDouble(a -> a).average().orElse(0.0); + + + + for (final List fieldsList : catMap.values()) { + // Test: Is every category +/- n% of the average? + final double margin = 0.75; + final int noFields = fieldsList.size(); + + double lower = iqr_mean_fields_per_category * (1.0 - margin); + double upper = iqr_mean_fields_per_category * (1.0 + margin); + + assertTrue(noFields >= lower && noFields <= upper); + } + + } + +} diff --git a/base/uk.ac.stfc.isis.ibex.journal/src/uk/ac/stfc/isis/ibex/journal/JournalField.java b/base/uk.ac.stfc.isis.ibex.journal/src/uk/ac/stfc/isis/ibex/journal/JournalField.java index cdb5bc1428..c027077142 100644 --- a/base/uk.ac.stfc.isis.ibex.journal/src/uk/ac/stfc/isis/ibex/journal/JournalField.java +++ b/base/uk.ac.stfc.isis.ibex.journal/src/uk/ac/stfc/isis/ibex/journal/JournalField.java @@ -3,6 +3,7 @@ import java.util.ArrayList; import java.util.Arrays; +import uk.ac.stfc.isis.ibex.journal.JournalFieldCategoriser.JournalFieldCategory; import uk.ac.stfc.isis.ibex.journal.JournalSort.JournalSortDirection; import uk.ac.stfc.isis.ibex.journal.formatters.DateTimeJournalFormatter; import uk.ac.stfc.isis.ibex.journal.formatters.DecimalPlacesFormatter; @@ -14,231 +15,261 @@ * Enum constants representing the column names in the SQL schema. */ public enum JournalField { - - /** - * Run number. - */ - RUN_NUMBER("Run number", "run_number", JournalSortDirection.DESCENDING), - /** - * Title. - */ - TITLE("Title", "title", JournalSortDirection.ASCENDING), - /** - * Start time. - */ - START_TIME("Start time", "start_time", new DateTimeJournalFormatter(), JournalSortDirection.DESCENDING), - /** - * End time. - */ - END_TIME("End time", "end_time", new DateTimeJournalFormatter(), JournalSortDirection.DESCENDING), - /** - * Duration. - */ - DURATION("Duration", "duration", new DurationJournalFormatter(), JournalSortDirection.ASCENDING), - /** - * Uamps. - */ - UAMPS("Uamps", "uamps", new DecimalPlacesFormatter(4), JournalSortDirection.DESCENDING), - /** - * Rb number. - */ - RB_NUMBER("RB number", "rb_number", JournalSortDirection.DESCENDING), - /** - * Users. - */ - USERS("Users", "users", JournalSortDirection.ASCENDING), - /** - * Simulation mode. - */ - SIMULATION_MODE("Simulation mode", "simulation_mode", JournalSortDirection.DESCENDING), - /** - * Local contact. - */ - LOCAL_CONTACT("Local contact", "local_contact", JournalSortDirection.ASCENDING), - /** - * User institute. - */ - USER_INSTITUTE("User institute", "user_institute", JournalSortDirection.ASCENDING), - /** - * Instrument name. - */ - INSTRUMENT_NAME("Instrument name", "instrument_name", JournalSortDirection.ASCENDING), - /** - * Sample id. - */ - SAMPLE_ID("Sample ID", "sample_id", JournalSortDirection.ASCENDING), - /** - * Measurement first run. - */ - MEASUREMENT_FIRST_RUN("Measurement first run", "measurement_first_run", JournalSortDirection.DESCENDING), - /** - * Measurement id. - */ - MEASUREMENT_ID("Measurement ID", "measurement_id", JournalSortDirection.DESCENDING), - /** - * Measurement label. - */ - MEASUREMENT_LABEL("Measurement label", "measurement_label", JournalSortDirection.ASCENDING), - /** - * Measurement type. - */ - MEASUREMENT_TYPE("Measurement type", "measurement_type", JournalSortDirection.ASCENDING), - /** - * Measurement subid. - */ - MEASUREMENT_SUBID("Measurement subid", "measurement_subid", JournalSortDirection.DESCENDING), - /** - * Raw frames. - */ - RAW_FRAMES("Raw frames", "raw_frames", JournalSortDirection.DESCENDING), - /** - * Good frames. - */ - GOOD_FRAMES("Good frames", "good_frames", JournalSortDirection.DESCENDING), - /** - * Number periods. - */ - NUMBER_PERIODS("Number periods", "number_periods", JournalSortDirection.ASCENDING), - /** - * Number spectra. - */ - NUMBER_SPECTRA("Number spectra", "number_spectra", JournalSortDirection.DESCENDING), - /** - * Number detectors. - */ - NUMBER_DETECTORS("Number detectors", "number_detectors", JournalSortDirection.DESCENDING), - /** - * Number time regimes. - */ - NUMBER_TIME_REGIMES("Number time regimes", "number_time_regimes", JournalSortDirection.DESCENDING), - /** - * Frame sync. - */ - FRAME_SYNC("Frame sync", "frame_sync", JournalSortDirection.ASCENDING), - /** - * Icp version. - */ - ICP_VERSION("ICP version", "icp_version", JournalSortDirection.ASCENDING), - /** - * Detector table. - */ - DETECTOR_TABLE("Detector table", "detector_table", JournalSortDirection.ASCENDING), - /** - * Spectra table. - */ - SPECTRA_TABLE("Spectra table", "spectra_table", JournalSortDirection.ASCENDING), - /** - * Wiring table. - */ - WIRING_TABLE("Wiring table", "wiring_table", JournalSortDirection.ASCENDING), - /** - * Monitor spectrum. - */ - MONITOR_SPECTRUM("Monitor spectrum", "monitor_spectrum", JournalSortDirection.ASCENDING), - /** - * Monitor sum. - */ - MONITOR_SUM("Monitor sum", "monitor_sum", JournalSortDirection.DESCENDING), - /** - * Total mevents. - */ - TOTAL_MEVENTS("Total mevents", "total_mevents", JournalSortDirection.DESCENDING), - /** - * Comment. - */ - COMMENT("Comment", "comment", JournalSortDirection.ASCENDING), - /** - * Field label. - */ - FIELD_LABEL("Field label", "field_label", JournalSortDirection.ASCENDING), - /** - * Instrument geometry. - */ - INSTRUMENT_GEOMETRY("Instrument geometry", "instrument_geometry", JournalSortDirection.ASCENDING), - /** - * Script name. - */ - SCRIPT_NAME("Script name", "script_name", JournalSortDirection.ASCENDING), - /** - * Sample name. - */ - SAMPLE_NAME("Sample name", "sample_name", JournalSortDirection.ASCENDING), - /** - * Sample orientation. - */ - SAMPLE_ORIENTATION("Sample orientation", "sample_orientation", JournalSortDirection.ASCENDING), - /** - * Temperature label. - */ - TEMPERATURE_LABEL("Temperature label", "temperature_label", JournalSortDirection.ASCENDING), - /** - * Np ratio. - */ - NP_RATIO("NP ratio", "np_ratio", JournalSortDirection.ASCENDING), - /** - * Isis cycle. - */ - ISIS_CYCLE("Isis cycle", "isis_cycle", JournalSortDirection.DESCENDING), - /** - * Event mode. - */ - EVENT_MODE("Event mode", "event_mode", JournalSortDirection.DESCENDING); - + + /** + * Run number. + */ + RUN_NUMBER("Run Number", JournalFieldCategory.NAME, "run_number", JournalSortDirection.DESCENDING), + /** + * Title. + */ + TITLE("Title", JournalFieldCategory.TEXT, "title", JournalSortDirection.ASCENDING), + /** + * Start time. + */ + START_TIME("Start Time", JournalFieldCategory.TIME_AND_FRAME, "start_time", new DateTimeJournalFormatter(), + JournalSortDirection.DESCENDING), + /** + * End time. + */ + END_TIME("End time", JournalFieldCategory.TIME_AND_FRAME, "end_time", new DateTimeJournalFormatter(), + JournalSortDirection.DESCENDING), + /** + * Duration. + */ + DURATION("Duration", JournalFieldCategory.TIME_AND_FRAME, "duration", new DurationJournalFormatter(), + JournalSortDirection.ASCENDING), + /** + * Uamps. + */ + UAMPS("Uamps", JournalFieldCategory.MEASURE, "uamps", new DecimalPlacesFormatter(4), + JournalSortDirection.DESCENDING), + /** + * Rb number. + */ + RB_NUMBER("RB number", JournalFieldCategory.EXP, "rb_number", JournalSortDirection.DESCENDING), + /** + * Users. + */ + USERS("Users", JournalFieldCategory.EXP, "users", JournalSortDirection.ASCENDING), + /** + * Simulation mode. + */ + SIMULATION_MODE("Simulation mode", JournalFieldCategory.EXP, "simulation_mode", JournalSortDirection.DESCENDING), + /** + * Local contact. + */ + LOCAL_CONTACT("Local contact", JournalFieldCategory.EXP, "local_contact", JournalSortDirection.ASCENDING), + /** + * User institute. + */ + USER_INSTITUTE("User institute", JournalFieldCategory.EXP, "user_institute", JournalSortDirection.ASCENDING), + /** + * Instrument name. + */ + INSTRUMENT_NAME("Instrument name", JournalFieldCategory.NAME, "instrument_name", JournalSortDirection.ASCENDING), + /** + * Sample id. + */ + SAMPLE_ID("Sample ID", JournalFieldCategory.SAMPLE, "sample_id", JournalSortDirection.ASCENDING), + /** + * Measurement first run. + */ + MEASUREMENT_FIRST_RUN("Measurement first run", JournalFieldCategory.MEASURE, "measurement_first_run", + JournalSortDirection.DESCENDING), + /** + * Measurement id. + */ + MEASUREMENT_ID("Measurement ID", JournalFieldCategory.MEASURE, "measurement_id", JournalSortDirection.DESCENDING), + /** + * Measurement label. + */ + MEASUREMENT_LABEL("Measurement label", JournalFieldCategory.TEXT, "measurement_label", + JournalSortDirection.ASCENDING), + /** + * Measurement type. + */ + MEASUREMENT_TYPE("Measurement type", JournalFieldCategory.MEASURE, "measurement_type", + JournalSortDirection.ASCENDING), + /** + * Measurement subid. + */ + MEASUREMENT_SUBID("Measurement subid", JournalFieldCategory.MEASURE, "measurement_subid", + JournalSortDirection.DESCENDING), + /** + * Raw frames. + */ + RAW_FRAMES("Raw frames", JournalFieldCategory.TIME_AND_FRAME, "raw_frames", JournalSortDirection.DESCENDING), + /** + * Good frames. + */ + GOOD_FRAMES("Good frames", JournalFieldCategory.TIME_AND_FRAME, "good_frames", JournalSortDirection.DESCENDING), + /** + * Number periods. + */ + NUMBER_PERIODS("Number periods", JournalFieldCategory.NUMBER, "number_periods", JournalSortDirection.ASCENDING), + /** + * Number spectra. + */ + NUMBER_SPECTRA("Number spectra", JournalFieldCategory.NUMBER, "number_spectra", JournalSortDirection.DESCENDING), + /** + * Number detectors. + */ + NUMBER_DETECTORS("Number detectors", JournalFieldCategory.NUMBER, "number_detectors", + JournalSortDirection.DESCENDING), + /** + * Number time regimes. + */ + NUMBER_TIME_REGIMES("Number time regimes", JournalFieldCategory.NUMBER, "number_time_regimes", + JournalSortDirection.DESCENDING), + /** + * Frame sync. + */ + FRAME_SYNC("Frame sync", JournalFieldCategory.TIME_AND_FRAME, "frame_sync", JournalSortDirection.ASCENDING), + /** + * Icp version. + */ + ICP_VERSION("ICP version", JournalFieldCategory.INSTRUMENT, "icp_version", JournalSortDirection.ASCENDING), + /** + * Detector table. + */ + DETECTOR_TABLE("Detector table", JournalFieldCategory.TABLE, "detector_table", JournalSortDirection.ASCENDING), + /** + * Spectra table. + */ + SPECTRA_TABLE("Spectra table", JournalFieldCategory.TABLE, "spectra_table", JournalSortDirection.ASCENDING), + /** + * Wiring table. + */ + WIRING_TABLE("Wiring table", JournalFieldCategory.TABLE, "wiring_table", JournalSortDirection.ASCENDING), + /** + * Monitor spectrum. + */ + MONITOR_SPECTRUM("Monitor spectrum", JournalFieldCategory.INSTRUMENT, "monitor_spectrum", + JournalSortDirection.ASCENDING), + /** + * Monitor sum. + */ + MONITOR_SUM("Monitor sum", JournalFieldCategory.INSTRUMENT, "monitor_sum", JournalSortDirection.DESCENDING), + /** + * Total mevents. + */ + TOTAL_MEVENTS("Total mevents", JournalFieldCategory.NUMBER, "total_mevents", JournalSortDirection.DESCENDING), + /** + * Comment. + */ + COMMENT("Comment", JournalFieldCategory.TEXT, "comment", JournalSortDirection.ASCENDING), + /** + * Field label. + */ + FIELD_LABEL("Field label", JournalFieldCategory.TEXT, "field_label", JournalSortDirection.ASCENDING), + /** + * Instrument geometry. + */ + INSTRUMENT_GEOMETRY("Instrument geometry", JournalFieldCategory.INSTRUMENT, "instrument_geometry", + JournalSortDirection.ASCENDING), + /** + * Script name. + */ + SCRIPT_NAME("Script name", JournalFieldCategory.NAME, "script_name", JournalSortDirection.ASCENDING), + /** + * Sample name. + */ + SAMPLE_NAME("Sample name", JournalFieldCategory.NAME, "sample_name", JournalSortDirection.ASCENDING), + /** + * Sample orientation. + */ + SAMPLE_ORIENTATION("Sample orientation", JournalFieldCategory.SAMPLE, "sample_orientation", + JournalSortDirection.ASCENDING), + /** + * Temperature label. + */ + TEMPERATURE_LABEL("Temperature label", JournalFieldCategory.TEXT, "temperature_label", + JournalSortDirection.ASCENDING), + /** + * Np ratio. + */ + NP_RATIO("NP ratio", JournalFieldCategory.INSTRUMENT, "np_ratio", JournalSortDirection.ASCENDING), + /** + * Isis cycle. + */ + ISIS_CYCLE("Isis cycle", JournalFieldCategory.EXP, "isis_cycle", JournalSortDirection.DESCENDING), + /** + * Event mode. + */ + EVENT_MODE("Event mode", JournalFieldCategory.EXP, "event_mode", JournalSortDirection.DESCENDING); + private final String friendlyName; - private final String sqlFieldName; - private final IJournalFormatter formatter; - private final JournalSortDirection sortDirection; + private final String sqlFieldName; + private final JournalFieldCategory category; + private final IJournalFormatter formatter; + private final JournalSortDirection sortDirection; + + JournalField(String friendlyName, JournalFieldCategory category, String sqlFieldName, + JournalSortDirection sortDirection) { + this(friendlyName, category, sqlFieldName, new NoopJournalFormatter(), sortDirection); + } + + JournalField(String friendlyName, JournalFieldCategory category, String sqlFieldName, IJournalFormatter formatter, + JournalSortDirection sortDirection) { + this.friendlyName = friendlyName; + this.category = category; + this.sqlFieldName = sqlFieldName; + this.formatter = formatter; + this.sortDirection = sortDirection; + } + + /** + * Gets the column name of this field in the SQL schema. + * + * @return the column name + */ + public String getSqlFieldName() { + return sqlFieldName; + } + + /** + * Gets a friendly, user-facing name of this field. + * + * @return the name + */ + public String getFriendlyName() { + return friendlyName; + } + + /** + * Gets a formatter to convert this field's SQL representation into a + * user-facing representation. + * + * @return the name + */ + public IJournalFormatter getFormatter() { + return formatter; + } + + /** + * Takes a friendly name and find its associated field. Throws an error if no + * such field exists. + * + * @param name the friendly name + * @return a journal field + */ + public static JournalField getFieldFromFriendlyName(String name) { + return (new ArrayList(Arrays.asList(JournalField.values()))).stream() + .filter(f -> f.getFriendlyName().equals(name)).findFirst().get(); + } + + /** + * @return the default sort direction + */ + public JournalSortDirection getSortDirection() { + return sortDirection; + } - JournalField(String friendlyName, String sqlFieldName, JournalSortDirection sortDirection) { - this(friendlyName, sqlFieldName, new NoopJournalFormatter(), sortDirection); - } - - JournalField(String friendlyName, String sqlFieldName, IJournalFormatter formatter, JournalSortDirection sortDirection) { - this.friendlyName = friendlyName; - this.sqlFieldName = sqlFieldName; - this.formatter = formatter; - this.sortDirection = sortDirection; - } - - /** - * Gets the column name of this field in the SQL schema. - * @return the column name - */ - public String getSqlFieldName() { - return sqlFieldName; - } - - /** - * Gets a friendly, user-facing name of this field. - * @return the name - */ - public String getFriendlyName() { - return friendlyName; - } - - /** - * Gets a formatter to convert this field's SQL representation into a user-facing representation. - * @return the name - */ - public IJournalFormatter getFormatter() { - return formatter; - } - - /** - * Takes a friendly name and find its associated field. Throws an error if no such field exists. - * @param name the friendly name - * @return a journal field - */ - public static JournalField getFieldFromFriendlyName(String name) { - return (new ArrayList(Arrays.asList(JournalField.values()))) - .stream().filter(f -> f.getFriendlyName().equals(name)) - .findFirst().get(); - } - - /** - * @return the default sort direction - */ - public JournalSortDirection getSortDirection() { - return sortDirection; - } + /** + * @return the respective category of the field + */ + public JournalFieldCategory getCategory() { + return category; + } } diff --git a/base/uk.ac.stfc.isis.ibex.journal/src/uk/ac/stfc/isis/ibex/journal/JournalFieldCategoriser.java b/base/uk.ac.stfc.isis.ibex.journal/src/uk/ac/stfc/isis/ibex/journal/JournalFieldCategoriser.java new file mode 100644 index 0000000000..202f808e2b --- /dev/null +++ b/base/uk.ac.stfc.isis.ibex.journal/src/uk/ac/stfc/isis/ibex/journal/JournalFieldCategoriser.java @@ -0,0 +1,174 @@ + +/* + * This file is part of the ISIS IBEX application. Copyright (C) 2012-2019 + * Science & Technology Facilities Council. All rights reserved. + * + * This program is distributed in the hope that it will be useful. This program + * and the accompanying materials are made available under the terms of the + * Eclipse Public License v1.0 which accompanies this distribution. EXCEPT AS + * EXPRESSLY SET FORTH IN THE ECLIPSE PUBLIC LICENSE V1.0, THE PROGRAM AND + * ACCOMPANYING MATERIALS ARE PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES + * OR CONDITIONS OF ANY KIND. See the Eclipse Public License v1.0 for more + * details. + * + * You should have received a copy of the Eclipse Public License v1.0 along with + * this program; if not, you can obtain a copy from + * https://www.eclipse.org/org/documents/epl-v10.php or + * http://opensource.org/licenses/eclipse-1.0.php + */ + +package uk.ac.stfc.isis.ibex.journal; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; + +/** + * Class encapsulating categories and respective Enums and Methods for + * JournalFields. + */ +public class JournalFieldCategoriser { + /** + * Enum used to categorise all fields in the journal viewer for grouping and + * reference in the UI. + */ + public enum JournalFieldCategory { + + /** + * Field groupings for times and frames. + */ + TIME_AND_FRAME("Times And Frames", "Field Groupings for times and frames."), + + /** + * Field groupings for names and labels. + */ + NAME("Identifiers", "Field Groupings for names and labels."), + + /** + * Field groupings for tables. + */ + TABLE("Table", "Field Groupings for tables."), + + /** + * Field groupings for measurement information. + */ + MEASURE("Measurement", "Field Groupings for measurement information."), + + /** + * Field groupings for experiment information and proposal. + */ + EXP("Experiment information", "Field groupings for experiement info and proposal"), + + /** + * Field grouping for instrument configuration and monitoring. + */ + INSTRUMENT("Instrument", "Field grouping for instrument configuration and monitoring"), + + /** + * Field grouping for samples. + */ + SAMPLE("Sample", "Field grouping for samples"), + + /** + * Field grouping for texts. + */ + TEXT("Text", "Field grouping for texts"), + + /** + * Field groupings for numbers. + */ + NUMBER("Counts", "Field Groupings for numbers of."); + + private final String title; + private final String tooltip; + + /** + * Initialise a JournalFieldCategory. + * + * @param title + * @param tooltip + */ + JournalFieldCategory(String title, String tooltip) { + this.title = title; + this.tooltip = tooltip; + } + + /** + * Gets the friendly title of a category. + * + * @return Title. + */ + public String getCategoryTitle() { + return title; + } + + /** + * Gets the descriptive tooltip for a category. + * + * @return Tooltip. + */ + public String getCategoryTooltip() { + return tooltip; + } + + } + + JournalField categoryField; + JournalFieldCategory category; + + /** + * Create a journal field category class. + * + * @param categoryField A JournalField belonging in a category. + * @param category The JournalFieldCategory that encompasses the categoryField. + */ + public JournalFieldCategoriser(JournalField categoryField, JournalFieldCategory category) { + this.categoryField = categoryField; + this.category = category; + } + + /** + * Calculate and returns a TreeMap of JournalFields organised into lists + * respective of their category. + * + * @return A TreeMap. Category: List of JournalFields in that category. + */ + public static Map> calculateCategoryMap() { + final Map> fieldsByCategory = new TreeMap<>(); + for (final JournalField property : JournalField.values()) { + final String category = property.getCategory().toString(); + if (fieldsByCategory.containsKey(category)) { + fieldsByCategory.get(category).add(property); + } else { + final List catList = new ArrayList<>(); + catList.add(property); + fieldsByCategory.put(category, catList); + } + } + return fieldsByCategory; + } + + /** + * @return the categoryField + */ + public JournalField getCategoryField() { + return categoryField; + } + + /** + * @param rawName The Enum identifier as a string + * @return a friendly UI-facing string, i.e. title of the Enum. + */ + public static String getFriendlyCategoryName(String rawName) { + return JournalFieldCategory.valueOf(rawName).getCategoryTitle(); + } + + /** + * @return the category + */ + public JournalFieldCategory getCategory() { + return category; + } + +} diff --git a/base/uk.ac.stfc.isis.ibex.ui.journalviewer/src/uk/ac/stfc/isis/ibex/ui/journalviewer/JournalViewerView.java b/base/uk.ac.stfc.isis.ibex.ui.journalviewer/src/uk/ac/stfc/isis/ibex/ui/journalviewer/JournalViewerView.java index ccbcb644bf..59d2f170b6 100644 --- a/base/uk.ac.stfc.isis.ibex.ui.journalviewer/src/uk/ac/stfc/isis/ibex/ui/journalviewer/JournalViewerView.java +++ b/base/uk.ac.stfc.isis.ibex.ui.journalviewer/src/uk/ac/stfc/isis/ibex/ui/journalviewer/JournalViewerView.java @@ -19,8 +19,15 @@ package uk.ac.stfc.isis.ibex.ui.journalviewer; +import uk.ac.stfc.isis.ibex.journal.JournalFieldCategoriser; + +import java.util.Map; +import java.util.HashMap; +import java.util.TreeMap; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.List; import javax.annotation.PostConstruct; @@ -36,6 +43,7 @@ import org.eclipse.swt.events.KeyEvent; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.graphics.Color; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.layout.RowData; @@ -43,6 +51,7 @@ import org.eclipse.swt.widgets.Button; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Group; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.ProgressBar; import org.eclipse.swt.widgets.TableColumn; @@ -101,6 +110,10 @@ public class JournalViewerView { private Composite searchControls; private Composite basicControls; + private final Map checkboxes = new HashMap<>(); + private static final Color CHECKBOXMATCH = SWTResourceManager.getColor(SWT.COLOR_BLACK); + private static final Color CHECKBOXNOTMATCH = SWTResourceManager.getColor(SWT.COLOR_GRAY); + /** * Create contents of the view part. * @@ -121,6 +134,18 @@ public void createPartControl(final Composite parent) { lblTitle.setFont(SWTResourceManager.getFont("Segoe UI", 16, SWT.BOLD)); lblTitle.setText("Journal Viewer"); + Composite searchRow = new Composite(parent, SWT.FILL); + searchRow.setLayout(new GridLayout(2, false)); + searchRow.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false)); + + Label searchLabel = new Label(searchRow, SWT.NONE); + searchLabel.setText("Search: "); + searchLabel.setLayoutData(new GridData(SWT.END, SWT.CENTER, false, false)); + + Text checkboxSearch = new Text(searchRow, SWT.BORDER); + checkboxSearch.setLayoutData(new GridData(SWT.END, SWT.CENTER, false, false)); + checkboxSearch.addModifyListener(e -> filterCheckboxes(checkboxSearch.getText())); + Composite selectedContainer = new Composite(parent, SWT.FILL); RowLayout rl = new RowLayout(); rl.justify = false; @@ -151,20 +176,12 @@ public void createPartControl(final Composite parent) { btnFirstPage = new IBEXButton(basicControls, SWT.NONE, _ -> { setProgressIndicatorsVisible(true); model.firstPage().thenAccept(_ -> setProgressIndicatorsVisible(false)); - }) - .text("<<") - .tooltip("Go to the first page.") - .layoutData(IBEXButton.defaultRow) - .get(); + }).text("<<").tooltip("Go to the first page.").layoutData(IBEXButton.defaultRow).get(); btnPrevPage = new IBEXButton(basicControls, SWT.NONE, _ -> { setProgressIndicatorsVisible(true); model.prevPage().thenAccept(_ -> setProgressIndicatorsVisible(false)); - }) - .text("< Prev") - .tooltip("Go to the previous page.") - .layoutData(IBEXButton.defaultRow) - .get(); + }).text("< Prev").tooltip("Go to the previous page.").layoutData(IBEXButton.defaultRow).get(); textPageNumber = new Text(basicControls, SWT.BORDER); RowData textPageNumberData = new RowData(); @@ -175,29 +192,18 @@ public void createPartControl(final Composite parent) { btnNextPage = new IBEXButton(basicControls, SWT.NONE, _ -> { setProgressIndicatorsVisible(true); model.nextPage().thenAccept(_ -> setProgressIndicatorsVisible(false)); - }) - .text("Next >") - .tooltip("Go to the next page.") - .layoutData(IBEXButton.defaultRow) - .get(); + }).text("Next >").tooltip("Go to the next page.").layoutData(IBEXButton.defaultRow).get(); btnLastPage = new IBEXButton(basicControls, SWT.NONE, _ -> { setProgressIndicatorsVisible(true); model.lastPage().thenAccept(_ -> setProgressIndicatorsVisible(false)); - }) - .text(">>") - .tooltip("Go to the last page.") - .layoutData(IBEXButton.defaultRow) - .get(); + }).text(">>").tooltip("Go to the last page.").layoutData(IBEXButton.defaultRow).get(); new IBEXButton(basicControls, SWT.NONE, _ -> { resetPageNumber(); setProgressIndicatorsVisible(true); model.setPageNumber(1).thenAccept(_ -> setProgressIndicatorsVisible(false)); - }) - .text("Refresh") - .tooltip("Refresh the journal.") - .layoutData(IBEXButton.defaultRow); + }).text("Refresh").tooltip("Refresh the journal.").layoutData(IBEXButton.defaultRow); searchControls = new Composite(controls, SWT.NONE); RowLayout rlSearchControls = new RowLayout(SWT.HORIZONTAL); @@ -208,11 +214,8 @@ public void createPartControl(final Composite parent) { RowLayout rlFilterControl = new RowLayout(SWT.HORIZONTAL); searchInput.setLayout(rlFilterControl); - btnSearch = new IBEXButton(searchControls, SWT.NONE, _ -> search()) - .text("Search") - .tooltip("Search the journal.") - .layoutData(new RowData(80, SWT.DEFAULT)) - .get(); + btnSearch = new IBEXButton(searchControls, SWT.NONE, _ -> search()).text("Search") + .tooltip("Search the journal.").layoutData(new RowData(80, SWT.DEFAULT)).get(); new IBEXButton(searchControls, SWT.NONE, _ -> { resetPageNumber(); @@ -220,10 +223,7 @@ public void createPartControl(final Composite parent) { model.resetActiveSearch(); setProgressIndicatorsVisible(true); model.setPageNumber(1).thenAccept(_ -> setProgressIndicatorsVisible(false)); - }) - .text("Clear") - .tooltip("Clear the search.") - .layoutData(IBEXButton.defaultRow); + }).text("Clear").tooltip("Clear the search.").layoutData(IBEXButton.defaultRow); progressBar = new ProgressBar(searchControls, SWT.INDETERMINATE); progressBar.setMaximum(80); @@ -233,22 +233,33 @@ public void createPartControl(final Composite parent) { error.setForeground(parent.getDisplay().getSystemColor(SWT.COLOR_RED)); error.setLayoutData(new RowData(200, SWT.DEFAULT)); - for (final JournalField property : JournalField.values()) { - final Button checkbox = new IBEXButton(selectedContainer, SWT.CHECK) - .text(property.getFriendlyName()) - .selected(model.getFieldSelected(property)) - .layoutData(IBEXButton.defaultRow) - .get(); - - checkbox.addSelectionListener(new SelectionAdapter() { - @Override - public void widgetSelected(SelectionEvent e) { - setProgressIndicatorsVisible(true); - model.setFieldSelected(property, checkbox.getSelection()) - .thenAccept(_ -> setProgressIndicatorsVisible(false)); - } - }); - } + // 1. Calculate a TreeMap of all JournalFields organised into categories. + final Map> fieldsByCategory = JournalFieldCategoriser.calculateCategoryMap(); + + // 2. Create containers to house each category. + fieldsByCategory.forEach((catName, journalFields) -> { + Group catGroup = new Group(selectedContainer, SWT.SHADOW_ETCHED_IN); + catGroup.setText(JournalFieldCategoriser.getFriendlyCategoryName(catName)); + catGroup.setLayout(new RowLayout(SWT.VERTICAL)); + + // 3. Loop through each list of JournalFields in the map as we create the + // containers + for (final JournalField jField : journalFields) { + final Button checkbox = new IBEXButton(catGroup, SWT.CHECK).text(jField.getFriendlyName()) + .selected(model.getFieldSelected(jField)).layoutData(IBEXButton.defaultRow).get(); + + checkboxes.put(jField, checkbox); + + checkbox.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + setProgressIndicatorsVisible(true); + model.setFieldSelected(jField, checkbox.getSelection()) + .thenAccept(_ -> setProgressIndicatorsVisible(false)); + } + }); + } + }); final int tableStyle = SWT.FILL | SWT.FULL_SELECTION; journalTable = new DataboundTable(parent, tableStyle, tableStyle) { @@ -352,6 +363,18 @@ private void resetPageNumber() { DISPLAY.asyncExec(() -> textPageNumber.setText("1")); } + private void filterCheckboxes(final String searchInput) { + String searchInputLow = searchInput.toLowerCase(); + for (Map.Entry entry : checkboxes.entrySet()) { + if (entry.getKey().getFriendlyName().toLowerCase().contains(searchInputLow)) { + entry.getValue().setForeground(CHECKBOXMATCH); + } else { + entry.getValue().setForeground(CHECKBOXNOTMATCH); + } + } + + } + private void bind() { bindingContext.bindValue(WidgetProperties.text().observe(lblError), BeanProperties.value("message").observe(model));