private void previousPage() {
    if (currentSource.hasPreviousPage()) {
      currentSource.previousPage();

      // set new text
      panel.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
      panel.refreshCurrentMarkup();
      panel.setCursor(null);

      // update display
      panel.updateCurrentPageDisplay(currentSource.getCurrentPage());

      // scroll to current selection
      ExtractedContentViewer.this.scrollToCurrentHit();

      // update controls if needed
      if (!currentSource.hasPreviousPage()) {
        panel.enablePrevPageControl(false);
      }
      if (currentSource.hasNextPage()) {
        panel.enableNextPageControl(true);
      }

      updateSearchControls();
    }
  }
  private void updatePageControls() {
    if (currentSource == null) return;

    final int currentPage = currentSource.getCurrentPage();
    final int totalPages = currentSource.getNumberPages();
    panel.updateTotalPageslDisplay(totalPages);
    panel.updateCurrentPageDisplay(currentPage);

    if (totalPages == 1) {
      panel.enableNextPageControl(false);
      panel.enablePrevPageControl(false);
    } else {
      if (currentSource.hasNextPage()) {
        panel.enableNextPageControl(true);
      } else {
        panel.enableNextPageControl(false);
      }

      if (currentSource.hasPreviousPage()) {
        panel.enablePrevPageControl(true);
      } else {
        panel.enablePrevPageControl(false);
      }
    }
  }
  private void scrollToCurrentHit() {
    final MarkupSource source = panel.getSelectedSource();
    if (source == null || !source.isSearchable()) {
      return;
    }

    // using invokeLater to wait for ComboBox selection to complete
    EventQueue.invokeLater(
        new Runnable() {

          @Override
          public void run() {
            panel.scrollToAnchor(source.getAnchorPrefix() + Integer.toString(source.currentItem()));
          }
        });
  }
  private void updateSearchControls() {
    // setup search controls
    if (currentSource != null && currentSource.isSearchable()) {

      panel.updateCurrentMatchDisplay(currentSource.currentItem());
      panel.updateTotaMatcheslDisplay(currentSource.getNumberHits());

      if (currentSource.hasNextItem() || currentSource.hasNextPage()) {
        panel.enableNextMatchControl(true);
      } else {
        panel.enableNextMatchControl(false);
      }

      if (currentSource.hasPreviousItem() || currentSource.hasPreviousPage()) {
        panel.enablePrevMatchControl(true);
      } else {
        panel.enablePrevMatchControl(false);
      }

    } else {
      panel.enableNextMatchControl(false);
      panel.enablePrevMatchControl(false);
      panel.updateCurrentMatchDisplay(0);
      panel.updateTotaMatcheslDisplay(0);
    }
  }
  @Override
  public void setNode(final Node selectedNode) {
    // TODO why setNode() is called twice for the same node each time

    // to clear the viewer
    if (selectedNode == null) {
      currentNode = null;
      resetComponent();
      return;
    }

    this.currentNode = selectedNode;

    // sources are custom markup from the node (if available) and default
    // markup is fetched from solr
    List<MarkupSource> sources = new ArrayList<MarkupSource>();

    // add additional registered sources for this node
    sources.addAll(selectedNode.getLookup().lookupAll(MarkupSource.class));

    if (solrHasContent(selectedNode)) {
      Content content = selectedNode.getLookup().lookup(Content.class);
      if (content == null) {
        return;
      }

      // add to page tracking if not there yet
      final long contentID = content.getId();

      MarkupSource newSource =
          new MarkupSource() {

            private boolean inited = false;
            private int numPages = 0;
            private int currentPage = 0;
            private boolean hasChunks = false;

            @Override
            public int getCurrentPage() {
              return this.currentPage;
            }

            @Override
            public boolean hasNextPage() {
              return currentPage < numPages;
            }

            @Override
            public boolean hasPreviousPage() {
              return currentPage > 1;
            }

            @Override
            public int nextPage() {
              if (!hasNextPage()) {
                throw new IllegalStateException("No next page.");
              }
              ++currentPage;
              return currentPage;
            }

            @Override
            public int previousPage() {
              if (!hasPreviousPage()) {
                throw new IllegalStateException("No previous page.");
              }
              --currentPage;
              return currentPage;
            }

            @Override
            public boolean hasNextItem() {
              throw new UnsupportedOperationException("Not supported, not a searchable source.");
            }

            @Override
            public boolean hasPreviousItem() {
              throw new UnsupportedOperationException("Not supported, not a searchable source.");
            }

            @Override
            public int nextItem() {
              throw new UnsupportedOperationException("Not supported, not a searchable source.");
            }

            @Override
            public int previousItem() {
              throw new UnsupportedOperationException("Not supported, not a searchable source.");
            }

            @Override
            public int currentItem() {
              throw new UnsupportedOperationException("Not supported, not a searchable source.");
            }

            @Override
            public String getMarkup() {
              try {
                String content =
                    StringEscapeUtils.escapeHtml(
                        getSolrContent(selectedNode, currentPage, hasChunks));
                return "<pre>" + content.trim() + "</pre>";
              } catch (SolrServerException ex) {
                logger.log(Level.WARNING, "Couldn't get extracted content.", ex);
                return "";
              }
            }

            @Override
            public String toString() {
              return "Extracted Content";
            }

            @Override
            public boolean isSearchable() {
              return false;
            }

            @Override
            public String getAnchorPrefix() {
              return "";
            }

            @Override
            public int getNumberHits() {
              return 0;
            }

            @Override
            public LinkedHashMap<Integer, Integer> getHitsPages() {
              return null;
            }

            @Override
            public int getNumberPages() {
              if (inited) {
                return this.numPages;
              }

              final Server solrServer = KeywordSearch.getServer();

              try {
                numPages = solrServer.queryNumFileChunks(contentID);
                if (numPages == 0) {
                  numPages = 1;
                  hasChunks = false;
                } else {
                  hasChunks = true;
                }
                inited = true;
              } catch (SolrServerException ex) {
                logger.log(Level.WARNING, "Could not get number of chunks: ", ex);

              } catch (NoOpenCoreException ex) {
                logger.log(Level.WARNING, "Could not get number of chunks: ", ex);
              }
              return numPages;
            }
          };

      currentSource = newSource;
      sources.add(newSource);

      // init pages
      final int totalPages = currentSource.getNumberPages();
      int currentPage = currentSource.getCurrentPage();
      if (currentPage == 0 && currentSource.hasNextPage()) {
        currentSource.nextPage();
      }

      updatePageControls();
    }

    // first source will be the default displayed
    setPanel(sources);
    // If node has been selected before, return to the previous position
    scrollToCurrentHit();
  }
    @Override
    public void actionPerformed(ActionEvent e) {
      MarkupSource source = panel.getSelectedSource();
      final boolean hasPreviousItem = source.hasPreviousItem();
      final boolean hasPreviousPage = source.hasPreviousPage();
      int indexVal = 0;
      if (hasPreviousItem || hasPreviousPage) {
        if (!hasPreviousItem) {
          // flip the page
          previousPage();
          indexVal = source.currentItem();
        } else {
          indexVal = source.previousItem();
        }

        // scroll
        panel.scrollToAnchor(source.getAnchorPrefix() + Integer.toString(indexVal));

        // update display
        panel.updateCurrentMatchDisplay(source.currentItem());
        panel.updateTotaMatcheslDisplay(source.getNumberHits());

        // update controls if needed
        if (!source.hasPreviousItem() && !source.hasPreviousPage()) {
          panel.enablePrevMatchControl(false);
        }
        if (source.hasNextItem() || source.hasNextPage()) {
          panel.enableNextMatchControl(true);
        }
      }
    }