From 259e0bc8bddd9ad4a0c801fead91a5c4404e2e57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=BE=D0=BB=D0=BE=D0=B4=D0=B8=D0=BD=20=D0=93=D0=B5?= =?UTF-8?q?=D1=80=D0=BC=D0=B0=D0=BD?= Date: Tue, 1 Sep 2026 17:13:48 +0400 Subject: [PATCH] feat(search): add occurrence counting menu and student settings - add occurrence counting menu using StudentOccurrenceIndexFinder - add student settings window for configuring search target - display occurrence count and matching indices - update current collection menu to delegate occurrence counting --- .../ui/collection/CurrentCollectionMenu.java | 14 +- .../counting/CountOccurrencesMenu.java | 109 +++++++++++ .../OccurrenceStudentSettingsWindow.java | 172 ++++++++++++++++++ 3 files changed, 285 insertions(+), 10 deletions(-) create mode 100644 app/src/main/java/ru/project/ui/collection/counting/CountOccurrencesMenu.java create mode 100644 app/src/main/java/ru/project/ui/collection/counting/OccurrenceStudentSettingsWindow.java diff --git a/app/src/main/java/ru/project/ui/collection/CurrentCollectionMenu.java b/app/src/main/java/ru/project/ui/collection/CurrentCollectionMenu.java index 516c020..af294af 100644 --- a/app/src/main/java/ru/project/ui/collection/CurrentCollectionMenu.java +++ b/app/src/main/java/ru/project/ui/collection/CurrentCollectionMenu.java @@ -4,6 +4,7 @@ import com.googlecode.lanterna.gui2.ActionListBox; import com.googlecode.lanterna.gui2.WindowBasedTextGUI; import ru.project.model.AppState; import ru.project.ui.base.BaseModalWindow; +import ru.project.ui.collection.counting.CountOccurrencesMenu; import ru.project.ui.collection.search.SearchCollectionMenu; import ru.project.ui.collection.sorting.SortCollectionMenu; import ru.project.ui.common.CollectionDisplayWindow; @@ -15,8 +16,7 @@ public class CurrentCollectionMenu extends BaseModalWindow { private final SortCollectionMenu sortCollectionMenu; private final SearchCollectionMenu searchCollectionMenu; - - // private final CountOccurrencesMenu countOccurrencesMenu; + private final CountOccurrencesMenu countOccurrencesMenu; public CurrentCollectionMenu(WindowBasedTextGUI gui, AppState state) { @@ -28,7 +28,7 @@ public class CurrentCollectionMenu extends BaseModalWindow { this.searchCollectionMenu = new SearchCollectionMenu(gui, state); - // this.countCollectionMenu = new CountOccurrencesMenu(gui, state); + this.countOccurrencesMenu = new CountOccurrencesMenu(gui, state); ActionListBox menu = new ActionListBox(); menu.addItem("Показать коллекцию...", this::showCurrentCollection); @@ -39,7 +39,7 @@ public class CurrentCollectionMenu extends BaseModalWindow { menu.addItem("Меню поиска элемента в коллекции", searchCollectionMenu::showModal); - menu.addItem("Меню подсчёта количества вхождений элемента", this::countOccurrences); + menu.addItem("Меню подсчёта количества вхождений элемента", countOccurrencesMenu::showModal); menu.addItem("Выйти", this::close); @@ -57,10 +57,4 @@ public class CurrentCollectionMenu extends BaseModalWindow { window.showModal(); } - - private void countOccurrences() { - // CountOccurrencesMenu countOccurrencesMenu = new CountOccurrencesMenu(gui, state); - // - // countOccurrencesMenu.showModal(); - } } diff --git a/app/src/main/java/ru/project/ui/collection/counting/CountOccurrencesMenu.java b/app/src/main/java/ru/project/ui/collection/counting/CountOccurrencesMenu.java new file mode 100644 index 0000000..26cd626 --- /dev/null +++ b/app/src/main/java/ru/project/ui/collection/counting/CountOccurrencesMenu.java @@ -0,0 +1,109 @@ +package ru.project.ui.collection.counting; + +import com.googlecode.lanterna.gui2.Button; +import com.googlecode.lanterna.gui2.Label; +import com.googlecode.lanterna.gui2.LinearLayout; +import com.googlecode.lanterna.gui2.Panel; +import com.googlecode.lanterna.gui2.WindowBasedTextGUI; +import ru.project.collection.MyList; +import ru.project.finder.StudentOccurrenceIndexFinder; +import ru.project.model.AppState; +import ru.project.student.Student; +import ru.project.ui.base.BaseModalWindow; +import ru.project.ui.common.MessageWindow; + +public class CountOccurrencesMenu extends BaseModalWindow { + + private final AppState state; + + private final OccurrenceStudentSettingsWindow occurrenceStudentSettingsWindow; + + private Student selectedStudent; + private int occurrences; + + private final Label studentLabel; + private final Label occurrencesLabel; + + public CountOccurrencesMenu(WindowBasedTextGUI gui, AppState state) { + + super("Меню подсчёта количества вхождений элемента", gui); + + this.state = state; + this.selectedStudent = null; + this.occurrences = 0; + + this.studentLabel = new Label("Элемент для поиска: не задан"); + + this.occurrencesLabel = new Label("Количество вхождений: не подсчитано"); + + this.occurrenceStudentSettingsWindow = + new OccurrenceStudentSettingsWindow( + gui, + student -> { + selectedStudent = student; + + studentLabel.setText("Элемент для поиска: задан"); + }); + + Panel panel = new Panel(); + panel.setLayoutManager(new LinearLayout()); + + panel.addComponent(studentLabel); + panel.addComponent(occurrencesLabel); + + panel.addComponent(new Button("Настроить элемент для поиска...", this::setupStudent)); + + panel.addComponent(new Button("Выполнить подсчёт", this::countOccurrences)); + + panel.addComponent(new Button("Выйти", this::close)); + + setComponent(panel); + } + + private void setupStudent() { + occurrenceStudentSettingsWindow.showModal(); + } + + private void countOccurrences() { + MyList collection = state.getMainCollection(); + + if (collection == null || collection.isEmpty()) { + MessageWindow.showModal(gui, "Текущая коллекция пуста."); + + return; + } + + if (selectedStudent == null) { + MessageWindow.showModal(gui, "Сначала настройте элемент для поиска."); + + return; + } + + StudentOccurrenceIndexFinder finder = new StudentOccurrenceIndexFinder(); + + int[] occurrenceIndices = finder.findOccurrences(collection, selectedStudent); + + occurrences = occurrenceIndices.length; + + StringBuilder result = new StringBuilder(); + + result.append("Количество вхождений: ").append(occurrences); + + result.append("\nИндексы: "); + + if (occurrenceIndices.length == 0) { + result.append("нет"); + } else { + for (int i = 0; i < occurrenceIndices.length; i++) { + + if (i > 0) { + result.append(", "); + } + + result.append(occurrenceIndices[i]); + } + } + + occurrencesLabel.setText(result.toString()); + } +} diff --git a/app/src/main/java/ru/project/ui/collection/counting/OccurrenceStudentSettingsWindow.java b/app/src/main/java/ru/project/ui/collection/counting/OccurrenceStudentSettingsWindow.java new file mode 100644 index 0000000..06cd33d --- /dev/null +++ b/app/src/main/java/ru/project/ui/collection/counting/OccurrenceStudentSettingsWindow.java @@ -0,0 +1,172 @@ +package ru.project.ui.collection.counting; + +import com.googlecode.lanterna.gui2.Button; +import com.googlecode.lanterna.gui2.Label; +import com.googlecode.lanterna.gui2.LinearLayout; +import com.googlecode.lanterna.gui2.Panel; +import com.googlecode.lanterna.gui2.WindowBasedTextGUI; +import java.util.function.Consumer; +import ru.project.student.Student; +import ru.project.student.StudentBuilder; +import ru.project.ui.base.BaseModalWindow; +import ru.project.ui.common.InputValueWindow; +import ru.project.ui.common.MessageWindow; + +public class OccurrenceStudentSettingsWindow extends BaseModalWindow { + + private final StudentBuilder studentBuilder; + private final Consumer onApply; + + private String groupNumber; + private Double averageGrade; + private Integer recordBookNumber; + + private final Label groupNumberLabel; + private final Label averageGradeLabel; + private final Label recordBookNumberLabel; + + public OccurrenceStudentSettingsWindow(WindowBasedTextGUI gui, Consumer onApply) { + + super("Настройка элемента для поиска", gui); + + this.studentBuilder = new StudentBuilder(); + this.onApply = onApply; + + Panel panel = new Panel(); + panel.setLayoutManager(new LinearLayout()); + + groupNumberLabel = new Label("Номер группы: не задан"); + + averageGradeLabel = new Label("Средний балл: не задан"); + + recordBookNumberLabel = new Label("Номер зачётной книжки: не задан"); + + panel.addComponent(groupNumberLabel); + panel.addComponent(averageGradeLabel); + panel.addComponent(recordBookNumberLabel); + + panel.addComponent(new Button("Задать номер группы...", this::setGroupNumber)); + + panel.addComponent(new Button("Задать средний балл...", this::setAverageGrade)); + + panel.addComponent(new Button("Задать номер зачётной книжки...", this::setRecordBookNumber)); + + panel.addComponent(new Button("Применить", this::apply)); + + panel.addComponent(new Button("Выйти", this::close)); + + setComponent(panel); + } + + private void setGroupNumber() { + InputValueWindow window = + new InputValueWindow( + gui, + "Номер группы", + "Введите номер группы (например, A12):", + groupNumber, + value -> { + if (!value.matches(Student.getGroupNumberPattern())) { + + MessageWindow.showModal(gui, "Номер группы должен соответствовать формату A12."); + + return false; + } + + groupNumber = value; + studentBuilder.setGroupNumber(value); + + groupNumberLabel.setText("Номер группы: " + groupNumber); + + return true; + }); + + window.showModal(); + } + + private void setAverageGrade() { + InputValueWindow window = + new InputValueWindow( + gui, + "Средний балл", + "Введите средний балл от 0.0 до 5.0:", + averageGrade == null ? null : String.valueOf(averageGrade), + value -> { + try { + double parsedValue = Double.parseDouble(value); + + if (parsedValue < Student.getMinAverageGrade() + || parsedValue > Student.getMaxAverageGrade()) { + + MessageWindow.showModal(gui, "Средний балл должен быть от 0.0 до 5.0."); + + return false; + } + + averageGrade = parsedValue; + studentBuilder.setAverageGrade(parsedValue); + + averageGradeLabel.setText("Средний балл: " + averageGrade); + + return true; + + } catch (NumberFormatException e) { + MessageWindow.showModal(gui, "Введите корректное число."); + + return false; + } + }); + + window.showModal(); + } + + private void setRecordBookNumber() { + InputValueWindow window = + new InputValueWindow( + gui, + "Номер зачётной книжки", + "Введите номер зачётной книжки:", + recordBookNumber == null ? null : String.valueOf(recordBookNumber), + value -> { + try { + int parsedValue = Integer.parseInt(value); + + if (parsedValue < Student.getMinRecordBookNumber()) { + + MessageWindow.showModal(gui, "Номер зачётной книжки должен быть положительным."); + + return false; + } + + recordBookNumber = parsedValue; + studentBuilder.setRecordBookNumber(parsedValue); + + recordBookNumberLabel.setText("Номер зачётной книжки: " + recordBookNumber); + + return true; + + } catch (NumberFormatException e) { + MessageWindow.showModal(gui, "Введите целое число."); + + return false; + } + }); + + window.showModal(); + } + + private void apply() { + if (groupNumber == null || averageGrade == null || recordBookNumber == null) { + + MessageWindow.showModal(gui, "Необходимо задать все поля элемента."); + + return; + } + + Student student = studentBuilder.build(); + + onApply.accept(student); + + MessageWindow.showModal(gui, "Элемент для поиска успешно настроен."); + } +}