// Assumption: alGamesInfo is set to a valid object and noOfGames = alGamesInfo.size().
  public void setSelectedGameIndex(int newGameIndex) {
    if ((newGameIndex < 1) || (newGameIndex > noOfGames - 2))
      // => Invalid game index passed.
      selGameIndex = 1; // reset
    else selGameIndex = newGameIndex;

    // FIXME: Handle case when alGamesInfo.get(%index%).getLogo() = null (when %id%.png is absent)
    // Create top blurred image.
    BufferedImage biTemp = alGamesInfo.get(selGameIndex - 1).getLogo();
    biTopBlurred =
        UIHelpers.EmptyCompatibleImage(
            biTemp.getWidth(), biTemp.getHeight(), biTemp.getColorModel().getTransparency());
    blurOp.filter(biTemp, biTopBlurred);
    biTopBlurred = blurOp.filter(biTopBlurred, null);
    biTopBlurred = blurOp.filter(biTopBlurred, null);

    // Create bottom blurred image.
    // TODO: NULL pointer exception occurs here is there are no
    // logo and screenshot images available for a game
    biTemp = alGamesInfo.get(selGameIndex + 1).getLogo();
    biBottomBlurred =
        UIHelpers.EmptyCompatibleImage(
            biTemp.getWidth(), biTemp.getHeight(), biTemp.getColorModel().getTransparency());
    blurOp.filter(biTemp, biBottomBlurred);
    biBottomBlurred = blurOp.filter(biBottomBlurred, null);
    biBottomBlurred = blurOp.filter(biBottomBlurred, null);
  }
  // Used by GamePadPanel constructor only
  private void PopulateGamesList(List<GamesListbox.GameStruct> alGamesInfo) {
    NodeList gameList = docGameXML.getElementsByTagName("game");
    Element elmGame;
    File logoImageFile;
    BufferedImage biLogo;
    int i;
    String id;
    int nGameFiles = 0;

    alGamesInfo.add(
        new GamesListbox.GameStruct(
            "",
            "Top Filler",
            UIHelpers.EmptyCompatibleImage(
                currTheme.getMonogramWidth(), currTheme.getMonogramHeight())));
    for (i = 0; i < gameList.getLength(); i++) {
      elmGame = (Element) gameList.item(i);
      id = elmGame.getAttribute("id");
      if (!id.isEmpty()) {
        File gamesDirectory = new File(romsFolder, id);
        if (gamesDirectory.isDirectory()) {
          // TODO: Need some more validation to determine if the game is installed
          // Need to check at least one complete set of files is installed in these folders
          // Checking every file will be time consuming as number of installed games increase.
          // Maybe just the number of files (as per number of <rom> nodes in the primary
          // fileset element), actual game load will validate the file.
          // Number of files equal to or more than the number of <rom> nodes is ok as it may have
          // additional copyright/readme files

          // TODO: Shouldn't we move this code to game Hashmap creation?
          NodeList elmGameFiles = elmGame.getElementsByTagName("fileset");
          int j;
          int nGameFilesets = elmGameFiles.getLength();
          for (j = 0; j < nGameFilesets; j++) {
            Element fileset;
            fileset = (Element) elmGameFiles.item(j);
            if (fileset.getAttribute("primary").equalsIgnoreCase("true")) {
              NodeList elmRomfiles = fileset.getElementsByTagName("rom");
              nGameFiles = elmRomfiles.getLength();
            }
          }

          int nFolderFiles = gamesDirectory.listFiles().length;
          if (nFolderFiles >= nGameFiles) {
            biLogo = null;
            logoImageFile = new File(monogramsPath, id + ".png");
            if (logoImageFile.isFile()) biLogo = UIHelpers.LoadBufferedImage(logoImageFile);
            else biLogo = UIHelpers.LoadBufferedImage(new File(monogramsPath, "nologo.png"));

            alGamesInfo.add(new GamesListbox.GameStruct(id, elmGame.getAttribute("name"), biLogo));
          }
        }
      }
    }
  }
  // Used by paintComponent() only
  private void LoadScreenshotImage() {
    int imageWidth, imageHeight;
    double aspectRatio;

    if (lstGames.hasNoGames()) {
      biScreenshot = InstallHintScreenshot();

      scaledScrLeft = currTheme.getScreenshotLeft();
      scaledScrTop = currTheme.getScreenshotTop();
      scaledScrHeight = screenshotHeight;
      scaledScrWidth = screenshotWidth;
      return;
    }

    File screenshotFile = new File(screenshotsPath, lstGames.getSelectedGameId() + ".png");
    if (screenshotFile.isFile()) {
      biScreenshot = UIHelpers.LoadBufferedImage(screenshotFile);
    } else {
      // TODO: Create "Screenshot Not Available" image in memory or load it from .jar file
      biScreenshot = UIHelpers.LoadBufferedImage(new File(screenshotsPath, "noscreenshot.png"));
    }

    imageWidth = biScreenshot.getWidth();
    imageHeight = biScreenshot.getHeight();
    aspectRatio = imageWidth / (double) imageHeight;
    scaledScrLeft = currTheme.getScreenshotLeft();
    scaledScrTop = currTheme.getScreenshotTop();

    if (aspectRatio < 1) {
      // => screenshot PNG image is in portrait mode.
      // Scale the PNG image such that its height becomes = Screenshot window height
      scaledScrHeight = screenshotHeight;
      scaledScrWidth = (int) Math.ceil(aspectRatio * screenshotHeight);
      scaledScrLeft += (screenshotWidth - scaledScrWidth) / 2;
    } else {
      // => screenshot PNG image is in landscape mode.
      // Scale the PNG image such that its width becomes = Screenshot window width
      scaledScrWidth = screenshotWidth;
      scaledScrHeight = (int) Math.ceil(screenshotWidth / (double) aspectRatio);
      scaledScrTop += (screenshotHeight - scaledScrHeight) / 2;
    }
  }
  // Used only by LoadScreenshotImage() only
  private BufferedImage InstallHintScreenshot() {
    Font ft = new Font(Font.DIALOG, Font.PLAIN, 16);
    BufferedImage biHintScreenshot =
        UIHelpers.EmptyCompatibleImage(screenshotWidth, screenshotHeight);
    Graphics2D g2d = biHintScreenshot.createGraphics();

    g2d.setBackground(Color.WHITE);
    g2d.clearRect(0, 0, screenshotWidth, screenshotHeight);
    g2d.setFont(ft);
    g2d.setColor(Color.BLACK);
    g2d.drawString("Install Games", 15, 25);

    g2d.dispose();
    return biHintScreenshot;
  }
  // Used only by GamesListbox constructor
  private BufferedImage InstallHintMonogram() {
    Font ft = new Font(Font.DIALOG, Font.PLAIN, 16);
    BufferedImage biHintMonogram =
        UIHelpers.EmptyCompatibleImage(currTheme.getMonogramWidth(), currTheme.getMonogramHeight());
    Graphics2D g2d = biHintMonogram.createGraphics();

    g2d.setBackground(Color.WHITE);
    g2d.clearRect(0, 0, currTheme.getMonogramWidth(), currTheme.getMonogramHeight());
    g2d.setFont(ft);
    g2d.setColor(Color.BLACK);
    g2d.drawString("Install Games", 15, 25);

    g2d.dispose();
    return biHintMonogram;
  }
  public void DoCommand(ActionEvent e) {
    String currAction = e.getActionCommand();

    if (currAction.equals(ArcadeTheme.UP_BUTTON_ACTION)) {
      if (lstGames.ScrollDown()) {
        bLoadScreenshot = true; // Position is important!
        this.repaint();
      }
    } else if (currAction.equals(ArcadeTheme.DOWN_BUTTON_ACTION)) {
      if (lstGames.ScrollUp()) {
        bLoadScreenshot = true; // Position is important!
        this.repaint();
      }
    } else if (currAction.equals(ArcadeTheme.LOAD_GAME_BUTTON_ACTION)) {
      if (lstGames.hasNoGames())
        JOptionPane.showMessageDialog(
            this,
            "You need to install at least " + "one game before trying to load.",
            "No Game Installed",
            JOptionPane.WARNING_MESSAGE);
      else
        parentFrame.BurnUsingProgrammer(
            lstGames.getSelectedGameId(), lstGames.getSelectedGameName());
    } else if (currAction.equals(ArcadeTheme.HOW_TO_PLAY_BUTTON_ACTION)) {
      if (lstGames.hasNoGames())
        JOptionPane.showMessageDialog(
            this, "No games are installed", "No Game Installed", JOptionPane.WARNING_MESSAGE);
      else {
        if (dlgHowtoPlay == null) dlgHowtoPlay = new HowtoPlayDialog(parentFrame);

        dlgHowtoPlay.PopulateNShow(
            lstGames.getSelectedGameId(), lstGames.getSelectedGameName(), selJoystickId);
      }
    } else if (currAction.equals(ArcadeTheme.INSTALL_GAME_BUTTON_ACTION)) {
      File romZipFile =
          UIHelpers.ShowFileOpen(
              "Open Zip file containing game rom files", zipFileFilter, ".zip", null, null);
      if (romZipFile != null) {
        if (InstallGame(romZipFile)) {
          if (!bReinstalledGame) {
            File logoImageFile;
            BufferedImage biLogo = null;
            logoImageFile = new File(monogramsPath, installedGameId + ".png");
            if (logoImageFile.isFile()) biLogo = UIHelpers.LoadBufferedImage(logoImageFile);
            else biLogo = UIHelpers.LoadBufferedImage(new File(monogramsPath, "nologo.png"));
            lstGames.AddInstalledGame(
                installedGameIndex,
                new GamesListbox.GameStruct(installedGameId, installedGameName, biLogo));

            bLoadScreenshot = true; // Position is important!
            this.repaint();
          }
        }
      }

    } else if (currAction.equals(ArcadeTheme.PREFERENCES_BUTTON_ACTION)) {
      // Always instantiate dlgPreferences
      dlgPreferences = new PreferencesDialog(parentFrame, docPlatformXML);
      dlgPreferences.PopulateForm();
      if (dlgPreferences.isOKClicked()) {
        // Propogate changed application settings to required settings variables.
        selJoystickId = programSettings.getStringProperty("JoystickId");
        parentFrame.setSelectedPlatformId(programSettings.getStringProperty("PlatformId"));
        parentFrame.setWrite2Target(programSettings.getStringProperty("Writeto"));
      }
      /* User is not likely to invoke Preferences dialogbox many times, so it makes no
        sense to cache it in memory. Further, the dialogbox does not have to free any
        external resources such as files, etc. Hence, it is best to dispose it when user
        dismisses it.
      */
      dlgPreferences.dispose();
    } else if (currAction.equals(ArcadeTheme.UPDATE_BUTTON_ACTION)) {
      parentFrame.PlaceGlassPane("Checking for Updates");

      new SwingWorker<List<Boolean>, Void>() {
        /* It is tempting to declare List<Boolean> alPossibleErrors here - as class
          level variable. This way, it will be available to doInBackground() as well
          as done(). But bear in mind that said procedures are being executed on 2
          different threads. Thus, accessing alPossibleErrors becomes a cross-thread
          issue. Even though it is possible that no issue will arise, it is better
          to let Java handle it. So, return alPossibleErrors in doInBackground()
          and retrieve it using get() in done().
        */

        // Executed on a worker (background) thread
        // Should never touch any Swing component
        @Override
        protected List<Boolean> doInBackground() {
          List<Boolean> alPossibleErrors = new ArrayList<Boolean>(3);
          if (UpdateDialog.CheckForUpdates(alPossibleErrors)) {
            return alPossibleErrors;
          } else return null;
        }

        // Executed on EDT
        @Override
        protected void done() {
          try {
            parentFrame.RemoveGlassPane();

            List<Boolean> alPossibleErrors = get();
            if (alPossibleErrors == null)
              JOptionPane.showMessageDialog(
                  parentFrame,
                  "There was an error connecting Gadgetfactory website",
                  "Update Error",
                  JOptionPane.ERROR_MESSAGE);
            else if (alPossibleErrors.get(0).equals(Boolean.TRUE))
              JOptionPane.showMessageDialog(
                  parentFrame,
                  "You are not connected to Internet. " + "Please connect before updating",
                  "Update Error",
                  JOptionPane.ERROR_MESSAGE);
            else if (alPossibleErrors.get(1).equals(Boolean.TRUE))
              JOptionPane.showMessageDialog(
                  parentFrame,
                  "It is taking too long to connect to Gadgetfactory " + "website, aborting update",
                  "Update Error",
                  JOptionPane.ERROR_MESSAGE);
            else {
              /* User is not likely to invoke Update dialogbox many times, so it makes
                sense to use it on "on-demand" basis - so that unnecessary memory will
                not be allocated. Hence, it is best to instantiate it and dispose it
                when user dismisses it.
              */
              dlgUpdate =
                  new UpdateDialog(
                      parentFrame, docGameXML, docHardwareXML, romsFolder, imagesFolder);
              dlgUpdate.PopulateForm();
              //							if (dlgPreferences.isOKClicked()) {
              //							}
              dlgUpdate.dispose();
            }
          } catch (InterruptedException e) {
            System.err.println("(Anonymous).done\t" + e.getMessage());
          } catch (ExecutionException e) {
            System.err.println("(Anonymous).done\t" + e.getMessage());
          }
        }
      }.execute();
    } else if (currAction.equals(ArcadeTheme.HELP_BUTTON_ACTION)) {
      File helpFile = new File(AppPath, "help/index.html");
      HelperFunctions.BrowseURL(helpFile.toURI().toString(), runningonWindows);
    } else if (currAction.equals(ArcadeTheme.EXIT_BUTTON_ACTION)) {
      parentFrame.CleanupAndExit();
    }
  }
  public GamePadPanel(
      PapilioArcade fraMain,
      Document docGameXML,
      Document docPlatformXML,
      Document docHardwareXML,
      File imagesFolder) {
    List<GamesListbox.GameStruct> alGamesInfo = new ArrayList<GamesListbox.GameStruct>(25);

    this.shapedScreenshot = currTheme.getScreenshotPath();
    this.docGameXML = docGameXML;
    this.docPlatformXML = docPlatformXML;
    this.docHardwareXML = docHardwareXML;
    this.imagesFolder = imagesFolder;

    bLoadScreenshot = true;
    parentFrame = fraMain;
    screenshotsPath = new File(imagesFolder, "screenshots");
    monogramsPath = new File(imagesFolder, "monograms");
    biGamePad = UIHelpers.LoadBufferedImage(new File(imagesFolder, "gamepad.png"));
    screenshotWidth = currTheme.getScreenshotWidth();
    screenshotHeight = currTheme.getScreenshotHeight();

    this.setPreferredSize(new Dimension(GAMEPAD_IMAGE_WIDTH, GAMEPAD_IMAGE_HEIGHT));
    this.setLayout(null);
    GamePadMouseAdapter ma = new GamePadMouseAdapter();
    this.addMouseListener(ma);
    this.addMouseMotionListener(ma);

    PopulateGamesList(alGamesInfo);
    lstGames = new GamesListbox(alGamesInfo);
    this.add(lstGames);

    btnUp.setLocation(currTheme.getButton(ArcadeTheme.UP_BUTTON));
    btnUp.setActionCommand(ArcadeTheme.UP_BUTTON_ACTION);
    btnUp.addActionListener(this);
    this.add(btnUp);

    btnDown.setLocation(currTheme.getButton(ArcadeTheme.DOWN_BUTTON));
    btnDown.setActionCommand(ArcadeTheme.DOWN_BUTTON_ACTION);
    btnDown.addActionListener(this);
    this.add(btnDown);

    btnLoadGame.setLocation(currTheme.getButton(ArcadeTheme.LOAD_GAME_BUTTON));
    btnLoadGame.setActionCommand(ArcadeTheme.LOAD_GAME_BUTTON_ACTION);
    btnLoadGame.addActionListener(this);
    this.add(btnLoadGame);

    btnHowToPlay.setLocation(currTheme.getButton(ArcadeTheme.HOW_TO_PLAY_BUTTON));
    btnHowToPlay.setActionCommand(ArcadeTheme.HOW_TO_PLAY_BUTTON_ACTION);
    btnHowToPlay.addActionListener(this);
    this.add(btnHowToPlay);

    btnInstall.setLocation(currTheme.getButton(ArcadeTheme.INSTALL_GAME_BUTTON));
    btnInstall.setActionCommand(ArcadeTheme.INSTALL_GAME_BUTTON_ACTION);
    btnInstall.addActionListener(this);
    this.add(btnInstall);

    btnPreferences.setLocation(currTheme.getButton(ArcadeTheme.PREFERENCES_BUTTON));
    btnPreferences.setActionCommand(ArcadeTheme.PREFERENCES_BUTTON_ACTION);
    btnPreferences.addActionListener(this);
    this.add(btnPreferences);

    btnUpdate.setLocation(currTheme.getButton(ArcadeTheme.UPDATE_BUTTON));
    btnUpdate.setActionCommand(ArcadeTheme.UPDATE_BUTTON_ACTION);
    btnUpdate.addActionListener(this);
    this.add(btnUpdate);

    btnHelp.setLocation(currTheme.getButton(ArcadeTheme.HELP_BUTTON));
    btnHelp.setActionCommand(ArcadeTheme.HELP_BUTTON_ACTION);
    btnHelp.addActionListener(this);
    this.add(btnHelp);

    btnExit.setLocation(currTheme.getButton(ArcadeTheme.EXIT_BUTTON));
    btnExit.setActionCommand(ArcadeTheme.EXIT_BUTTON_ACTION);
    btnExit.addActionListener(this);
    this.add(btnExit);

    // Keyboard actionlistener
    ActionListener kbdActionListener =
        new ActionListener() {
          @Override
          public void actionPerformed(ActionEvent actionEvent) {
            DoCommand(actionEvent);
          }
        };
    this.registerKeyboardAction(
        kbdActionListener,
        ArcadeTheme.HELP_BUTTON_ACTION,
        KeyStroke.getKeyStroke(KeyEvent.VK_F1, 0),
        JComponent.WHEN_FOCUSED);
    this.registerKeyboardAction(
        kbdActionListener,
        ArcadeTheme.UP_BUTTON_ACTION,
        KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0),
        JComponent.WHEN_FOCUSED);
    this.registerKeyboardAction(
        kbdActionListener,
        ArcadeTheme.DOWN_BUTTON_ACTION,
        KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0),
        JComponent.WHEN_FOCUSED);
    this.registerKeyboardAction(
        kbdActionListener,
        ArcadeTheme.HOW_TO_PLAY_BUTTON_ACTION,
        KeyStroke.getKeyStroke(KeyEvent.VK_SLASH, 0),
        JComponent.WHEN_FOCUSED);
    this.registerKeyboardAction(
        kbdActionListener,
        ArcadeTheme.HOW_TO_PLAY_BUTTON_ACTION,
        KeyStroke.getKeyStroke(KeyEvent.VK_SLASH, InputEvent.SHIFT_DOWN_MASK),
        JComponent.WHEN_FOCUSED);
    this.registerKeyboardAction(
        kbdActionListener,
        ArcadeTheme.LOAD_GAME_BUTTON_ACTION,
        KeyStroke.getKeyStroke(KeyEvent.VK_F5, 0),
        JComponent.WHEN_FOCUSED);
  }