2424import de .doubleslash .keeptime .model .Project ;
2525import de .doubleslash .keeptime .rest .integration .heimat .model .ExistingAndInvalidMappings ;
2626import de .doubleslash .keeptime .rest .integration .heimat .model .HeimatTask ;
27+ import de .doubleslash .keeptime .viewpopup .SearchPopup ;
2728import javafx .application .Platform ;
2829import javafx .beans .property .SimpleBooleanProperty ;
2930import javafx .beans .property .SimpleObjectProperty ;
3536import javafx .fxml .FXML ;
3637import javafx .scene .control .*;
3738import javafx .scene .control .cell .CheckBoxTableCell ;
39+ import javafx .scene .layout .HBox ;
40+ import javafx .scene .layout .Priority ;
3841import javafx .scene .layout .VBox ;
3942import javafx .stage .Stage ;
4043import org .slf4j .Logger ;
@@ -86,15 +89,29 @@ private void initialize() {
8689 tasksForDateDatePicker .setValue (LocalDate .now ());
8790 tasksForDateDatePicker .setDisable (true );
8891 // TODO add listener on this thing
89- // but what happens with mapped projects not existing at that date? but actually not related to this feature alone
9092
91- final List <HeimatTask > externalProjects = heimatController .getTasks (tasksForDateDatePicker .getValue ());
93+ final List <HeimatTask > externalProjects = heimatController .getAllKnownHeimatTasks (tasksForDateDatePicker .getValue ());
94+
9295 final ExistingAndInvalidMappings existingAndInvalidMappings = heimatController .getExistingProjectMappings (
9396 externalProjects );
94- final List <HeimatController .ProjectMapping > previousProjectMappings = existingAndInvalidMappings .validMappings ();
9597
98+
99+ final List <HeimatController .ProjectMapping > previousProjectMappings = existingAndInvalidMappings .validMappings ();
96100 final ObservableList <HeimatController .ProjectMapping > newProjectMappings = FXCollections .observableArrayList (
97101 previousProjectMappings );
102+
103+ Platform .runLater (() -> {
104+ List <String > warnings = existingAndInvalidMappings .invalidMappingsAsString ();
105+ if (!warnings .isEmpty ()) {
106+ if (showInvalidMappingsDialog (warnings )) {
107+ newProjectMappings .stream ()
108+ .filter (HeimatController .ProjectMapping ::isPendingRemoval )
109+ .forEach (pm -> pm .setHeimatTask (null ));
110+ mappingTableView .refresh ();
111+ }
112+ }
113+ });
114+
98115 final FilteredList <HeimatController .ProjectMapping > value = new FilteredList <>(newProjectMappings ,
99116 pm -> pm .getProject ().isWork ());
100117 mappingTableView .setItems (value );
@@ -103,58 +120,56 @@ private void initialize() {
103120 TableColumn <HeimatController .ProjectMapping , String > keepTimeColumn = new TableColumn <>("KeepTime project" );
104121 keepTimeColumn .setCellValueFactory (data -> new SimpleStringProperty (data .getValue ().getProject ().getName ()));
105122
123+ keepTimeColumn .setCellFactory (col -> new TableCell <>() {
124+ @ Override
125+ protected void updateItem (String item , boolean empty ) {
126+ super .updateItem (item , empty );
127+ if (empty || item == null ) {
128+ setText (null );
129+ setTooltip (null );
130+ } else {
131+ setText (item );
132+ Tooltip tooltip = new Tooltip (item );
133+ setTooltip (tooltip );
134+ }
135+ }
136+ });
137+
106138 // External Project column with dropdown
107139 final ObservableList <HeimatTask > externalProjectsObservableList = FXCollections .observableArrayList (
108140 externalProjects );
109- externalProjectsObservableList .add (0 , null ); // option to clear selection
110141
111142 TableColumn <HeimatController .ProjectMapping , HeimatTask > externalColumn = new TableColumn <>("HEIMAT project" );
112143 externalColumn .setCellValueFactory (data -> new SimpleObjectProperty <>(data .getValue ().getHeimatTask ()));
113144 externalColumn .setCellFactory (col -> new TableCell <>() {
114- // TODO search in box would be nice
115- private final ComboBox <HeimatTask > comboBox = new ComboBox <>(externalProjectsObservableList );
145+ private final SearchPopup <HeimatTask > searchPopup = new SearchPopup <>(externalProjectsObservableList );
146+
147+ {
148+ searchPopup .setDisplayTextFunction (ht -> ht == null ? "" : ht .taskHolderName () + " - " + ht .name ());
149+ searchPopup .setClearFieldAfterSelection (false );
150+ searchPopup .setPromptText ("Search Project..." );
151+ searchPopup .setOnItemSelected ((selectedTask , popup ) -> {
152+ HeimatController .ProjectMapping mapping = getTableView ().getItems ().get (getIndex ());
153+ mapping .setHeimatTask (selectedTask );
154+ searchPopup .setComboBoxTooltip (selectedTask .name () + " - " + selectedTask .id ());
155+ updateItem (selectedTask , false );
156+ });
157+ }
116158
117159 @ Override
118160 protected void updateItem (HeimatTask item , boolean empty ) {
119161 super .updateItem (item , empty );
120- // selected item
121- comboBox .setButtonCell (new ListCell <>() {
122- @ Override
123- protected void updateItem (HeimatTask item , boolean empty ) {
124- super .updateItem (item , empty );
125- if (empty || item == null ) {
126- setText (null );
127- } else {
128- setText (item .taskHolderName () + " - " + item .name ());
129- }
130- }
131- });
132-
133- // Dropdown
134- comboBox .setCellFactory (param -> new ListCell <>() {
135- @ Override
136- protected void updateItem (HeimatTask item , boolean empty ) {
137- super .updateItem (item , empty );
138- if (item == null || empty ) {
139- setGraphic (null );
140- setText (null );
141- } else {
142- // TODO maybe show if the project was already mapped
143- setText (item .taskHolderName () + " - " + item .name ());
144- }
145- }
146- });
147-
148162 if (empty ) {
149163 setGraphic (null );
150164 setText (null );
151165 } else {
152- comboBox .setValue (getTableView ().getItems ().get (getIndex ()).getHeimatTask ());
153- comboBox .setOnAction (e -> {
154- HeimatController .ProjectMapping mapping = getTableView ().getItems ().get (getIndex ());
155- mapping .setHeimatTask (comboBox .getValue ());
156- });
157- setGraphic (comboBox );
166+ searchPopup .setSelectedItem (item );
167+ if (item != null ) {
168+ searchPopup .setComboBoxTooltip (item .name () + " - " + item .id ());
169+ } else {
170+ searchPopup .setComboBoxTooltip ("" );
171+ }
172+ setGraphic (searchPopup .getComboBox ());
158173 setText (null );
159174 }
160175 }
@@ -179,7 +194,7 @@ protected void updateItem(HeimatTask item, boolean empty) {
179194 final Project project = controller .addNewProject (
180195 new Project (toBeCreatedHeimatTask .name () + " - " + toBeCreatedHeimatTask .taskHolderName (),
181196 toBeCreatedHeimatTask .bookingHint (), ColorHelper .randomColor (), true , sortIndex ));
182- newProjectMappings .add (new HeimatController .ProjectMapping (project , toBeCreatedHeimatTask ));
197+ newProjectMappings .add (new HeimatController .ProjectMapping (project , toBeCreatedHeimatTask , false ));
183198 }
184199 });
185200
@@ -189,11 +204,6 @@ protected void updateItem(HeimatTask item, boolean empty) {
189204 });
190205
191206 cancelButton .setOnAction (ae -> thisStage .close ());
192-
193- List <String > warnings = existingAndInvalidMappings .invalidMappingsAsString ();
194- if (!warnings .isEmpty ()) {
195- Platform .runLater (() -> showInvalidMappingsDialog (warnings ));
196- }
197207 }
198208
199209 private List <HeimatTask > showMultiSelectDialog (final List <HeimatTask > externalProjects ,
@@ -210,6 +220,11 @@ private List<HeimatTask> showMultiSelectDialog(final List<HeimatTask> externalPr
210220 ButtonType cancelButtonType = new ButtonType ("Cancel" , ButtonBar .ButtonData .CANCEL_CLOSE );
211221 dialog .getDialogPane ().getButtonTypes ().addAll (okButtonType , cancelButtonType );
212222
223+ // Observable and filtered list
224+ ObservableList <HeimatTask > baseList = FXCollections .observableArrayList (externalProjects );
225+ FilteredList <HeimatTask > filteredList = new FilteredList <>(baseList , t -> true );
226+
227+ // Name Column
213228 TableView <HeimatTask > tableView = new TableView <>();
214229 TableColumn <HeimatTask , HeimatTask > nameColumn = new TableColumn <>("HEIMAT project" );
215230 nameColumn .setCellValueFactory (data -> new SimpleObjectProperty <>(data .getValue ()));
@@ -238,9 +253,10 @@ protected void updateItem(HeimatTask item, boolean empty) {
238253 tableView .setEditable (false );
239254
240255 tableView .getSelectionModel ().setSelectionMode (SelectionMode .MULTIPLE );
241- tableView .setItems (FXCollections . observableArrayList ( externalProjects ) );
256+ tableView .setItems (filteredList );
242257
243- Button selectAllUnmappedButton = new Button ("Select unmapped projects (" + unmappedHeimatTasks .size () + ")" );
258+ Button selectAllUnmappedButton = new Button ("Select unmapped projects ("
259+ + unmappedHeimatTasks .size () + ")" );
244260 selectAllUnmappedButton .getStyleClass ().add ("secondary-button" );
245261 selectAllUnmappedButton .setOnAction (e -> {
246262 tableView .getSelectionModel ().clearSelection ();
@@ -250,7 +266,27 @@ protected void updateItem(HeimatTask item, boolean empty) {
250266 tableView .requestFocus ();
251267 });
252268
253- VBox content = new VBox (10 , selectAllUnmappedButton , tableView );
269+ TextField searchField = new TextField ();
270+ searchField .setPromptText ("Search..." );
271+ searchField .textProperty ().addListener ((obs , oldText , newText ) -> {
272+ String filter = newText == null ? "" : newText .trim ().toLowerCase ();
273+ filteredList .setPredicate (task -> {
274+ if (filter .isEmpty ()) return true ;
275+ return task .taskHolderName ().toLowerCase ().contains (filter )
276+ || task .name ().toLowerCase ().contains (filter );
277+ });
278+
279+ long visibleUnmapped = filteredList .stream ().filter (unmappedHeimatTasks ::contains ).count ();
280+ selectAllUnmappedButton .setText ("Select unmapped projects ("
281+ + visibleUnmapped + ")" );
282+ });
283+ searchField .getStyleClass ().add ("text-field" );
284+ searchField .setMaxWidth (Double .MAX_VALUE );
285+ HBox .setHgrow (searchField , Priority .ALWAYS );
286+
287+ HBox headContent = new HBox (50 , selectAllUnmappedButton , searchField );
288+
289+ VBox content = new VBox (10 , headContent , tableView );
254290 dialog .getDialogPane ().setContent (content );
255291 final List <HeimatTask > emptyList = List .of ();
256292 dialog .setResultConverter (dialogButton -> {
@@ -279,11 +315,17 @@ protected void updateItem(HeimatTask item, boolean empty) {
279315 return result .orElse (emptyList );
280316 }
281317
282- private void showInvalidMappingsDialog (final List <String > warnings ) {
283- Dialog <Void > dialog = new Dialog <>();
318+ private boolean showInvalidMappingsDialog (final List <String > warnings ) {
319+ Dialog <ButtonType > dialog = new Dialog <>();
320+
284321 dialog .initOwner (this .thisStage );
322+
323+ Stage dialogStage = (Stage ) dialog .getDialogPane ().getScene ().getWindow ();
324+ dialogStage .getIcons ().addAll (this .thisStage .getIcons ());
325+
285326 dialog .setTitle ("Invalid mappings" );
286- dialog .setHeaderText ("Please note to following issue:" );
327+ dialog .setHeaderText ("The following projects are no longer available.\n "
328+ + "Would you like to remove them from your mapping list?" );
287329
288330 VBox warningBox = new VBox (10 );
289331 for (String warning : warnings ) {
@@ -299,10 +341,11 @@ private void showInvalidMappingsDialog(final List<String> warnings) {
299341 dialog .getDialogPane ().setContent (scrollPane );
300342 dialog .getDialogPane ().setMinWidth (400 );
301343
302- // Add OK button
303- ButtonType okButton = new ButtonType ("OK " , ButtonBar .ButtonData .OK_DONE );
304- dialog .getDialogPane ().getButtonTypes ().add ( okButton );
344+ ButtonType removeButton = new ButtonType ( "Remove" , ButtonBar . ButtonData . YES );
345+ ButtonType keepButton = new ButtonType ("Keep " , ButtonBar .ButtonData .NO );
346+ dialog .getDialogPane ().getButtonTypes ().setAll ( removeButton , keepButton );
305347
306- dialog .showAndWait ();
348+ Optional <ButtonType > result = dialog .showAndWait ();
349+ return result .isPresent () && result .get () == removeButton ;
307350 }
308351}
0 commit comments