001package com.studentgui.apptheming; 002 003import java.awt.Color; 004import java.awt.Graphics2D; 005import java.awt.RenderingHints; 006import java.awt.event.ActionEvent; 007import java.awt.event.InputEvent; 008import java.awt.event.KeyEvent; 009import java.awt.image.BufferedImage; 010import java.io.File; 011import java.io.IOException; 012import java.net.JarURLConnection; 013import java.net.URI; 014import java.net.URL; 015import java.net.URLConnection; 016import java.util.ArrayList; 017import java.util.Enumeration; 018import java.util.List; 019import java.util.jar.JarEntry; 020import java.util.jar.JarFile; 021 022import javax.swing.AbstractAction; 023import javax.swing.ImageIcon; 024import javax.swing.JMenu; 025import javax.swing.JMenuBar; 026import javax.swing.JMenuItem; 027import javax.swing.KeyStroke; 028 029import com.studentgui.app.Main; 030 031/** 032 * Application theming and menu bar construction utilities. 033 * 034 * <p>Provides centralized menu bar factory for the main application window with 035 * keyboard shortcuts, mnemonics, and accessibility support. The menu structure 036 * organizes assessment pages into logical categories:</p> 037 * 038 * <ul> 039 * <li><b>Navigate Menu:</b> Primary navigation menu containing: 040 * <ul> 041 * <li><b>Home:</b> Returns to homepage (Ctrl+Alt+H)</li> 042 * <li><b>Tactile Submenu:</b> Braille and Abacus skills pages (alphabetical)</li> 043 * <li><b>Technology Submenu:</b> Device-specific pages (BrailleNote, BrailleSense, iOS, ScreenReader, etc.)</li> 044 * <li><b>Communication Submenu:</b> Contact Log and Session Notes</li> 045 * <li><b>Other Skills Submenu:</b> CVI, Digital Literacy, Keyboarding, Observations, Instructional Materials</li> 046 * </ul> 047 * </li> 048 * </ul> 049 * 050 * <p><b>Accessibility Features:</b></p> 051 * <ul> 052 * <li>All menu items include accessible names and descriptions</li> 053 * <li>Keyboard shortcuts use Ctrl+Alt+Letter combinations to avoid conflicts</li> 054 * <li>Mnemonics provided for primary menu items (Alt+H for Home, etc.)</li> 055 * <li>Color-coded icons generated programmatically via {@link #makeIcon(Color, int)}</li> 056 * </ul> 057 * 058 * <p><b>Icon Generation:</b> Menu items display small colored square icons for 059 * visual differentiation. Icons are generated at runtime as 12×12px {@link BufferedImage} 060 * instances with anti-aliased rendering for smooth appearance across themes.</p> 061 * 062 * <p><b>Menu Structure Rationale:</b></p> 063 * <ul> 064 * <li>Tactile skills (Braille, Abacus) grouped separately from technology devices</li> 065 * <li>Technology submenu organized by device type (notetakers, mobile OS, desktop screen readers)</li> 066 * <li>Communication tools (Contact Log, Session Notes) kept together for workflow consistency</li> 067 * <li>Remaining assessment pages grouped under "Other Skills" for flexibility</li> 068 * </ul> 069 * 070 * <p><b>Navigation Integration:</b> All menu items invoke the main navigation logic in {@link com.studentgui.app.Main} 071 * to switch the main content panel. Page identifiers are lowercase strings matching page class names 072 * (e.g., "braille", "abacus", "braillenote").</p> 073 * 074 * <p><b>Theme Management:</b> Currently limited to menu bar construction. Future expansion 075 * may include FlatLaf theme switching, custom color schemes, or icon set selection.</p> 076 * 077 * @see com.studentgui.app.Main 078 * @see javax.swing.JMenuBar 079 * @see javax.swing.JMenu 080 * @see javax.swing.JMenuItem 081 */ 082public class Theme { 083 /** 084 * Build and return the application menu bar used in the main frame. 085 * 086 * @return a {@link JMenuBar} instance containing the application's menus 087 */ 088 public static JMenuBar createMenuBar() { 089 JMenuBar mb = new JMenuBar(); 090 JMenu nav = new JMenu("Navigate"); 091 092 // Home 093 JMenuItem home = new JMenuItem(new AbstractAction("Home") { 094 @Override 095 /** 096 * Handle the Home menu item selection by navigating the main frame 097 * to the application homepage. 098 * 099 * @param e the ActionEvent triggered by selecting the Home menu item 100 */ 101 102 public void actionPerformed(final ActionEvent e) { Main.showPage("homepage", null); } 103 }); 104 home.setMnemonic('H'); 105 home.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_H, InputEvent.CTRL_DOWN_MASK | InputEvent.ALT_DOWN_MASK)); 106 home.setIcon(makeIcon(new Color(0x4A90E2), 12)); 107 home.getAccessibleContext().setAccessibleName("Home"); 108 home.getAccessibleContext().setAccessibleDescription("Open the Home page"); 109 nav.add(home); 110 nav.addSeparator(); 111 112 // Tactile section (alphabetical) 113 JMenu tactile = new JMenu("Tactile"); 114 JMenuItem abacus = new JMenuItem(new AbstractAction("Abacus") { 115 @Override public void actionPerformed(final ActionEvent e) { Main.showPage("abacus", null); } 116 }); 117 abacus.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_A, InputEvent.CTRL_DOWN_MASK | InputEvent.ALT_DOWN_MASK)); 118 abacus.setIcon(makeIcon(new Color(0xF5A623), 12)); 119 abacus.getAccessibleContext().setAccessibleName("Abacus"); 120 abacus.getAccessibleContext().setAccessibleDescription("Open the Abacus skills page"); 121 tactile.add(abacus); 122 123 JMenuItem braille = new JMenuItem(new AbstractAction("Braille") { 124 @Override public void actionPerformed(final ActionEvent e) { Main.showPage("braille", null); } 125 }); 126 braille.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_B, InputEvent.CTRL_DOWN_MASK | InputEvent.ALT_DOWN_MASK)); 127 braille.setIcon(makeIcon(new Color(0x50E3C2), 12)); 128 braille.getAccessibleContext().setAccessibleName("Braille"); 129 braille.getAccessibleContext().setAccessibleDescription("Open the Braille skills page"); 130 tactile.add(braille); 131 132 nav.add(tactile); 133 nav.addSeparator(); 134 135 // Technology section (alphabetical) 136 JMenu tech = new JMenu("Technology"); 137 JMenuItem brailleNote = new JMenuItem(new AbstractAction("BrailleNote Touch") { 138 @Override public void actionPerformed(final ActionEvent e) { Main.showPage("braillenote", null); } 139 }); 140 brailleNote.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_G, InputEvent.CTRL_DOWN_MASK | InputEvent.ALT_DOWN_MASK)); 141 brailleNote.setIcon(makeIcon(new Color(0x7B61FF), 12)); 142 brailleNote.getAccessibleContext().setAccessibleName("BrailleNote Touch"); 143 brailleNote.getAccessibleContext().setAccessibleDescription("Open the BrailleNote Touch page"); 144 tech.add(brailleNote); 145 146 JMenuItem brailleSense = new JMenuItem(new AbstractAction("Braille Sense") { 147 @Override public void actionPerformed(final ActionEvent e) { Main.showPage("braillesense", null); } 148 }); 149 brailleSense.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, InputEvent.CTRL_DOWN_MASK | InputEvent.ALT_DOWN_MASK)); 150 brailleSense.setIcon(makeIcon(new Color(0xF8E71C), 12)); 151 brailleSense.getAccessibleContext().setAccessibleName("Braille Sense"); 152 brailleSense.getAccessibleContext().setAccessibleDescription("Open the Braille Sense page"); 153 tech.add(brailleSense); 154 155 JMenuItem dl = new JMenuItem(new AbstractAction("Digital Literacy") { 156 @Override public void actionPerformed(final ActionEvent e) { Main.showPage("digitalliteracy", null); } 157 }); 158 dl.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_D, InputEvent.CTRL_DOWN_MASK | InputEvent.ALT_DOWN_MASK)); 159 dl.setIcon(makeIcon(new Color(0x7ED321), 12)); 160 dl.getAccessibleContext().setAccessibleName("Digital Literacy"); 161 dl.getAccessibleContext().setAccessibleDescription("Open the Digital Literacy page"); 162 tech.add(dl); 163 164 JMenuItem ios = new JMenuItem(new AbstractAction("iOS") { 165 @Override public void actionPerformed(final ActionEvent e) { Main.showPage("ios", null); } 166 }); 167 ios.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_I, InputEvent.CTRL_DOWN_MASK | InputEvent.ALT_DOWN_MASK)); 168 ios.setIcon(makeIcon(new Color(0x00A5E0), 12)); 169 ios.getAccessibleContext().setAccessibleName("iOS"); 170 ios.getAccessibleContext().setAccessibleDescription("Open the iOS accessibility page"); 171 tech.add(ios); 172 173 JMenuItem keyboarding = new JMenuItem(new AbstractAction("Keyboarding") { 174 @Override public void actionPerformed(final ActionEvent e) { Main.showPage("keyboarding", null); } 175 }); 176 keyboarding.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_K, InputEvent.CTRL_DOWN_MASK | InputEvent.ALT_DOWN_MASK)); 177 keyboarding.setIcon(makeIcon(new Color(0x8B572A), 12)); 178 keyboarding.getAccessibleContext().setAccessibleName("Keyboarding"); 179 keyboarding.getAccessibleContext().setAccessibleDescription("Open the Keyboarding skills page"); 180 tech.add(keyboarding); 181 182 JMenuItem screenReader = new JMenuItem(new AbstractAction("Screen Reader") { 183 @Override public void actionPerformed(final ActionEvent e) { Main.showPage("screenreader", null); } 184 }); 185 screenReader.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_R, InputEvent.CTRL_DOWN_MASK | InputEvent.ALT_DOWN_MASK)); 186 screenReader.setIcon(makeIcon(new Color(0x417505), 12)); 187 screenReader.getAccessibleContext().setAccessibleName("Screen Reader"); 188 screenReader.getAccessibleContext().setAccessibleDescription("Open the Screen Reader page"); 189 tech.add(screenReader); 190 191 nav.add(tech); 192 nav.addSeparator(); 193 194 // Misc (alphabetical) 195 JMenu misc = new JMenu("Misc"); 196 JMenuItem contactLog = new JMenuItem(new AbstractAction("Contact Log") { 197 @Override public void actionPerformed(final ActionEvent e) { Main.showPage("contactlog", null); } 198 }); 199 contactLog.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_C, InputEvent.CTRL_DOWN_MASK | InputEvent.ALT_DOWN_MASK)); 200 contactLog.setIcon(makeIcon(new Color(0xF18805), 12)); 201 contactLog.getAccessibleContext().setAccessibleName("Contact Log"); 202 contactLog.getAccessibleContext().setAccessibleDescription("Open the Contact Log page"); 203 misc.add(contactLog); 204 205 JMenuItem observations = new JMenuItem(new AbstractAction("Observations") { 206 @Override public void actionPerformed(final ActionEvent e) { Main.showPage("observations", null); } 207 }); 208 observations.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_O, InputEvent.CTRL_DOWN_MASK | InputEvent.ALT_DOWN_MASK)); 209 observations.setIcon(makeIcon(new Color(0x50E3C2), 12)); 210 observations.getAccessibleContext().setAccessibleName("Observations"); 211 observations.getAccessibleContext().setAccessibleDescription("Open the Observations page"); 212 misc.add(observations); 213 214 JMenuItem sessionNotes = new JMenuItem(new AbstractAction("Session Notes") { 215 @Override public void actionPerformed(final ActionEvent e) { Main.showPage("sessionnotes", null); } 216 }); 217 sessionNotes.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_N, InputEvent.CTRL_DOWN_MASK | InputEvent.ALT_DOWN_MASK)); 218 sessionNotes.setIcon(makeIcon(new Color(0xD0021B), 12)); 219 sessionNotes.getAccessibleContext().setAccessibleName("Session Notes"); 220 sessionNotes.getAccessibleContext().setAccessibleDescription("Open the Session Notes page"); 221 misc.add(sessionNotes); 222 223 nav.add(misc); 224 225 mb.add(nav); 226 227 // Themes menu (top-level) 228 JMenu themesMenu = new JMenu("Themes"); 229 // Read persisted theme choice so we can mark the active menu item 230 String currentTheme = com.studentgui.apphelpers.Settings.get("theme", "light"); 231 232 javax.swing.ButtonGroup themeGroup = new javax.swing.ButtonGroup(); 233 234 javax.swing.JRadioButtonMenuItem light = new javax.swing.JRadioButtonMenuItem(new AbstractAction("Light") { 235 @Override public void actionPerformed(final ActionEvent e) { Main.setTheme("light"); com.studentgui.apphelpers.Settings.put("theme", "light"); } 236 }); 237 light.setIcon(makeIcon(new Color(0x000000), 12)); 238 light.getAccessibleContext().setAccessibleName("Light theme"); 239 light.getAccessibleContext().setAccessibleDescription("Switch to the light theme"); 240 if ("light".equalsIgnoreCase(currentTheme) || "flatlightlaf".equalsIgnoreCase(currentTheme)) { 241 light.setSelected(true); 242 } 243 themeGroup.add(light); 244 themesMenu.add(light); 245 246 javax.swing.JRadioButtonMenuItem dark = new javax.swing.JRadioButtonMenuItem(new AbstractAction("Dark") { 247 @Override public void actionPerformed(final ActionEvent e) { Main.setTheme("dark"); com.studentgui.apphelpers.Settings.put("theme", "dark"); } 248 }); 249 dark.setIcon(makeIcon(new Color(0x2C2C2C), 12)); 250 dark.getAccessibleContext().setAccessibleName("Dark theme"); 251 dark.getAccessibleContext().setAccessibleDescription("Switch to the dark theme"); 252 if ("dark".equalsIgnoreCase(currentTheme) || "flatdarklaf".equalsIgnoreCase(currentTheme)) { 253 dark.setSelected(true); 254 } 255 themeGroup.add(dark); 256 themesMenu.add(dark); 257 258 javax.swing.JRadioButtonMenuItem intellij = new javax.swing.JRadioButtonMenuItem(new AbstractAction("IntelliJ (Darcula)") { 259 @Override public void actionPerformed(final ActionEvent e) { Main.setTheme("darcula"); com.studentgui.apphelpers.Settings.put("theme", "darcula"); } 260 }); 261 intellij.setIcon(makeIcon(new Color(0x4A4A4A), 12)); 262 intellij.getAccessibleContext().setAccessibleName("IntelliJ Darcula"); 263 intellij.getAccessibleContext().setAccessibleDescription("Switch to the IntelliJ Darcula theme"); 264 if ("darcula".equalsIgnoreCase(currentTheme)) { 265 intellij.setSelected(true); 266 } 267 themeGroup.add(intellij); 268 themesMenu.add(intellij); 269 themesMenu.addSeparator(); 270 271 // Dynamically add all IntelliJ themes available from flatlaf-intellij-themes 272 // Discover and add IntelliJ themes from the flatlaf-intellij-themes artifact if present 273 List<String> intellijThemes = listClassesInPackage("com.formdev.flatlaf.intellijthemes"); 274 if (!intellijThemes.isEmpty()) { 275 JMenu intellijGroup = new JMenu("IntelliJ Themes"); 276 for (String cls : intellijThemes) { 277 final String className = cls; 278 JMenuItem mi = new JMenuItem(new AbstractAction(simpleName(className)) { 279 @Override public void actionPerformed(final ActionEvent e) { Main.setTheme(className); com.studentgui.apphelpers.Settings.put("theme", className); } 280 }); 281 mi.setIcon(makeIcon(new Color(0x888888), 10)); 282 mi.getAccessibleContext().setAccessibleName(className); 283 mi.getAccessibleContext().setAccessibleDescription("Apply " + className); 284 intellijGroup.add(mi); 285 } 286 themesMenu.add(intellijGroup); 287 } 288 289 // Material themes: if user adds flatlaf-themes or material themes library, we can try to load them by class name 290 JMenu materialGroup = new JMenu("Material Themes"); 291 List<String> materialThemes = listClassesInPackage("com.formdev.flatlaf.materialthemes"); 292 for (String cls : materialThemes) { 293 final String className = cls; 294 JMenuItem mi = new JMenuItem(new AbstractAction(simpleName(className)) { 295 @Override public void actionPerformed(final ActionEvent e) { Main.setTheme(className); com.studentgui.apphelpers.Settings.put("theme", className); } 296 }); 297 mi.setIcon(makeIcon(new Color(0x666666), 10)); 298 mi.getAccessibleContext().setAccessibleName(className); 299 mi.getAccessibleContext().setAccessibleDescription("Apply " + className); 300 materialGroup.add(mi); 301 } 302 themesMenu.add(materialGroup); 303 304 // Material Theme UI Lite (popular collection) - add specific known classes when available 305 JMenu materialLiteGroup = new JMenu("Material Theme UI Lite"); 306 String[][] lite = new String[][]{ 307 {"Arc Dark (Material)", "com.formdev.flatlaf.intellijthemes.materialthemeuilite.FlatArcDarkIJTheme"}, 308 {"Arc Dark Contrast (Material)", "com.formdev.flatlaf.intellijthemes.materialthemeuilite.FlatArcDarkContrastIJTheme"}, 309 {"Atom One Dark (Material)", "com.formdev.flatlaf.intellijthemes.materialthemeuilite.FlatAtomOneDarkIJTheme"}, 310 {"Atom One Dark Contrast (Material)", "com.formdev.flatlaf.intellijthemes.materialthemeuilite.FlatAtomOneDarkContrastIJTheme"}, 311 {"Atom One Light (Material)", "com.formdev.flatlaf.intellijthemes.materialthemeuilite.FlatAtomOneLightIJTheme"}, 312 {"Atom One Light Contrast (Material)", "com.formdev.flatlaf.intellijthemes.materialthemeuilite.FlatAtomOneLightContrastIJTheme"}, 313 {"Dracula (Material)", "com.formdev.flatlaf.intellijthemes.materialthemeuilite.FlatDraculaIJTheme"}, 314 {"Dracula Contrast (Material)", "com.formdev.flatlaf.intellijthemes.materialthemeuilite.FlatDraculaContrastIJTheme"}, 315 {"GitHub (Material)", "com.formdev.flatlaf.intellijthemes.materialthemeuilite.FlatGitHubIJTheme"}, 316 {"GitHub Contrast (Material)", "com.formdev.flatlaf.intellijthemes.materialthemeuilite.FlatGitHubContrastIJTheme"}, 317 {"GitHub Dark (Material)", "com.formdev.flatlaf.intellijthemes.materialthemeuilite.FlatGitHubDarkIJTheme"}, 318 {"GitHub Dark Contrast (Material)", "com.formdev.flatlaf.intellijthemes.materialthemeuilite.FlatGitHubDarkContrastIJTheme"}, 319 {"Light Owl (Material)", "com.formdev.flatlaf.intellijthemes.materialthemeuilite.FlatLightOwlIJTheme"}, 320 {"Light Owl Contrast (Material)", "com.formdev.flatlaf.intellijthemes.materialthemeuilite.FlatLightOwlContrastIJTheme"}, 321 {"Material Darker (Material)", "com.formdev.flatlaf.intellijthemes.materialthemeuilite.FlatMaterialDarkerIJTheme"}, 322 {"Material Darker Contrast (Material)", "com.formdev.flatlaf.intellijthemes.materialthemeuilite.FlatMaterialDarkerContrastIJTheme"}, 323 {"Material Deep Ocean (Material)", "com.formdev.flatlaf.intellijthemes.materialthemeuilite.FlatMaterialDeepOceanIJTheme"}, 324 {"Material Deep Ocean Contrast (Material)", "com.formdev.flatlaf.intellijthemes.materialthemeuilite.FlatMaterialDeepOceanContrastIJTheme"}, 325 {"Material Lighter (Material)", "com.formdev.flatlaf.intellijthemes.materialthemeuilite.FlatMaterialLighterIJTheme"}, 326 {"Material Lighter Contrast (Material)", "com.formdev.flatlaf.intellijthemes.materialthemeuilite.FlatMaterialLighterContrastIJTheme"}, 327 {"Material Oceanic (Material)", "com.formdev.flatlaf.intellijthemes.materialthemeuilite.FlatMaterialOceanicIJTheme"}, 328 {"Material Oceanic Contrast (Material)", "com.formdev.flatlaf.intellijthemes.materialthemeuilite.FlatMaterialOceanicContrastIJTheme"}, 329 {"Material Palenight (Material)", "com.formdev.flatlaf.intellijthemes.materialthemeuilite.FlatMaterialPalenightIJTheme"}, 330 {"Material Palenight Contrast (Material)", "com.formdev.flatlaf.intellijthemes.materialthemeuilite.FlatMaterialPalenightContrastIJTheme"}, 331 {"Monokai Pro (Material)", "com.formdev.flatlaf.intellijthemes.materialthemeuilite.FlatMonokaiProIJTheme"}, 332 {"Monokai Pro Contrast (Material)", "com.formdev.flatlaf.intellijthemes.materialthemeuilite.FlatMonokaiProContrastIJTheme"}, 333 {"Moonlight (Material)", "com.formdev.flatlaf.intellijthemes.materialthemeuilite.FlatMoonlightIJTheme"}, 334 {"Moonlight Contrast (Material)", "com.formdev.flatlaf.intellijthemes.materialthemeuilite.FlatMoonlightContrastIJTheme"}, 335 {"Night Owl (Material)", "com.formdev.flatlaf.intellijthemes.materialthemeuilite.FlatNightOwlIJTheme"}, 336 {"Night Owl Contrast (Material)", "com.formdev.flatlaf.intellijthemes.materialthemeuilite.FlatNightOwlContrastIJTheme"}, 337 {"Solarized Dark (Material)", "com.formdev.flatlaf.intellijthemes.materialthemeuilite.FlatSolarizedDarkIJTheme"}, 338 {"Solarized Dark Contrast (Material)", "com.formdev.flatlaf.intellijthemes.materialthemeuilite.FlatSolarizedDarkContrastIJTheme"}, 339 {"Solarized Light (Material)", "com.formdev.flatlaf.intellijthemes.materialthemeuilite.FlatSolarizedLightIJTheme"}, 340 {"Solarized Light Contrast (Material)", "com.formdev.flatlaf.intellijthemes.materialthemeuilite.FlatSolarizedLightContrastIJTheme"} 341 }; 342 for (String[] entry : lite) { 343 final String label = entry[0]; 344 final String className = entry[1]; 345 try { 346 Class.forName(className); 347 JMenuItem mi = new JMenuItem(new AbstractAction(label) { 348 @Override public void actionPerformed(final ActionEvent e) { Main.setTheme(className); com.studentgui.apphelpers.Settings.put("theme", className); } 349 }); 350 mi.setIcon(makeIcon(new Color(0x666666), 10)); 351 mi.getAccessibleContext().setAccessibleName(className); 352 mi.getAccessibleContext().setAccessibleDescription("Apply " + className); 353 materialLiteGroup.add(mi); 354 } catch (ClassNotFoundException cnfe) { 355 // class not on classpath - ignore 356 } 357 } 358 // Only add the group if at least one theme was found 359 if (materialLiteGroup.getMenuComponentCount() > 0) { 360 themesMenu.add(materialLiteGroup); 361 } 362 363 mb.add(themesMenu); 364 return mb; 365 } 366 367 /** 368 * Private constructor to prevent instantiation of this utility class. 369 */ 370 private Theme() { 371 throw new AssertionError("Not instantiable"); 372 } 373 374 /** 375 * Create a small square color icon used for menu items. Kept local to avoid 376 * needing external resources; a simple filled rounded rectangle is drawn. 377 */ 378 private static ImageIcon makeIcon(final Color color, final int size) { 379 BufferedImage img = new BufferedImage(size, size, BufferedImage.TYPE_INT_ARGB); 380 Graphics2D g = img.createGraphics(); 381 try { 382 g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); 383 g.setColor(color); 384 g.fillRoundRect(0, 0, size, size, Math.max(2, size/4), Math.max(2, size/4)); 385 } finally { 386 g.dispose(); 387 } 388 return new ImageIcon(img); 389 } 390 391 // Return the simple class name from a fully-qualified class name 392 private static String simpleName(final String fqcn) { 393 int idx = fqcn.lastIndexOf('.'); 394 return idx >= 0 ? fqcn.substring(idx + 1) : fqcn; 395 } 396 397 // List classes in a package by scanning classpath entries. This is a best-effort 398 // method: it handles classes inside jars and on the filesystem. 399 private static List<String> listClassesInPackage(final String packageName) { 400 List<String> results = new ArrayList<>(); 401 String path = packageName.replace('.', '/'); 402 try { 403 ClassLoader cl = Thread.currentThread().getContextClassLoader(); 404 Enumeration<URL> resources = cl.getResources(path); 405 while (resources.hasMoreElements()) { 406 URL url = resources.nextElement(); 407 URLConnection conn = url.openConnection(); 408 if (conn instanceof JarURLConnection) { 409 JarURLConnection juc = (JarURLConnection) conn; 410 try (JarFile jar = juc.getJarFile()) { 411 Enumeration<JarEntry> entries = jar.entries(); 412 while (entries.hasMoreElements()) { 413 JarEntry je = entries.nextElement(); 414 String name = je.getName(); 415 if (name.startsWith(path) && name.endsWith(".class") && !je.isDirectory()) { 416 String cls = name.replace('/', '.').replaceAll("\\.class$", ""); 417 results.add(cls); 418 } 419 } 420 } 421 } else { 422 try { 423 URI uri = url.toURI(); 424 File folder = new File(uri); 425 if (folder.isDirectory()) { 426 File[] files = folder.listFiles(); 427 if (files != null) { 428 for (File f : files) { 429 if (f.isFile() && f.getName().endsWith(".class")) { 430 String cls = packageName + "." + f.getName().replaceAll("\\.class$", ""); 431 results.add(cls); 432 } 433 } 434 } 435 } 436 } catch (java.net.URISyntaxException ioe) { 437 // ignore 438 } 439 } 440 } 441 } catch (IOException e) { 442 // ignore 443 } 444 return results; 445 } 446}