package de.dhbw.humbuch.view;
import java.io.ByteArrayOutputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.inject.Inject;
import com.vaadin.event.FieldEvents.TextChangeEvent;
import com.vaadin.event.FieldEvents.TextChangeListener;
import com.vaadin.navigator.View;
import com.vaadin.navigator.ViewChangeListener.ViewChangeEvent;
import com.vaadin.server.StreamResource;
import com.vaadin.ui.Alignment;
import com.vaadin.ui.Button;
import com.vaadin.ui.Button.ClickEvent;
import com.vaadin.ui.Button.ClickListener;
import com.vaadin.ui.HorizontalLayout;
import com.vaadin.ui.MenuBar;
import com.vaadin.ui.MenuBar.Command;
import com.vaadin.ui.MenuBar.MenuItem;
import com.vaadin.ui.TextField;
import com.vaadin.ui.VerticalLayout;
import de.davherrmann.mvvm.BasicState;
import de.davherrmann.mvvm.State;
import de.davherrmann.mvvm.StateChangeListener;
import de.davherrmann.mvvm.ViewModelComposer;
import de.davherrmann.mvvm.annotations.BindState;
import de.dhbw.humbuch.model.entity.BorrowedMaterial;
import de.dhbw.humbuch.model.entity.Grade;
import de.dhbw.humbuch.model.entity.Student;
import de.dhbw.humbuch.model.entity.TeachingMaterial;
import de.dhbw.humbuch.util.PDFClassList;
import de.dhbw.humbuch.util.PDFHandler;
import de.dhbw.humbuch.util.PDFInformationProcessor;
import de.dhbw.humbuch.util.PDFStudentList;
import de.dhbw.humbuch.view.components.ConfirmDialog;
import de.dhbw.humbuch.view.components.PrintingComponent;
import de.dhbw.humbuch.view.components.StudentMaterialSelector;
import de.dhbw.humbuch.view.components.StudentMaterialSelectorObserver;
import de.dhbw.humbuch.viewmodel.LendingViewModel;
import de.dhbw.humbuch.viewmodel.LendingViewModel.MaterialListGrades;
import de.dhbw.humbuch.viewmodel.LendingViewModel.StudentsWithUnreceivedBorrowedMaterials;
import de.dhbw.humbuch.viewmodel.LendingViewModel.TeachingMaterials;
import de.dhbw.humbuch.viewmodel.StudentInformationViewModel;
import de.dhbw.humbuch.viewmodel.StudentInformationViewModel.Students;
/**
* This view displays the Lendingscreen. It holds a horizontal headerbar
* containing actions and a StudentMaterialSelector with all information about
* the lent books of students. It is used to lent books and print student and
* class lists.
*
* @author Henning Muszynski
* */
public class LendingView extends VerticalLayout implements View,
ViewInformation, StudentMaterialSelectorObserver {
private static final long serialVersionUID = -6400075534193735694L;
private final static Logger LOG = LoggerFactory
.getLogger(LendingView.class);
private static final String TITLE = "Ausleihe";
private static final String MANUAL_LENDING_TITLE = "Manuelle Ausleihe";
private static final String SAVE_SELECTED_LENDING = "Material erhalten";
private static final String MANUAL_LENDING = "Manuelle Ausleihe";
private static final String MENU_PRINT = "Listen drucken";
private static final String MENU_ITEM_STUDENT_LIST = "Schüler Liste";
private static final String MENU_ITEM_CLASS_LIST = "Klassen Liste";
private static final String CLASS_LIST_PDF = "KlassenListe.pdf";
private static final String CLASS_LIST_WINDOW_TITLE = "Klassen Liste";
private static final String STUDENT_LIST_PDF = "SchuelerAusleihListe.pdf";
private static final String STUDENT_LIST_WINDOW_TITLE = "Schüler Ausleih Liste";
private static final String FILTER_STUDENT = "Schüler filtern";
private static final String MSG_CONFIRM_RECEIVE = "Sind alle Listen für die ausgewählten Lehrmaterialien unterschrieben vorhanden?";
private HorizontalLayout horizontalLayoutHeaderBar;
private HorizontalLayout horizontalLayoutActions;
private StudentMaterialSelector studentMaterialSelector;
private TextField textFieldStudentFilter;
private Button buttonSaveSelectedData;
private Button buttonManualLending;
private MenuBar menuBarPrinting;
private MenuItem menuItemPrinting;
private MenuItem subMenuItemClassList;
private MenuItem subMenuItemStudentList;
private Command menuCommandClassList;
private Command menuCommandStudentList;
private LendingViewModel lendingViewModel;
private StudentInformationViewModel studentInformationViewModel;
private ConfirmDialog.Listener confirmListener;
@BindState(StudentsWithUnreceivedBorrowedMaterials.class)
private State<Map<Grade, Map<Student, List<BorrowedMaterial>>>> gradeAndStudentsWithMaterials = new BasicState<Map<Grade, Map<Student, List<BorrowedMaterial>>>>(
Map.class);
@BindState(MaterialListGrades.class)
public State<Map<Grade, Map<TeachingMaterial, Integer>>> materialListGrades = new BasicState<>(
Map.class);
@BindState(TeachingMaterials.class)
private State<Collection<TeachingMaterial>> teachingMaterials = new BasicState<>(
Collection.class);
@BindState(Students.class)
public State<Collection<Student>> students = new BasicState<>(
Collection.class);
/**
* Default constructor gets injected. It initializes all views and builds
* the layout. It connects the viewmodel automatically. All parameter get
* injected.
*
* @param viewModelComposer
* the viewmodel composer
* @param lendingViewModel
* the lending viewmodel
* @param studentInformationViewModel
* the student information viewmodel
* */
@Inject
public LendingView(ViewModelComposer viewModelComposer,
LendingViewModel lendingViewModel,
StudentInformationViewModel studentInformationViewModel) {
this.lendingViewModel = lendingViewModel;
this.studentInformationViewModel = studentInformationViewModel;
init();
buildLayout();
bindViewModel(viewModelComposer, lendingViewModel,
studentInformationViewModel);
}
/*
* The init method is responsible for initializing all member variables and
* view components. It configures the components and finally builds the
* layout.
*/
private void init() {
horizontalLayoutHeaderBar = new HorizontalLayout();
horizontalLayoutActions = new HorizontalLayout();
studentMaterialSelector = new StudentMaterialSelector();
textFieldStudentFilter = new TextField();
buttonSaveSelectedData = new Button(SAVE_SELECTED_LENDING);
buttonManualLending = new Button(MANUAL_LENDING);
menuBarPrinting = new MenuBar();
buttonSaveSelectedData.setEnabled(false);
textFieldStudentFilter.setInputPrompt(FILTER_STUDENT);
textFieldStudentFilter.setWidth("50%");
textFieldStudentFilter.setImmediate(true);
defineMenuCommands();
menuItemPrinting = menuBarPrinting.addItem(MENU_PRINT, null);
subMenuItemClassList = menuItemPrinting.addItem(MENU_ITEM_CLASS_LIST,
menuCommandClassList);
subMenuItemClassList.setEnabled(false);
subMenuItemStudentList = menuItemPrinting.addItem(
MENU_ITEM_STUDENT_LIST, menuCommandStudentList);
subMenuItemStudentList.setEnabled(false);
studentMaterialSelector.registerAsObserver(this);
studentMaterialSelector.setSizeFull();
addListeners();
}
/*
* Builds the layout.
*/
private void buildLayout() {
horizontalLayoutHeaderBar.setWidth("100%");
horizontalLayoutActions.setSpacing(true);
setSpacing(true);
setMargin(true);
setSizeFull();
horizontalLayoutActions.addComponent(buttonSaveSelectedData);
horizontalLayoutActions.addComponent(buttonManualLending);
horizontalLayoutActions.addComponent(menuBarPrinting);
horizontalLayoutHeaderBar.addComponent(textFieldStudentFilter);
horizontalLayoutHeaderBar.addComponent(horizontalLayoutActions);
horizontalLayoutHeaderBar.setComponentAlignment(
horizontalLayoutActions, Alignment.MIDDLE_RIGHT);
horizontalLayoutHeaderBar.setComponentAlignment(textFieldStudentFilter,
Alignment.MIDDLE_LEFT);
horizontalLayoutHeaderBar.setExpandRatio(textFieldStudentFilter, 1);
addComponent(horizontalLayoutHeaderBar);
addComponent(studentMaterialSelector);
setExpandRatio(studentMaterialSelector, 1);
}
/*
* General listener method. It adds a listener to the confirm dialog and
* calls all sub methods which add listeners as well.
*/
private void addListeners() {
confirmListener = new ConfirmDialog.Listener() {
private static final long serialVersionUID = 3854273511956714408L;
@Override
public void onClose(ConfirmDialog dialog) {
if (dialog.isConfirmed()) {
setMaterialsReceived();
}
}
};
addStateChangeListenersToStates();
addFilterListeners();
addButtonListeners();
}
/*
* Defines the menu command. The menubar is styled as button which when
* clicked two menu commands appear. They allow to choose between printing a
* student or class list.
*/
private void defineMenuCommands() {
menuCommandClassList = new Command() {
private static final long serialVersionUID = 7304218414715312144L;
@Override
public void menuSelected(MenuItem selectedItem) {
LendingView.this.lendingViewModel
.generateMaterialListGrades(studentMaterialSelector
.getCurrentlySelectedGrades());
}
};
menuCommandStudentList = new Command() {
private static final long serialVersionUID = -5544295528932232629L;
@Override
public void menuSelected(MenuItem selectedItem) {
doStudentListPrinting();
}
};
}
/*
* Adds the listeners to the states in order to get notified whenever a
* state changes. This view is listening to changes of the grade and
* students state as well as the material list state.
*/
private void addStateChangeListenersToStates() {
gradeAndStudentsWithMaterials
.addStateChangeListener(new StateChangeListener() {
@Override
public void stateChange(Object value) {
if (value == null) {
return;
}
updateStudentsWithUnreceivedBorrowedMaterials();
}
});
materialListGrades.addStateChangeListener(new StateChangeListener() {
@Override
public void stateChange(Object value) {
if (value == null) {
return;
}
doClassListPrinting();
}
});
}
/*
* Adds listeners to the buttons. ClickListener are added to the save and
* manual lending button.
*/
private void addButtonListeners() {
buttonSaveSelectedData.addClickListener(new ClickListener() {
private static final long serialVersionUID = -7803362393771729291L;
@Override
public void buttonClick(ClickEvent event) {
ConfirmDialog.show(MSG_CONFIRM_RECEIVE, confirmListener);
}
});
buttonManualLending.addClickListener(new ClickListener() {
private static final long serialVersionUID = -526627937959389240L;
@Override
public void buttonClick(ClickEvent event) {
doManualLending();
}
});
}
/*
* Adds the listener to the filter above the StudentMaterialSelector. This
* allows a live search for the students names.
*/
private void addFilterListeners() {
textFieldStudentFilter.addTextChangeListener(new TextChangeListener() {
private static final long serialVersionUID = -2524687738109998947L;
@Override
public void textChange(TextChangeEvent event) {
studentMaterialSelector.setFilterString(event.getText());
}
});
}
/*
* Starts the manual lending process. When no student is selected a
* SelectStudentPopupWindow is shown otherwise the ManualProcessPopupWindow
* is shown. When multiple students are selected nothing happens (this
* should never happen since the button get is disabled when multiple
* students are selected).
*/
private void doManualLending() {
HashSet<Student> selectedStudents = (HashSet<Student>) studentMaterialSelector
.getCurrentlySelectedStudents();
if (selectedStudents.size() == 0) {
SelectStudentPopupWindow sspw = new SelectStudentPopupWindow(
MANUAL_LENDING_TITLE, LendingView.this, students.get());
getUI().addWindow(sspw);
} else if (selectedStudents.size() == 1) {
// This loop runs only once
for (Student student : selectedStudents) {
ManualProcessPopupWindow mlpw = new ManualProcessPopupWindow(
LendingView.this, student);
getUI().addWindow(mlpw);
}
}
}
/*
* This method triggers the pdf creation of a class list. The pdf is created
* for the selected class in the StudentMaterialSelector. It is possible to
* create multiple pdfs (meaning the pdf having multiple pages) when
* multiple classes are selected.
*/
private void doClassListPrinting() {
Map<Grade, Map<TeachingMaterial, Integer>> gradesAndTeachingMaterials = materialListGrades
.get();
if (gradesAndTeachingMaterials != null) {
ByteArrayOutputStream baos = new PDFClassList(
gradesAndTeachingMaterials)
.createByteArrayOutputStreamForPDF();
if (baos != null) {
String fileNameIncludingHash = "" + new Date().hashCode() + "_"
+ CLASS_LIST_PDF;
StreamResource sr = new StreamResource(
new PDFHandler.PDFStreamSource(baos),
fileNameIncludingHash);
new PrintingComponent(sr, CLASS_LIST_WINDOW_TITLE);
}
} else {
LOG.warn("Grades and Teaching materials are null. No list will be generated / shown.");
}
}
/*
* This method triggers the pdf creation of a student list. The pdf is
* created for the selected students in the StudentMaterialSelector. It is
* possible to create multiple pdfs (meaning the pdf having multiple pages)
* when multiple students or classes are selected.
*/
private void doStudentListPrinting() {
LinkedHashMap<Student, List<BorrowedMaterial>> informationForPdf = getPdfInformationFromStundentMaterialSelector();
if (informationForPdf != null) {
Set<PDFStudentList.Builder> builders = new LinkedHashSet<PDFStudentList.Builder>();
for (Student student : informationForPdf.keySet()) {
PDFStudentList.Builder builder = new PDFStudentList.Builder()
.lendingList(informationForPdf.get(student));
builders.add(builder);
}
ByteArrayOutputStream baos = new PDFStudentList(builders)
.createByteArrayOutputStreamForPDF();
if (baos != null) {
String fileNameIncludingHash = "" + new Date().hashCode() + "_"
+ STUDENT_LIST_PDF;
StreamResource sr = new StreamResource(
new PDFHandler.PDFStreamSource(baos),
fileNameIncludingHash);
new PrintingComponent(sr, STUDENT_LIST_WINDOW_TITLE);
}
}
}
/*
* Collects all information needed for the pdf generation from the
* StudentMaterialSelector. The information is collected and processed. It
* gets sorted and applied to the needed data structure.
*
* @return all information needed for the pdf generation from the
* StudentMaterialSelector
*/
private LinkedHashMap<Student, List<BorrowedMaterial>> getPdfInformationFromStundentMaterialSelector() {
HashSet<BorrowedMaterial> allSelectedMaterials = studentMaterialSelector
.getCurrentlySelectedBorrowedMaterials();
HashSet<Student> allSelectedStudents = studentMaterialSelector
.getCurrentlySelectedStudents();
return PDFInformationProcessor.linkStudentsAndMaterials(
allSelectedMaterials, allSelectedStudents);
}
/*
* This method is called after the save button is pressed and the appearing
* confirm dialog is accepted. It then communicates with the view model in
* order to save the selected borrowed materials.
*/
private void setMaterialsReceived() {
LendingView.this.lendingViewModel
.setBorrowedMaterialsReceived(studentMaterialSelector
.getCurrentlySelectedBorrowedMaterials());
}
/*
* This method is called whenever the the students or their material get
* updated.
*/
private void updateStudentsWithUnreceivedBorrowedMaterials() {
studentMaterialSelector
.setGradesAndStudentsWithMaterials(gradeAndStudentsWithMaterials
.get());
}
/*
* Binds the view model.
*/
private void bindViewModel(ViewModelComposer viewModelComposer,
Object... viewModels) {
try {
viewModelComposer.bind(this, viewModels);
} catch (IllegalAccessException | NoSuchElementException
| UnsupportedOperationException e) {
e.printStackTrace();
}
}
/**
* Update procedure from the view model in order to get new information
* without the need to manually refresh.
*/
@Override
public void update() {
// Get information about current selection of student material selector
HashSet<Student> students = studentMaterialSelector
.getCurrentlySelectedStudents();
HashSet<BorrowedMaterial> materials = studentMaterialSelector
.getCurrentlySelectedBorrowedMaterials();
HashSet<Grade> grades = studentMaterialSelector
.getCurrentlySelectedGrades();
// Adapt manual lending button
if (students.size() <= 1) {
buttonManualLending.setEnabled(true);
} else {
buttonManualLending.setEnabled(false);
}
// Adapt student list button
if (students.size() >= 1) {
subMenuItemStudentList.setEnabled(true);
} else {
subMenuItemStudentList.setEnabled(false);
}
// Adapt class list button
if (grades.size() >= 1) {
subMenuItemClassList.setEnabled(true);
} else {
subMenuItemClassList.setEnabled(false);
}
// Adapt save button
if (materials.size() >= 1) {
buttonSaveSelectedData.setEnabled(true);
} else {
buttonSaveSelectedData.setEnabled(false);
}
}
/**
* Returns a list of all teaching materials. This is used to create the list
* for the manual lending process for example.
*
* @return a list of all teaching materials
* */
public ArrayList<TeachingMaterial> getTeachingMaterials() {
return new ArrayList<TeachingMaterial>(teachingMaterials.get());
}
/**
* Saves all Teaching Materials with the specified return date for all
* students. This method is used for the manual lending process and is
* normally used to update the teaching materials of one student. The passed
* structure has to be valid since no further validation is executed.
*
* @param saveStructure
* a map containing all students and their teaching materials
* including a return date.
* */
public void saveTeachingMaterialsForStudents(
HashMap<Student, HashMap<TeachingMaterial, Date>> saveStructure) {
// the outer loop runs only once
for (Student student : saveStructure.keySet()) {
HashMap<TeachingMaterial, Date> materialsWithDates = saveStructure
.get(student);
for (TeachingMaterial material : materialsWithDates.keySet()) {
lendingViewModel.doManualLending(student, material,
materialsWithDates.get(material));
}
}
}
/**
* This method is alway called when the view is entered (navigated to). It
* refreshes the viewmodel and thus updates the StudentMaterialSelector and
* other view components.
*
* @param event
* the event is not used.
* */
@Override
public void enter(ViewChangeEvent event) {
studentInformationViewModel.refresh();
lendingViewModel.refresh();
}
/**
* Returns the title of this view.
*
* @return the title of this view
* */
@Override
public String getTitle() {
return TITLE;
}
}