View Javadoc
1   /*
2    * Themis: Java Project Framework
3    * Copyright 2026. Tony Washer
4    *
5    * Licensed under the Apache License, Version 2.0 (the "License"); you may not
6    * use this file except in compliance with the License.  You may obtain a copy
7    * of the License at
8    *
9    *   http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
14   * License for the specific language governing permissions and limitations under
15   * the License.
16   */
17  
18  package io.github.tonywasher.joceanus.themis.gui.source;
19  
20  import io.github.tonywasher.joceanus.oceanus.event.OceanusEventManager;
21  import io.github.tonywasher.joceanus.oceanus.event.OceanusEventRegistrar;
22  import io.github.tonywasher.joceanus.oceanus.event.OceanusEventRegistrar.OceanusEventProvider;
23  import io.github.tonywasher.joceanus.tethys.api.base.TethysUIComponent;
24  import io.github.tonywasher.joceanus.tethys.api.base.TethysUIEvent;
25  import io.github.tonywasher.joceanus.tethys.api.button.TethysUIScrollButtonManager;
26  import io.github.tonywasher.joceanus.tethys.api.control.TethysUIControlFactory;
27  import io.github.tonywasher.joceanus.tethys.api.control.TethysUILabel;
28  import io.github.tonywasher.joceanus.tethys.api.factory.TethysUIFactory;
29  import io.github.tonywasher.joceanus.tethys.api.menu.TethysUIScrollItem;
30  import io.github.tonywasher.joceanus.tethys.api.menu.TethysUIScrollMenu;
31  import io.github.tonywasher.joceanus.tethys.api.pane.TethysUIBoxPaneManager;
32  import io.github.tonywasher.joceanus.tethys.api.pane.TethysUIPaneFactory;
33  import io.github.tonywasher.joceanus.themis.gui.base.ThemisUIResource;
34  import io.github.tonywasher.joceanus.themis.parser.base.ThemisChar;
35  import io.github.tonywasher.joceanus.themis.parser.proj.ThemisModule;
36  import io.github.tonywasher.joceanus.themis.parser.proj.ThemisPackage;
37  
38  /**
39   * Button to select package from module.
40   */
41  public class ThemisUISourcePackageSelect
42          implements OceanusEventProvider<TethysUIEvent>, TethysUIComponent {
43      /**
44       * The Event Manager.
45       */
46      private final OceanusEventManager<TethysUIEvent> theEventManager;
47  
48      /**
49       * The scroll button.
50       */
51      private final TethysUIScrollButtonManager<ThemisPackage> theButton;
52  
53      /**
54       * The package menu.
55       */
56      private final TethysUIScrollMenu<ThemisPackage> thePackageMenu;
57  
58      /**
59       * The package select.
60       */
61      private final ThemisUISourceFileSelect theFileSelect;
62  
63      /**
64       * The prefixLabel.
65       */
66      private final TethysUILabel thePrefixLabel;
67  
68      /**
69       * The panel.
70       */
71      private final TethysUIBoxPaneManager thePanel;
72  
73      /**
74       * The current module.
75       */
76      private ThemisModule theModule;
77  
78      /**
79       * The current package.
80       */
81      private ThemisPackage thePackage;
82  
83      /**
84       * The current prefix.
85       */
86      private String thePrefix;
87  
88      /**
89       * Constructor.
90       *
91       * @param pFactory    the factory
92       * @param pFileSelect the fileSelectButton
93       */
94      ThemisUISourcePackageSelect(final TethysUIFactory<?> pFactory,
95                                  final ThemisUISourceFileSelect pFileSelect) {
96          /* Store the file select */
97          theFileSelect = pFileSelect;
98  
99          /* Create Event Manager */
100         theEventManager = new OceanusEventManager<>();
101 
102         /* Create the labels */
103         final TethysUIControlFactory myControls = pFactory.controlFactory();
104         thePrefixLabel = myControls.newLabel();
105         final TethysUILabel myPromptLabel = myControls.newLabel(ThemisUIResource.PROMPT_PACKAGE.getValue());
106 
107         /* Create the button */
108         theButton = pFactory.buttonFactory().newScrollButton(ThemisPackage.class);
109         thePackageMenu = theButton.getMenu();
110 
111         /* Create the panel */
112         final TethysUIPaneFactory myPanes = pFactory.paneFactory();
113         thePanel = myPanes.newHBoxPane();
114         thePanel.addNode(myPromptLabel);
115         thePanel.addNode(thePrefixLabel);
116         thePanel.addNode(theButton);
117 
118         /* Set listeners */
119         final OceanusEventRegistrar<TethysUIEvent> myRegistrar = theButton.getEventRegistrar();
120         myRegistrar.addEventListener(TethysUIEvent.NEWVALUE, e -> handleNewPackage());
121         theButton.setMenuConfigurator(e -> buildPackageMenu());
122     }
123 
124     @Override
125     public OceanusEventRegistrar<TethysUIEvent> getEventRegistrar() {
126         return theEventManager.getEventRegistrar();
127     }
128 
129     @Override
130     public TethysUIComponent getUnderlying() {
131         return thePanel;
132     }
133 
134     /**
135      * Obtain the current package.
136      *
137      * @return the current package
138      */
139     ThemisPackage getCurrentPackage() {
140         return thePackage;
141     }
142 
143     /**
144      * Set the current module.
145      *
146      * @param pModule the current module
147      */
148     void setCurrentModule(final ThemisModule pModule) {
149         /* Store the module */
150         theModule = pModule;
151 
152         /* Determine the prefix */
153         determinePrefix();
154 
155         /* Set the default package */
156         final ThemisPackage myPackage = getDefaultPackage();
157         theButton.setValue(myPackage, getDisplayName(myPackage));
158         handleNewPackage();
159     }
160 
161     /**
162      * Obtain the default package.
163      *
164      * @return the default package
165      */
166     private ThemisPackage getDefaultPackage() {
167         /* If we have a non-null module */
168         if (theModule != null) {
169             /* Loop through the available packages */
170             for (ThemisPackage myPackage : theModule.getPackages()) {
171                 /* Skip package if necessary */
172                 if (skipPackage(myPackage)) {
173                     continue;
174                 }
175 
176                 /* Return the valid package */
177                 return myPackage;
178             }
179         }
180 
181         /* No package */
182         return null;
183     }
184 
185     /**
186      * Determine the prefix.
187      */
188     private void determinePrefix() {
189         /* Initialise the prefix */
190         thePrefix = null;
191         theButton.setVisible(false);
192 
193         /* If we have a non-null modules */
194         if (theModule != null) {
195             /* Loop through the available packages */
196             for (ThemisPackage myPackage : theModule.getPackages()) {
197                 /* Adjust the prefix */
198                 adjustPrefix(myPackage);
199             }
200         }
201 
202         /* Update the prefixLabel */
203         thePrefixLabel.setText(thePrefix);
204         thePrefixLabel.setVisible(thePrefix != null && !thePrefix.isEmpty());
205     }
206 
207     /**
208      * Adjust prefix.
209      *
210      * @param pPackage the package
211      */
212     private void adjustPrefix(final ThemisPackage pPackage) {
213         /* Ignore placeHolder */
214         if (skipPackage(pPackage)) {
215             return;
216         }
217 
218         /* If we do not have a prefix */
219         final String myName = pPackage.getPackage();
220         if (thePrefix == null) {
221             thePrefix = myName;
222 
223             /* else if we need to change the prefix */
224         } else {
225             /* We have more than one package so display selection button */
226             theButton.setVisible(true);
227 
228             /* Determine common prefix */
229             thePrefix = getCommonPrefix(myName, thePrefix);
230         }
231     }
232 
233     /**
234      * Obtain common prefix.
235      *
236      * @param pFirst  the first name
237      * @param pSecond the second name
238      * @return the prefix
239      */
240     private String getCommonPrefix(final String pFirst,
241                                    final String pSecond) {
242         if (pFirst.equals(pSecond)) {
243             return pFirst;
244         }
245         return pFirst.length() >= pSecond.length()
246                 ? getCommonPrefix(getParentName(pFirst), pSecond)
247                 : getCommonPrefix(pFirst, getParentName(pSecond));
248     }
249 
250     /**
251      * Obtain Parent Name.
252      *
253      * @param pName the name
254      * @return the parent name
255      */
256     private String getParentName(final String pName) {
257         /* Determine the short name */
258         final int iIndex = pName.lastIndexOf(ThemisChar.PERIOD);
259         return iIndex == -1 ? "" : pName.substring(0, iIndex);
260     }
261 
262     /**
263      * Obtain the displayName of the package.
264      *
265      * @param pPackage the package
266      * @return the displayName
267      */
268     private String getDisplayName(final ThemisPackage pPackage) {
269         final int myPrefixLen = thePrefix == null ? 0 : thePrefix.length();
270         return pPackage == null ? "" : pPackage.getPackage().substring(myPrefixLen);
271     }
272 
273     /**
274      * Build the package menu.
275      */
276     private void buildPackageMenu() {
277         /* Reset the popUp menu */
278         thePackageMenu.removeAllItems();
279 
280         /* Record active item */
281         TethysUIScrollItem<ThemisPackage> myActive = null;
282         final ThemisPackage myCurr = theButton.getValue();
283 
284         /* Loop through the available packages */
285         for (ThemisPackage myPackage : theModule.getPackages()) {
286             /* skip package if required */
287             if (skipPackage(myPackage)) {
288                 continue;
289             }
290 
291             /* Create a new MenuItem and add it to the popUp */
292             final TethysUIScrollItem<ThemisPackage> myItem = thePackageMenu.addItem(myPackage, getDisplayName(myPackage));
293 
294             /* If this is the active package */
295             if (myPackage.equals(myCurr)) {
296                 /* Record it */
297                 myActive = myItem;
298             }
299         }
300 
301         /* Ensure active item is visible */
302         if (myActive != null) {
303             myActive.scrollToItem();
304         }
305     }
306 
307     /**
308      * Should we skip the package?
309      *
310      * @param pPackage the package
311      * @return true/false
312      */
313     private boolean skipPackage(final ThemisPackage pPackage) {
314         /* Skip placeholders */
315         return pPackage.isPlaceHolder();
316     }
317 
318     /**
319      * Handle new Package.
320      */
321     private void handleNewPackage() {
322         /* Select the new package */
323         thePackage = theButton.getValue();
324         theEventManager.fireEvent(TethysUIEvent.NEWVALUE);
325         theFileSelect.setCurrentPackage(thePackage);
326     }
327 }