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
This commit is contained in:
2026-09-01 17:13:48 +04:00
parent 84f9e2aa8d
commit 259e0bc8bd
3 changed files with 285 additions and 10 deletions
@@ -4,6 +4,7 @@ import com.googlecode.lanterna.gui2.ActionListBox;
import com.googlecode.lanterna.gui2.WindowBasedTextGUI; import com.googlecode.lanterna.gui2.WindowBasedTextGUI;
import ru.project.model.AppState; import ru.project.model.AppState;
import ru.project.ui.base.BaseModalWindow; 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.search.SearchCollectionMenu;
import ru.project.ui.collection.sorting.SortCollectionMenu; import ru.project.ui.collection.sorting.SortCollectionMenu;
import ru.project.ui.common.CollectionDisplayWindow; import ru.project.ui.common.CollectionDisplayWindow;
@@ -15,8 +16,7 @@ public class CurrentCollectionMenu extends BaseModalWindow {
private final SortCollectionMenu sortCollectionMenu; private final SortCollectionMenu sortCollectionMenu;
private final SearchCollectionMenu searchCollectionMenu; private final SearchCollectionMenu searchCollectionMenu;
private final CountOccurrencesMenu countOccurrencesMenu;
// private final CountOccurrencesMenu countOccurrencesMenu;
public CurrentCollectionMenu(WindowBasedTextGUI gui, AppState state) { public CurrentCollectionMenu(WindowBasedTextGUI gui, AppState state) {
@@ -28,7 +28,7 @@ public class CurrentCollectionMenu extends BaseModalWindow {
this.searchCollectionMenu = new SearchCollectionMenu(gui, state); this.searchCollectionMenu = new SearchCollectionMenu(gui, state);
// this.countCollectionMenu = new CountOccurrencesMenu(gui, state); this.countOccurrencesMenu = new CountOccurrencesMenu(gui, state);
ActionListBox menu = new ActionListBox(); ActionListBox menu = new ActionListBox();
menu.addItem("Показать коллекцию...", this::showCurrentCollection); menu.addItem("Показать коллекцию...", this::showCurrentCollection);
@@ -39,7 +39,7 @@ public class CurrentCollectionMenu extends BaseModalWindow {
menu.addItem("Меню поиска элемента в коллекции", searchCollectionMenu::showModal); menu.addItem("Меню поиска элемента в коллекции", searchCollectionMenu::showModal);
menu.addItem("Меню подсчёта количества вхождений элемента", this::countOccurrences); menu.addItem("Меню подсчёта количества вхождений элемента", countOccurrencesMenu::showModal);
menu.addItem("Выйти", this::close); menu.addItem("Выйти", this::close);
@@ -57,10 +57,4 @@ public class CurrentCollectionMenu extends BaseModalWindow {
window.showModal(); window.showModal();
} }
private void countOccurrences() {
// CountOccurrencesMenu countOccurrencesMenu = new CountOccurrencesMenu(gui, state);
//
// countOccurrencesMenu.showModal();
}
} }
@@ -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<Student> 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());
}
}
@@ -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<Student> 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<Student> 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, "Элемент для поиска успешно настроен.");
}
}