feat(collection): implement custom collection creation flow

- rename window base class usage to BaseModalWindow and update modal method calls
- add persistent CustomDataSettingsWindow instance to CreateCollectionMenu
- prepare custom data collection dynamically before showing the settings window
- apply custom data to AppState without immediately replacing mainCollection
- create mainCollection from prepared custom student data
- replace student preview message with collection preview window
- update input validation and StudentBuilder state handling
- rename collection-related methods and variables for clearer semantics
This commit is contained in:
2026-08-29 18:42:22 +04:00
parent 5c459a1017
commit 8f55486449
7 changed files with 139 additions and 108 deletions
+1 -1
View File
@@ -25,7 +25,7 @@ public final class App {
MainMenu mainMenu = new MainMenu(gui, state); MainMenu mainMenu = new MainMenu(gui, state);
mainMenu.show(); mainMenu.showModal();
} catch (Exception e) { } catch (Exception e) {
System.err.println("Произошла ошибка при выполнении программы:"); System.err.println("Произошла ошибка при выполнении программы:");
@@ -5,14 +5,17 @@ import com.googlecode.lanterna.gui2.Label;
import com.googlecode.lanterna.gui2.LinearLayout; import com.googlecode.lanterna.gui2.LinearLayout;
import com.googlecode.lanterna.gui2.Panel; import com.googlecode.lanterna.gui2.Panel;
import com.googlecode.lanterna.gui2.WindowBasedTextGUI; import com.googlecode.lanterna.gui2.WindowBasedTextGUI;
import ru.project.collection.MyLinkedList;
import ru.project.collection.MyList;
import ru.project.model.AppState; import ru.project.model.AppState;
import ru.project.model.FillMethod; import ru.project.model.FillMethod;
import ru.project.ui.base.BaseWindow; import ru.project.student.Student;
import ru.project.ui.base.BaseModalWindow;
import ru.project.ui.window.CustomDataSettingsWindow; import ru.project.ui.window.CustomDataSettingsWindow;
import ru.project.ui.window.InputValueWindow; import ru.project.ui.window.InputValueWindow;
import ru.project.ui.window.MessageWindow; import ru.project.ui.window.MessageWindow;
public class CreateCollectionMenu extends BaseWindow { public class CreateCollectionMenu extends BaseModalWindow {
private int collectionSize; private int collectionSize;
private FillMethod selectedFillMethod; private FillMethod selectedFillMethod;
@@ -20,16 +23,16 @@ public class CreateCollectionMenu extends BaseWindow {
private final Label sizeLabel; private final Label sizeLabel;
private final Label fillMethodLabel; private final Label fillMethodLabel;
private final AppState state; private final CustomDataSettingsWindow customDataSettingsWindow;
public CreateCollectionMenu(WindowBasedTextGUI gui, AppState state) { public CreateCollectionMenu(WindowBasedTextGUI gui, AppState state) {
super("Создание новой коллекции", gui); super("Создание новой коллекции", gui);
this.state = state;
collectionSize = 0; collectionSize = 0;
selectedFillMethod = FillMethod.CUSTOM; selectedFillMethod = FillMethod.CUSTOM;
customDataSettingsWindow = new CustomDataSettingsWindow(gui, state, () -> collectionSize);
Panel panel = new Panel(); Panel panel = new Panel();
panel.setLayoutManager(new LinearLayout()); panel.setLayoutManager(new LinearLayout());
@@ -40,21 +43,21 @@ public class CreateCollectionMenu extends BaseWindow {
panel.addComponent(sizeLabel); panel.addComponent(sizeLabel);
panel.addComponent(fillMethodLabel); panel.addComponent(fillMethodLabel);
panel.addComponent(new Button("Задать размер", this::openCollectionSizeInputValueWindow)); panel.addComponent(new Button("Задать размер", this::showCollectionSizeInputValueWindow));
panel.addComponent(new Button("Выбрать способ заполнения", this::selectFillMethod)); panel.addComponent(new Button("Выбрать способ заполнения", this::selectFillMethod));
panel.addComponent( panel.addComponent(
new Button("Настроить текущий способ заполнения", this::setupCurrentFillMethod)); new Button("Настроить текущий способ заполнения", this::setupCurrentFillMethod));
panel.addComponent(new Button("Создать новую коллекцию", this::createCollection)); panel.addComponent(new Button("Создать новую коллекцию", () -> updateMainCollection(state)));
panel.addComponent(new Button("Выйти", this::close)); panel.addComponent(new Button("Выйти", this::close));
setComponent(panel); setComponent(panel);
} }
private void openCollectionSizeInputValueWindow() { private void showCollectionSizeInputValueWindow() {
InputValueWindow window = InputValueWindow window =
new InputValueWindow( new InputValueWindow(
gui, gui,
@@ -63,21 +66,21 @@ public class CreateCollectionMenu extends BaseWindow {
collectionSize > 0 ? String.valueOf(collectionSize) : null, collectionSize > 0 ? String.valueOf(collectionSize) : null,
this::handleSizeEntered); this::handleSizeEntered);
window.show(); window.showModal();
} }
private boolean handleSizeEntered(String value) { private boolean handleSizeEntered(String value) {
try { try {
int enteredSize = Integer.parseInt(value); int enteredSize = Integer.parseInt(value);
if (enteredSize <= 0) { if (enteredSize <= 0) {
MessageWindow.show(gui, "Размер должен быть больше 0."); MessageWindow.showModal(gui, "Размер должен быть больше 0.");
return false; return false;
} }
collectionSize = enteredSize; collectionSize = enteredSize;
sizeLabel.setText("Размер: " + collectionSize); sizeLabel.setText("Размер: " + collectionSize);
return true; return true;
} catch (NumberFormatException e) { } catch (NumberFormatException e) {
MessageWindow.show(gui, "Введите целое число."); MessageWindow.showModal(gui, "Введите целое число.");
return false; return false;
} }
} }
@@ -85,22 +88,21 @@ public class CreateCollectionMenu extends BaseWindow {
private void setupCurrentFillMethod() { private void setupCurrentFillMethod() {
switch (selectedFillMethod) { switch (selectedFillMethod) {
case CUSTOM -> { case CUSTOM -> {
BaseWindow window = new CustomDataSettingsWindow(gui, state, collectionSize); customDataSettingsWindow.showModal();
window.show();
} }
case FILE -> case FILE ->
// TODO // TODO
MessageWindow.show(gui, "Не реализовано."); MessageWindow.showModal(gui, "Не реализовано.");
case RANDOM -> case RANDOM ->
MessageWindow.show(gui, "Для случайных данных дополнительные настройки не требуются."); MessageWindow.showModal(
gui, "Для случайных данных дополнительные настройки не требуются.");
} }
} }
private void selectFillMethod() { private void selectFillMethod() {
BaseWindow menu = BaseModalWindow menu =
new FillMethodSelectionWindow( new FillMethodSelectionWindow(
gui, gui,
fillMethod -> { fillMethod -> {
@@ -109,43 +111,52 @@ public class CreateCollectionMenu extends BaseWindow {
"Способ заполнения: " + getFillMethodName(selectedFillMethod)); "Способ заполнения: " + getFillMethodName(selectedFillMethod));
}); });
menu.show(); menu.showModal();
} }
private void createCollection() { private void updateMainCollection(AppState state) {
if (collectionSize <= 0) { if (collectionSize <= 0) {
MessageWindow.show(gui, "Сначала задайте размер коллекции."); MessageWindow.showModal(gui, "Сначала задайте размер коллекции.");
return; return;
} }
switch (selectedFillMethod) { switch (selectedFillMethod) {
case CUSTOM: case CUSTOM:
createCollectionFromCustomData(); createCollectionFromCustomData(state);
break; break;
case RANDOM: case RANDOM:
createCollectionFromRandomData(); createCollectionFromRandomData(state);
break; break;
case FILE: case FILE:
createCollectionFromFileData(); createCollectionFromFileData(state);
break; break;
} }
} }
private void createCollectionFromCustomData() { private void createCollectionFromCustomData(AppState state) {
// TODO MyList<Student> customDataCollection = state.getCustomDataCollection();
MessageWindow.show(gui, "Не реализовано.");
MyList<Student> mainCollection = new MyLinkedList<>();
for (int i = 0; i < customDataCollection.size(); i++) {
mainCollection.add(customDataCollection.get(i));
} }
private void createCollectionFromRandomData() { state.setMainCollection(mainCollection);
// TODO
MessageWindow.show(gui, "Не реализовано."); MessageWindow.showModal(gui, "Коллекция успешно создана.");
} }
private void createCollectionFromFileData() { private void createCollectionFromRandomData(AppState state) {
// TODO // TODO
MessageWindow.show(gui, "Не реализовано."); MessageWindow.showModal(gui, "Не реализовано.");
}
private void createCollectionFromFileData(AppState state) {
// TODO
MessageWindow.showModal(gui, "Не реализовано.");
} }
private String getFillMethodName(FillMethod fillMethod) { private String getFillMethodName(FillMethod fillMethod) {
@@ -4,9 +4,9 @@ import com.googlecode.lanterna.gui2.ActionListBox;
import com.googlecode.lanterna.gui2.WindowBasedTextGUI; import com.googlecode.lanterna.gui2.WindowBasedTextGUI;
import java.util.function.Consumer; import java.util.function.Consumer;
import ru.project.model.FillMethod; import ru.project.model.FillMethod;
import ru.project.ui.base.BaseWindow; import ru.project.ui.base.BaseModalWindow;
public class FillMethodSelectionWindow extends BaseWindow { public class FillMethodSelectionWindow extends BaseModalWindow {
private final Consumer<FillMethod> onFillMethodSelected; private final Consumer<FillMethod> onFillMethodSelected;
@@ -5,25 +5,26 @@ import com.googlecode.lanterna.gui2.WindowBasedTextGUI;
import ru.project.collection.MyList; import ru.project.collection.MyList;
import ru.project.model.AppState; import ru.project.model.AppState;
import ru.project.student.Student; import ru.project.student.Student;
import ru.project.ui.base.BaseWindow; import ru.project.ui.base.BaseModalWindow;
import ru.project.ui.window.CollectionDisplayWindow; import ru.project.ui.window.CollectionDisplayWindow;
import ru.project.ui.window.MessageWindow; import ru.project.ui.window.MessageWindow;
public class MainMenu extends BaseWindow { public class MainMenu extends BaseModalWindow {
private final ActionListBox menu; private final ActionListBox menu;
private final AppState state;
private final BaseModalWindow createCollectionMenu;
public MainMenu(WindowBasedTextGUI gui, AppState state) { public MainMenu(WindowBasedTextGUI gui, AppState state) {
super("Главное меню", gui); super("Главное меню", gui);
this.state = state; this.createCollectionMenu = new CreateCollectionMenu(gui, state);
menu = new ActionListBox(); menu = new ActionListBox();
menu.addItem("Показать текущую коллекцию", this::showCurrentCollection); menu.addItem("Показать текущую коллекцию", () -> showMainCollection(state));
menu.addItem("Меню создания новой коллекции", this::createCollection); menu.addItem("Меню создания новой коллекции", this::showCreateCollectionMenu);
menu.addItem("Меню сохранения текущей коллекции в файл", this::saveCollection); menu.addItem("Меню сохранения текущей коллекции в файл", this::saveCollection);
@@ -38,38 +39,36 @@ public class MainMenu extends BaseWindow {
setComponent(menu); setComponent(menu);
} }
private void showCurrentCollection() { private void showMainCollection(AppState state) {
MyList<Student> currentCollection = state.getCurrentCollection(); MyList<Student> mainCollection = state.getMainCollection();
BaseWindow window = new CollectionDisplayWindow(gui, currentCollection); BaseModalWindow window = new CollectionDisplayWindow(gui, mainCollection);
window.show(); window.showModal();
} }
private void createCollection() { private void showCreateCollectionMenu() {
BaseWindow window = new CreateCollectionMenu(gui, state); createCollectionMenu.showModal();
window.show();
} }
private void saveCollection() { private void saveCollection() {
// TODO // TODO
MessageWindow.show(gui, "Не реализовано."); MessageWindow.showModal(gui, "Не реализовано.");
} }
private void sortCollection() { private void sortCollection() {
// TODO // TODO
MessageWindow.show(gui, "Не реализовано."); MessageWindow.showModal(gui, "Не реализовано.");
} }
private void searchCollection() { private void searchCollection() {
// TODO // TODO
MessageWindow.show(gui, "Не реализовано."); MessageWindow.showModal(gui, "Не реализовано.");
} }
private void countOccurrences() { private void countOccurrences() {
// TODO // TODO
MessageWindow.show(gui, "Не реализовано."); MessageWindow.showModal(gui, "Не реализовано.");
} }
private void exit() { private void exit() {
@@ -12,9 +12,9 @@ import com.googlecode.lanterna.input.KeyStroke;
import com.googlecode.lanterna.input.KeyType; import com.googlecode.lanterna.input.KeyType;
import ru.project.collection.MyList; import ru.project.collection.MyList;
import ru.project.student.Student; import ru.project.student.Student;
import ru.project.ui.base.BaseWindow; import ru.project.ui.base.BaseModalWindow;
public class CollectionDisplayWindow extends BaseWindow { public class CollectionDisplayWindow extends BaseModalWindow {
private final MyList<Student> collection; private final MyList<Student> collection;
@@ -5,17 +5,18 @@ import com.googlecode.lanterna.gui2.Label;
import com.googlecode.lanterna.gui2.LinearLayout; import com.googlecode.lanterna.gui2.LinearLayout;
import com.googlecode.lanterna.gui2.Panel; import com.googlecode.lanterna.gui2.Panel;
import com.googlecode.lanterna.gui2.WindowBasedTextGUI; import com.googlecode.lanterna.gui2.WindowBasedTextGUI;
import java.util.function.Supplier;
import ru.project.collection.MyLinkedList; import ru.project.collection.MyLinkedList;
import ru.project.collection.MyList; import ru.project.collection.MyList;
import ru.project.model.AppState; import ru.project.model.AppState;
import ru.project.student.Student; import ru.project.student.Student;
import ru.project.student.StudentBuilder; import ru.project.student.StudentBuilder;
import ru.project.ui.base.BaseWindow; import ru.project.ui.base.BaseModalWindow;
public class CustomDataSettingsWindow extends BaseWindow { public class CustomDataSettingsWindow extends BaseModalWindow {
private final AppState state; private final AppState state;
private final int collectionSize; private final Supplier<Integer> requestedCollectionSize;
private final StudentBuilder studentBuilder; private final StudentBuilder studentBuilder;
private String groupNumber; private String groupNumber;
@@ -28,28 +29,17 @@ public class CustomDataSettingsWindow extends BaseWindow {
private final Label recordBookNumberLabel; private final Label recordBookNumberLabel;
private final Label indexLabel; private final Label indexLabel;
private final MyList<Student> preparedCustomDataCollection; private MyList<Student> preparedCustomDataCollection;
public CustomDataSettingsWindow(WindowBasedTextGUI gui, AppState state, int collectionSize) { public CustomDataSettingsWindow(
WindowBasedTextGUI gui, AppState state, Supplier<Integer> collectionSize) {
super("Настройка пользовательских данных", gui); super("Настройка пользовательских данных", gui);
this.state = state; this.state = state;
this.collectionSize = collectionSize; this.requestedCollectionSize = collectionSize;
this.studentBuilder = new StudentBuilder(); this.studentBuilder = new StudentBuilder();
this.preparedCustomDataCollection = new MyLinkedList<>();
MyList<Student> customDataCollection = state.getCustomDataCollection();
int elementsToCopy = Math.min(customDataCollection.size(), collectionSize);
for (int i = 0; i < elementsToCopy; i++) {
preparedCustomDataCollection.add(customDataCollection.get(i));
}
for (int i = elementsToCopy; i < collectionSize; i++) {
preparedCustomDataCollection.add(null);
}
Panel panel = new Panel(); Panel panel = new Panel();
panel.setLayoutManager(new LinearLayout()); panel.setLayoutManager(new LinearLayout());
@@ -86,6 +76,29 @@ public class CustomDataSettingsWindow extends BaseWindow {
setComponent(panel); setComponent(panel);
} }
@Override
public void showModal() {
prepareCustomDataCollection();
super.showModal();
}
private void prepareCustomDataCollection() {
preparedCustomDataCollection = new MyLinkedList<>();
int targetCollectionSize = requestedCollectionSize.get();
int currentCustomDataCollectionSize = state.getCustomDataCollection().size();
int elementsToCopy = Math.min(currentCustomDataCollectionSize, targetCollectionSize);
for (int i = 0; i < elementsToCopy; i++) {
preparedCustomDataCollection.add(state.getCustomDataCollection().get(i));
}
for (int i = elementsToCopy; i < targetCollectionSize; i++) {
preparedCustomDataCollection.add(null);
}
}
private void setGroupNumber() { private void setGroupNumber() {
InputValueWindow window = InputValueWindow window =
new InputValueWindow( new InputValueWindow(
@@ -95,7 +108,7 @@ public class CustomDataSettingsWindow extends BaseWindow {
groupNumber, groupNumber,
value -> { value -> {
if (!value.matches(Student.getGroupNumberPattern())) { if (!value.matches(Student.getGroupNumberPattern())) {
MessageWindow.show(gui, "Номер группы должен соответствовать формату A12."); MessageWindow.showModal(gui, "Номер группы должен соответствовать формату A12.");
return false; return false;
} }
@@ -107,7 +120,7 @@ public class CustomDataSettingsWindow extends BaseWindow {
return true; return true;
}); });
window.show(); window.showModal();
} }
private void setAverageGrade() { private void setAverageGrade() {
@@ -124,24 +137,27 @@ public class CustomDataSettingsWindow extends BaseWindow {
if (parsedValue < Student.getMinAverageGrade() if (parsedValue < Student.getMinAverageGrade()
|| parsedValue > Student.getMaxAverageGrade()) { || parsedValue > Student.getMaxAverageGrade()) {
MessageWindow.show(gui, "Средний балл должен быть от 0.0 до 5.0."); MessageWindow.showModal(gui, "Средний балл должен быть от 0.0 до 5.0.");
return false; return false;
} }
averageGrade = parsedValue; averageGrade = parsedValue;
studentBuilder.setAverageGrade(parsedValue); studentBuilder.setAverageGrade(parsedValue);
averageGradeLabel.setText("Средний балл: " + averageGrade); averageGradeLabel.setText("Средний балл: " + averageGrade);
return true;
} catch (NumberFormatException e) { } catch (NumberFormatException e) {
MessageWindow.show(gui, "Введите корректное число."); MessageWindow.showModal(gui, "Введите корректное число.");
return false; return false;
} }
return true;
}); });
window.show(); window.showModal();
} }
private void setRecordBookNumber() { private void setRecordBookNumber() {
@@ -156,24 +172,28 @@ public class CustomDataSettingsWindow extends BaseWindow {
int parsedValue = Integer.parseInt(value); int parsedValue = Integer.parseInt(value);
if (parsedValue < Student.getMinRecordBookNumber()) { if (parsedValue < Student.getMinRecordBookNumber()) {
MessageWindow.show(gui, "Номер зачётной книжки должен быть положительным.");
MessageWindow.showModal(gui, "Номер зачётной книжки должен быть положительным.");
return false; return false;
} }
recordBookNumber = parsedValue; recordBookNumber = parsedValue;
studentBuilder.setRecordBookNumber(parsedValue); studentBuilder.setRecordBookNumber(parsedValue);
recordBookNumberLabel.setText("Номер зачётной книжки: " + recordBookNumber); recordBookNumberLabel.setText("Номер зачётной книжки: " + recordBookNumber);
return true;
} catch (NumberFormatException e) { } catch (NumberFormatException e) {
MessageWindow.show(gui, "Введите целое число."); MessageWindow.showModal(gui, "Введите целое число.");
return false; return false;
} }
return true;
}); });
window.show(); window.showModal();
} }
private void setObjectIndex() { private void setObjectIndex() {
@@ -188,12 +208,16 @@ public class CustomDataSettingsWindow extends BaseWindow {
int index = Integer.parseInt(value); int index = Integer.parseInt(value);
if (index < 0) { if (index < 0) {
MessageWindow.show(gui, "Индекс объекта не может быть отрицательным."); MessageWindow.showModal(gui, "Индекс объекта не может быть отрицательным.");
return false; return false;
} }
if (index >= collectionSize) { int size = requestedCollectionSize.get();
MessageWindow.show(gui, "Индекс должен быть меньше размера коллекции.");
if (index >= size) {
MessageWindow.showModal(gui, "Индекс должен быть меньше размера коллекции.");
return false; return false;
} }
@@ -204,27 +228,28 @@ public class CustomDataSettingsWindow extends BaseWindow {
return true; return true;
} catch (NumberFormatException e) { } catch (NumberFormatException e) {
MessageWindow.show(gui, "Введите целое число."); MessageWindow.showModal(gui, "Введите целое число.");
return false; return false;
} }
}); });
window.show(); window.showModal();
} }
private void editObject() { private void editObject() {
if (!hasStudentData()) { if (!hasStudentData()) {
MessageWindow.show(gui, "Сначала задайте все поля студента."); MessageWindow.showModal(gui, "Сначала задайте все поля студента.");
return; return;
} }
if (selectedObjectIndex == null) { if (selectedObjectIndex == null) {
MessageWindow.show(gui, "Сначала задайте индекс объекта."); MessageWindow.showModal(gui, "Сначала задайте индекс объекта.");
return; return;
} }
if (selectedObjectIndex >= preparedCustomDataCollection.size()) { if (selectedObjectIndex >= preparedCustomDataCollection.size()) {
MessageWindow.show(gui, "Индекс объекта выходит за границы подготовленной коллекции."); MessageWindow.showModal(gui, "Индекс объекта выходит за границы подготовленной коллекции.");
return; return;
} }
@@ -232,31 +257,27 @@ public class CustomDataSettingsWindow extends BaseWindow {
preparedCustomDataCollection.set(selectedObjectIndex, student); preparedCustomDataCollection.set(selectedObjectIndex, student);
MessageWindow.show(gui, "Данные объекта успешно заданы."); MessageWindow.showModal(gui, "Данные объекта успешно заданы.");
} }
private void apply() { private void apply() {
MyList<Student> currentCollection = new MyLinkedList<>();
for (int i = 0; i < preparedCustomDataCollection.size(); i++) {
currentCollection.add(preparedCustomDataCollection.get(i));
}
state.setCustomDataCollection(preparedCustomDataCollection); state.setCustomDataCollection(preparedCustomDataCollection);
state.setCurrentCollection(currentCollection); prepareCustomDataCollection();
close(); MessageWindow.showModal(gui, "Пользовательские данные успешно применены.");
} }
private void preview() { private void preview() {
if (!hasStudentData()) { if (preparedCustomDataCollection == null || preparedCustomDataCollection.isEmpty()) {
MessageWindow.show(gui, "Для предпросмотра сначала задайте все поля студента.");
MessageWindow.showModal(gui, "Подготовленная коллекция пуста.");
return; return;
} }
Student student = studentBuilder.build(); CollectionDisplayWindow window = new CollectionDisplayWindow(gui, preparedCustomDataCollection);
MessageWindow.show(gui, student.toString()); window.showModal();
} }
private boolean hasStudentData() { private boolean hasStudentData() {
@@ -7,9 +7,9 @@ import com.googlecode.lanterna.gui2.Panel;
import com.googlecode.lanterna.gui2.TextBox; import com.googlecode.lanterna.gui2.TextBox;
import com.googlecode.lanterna.gui2.WindowBasedTextGUI; import com.googlecode.lanterna.gui2.WindowBasedTextGUI;
import java.util.function.Predicate; import java.util.function.Predicate;
import ru.project.ui.base.BaseWindow; import ru.project.ui.base.BaseModalWindow;
public class InputValueWindow extends BaseWindow { public class InputValueWindow extends BaseModalWindow {
private final TextBox textBox; private final TextBox textBox;
private final Predicate<String> onSave; private final Predicate<String> onSave;
@@ -45,7 +45,7 @@ public class InputValueWindow extends BaseWindow {
String value = textBox.getText().trim(); String value = textBox.getText().trim();
if (value.isEmpty()) { if (value.isEmpty()) {
MessageWindow.show(gui, "Значение не может быть пустым."); MessageWindow.showModal(gui, "Значение не может быть пустым.");
return; return;
} }