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 .SearchCombobox ;
28+ import java .time .LocalDate ;
29+ import java .util .List ;
30+ import java .util .Optional ;
31+
2732import javafx .application .Platform ;
2833import javafx .beans .property .SimpleBooleanProperty ;
2934import javafx .beans .property .SimpleObjectProperty ;
3338import javafx .collections .ObservableList ;
3439import javafx .collections .transformation .FilteredList ;
3540import javafx .fxml .FXML ;
36- import javafx .scene .control .*;
41+ import javafx .scene .control .Button ;
42+ import javafx .scene .control .ButtonBar ;
43+ import javafx .scene .control .ButtonType ;
44+ import javafx .scene .control .DatePicker ;
45+ import javafx .scene .control .Dialog ;
46+ import javafx .scene .control .Label ;
47+ import javafx .scene .control .ScrollPane ;
48+ import javafx .scene .control .SelectionMode ;
49+ import javafx .scene .control .TableCell ;
50+ import javafx .scene .control .TableColumn ;
51+ import javafx .scene .control .TableView ;
52+ import javafx .scene .control .TextField ;
53+ import javafx .scene .control .Tooltip ;
3754import javafx .scene .control .cell .CheckBoxTableCell ;
55+ import javafx .scene .layout .HBox ;
56+ import javafx .scene .layout .Priority ;
3857import javafx .scene .layout .VBox ;
3958import javafx .stage .Stage ;
4059import org .slf4j .Logger ;
4160import org .slf4j .LoggerFactory ;
4261import org .springframework .stereotype .Component ;
4362
44- import java .time .LocalDate ;
45- import java .util .List ;
46- import java .util .Optional ;
47-
4863@ Component
4964public class ExternalProjectsMapController {
5065
@@ -86,15 +101,27 @@ private void initialize() {
86101 tasksForDateDatePicker .setValue (LocalDate .now ());
87102 tasksForDateDatePicker .setDisable (true );
88103 // 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
90104
91105 final List <HeimatTask > externalProjects = heimatController .getTasks (tasksForDateDatePicker .getValue ());
92- final ExistingAndInvalidMappings existingAndInvalidMappings = heimatController .getExistingProjectMappings (
93- externalProjects );
94- final List <HeimatController .ProjectMapping > previousProjectMappings = existingAndInvalidMappings .validMappings ();
95106
107+ final ExistingAndInvalidMappings existingAndInvalidMappings = heimatController .getExistingProjectMappings (externalProjects );
108+
109+ final List <HeimatController .ProjectMapping > previousProjectMappings = existingAndInvalidMappings .validMappings ();
96110 final ObservableList <HeimatController .ProjectMapping > newProjectMappings = FXCollections .observableArrayList (
97111 previousProjectMappings );
112+
113+ Platform .runLater (() -> {
114+ List <String > warnings = existingAndInvalidMappings .invalidMappingsAsString ();
115+ if (!warnings .isEmpty ()) {
116+ if (showInvalidMappingsDialog (warnings )) {
117+ newProjectMappings .stream ()
118+ .filter (HeimatController .ProjectMapping ::isPendingRemoval )
119+ .forEach (pm -> pm .setHeimatTask (null ));
120+ mappingTableView .refresh ();
121+ }
122+ }
123+ });
124+
98125 final FilteredList <HeimatController .ProjectMapping > value = new FilteredList <>(newProjectMappings ,
99126 pm -> pm .getProject ().isWork ());
100127 mappingTableView .setItems (value );
@@ -103,58 +130,62 @@ private void initialize() {
103130 TableColumn <HeimatController .ProjectMapping , String > keepTimeColumn = new TableColumn <>("KeepTime project" );
104131 keepTimeColumn .setCellValueFactory (data -> new SimpleStringProperty (data .getValue ().getProject ().getName ()));
105132
133+ keepTimeColumn .setCellFactory (col -> new TableCell <>() {
134+ @ Override
135+ protected void updateItem (String item , boolean empty ) {
136+ super .updateItem (item , empty );
137+ if (empty || item == null ) {
138+ setText (null );
139+ setTooltip (null );
140+ } else {
141+ setText (item );
142+ Tooltip tooltip = new Tooltip (item );
143+ setTooltip (tooltip );
144+ }
145+ }
146+ });
147+
106148 // External Project column with dropdown
107149 final ObservableList <HeimatTask > externalProjectsObservableList = FXCollections .observableArrayList (
108150 externalProjects );
109- externalProjectsObservableList .add (0 , null ); // option to clear selection
110-
151+ externalProjectsObservableList .add (0 ,null );
111152 TableColumn <HeimatController .ProjectMapping , HeimatTask > externalColumn = new TableColumn <>("Heimat project" );
112153 externalColumn .setCellValueFactory (data -> new SimpleObjectProperty <>(data .getValue ().getHeimatTask ()));
113154 externalColumn .setCellFactory (col -> new TableCell <>() {
114- // TODO search in box would be nice
115- private final ComboBox <HeimatTask > comboBox = new ComboBox <>(externalProjectsObservableList );
155+ private final SearchCombobox <HeimatTask > searchPopup = new SearchCombobox <>(externalProjectsObservableList );
156+
157+ {
158+ searchPopup .setDisplayTextFunction (ht -> ht == null ? "" : ht .taskHolderName () + " - " + ht .name ());
159+ searchPopup .setClearFieldAfterSelection (false );
160+ searchPopup .setPromptText ("Search Project..." );
161+ searchPopup .setOnItemSelected ((selectedTask , popup ) -> {
162+ HeimatController .ProjectMapping mapping = getTableView ().getItems ().get (getIndex ());
163+ mapping .setHeimatTask (selectedTask );
164+ if (selectedTask != null )
165+ searchPopup .setComboBoxTooltip (selectedTask .name () + " - " + selectedTask .id ());
166+ updateItem (selectedTask , false );
167+ });
168+ }
116169
117170 @ Override
118171 protected void updateItem (HeimatTask item , boolean empty ) {
119172 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-
148173 if (empty ) {
149174 setGraphic (null );
150175 setText (null );
176+ setStyle (null );
151177 } 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 );
178+ searchPopup .setSelectedItem (item );
179+ if (item != null ) {
180+ searchPopup .setComboBoxTooltip (item .name () + " - " + item .id ());
181+ } else {
182+ searchPopup .setComboBoxTooltip ("" );
183+ }
184+
185+ // highlight mappings which do not exist anymore
186+ final String highlightStyle = item != null && !externalProjects .contains (item ) ? "-fx-background-color: lightsalmon;" : null ;
187+ setStyle (highlightStyle );
188+ setGraphic (searchPopup .getComboBox ());
158189 setText (null );
159190 }
160191 }
@@ -179,7 +210,7 @@ protected void updateItem(HeimatTask item, boolean empty) {
179210 final Project project = controller .addNewProject (
180211 new Project (toBeCreatedHeimatTask .name () + " - " + toBeCreatedHeimatTask .taskHolderName (),
181212 toBeCreatedHeimatTask .bookingHint (), ColorHelper .randomColor (), true , sortIndex ));
182- newProjectMappings .add (new HeimatController .ProjectMapping (project , toBeCreatedHeimatTask ));
213+ newProjectMappings .add (new HeimatController .ProjectMapping (project , toBeCreatedHeimatTask , false ));
183214 }
184215 });
185216
@@ -189,11 +220,6 @@ protected void updateItem(HeimatTask item, boolean empty) {
189220 });
190221
191222 cancelButton .setOnAction (ae -> thisStage .close ());
192-
193- List <String > warnings = existingAndInvalidMappings .invalidMappingsAsString ();
194- if (!warnings .isEmpty ()) {
195- Platform .runLater (() -> showInvalidMappingsDialog (warnings ));
196- }
197223 }
198224
199225 private List <HeimatTask > showMultiSelectDialog (final List <HeimatTask > externalProjects ,
@@ -210,6 +236,11 @@ private List<HeimatTask> showMultiSelectDialog(final List<HeimatTask> externalPr
210236 ButtonType cancelButtonType = new ButtonType ("Cancel" , ButtonBar .ButtonData .CANCEL_CLOSE );
211237 dialog .getDialogPane ().getButtonTypes ().addAll (okButtonType , cancelButtonType );
212238
239+ // Observable and filtered list
240+ ObservableList <HeimatTask > baseList = FXCollections .observableArrayList (externalProjects );
241+ FilteredList <HeimatTask > filteredList = new FilteredList <>(baseList , t -> true );
242+
243+ // Name Column
213244 TableView <HeimatTask > tableView = new TableView <>();
214245 TableColumn <HeimatTask , HeimatTask > nameColumn = new TableColumn <>("Heimat project" );
215246 nameColumn .setCellValueFactory (data -> new SimpleObjectProperty <>(data .getValue ()));
@@ -238,9 +269,10 @@ protected void updateItem(HeimatTask item, boolean empty) {
238269 tableView .setEditable (false );
239270
240271 tableView .getSelectionModel ().setSelectionMode (SelectionMode .MULTIPLE );
241- tableView .setItems (FXCollections . observableArrayList ( externalProjects ) );
272+ tableView .setItems (filteredList );
242273
243- Button selectAllUnmappedButton = new Button ("Select unmapped projects (" + unmappedHeimatTasks .size () + ")" );
274+ Button selectAllUnmappedButton = new Button ("Select unmapped projects ("
275+ + unmappedHeimatTasks .size () + ")" );
244276 selectAllUnmappedButton .getStyleClass ().add ("secondary-button" );
245277 selectAllUnmappedButton .setOnAction (e -> {
246278 tableView .getSelectionModel ().clearSelection ();
@@ -250,7 +282,27 @@ protected void updateItem(HeimatTask item, boolean empty) {
250282 tableView .requestFocus ();
251283 });
252284
253- VBox content = new VBox (10 , selectAllUnmappedButton , tableView );
285+ TextField searchField = new TextField ();
286+ searchField .setPromptText ("Search..." );
287+ searchField .textProperty ().addListener ((obs , oldText , newText ) -> {
288+ String filter = newText == null ? "" : newText .trim ().toLowerCase ();
289+ filteredList .setPredicate (task -> {
290+ if (filter .isEmpty ()) return true ;
291+ return task .taskHolderName ().toLowerCase ().contains (filter )
292+ || task .name ().toLowerCase ().contains (filter );
293+ });
294+
295+ long visibleUnmapped = filteredList .stream ().filter (unmappedHeimatTasks ::contains ).count ();
296+ selectAllUnmappedButton .setText ("Select unmapped projects ("
297+ + visibleUnmapped + ")" );
298+ });
299+ searchField .getStyleClass ().add ("text-field" );
300+ searchField .setMaxWidth (Double .MAX_VALUE );
301+ HBox .setHgrow (searchField , Priority .ALWAYS );
302+
303+ HBox headContent = new HBox (50 , selectAllUnmappedButton , searchField );
304+
305+ VBox content = new VBox (10 , headContent , tableView );
254306 dialog .getDialogPane ().setContent (content );
255307 final List <HeimatTask > emptyList = List .of ();
256308 dialog .setResultConverter (dialogButton -> {
@@ -279,11 +331,17 @@ protected void updateItem(HeimatTask item, boolean empty) {
279331 return result .orElse (emptyList );
280332 }
281333
282- private void showInvalidMappingsDialog (final List <String > warnings ) {
283- Dialog <Void > dialog = new Dialog <>();
334+ private boolean showInvalidMappingsDialog (final List <String > warnings ) {
335+ Dialog <ButtonType > dialog = new Dialog <>();
336+
284337 dialog .initOwner (this .thisStage );
338+
339+ Stage dialogStage = (Stage ) dialog .getDialogPane ().getScene ().getWindow ();
340+ dialogStage .getIcons ().addAll (this .thisStage .getIcons ());
341+
285342 dialog .setTitle ("Invalid mappings" );
286- dialog .setHeaderText ("Please note to following issue:" );
343+ dialog .setHeaderText ("The following projects are no longer available.\n "
344+ + "Would you like to remove them from your mapping list?" );
287345
288346 VBox warningBox = new VBox (10 );
289347 for (String warning : warnings ) {
@@ -299,10 +357,11 @@ private void showInvalidMappingsDialog(final List<String> warnings) {
299357 dialog .getDialogPane ().setContent (scrollPane );
300358 dialog .getDialogPane ().setMinWidth (400 );
301359
302- // Add OK button
303- ButtonType okButton = new ButtonType ("OK " , ButtonBar .ButtonData .OK_DONE );
304- dialog .getDialogPane ().getButtonTypes ().add ( okButton );
360+ ButtonType removeButton = new ButtonType ( "Remove" , ButtonBar . ButtonData . YES );
361+ ButtonType keepButton = new ButtonType ("Keep " , ButtonBar .ButtonData .NO );
362+ dialog .getDialogPane ().getButtonTypes ().setAll ( removeButton , keepButton );
305363
306- dialog .showAndWait ();
364+ Optional <ButtonType > result = dialog .showAndWait ();
365+ return result .isPresent () && result .get () == removeButton ;
307366 }
308367}
0 commit comments