View Javadoc
1   package de.spiritscorp.datasync.gui;
2   
3   /*-
4    * 		Data Sync
5    *
6    * 		Copyright ©   2022    The Spirit
7    * 		@email                        thespirit@spiritscorp.network
8    *
9    * 		This program is free software; you can redistribute it and/or modify
10   * 		it under the terms of the GNU General Public License as published by
11   * 		the Free Software Foundation; either version 3 of the License, or
12   * 		(at your option) any later version.
13   *
14   * 		This program is distributed in the hope that it will be useful,
15   * 		but WITHOUT ANY WARRANTY; without even the implied warranty of
16   * 		MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
17   * 		See the GNU General Public License for more details.
18   *
19   * 		You should have received a copy of the GNU General Public License
20   * 		along with this program. If not, see <http://www.gnu.org/licenses/>.
21   */
22  
23  import org.kordamp.ikonli.javafx.FontIcon;
24  import org.kordamp.ikonli.materialdesign2.MaterialDesignC;
25  import org.kordamp.ikonli.materialdesign2.MaterialDesignD;
26  import org.kordamp.ikonli.materialdesign2.MaterialDesignF;
27  import org.kordamp.ikonli.materialdesign2.MaterialDesignH;
28  import org.kordamp.ikonli.materialdesign2.MaterialDesignI;
29  import org.kordamp.ikonli.materialdesign2.MaterialDesignP;
30  import org.kordamp.ikonli.materialdesign2.MaterialDesignS;
31  
32  import de.spiritscorp.datasync.controller.SyncJobContext;
33  import de.spiritscorp.datasync.controller.ViewController;
34  import javafx.geometry.Insets;
35  import javafx.scene.control.Button;
36  import javafx.scene.control.ContextMenu;
37  import javafx.scene.control.Label;
38  import javafx.scene.control.ListCell;
39  import javafx.scene.control.ListView;
40  import javafx.scene.control.MenuButton;
41  import javafx.scene.control.MenuItem;
42  import javafx.scene.control.SeparatorMenuItem;
43  import javafx.scene.layout.Priority;
44  import javafx.scene.layout.VBox;
45  
46  /**
47   * Sidebar Navigation panel hosting the main execution links,
48   * managed tasks instances, and dynamic manipulation actions via context menus.
49   * * @author Tom Spirit
50   */
51  public final class SidebarView extends VBox {
52  
53  	private final ListView<SyncJobContext> sidebarListView;
54  	private final Gui mainGui;
55  	private final ViewController controller;
56  
57  	/**
58  	 * Constructs the graphical container and links action events directly into specified controllers interfaces.
59  	 *
60  	 * @param mainGui    Application root shell container.
61  	 * @param controller Associated decoupled interaction pipeline boundary coordinator.
62  	 */
63  	public SidebarView( Gui mainGui, ViewController controller ) {
64  		this.mainGui = mainGui;
65  		this.controller = controller;
66  		this.setSpacing( 16 );
67  		this.setPadding( new Insets( 16, 16, 24, 16 ) );
68  		this.setPrefWidth( 300 );
69  
70  		// Initialize application navigation switcher menu
71  		final MenuButton hamburgerMenu = new MenuButton( "Navigation", Gui.createIcon( MaterialDesignH.HAMBURGER ) );
72  		hamburgerMenu.setMaxWidth( Double.MAX_VALUE );
73  
74  		final MenuItem taskViewItem = new MenuItem( "Aktiver Task-Monitor", Gui.createIcon( MaterialDesignF.FOLDER ) );
75  		taskViewItem.setOnAction( _ -> controller.handleNavigate( Gui.ViewState.MONITOR ) );
76  
77  		final MenuItem settingsItem = new MenuItem( "Erweiterte Parameter", Gui.createIcon( MaterialDesignS.STORE_SETTINGS ) );
78  		settingsItem.setOnAction( _ -> controller.handleNavigate( Gui.ViewState.SETTINGS ) );
79  
80  		final MenuItem infoItem = new MenuItem( "System-Informationen", Gui.createIcon( MaterialDesignI.INFORMATION ) );
81  		infoItem.setOnAction( _ -> controller.handleNavigate( Gui.ViewState.INFO ) );
82  
83  		hamburgerMenu.getItems().addAll( taskViewItem, settingsItem, new SeparatorMenuItem(), infoItem );
84  
85  		// Section header label
86  		final Label sidebarHeader = new Label( "VERWALTETE TASK-INSTANZEN" );
87  		sidebarHeader.setStyle( "-fx-font-size: 11px; -fx-font-weight: bold; -fx-letter-spacing: 0.8px;" );
88  
89  		// Main ListView layout for tasks mapping
90  		sidebarListView = new ListView<>( mainGui.getJobList() );
91  		setupCellFactory();
92  
93  		// Control operation triggers
94  		final Button addJobButton = new Button( "Task hinzufügen", Gui.createIcon( MaterialDesignP.PLUS ) );
95  		addJobButton.setMaxWidth( Double.MAX_VALUE );
96  		addJobButton.setStyle( "-fx-background-color: #2ecc71; -fx-text-fill: white; -fx-font-weight: bold; -fx-padding: 11px;" );
97  		addJobButton.setOnAction( _ -> controller.handleCreateNewJob() );
98  
99  		final Button exitButton = new Button( "Programm beenden", Gui.createIcon( MaterialDesignP.POWER ) );
100 		exitButton.getGraphic().setStyle( "-fx-icon-color: white;" );
101 		exitButton.setMaxWidth( Double.MAX_VALUE );
102 		exitButton.setStyle( "-fx-background-color: #e74c3c; -fx-text-fill: white; -fx-font-weight: bold; -fx-padding: 11px;" );
103 		exitButton.setOnAction( _ -> controller.handleApplicationShutdown() );
104 
105 		this.getChildren().addAll( hamburgerMenu, sidebarHeader, sidebarListView, addJobButton, exitButton );
106 		VBox.setVgrow( sidebarListView, Priority.ALWAYS );
107 	}
108 
109 	/**
110 	 * Builds and styles custom Cell rendering including interactive management items.
111 	 */
112 	private void setupCellFactory() {
113 		sidebarListView.setCellFactory( lv -> {
114 			final ListCell<SyncJobContext> cell = new ListCell<>() {
115 				@Override
116 				protected void updateItem( SyncJobContext item, boolean empty ) {
117 					super.updateItem( item, empty );
118 					textProperty().unbind();
119 					if( empty || item == null ) {
120 						setText( "" );
121 						setGraphic( null );
122 					}else {
123 						textProperty().bind( item.jobNameProperty() );
124 						final FontIcon itemIcon = Gui.createIcon( MaterialDesignF.FOLDER );
125 						itemIcon.setStyle( "-fx-icon-color: #3498db;" );
126 						setGraphic( itemIcon );
127 					}
128 				}
129 			};
130 
131 			final ContextMenu contextMenu = new ContextMenu();
132 
133 			final MenuItem renameItem = new MenuItem( "Task umbenennen", Gui.createIcon( MaterialDesignS.SWAP_HORIZONTAL ) );
134 			renameItem.setOnAction( _ -> controller.handleRenameJob( cell ) );
135 
136 			final MenuItem duplicateItem = new MenuItem( "Task duplizieren", Gui.createIcon( MaterialDesignC.CONTENT_DUPLICATE ) );
137 			duplicateItem.setOnAction( _ -> controller.handleDuplicateJob( cell.getItem() ) );
138 
139 			final MenuItem deleteItem = new MenuItem( "Task löschen", Gui.createIcon( MaterialDesignD.DELETE ) );
140 			deleteItem.setStyle( "-fx-text-fill: #c0392b;" );
141 			deleteItem.setOnAction( _ -> controller.handleDeleteJob( cell.getItem() ) );
142 
143 			contextMenu.getItems().addAll( renameItem, duplicateItem, new SeparatorMenuItem(), deleteItem );
144 
145 			cell.setOnContextMenuRequested( event -> {
146 				// Guard clause: Block popup triggers on empty rows or missing models
147 				if( cell.isEmpty() || cell.getItem() == null ) {
148 					event.consume();
149 					return;
150 				}
151 
152 				// Explicitly verify if the mouse coordinates hit the bounded layout area of the graphic/text
153 				// This prevents triggers on empty spaces inside extended high-width list cells
154 				contextMenu.show( cell, event.getScreenX(), event.getScreenY() );
155 				event.consume();
156 			} );
157 
158 			return cell;
159 		} );
160 
161 		sidebarListView.getSelectionModel().selectedItemProperty().addListener( ( obs, oldVal, newVal ) -> {
162 			if( newVal != null ) mainGui.setCurrentActiveJob( newVal );
163 		} );
164 	}
165 
166 	/**
167 	 * @return The underlying task selection view platform.
168 	 */
169 	public ListView<SyncJobContext> getSidebarListView() { return sidebarListView; }
170 }