Revision: 14684
          http://gate.svn.sourceforge.net/gate/?rev=14684&view=rev
Author:   valyt
Date:     2011-12-07 14:44:34 +0000 (Wed, 07 Dec 2011)
Log Message:
-----------
- support for displaying documents
- support for paginating results page
- support for history tokens (allows bookmarking search results)
- various CSS fixes.

Modified Paths:
--------------
    
mimir/trunk/mimir-web/grails-app/controllers/gate/mimir/web/SearchController.groovy
    
mimir/trunk/mimir-web/grails-app/services/gate/mimir/web/server/GwtRpcService.groovy
    mimir/trunk/mimir-web/grails-app/taglib/gate/mimir/web/MimirTagLib.groovy
    mimir/trunk/mimir-web/grails-app/views/search/index.gsp
    mimir/trunk/mimir-web/src/gwt/gate/mimir/web/client/UI.java
    mimir/trunk/mimir-web/src/java/gate/mimir/web/client/GwtRpcService.java
    mimir/trunk/mimir-web/src/java/gate/mimir/web/client/GwtRpcServiceAsync.java
    mimir/trunk/mimir-web/web-app/css/mimir.css

Added Paths:
-----------
    mimir/trunk/mimir-web/grails-app/views/search/document.gsp
    
mimir/trunk/mimir-web/src/java/gate/mimir/web/client/MimirSearchException.java

Removed Paths:
-------------
    mimir/trunk/mimir-web/src/java/gate/mimir/web/client/GwtRpcException.java

Modified: 
mimir/trunk/mimir-web/grails-app/controllers/gate/mimir/web/SearchController.groovy
===================================================================
--- 
mimir/trunk/mimir-web/grails-app/controllers/gate/mimir/web/SearchController.groovy
 2011-12-07 14:00:02 UTC (rev 14683)
+++ 
mimir/trunk/mimir-web/grails-app/controllers/gate/mimir/web/SearchController.groovy
 2011-12-07 14:44:34 UTC (rev 14684)
@@ -102,6 +102,29 @@
   }
   
   /**
+  * Render the content of the given document.  Most of the magic happens in
+  * the documentContent tag of the GusTagLib.
+  */
+ def document = {
+   QueryRunner runner = searchService.getQueryRunner(params.queryId)
+   if(runner){
+     Index index = Index.findByIndexId(params.indexId)
+     return [
+         indexId:params.indexId,
+         documentRank: params.documentRank,
+         queryId:params.queryId,
+         documentTitle:runner.getDocumentTitle(params.documentRank as int),
+         baseHref:index?.isUriIsExternalLink() ? 
runner.getDocumentURI(params.documentRank as int) : null
+         ]
+   } else {
+     //query has expired
+     return [
+       queryId:params.queryId
+     ]
+   }
+ }
+  
+  /**
   * Action that forwards to the real GWT RPC controller.
   */
  def gwtRpc = {

Modified: 
mimir/trunk/mimir-web/grails-app/services/gate/mimir/web/server/GwtRpcService.groovy
===================================================================
--- 
mimir/trunk/mimir-web/grails-app/services/gate/mimir/web/server/GwtRpcService.groovy
        2011-12-07 14:00:02 UTC (rev 14683)
+++ 
mimir/trunk/mimir-web/grails-app/services/gate/mimir/web/server/GwtRpcService.groovy
        2011-12-07 14:44:34 UTC (rev 14684)
@@ -1,16 +1,16 @@
 /**
-*  GwtRpcService.java
-*
-*  Copyright (c) 1995-2010, The University of Sheffield. See the file
-*  COPYRIGHT.txt in the software or at http://gate.ac.uk/gate/COPYRIGHT.txt
-*
-*  This file is part of GATE (see http://gate.ac.uk/), and is free
-*  software, licenced under the GNU Library General Public License,
-*  Version 2, June 1991 (in the distribution as file licence.html,
-*  and also available at http://gate.ac.uk/gate/licence.html).
-*
-*  Valentin Tablan, 01 Dec 2011
-*/
+ *  GwtRpcService.java
+ *
+ *  Copyright (c) 1995-2010, The University of Sheffield. See the file
+ *  COPYRIGHT.txt in the software or at http://gate.ac.uk/gate/COPYRIGHT.txt
+ *
+ *  This file is part of GATE (see http://gate.ac.uk/), and is free
+ *  software, licenced under the GNU Library General Public License,
+ *  Version 2, June 1991 (in the distribution as file licence.html,
+ *  and also available at http://gate.ac.uk/gate/licence.html).
+ *
+ *  Valentin Tablan, 01 Dec 2011
+ */
 package gate.mimir.web.server
 
 import gate.mimir.gus.client.SearchException;
@@ -26,7 +26,7 @@
 import gate.mimir.search.query.Binding;
 import gate.mimir.web.Index;
 import gate.mimir.web.client.DocumentData;
-import gate.mimir.web.client.GwtRpcException
+import gate.mimir.web.client.MimirSearchException
 import gate.mimir.web.client.ResultsData;
 import gate.mimir.web.client.DocumentData;
 
@@ -69,22 +69,22 @@
       try {
         def index = Index.findByIndexId(indexId)
         if(!index) {
-          throw new GwtRpcException("Invalid index ID ${indexId}")
+          throw new MimirSearchException("Invalid index ID ${indexId}")
         }
         else if(index.state != Index.SEARCHING) {
-          throw new GwtRpcException("Index ${indexId} is not open for 
searching")
+          throw new MimirSearchException("Index ${indexId} is not open for 
searching")
         }
         else {
           String queryId = searchService.postQuery(index, query)
           runningQueries.add(queryId)
           return queryId
         }
-      } catch(GwtRpcException e) {
+      } catch(MimirSearchException e) {
         log.warn("Exception starting search", e)
         throw e
       } catch(Exception e) {
         log.warn("Exception starting search", e)
-        throw new GwtRpcException("Could not start search. Error 
was:\n${e.message}");
+        throw new MimirSearchException("Could not start search. Error 
was:\n${e.message}");
       }
     }
   }
@@ -101,9 +101,9 @@
   }
 
   /**
-  * Obtains the types of annotation known to the index, and their features.
+   * Obtains the types of annotation known to the index, and their features.
    * This method supports auto-completion in the GWT UI.
-  */
+   */
   String[][] getAnnotationsConfig(String indexId){
     Index.withTransaction {
       Index index = Index.findByIndexId(indexId)
@@ -111,60 +111,60 @@
         return index.annotationsConfig()
       }
       else {
-        return ([] as String[][])
+        return ([]as String[][])
       }
     }
   }
 
   @Override
-  public ResultsData getResultsData(String queryId, 
-        int firstDocumentRank, int documentsCount) throws GwtRpcException {
+  public ResultsData getResultsData(String queryId,
+  int firstDocumentRank, int documentsCount) throws MimirSearchException {
     QueryRunner qRunner = searchService.getQueryRunner(queryId);
     if(qRunner) {
       ResultsData rData = new ResultsData(
-        resultsTotal:qRunner.getDocumentsCount(),
-        resultsPartial: qRunner.getCurrentDocumentsCount())
+          resultsTotal:qRunner.getDocumentsCount(),
+          resultsPartial: qRunner.getCurrentDocumentsCount())
       if(firstDocumentRank >= 0) {
         // also obtain some documents data
         List<DocumentData> documents = []
-        for(int docRank = firstDocumentRank; 
-            docRank < firstDocumentRank + documentsCount; 
-            docRank++) {
+        for(int docRank = firstDocumentRank;
+        docRank < firstDocumentRank + documentsCount;
+        docRank++) {
           DocumentData docData = new DocumentData(
-            documentRank:docRank,
-            documentTitle:qRunner.getDocumentTitle(docRank),
-            documentUri:qRunner.getDocumentURI(docRank))
+              documentRank:docRank,
+              documentTitle:qRunner.getDocumentTitle(docRank),
+              documentUri:qRunner.getDocumentURI(docRank))
           // create the snippets
           List<String[]> snippets = new ArrayList<String[]>();
           List<Binding> hits = qRunner.getDocumentHits(docRank).collect{it};
-          StringBuilder str = new StringBuilder()
           3.times {
             if(hits) {
               String[] snippet = new String[3];
               Binding aHit = hits.remove(0)
               int termPos = Math.max(0, aHit.termPosition - 3)
               if(termPos < aHit.termPosition) {
-                String[][] left =  qRunner.getDocumentText(docRank, termPos,
-                  aHit.termPosition - termPos)
-                left[0].each { str << (it + ' ') }
-                snippet[0] = str.toString()
+                snippet[0] = qRunner.getDocumentText(docRank, termPos, 
+                  aHit.termPosition - termPos).toList().transpose().inject('') 
{ 
+                    acc, val -> acc + val[0] + (val[1] ? ' ' : '')
+                } 
               } else {
-                snippet[0] = "";
+                snippet[0] = '';
               }
-              str = new StringBuilder()
-              qRunner.getDocumentText(docRank, aHit.termPosition, 
-                aHit.length)[0].each{ str << (it + ' ')}
-              snippet[1] = str.toString()
-              str = new StringBuilder()
-              qRunner.getDocumentText(docRank, 
-                aHit.termPosition + aHit.length, 3)[0].each{str << (it?:'' + ' 
')}
-              snippet[2] = str.toString()
-              snippets << snippet  
+              snippet[1] = qRunner.getDocumentText(docRank, aHit.termPosition,
+                aHit.length).toList().transpose().inject('') { 
+                  acc, val -> acc + val[0] + (val[1] ? ' ' : '')
+                }
+              snippet[2] = qRunner.getDocumentText(docRank,
+                aHit.termPosition + aHit.length, 3).toList().
+                transpose().inject('') { 
+                  acc, val -> acc + val[0] + (val[1] ? ' ' : '')
+                }
+              snippets << snippet
             }
           }
           if(hits) {
             // more than 3 hits: show ellipsis
-            snippets.add(["   ", "...", "   "] as String[])
+            snippets.add(["   ", "...", "   "]as String[])
           }
           docData.snippets = snippets
           documents.add(docData)
@@ -173,12 +173,11 @@
       }
       return rData
     } else {
-      throw new GwtRpcException("Could not find your query. " + 
+      throw new MimirSearchException(MimirSearchException.QUERY_ID_NOT_KNOWN,
+          "Could not find your query. " +
           "Your search session may have expired, in which case you will need " 
+
           "to start your search again.")
     }
   }
 
-  
-  
 }

Modified: 
mimir/trunk/mimir-web/grails-app/taglib/gate/mimir/web/MimirTagLib.groovy
===================================================================
--- mimir/trunk/mimir-web/grails-app/taglib/gate/mimir/web/MimirTagLib.groovy   
2011-12-07 14:00:02 UTC (rev 14683)
+++ mimir/trunk/mimir-web/grails-app/taglib/gate/mimir/web/MimirTagLib.groovy   
2011-12-07 14:44:34 UTC (rev 14684)
@@ -12,12 +12,24 @@
  */
 package gate.mimir.web;
 
+import gate.mimir.search.QueryRunner;
+
 import java.util.Locale;
 
 import java.text.NumberFormat;
 
-class MimirTagLib {
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+
+class MimirTagLib implements ApplicationContextAware {
   
+  /* This is slightly messy - I want to autowire the gusService but taglibs are
+  * singletons and the gusService is session-scoped.  So instead I inject the
+  * applicationContext and then fetch the gusService bean from the context at
+  * call time to get the right instance for the current session.
+  */
+  ApplicationContext applicationContext
+  
   static namespace = "mimir"
 
   /**
@@ -203,4 +215,20 @@
       out << "<p><i>Information not available</i></p>\n"
     }
   }
+  
+  def documentContent = { attrs, body ->
+    def queryId = attrs.queryId
+    def documentRank = attrs.documentRank as int
+    try {
+      QueryRunner qRunner =  
applicationContext.searchService.getQueryRunner(queryId)
+      if(qRunner) {
+        qRunner.renderDocument(documentRank, out)
+      } else {
+        out << g.message(code:"gus.bad.query.id", args:[queryId])
+      }
+    } catch(Exception ex) {
+      log.error("Exception rendering document ${documentRank}", ex)
+      out << g.message(code:"gus.renderDocument.exception", args:[ex.message])
+    }
+  }
 }

Added: mimir/trunk/mimir-web/grails-app/views/search/document.gsp
===================================================================
--- mimir/trunk/mimir-web/grails-app/views/search/document.gsp                  
        (rev 0)
+++ mimir/trunk/mimir-web/grails-app/views/search/document.gsp  2011-12-07 
14:44:34 UTC (rev 14684)
@@ -0,0 +1,19 @@
+<html>
+    <head>
+        <title><g:message code="gus.document.title" args="${[documentTitle]}" 
/></title>
+        <meta name="layout" content="mimir" />
+        <%-- 
+        <g:if test="${baseHref}"><base href="${baseHref}"></g:if> 
+        --%>        
+    </head>
+    <body>
+      <g:if test="${documentTitle != null}">
+       <h1><g:message code="gus.document.heading" args="${[documentTitle]}" 
/></h1>
+        <mimir:documentContent indexId="${indexId}" 
documentRank="${documentRank}"
+            queryId="${queryId}" />
+      </g:if>
+      <g:else>
+        <p>Cannot find query with given ID; perhaps your session expired. 
Please try your search again!</p>
+      </g:else>
+    </body>
+</html>

Modified: mimir/trunk/mimir-web/grails-app/views/search/index.gsp
===================================================================
--- mimir/trunk/mimir-web/grails-app/views/search/index.gsp     2011-12-07 
14:00:02 UTC (rev 14683)
+++ mimir/trunk/mimir-web/grails-app/views/search/index.gsp     2011-12-07 
14:44:34 UTC (rev 14684)
@@ -32,9 +32,9 @@
   <!-- blank for a completely dynamic interface.  -->
   
   <h1>Searching Index ${index?.name}</h1>
-  <div class="searchbox" id="searchBox"> </div>
+  <div class="searchBox" id="searchBox"> </div>
   <div class="bluebar" id="resultsBar"></div>
-  <div id="searchResults">
-  </div>
+  <div id="searchResults" class="searchResults"></div>
+  <div id="pageLinks" class="pageLinks bluebar"></div>
 </body>
 </html>

Modified: mimir/trunk/mimir-web/src/gwt/gate/mimir/web/client/UI.java
===================================================================
--- mimir/trunk/mimir-web/src/gwt/gate/mimir/web/client/UI.java 2011-12-07 
14:00:02 UTC (rev 14683)
+++ mimir/trunk/mimir-web/src/gwt/gate/mimir/web/client/UI.java 2011-12-07 
14:44:34 UTC (rev 14684)
@@ -16,6 +16,7 @@
 
 import java.util.ArrayList;
 import java.util.List;
+import sun.awt.motif.MInputMethod;
 
 import gate.mimir.gus.client.GusService;
 import gate.mimir.gus.client.GusServiceAsync;
@@ -26,16 +27,27 @@
 import com.google.gwt.dom.client.Element;
 import com.google.gwt.event.dom.client.ClickEvent;
 import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.event.logical.shared.ValueChangeEvent;
+import com.google.gwt.event.logical.shared.ValueChangeHandler;
+import com.google.gwt.http.client.URL;
+import com.google.gwt.user.client.History;
 import com.google.gwt.user.client.Timer;
 import com.google.gwt.user.client.Window;
 import com.google.gwt.user.client.rpc.AsyncCallback;
 import com.google.gwt.user.client.rpc.ServiceDefTarget;
+import com.google.gwt.user.client.ui.Anchor;
 import com.google.gwt.user.client.ui.Button;
+import com.google.gwt.user.client.ui.FlowPanel;
+import com.google.gwt.user.client.ui.HTML;
 import com.google.gwt.user.client.ui.HTMLPanel;
+import com.google.gwt.user.client.ui.Hyperlink;
+import com.google.gwt.user.client.ui.InlineHTML;
+import com.google.gwt.user.client.ui.InlineHyperlink;
 import com.google.gwt.user.client.ui.InlineLabel;
 import com.google.gwt.user.client.ui.Label;
 import com.google.gwt.user.client.ui.Panel;
 import com.google.gwt.user.client.ui.TextArea;
+import com.google.gwt.user.client.ui.Widget;
 
 /**
  * Entry point classes define <code>onModuleLoad()</code>.
@@ -46,38 +58,71 @@
    * A Timer implementation that fetches the latest results information from 
the 
    * server and updates the results display accordingly.
    */
-  protected class StatsUpdater extends Timer {
+  protected class ResultsUpdater extends Timer {
+    
+    private int newFirstDocument;
+    
+    public ResultsUpdater(int newFirstDocument) {
+      super();
+      this.newFirstDocument = newFirstDocument;
+    }
 
     @Override
     public void run() {
+      if(newFirstDocument != firstDocumentOnPage) {
+        // new page: clear data and old display
+        documentsData.clear();
+        searchResultsPanel.clear();
+        firstDocumentOnPage = newFirstDocument;
+      }
       // calculate which documents we need now
       int firstDoc = firstDocumentOnPage + documentsData.size();
       int docCount = maxDocumentsOnPage - documentsData.size(); 
       if(docCount <= 0) {
         firstDoc = -1;
+      } else {
+        feedbackLabel.setText("Working");
       }
       gwtRpcService.getResultsData(queryId, firstDoc, docCount, 
         new AsyncCallback<ResultsData>() {
         @Override
         public void onSuccess(ResultsData result) {
           updatePage(result);
-          if(result.getResultsTotal() < 0) {
-            // more to come
-            schedule(500);
-          }
+          boolean allDone = (result.getResultsTotal() >= 0) &&
+                ((documentsData.size() == maxDocumentsOnPage) ||
+                  firstDocumentOnPage + documentsData.size() == 
result.getResultsTotal());
+          if(!allDone) schedule(500);
         }
         
         @Override
         public void onFailure(Throwable caught) {
-          // ignore and try again later
-          schedule(500);
-          // throw the exception so it's seen
-          throw new RuntimeException(caught);
+          if(caught instanceof MimirSearchException &&
+             ((MimirSearchException)caught).getErrorCode() ==
+             MimirSearchException.QUERY_ID_NOT_KNOWN) {
+            // query ID not known -> re-post the query
+            if(queryString != null && queryString.length() > 0){
+              queryId = null;
+              postQuery(queryString);
+            }
+          } else {
+            // ignore and try again later
+            schedule(500);
+            // re-throw the exception so it's seen (useful when debugging)
+            throw new RuntimeException(caught);
+          }
         }
       });
     }
   }
   
+  private native String getIndexId() /*-{
+    return $wnd.indexId;
+  }-*/;
+
+  private native String getUriIsLink() /*-{
+    return $wnd.uriIsLink;
+  }-*/;
+
   private GwtRpcServiceAsync gwtRpcService;
   
   protected TextArea searchBox;
@@ -86,10 +131,18 @@
   
   protected String queryId;
   
-  protected Label resultStatsLabel;
+  protected String queryString;
   
+  protected String indexId;
+  
+  protected boolean uriIsLink;
+  
+  protected Label feedbackLabel;
+  
   protected HTMLPanel searchResultsPanel;
   
+  protected HTMLPanel pageLinksPanel;
+  
   /**
    * The rank of the first document on page.
    */
@@ -97,6 +150,8 @@
   
   protected int maxDocumentsOnPage = 20;
   
+  protected int maxPages = 20;
+  
   protected int maxSnippetLength = 100;
   
   private List<DocumentData> documentsData;
@@ -111,12 +166,24 @@
     String rpcUrl = GWT.getHostPageBaseURL() + "gwtRpc";
     endpoint.setServiceEntryPoint(rpcUrl);
     
-    queryId = null;
+    indexId = getIndexId();
+    uriIsLink = Boolean.parseBoolean(getUriIsLink());
     
+    initLocalData();
     initGui();
     initListeners();
   }
   
+  protected void initLocalData() {
+    queryId = null;
+    firstDocumentOnPage = 0;
+    if(documentsData == null){
+      documentsData = new ArrayList<DocumentData>(maxDocumentsOnPage);
+    } else {
+      documentsData.clear();
+    }
+  }
+  
   protected void initGui() {
     HTMLPanel searchDiv = 
HTMLPanel.wrap(Document.get().getElementById("searchBox"));
     
@@ -127,14 +194,15 @@
     
     searchButton = new Button();
     searchButton.setText("Search");
+    searchButton.addStyleName("searchButton");
     searchDiv.add(searchButton);
     
     HTMLPanel resultsBar = 
HTMLPanel.wrap(Document.get().getElementById("resultsBar"));
-    resultStatsLabel = new Label("Ready");
-    resultsBar.add(resultStatsLabel);
+    feedbackLabel = new Label("Ready");
+    resultsBar.add(feedbackLabel);
 
     searchResultsPanel = 
HTMLPanel.wrap(Document.get().getElementById("searchResults"));
-    
+    pageLinksPanel = 
HTMLPanel.wrap(Document.get().getElementById("pageLinks"));
   }
   
   protected void initListeners() {
@@ -144,32 +212,103 @@
         startSearch();
       }
     });
+    
+    History.addValueChangeHandler(new ValueChangeHandler<String>() {
+      @Override
+      public void onValueChange(ValueChangeEvent<String> event) {
+        String historyToken = event.getValue();
+        if(historyToken != null && historyToken.length() > 0) {
+          String newQueryId = null;
+          String newQueryString = null;
+          int newFirstDoc = 0;
+          
+          String[] elems = historyToken.split("\\&");
+          for(String elem : elems) {
+            String[] keyVal = elem.split("=");
+            String key = keyVal[0].trim();
+            String value = keyVal[1].trim();
+            if(key.equalsIgnoreCase("queryId")) {
+              newQueryId = URL.decodeQueryString(value);
+            } else if(key.equalsIgnoreCase("queryString")) {
+              newQueryString = URL.decodeQueryString(value);
+            } else if(key.equalsIgnoreCase("firstDoc")) {
+              try{
+                newFirstDoc =Integer.parseInt(value);
+              } catch (NumberFormatException nfe) {
+                // ignore, and start results from zero
+              }
+            }
+          }
+          // now update the display accordingly
+          if(newQueryId != null && newQueryId.length() > 0) {
+            queryId = newQueryId;
+            queryString = newQueryString;
+            if(!searchBox.getText().trim().equalsIgnoreCase(
+              newQueryString.trim())){
+              searchBox.setText(newQueryString);
+            }
+            new ResultsUpdater(newFirstDoc).schedule(10);
+          }
+        }
+      }
+    });
+    // now read the current history
+    String historyToken = History.getToken(); 
+    if(historyToken != null && historyToken.length() > 0) {
+      History.fireCurrentHistoryState();
+    }
   }
   
   protected void startSearch() {
-    String query = searchBox.getText();
-    gwtRpcService.search(getIndexId(), query, new AsyncCallback<String>() {
+    // clean up old state
+    if(queryId != null) {
+      // release old query
+      gwtRpcService.releaseQuery(queryId, new AsyncCallback<Void>() {
+        @Override
+        public void onSuccess(Void result) {}
+        @Override
+        public void onFailure(Throwable caught) {}
+      });
+    }
+    // reset internal data
+    initLocalData();
+    // post the new query
+    postQuery(searchBox.getText());
+  }
+  
+  protected void postQuery(final String newQueryString) {
+    feedbackLabel.setText("Working...");
+    // clear the old display
+    searchResultsPanel.clear();
+    gwtRpcService.search(getIndexId(), newQueryString, new 
AsyncCallback<String>() {
       @Override
       public void onFailure(Throwable caught) {
-        // TODO Auto-generated method stub
-        
+        feedbackLabel.setText(caught.getLocalizedMessage());
       }
       @Override
-      public void onSuccess(String result) {
-        queryId = result;
-        firstDocumentOnPage = 0;
-        documentsData = new ArrayList<DocumentData>(maxDocumentsOnPage);
-        searchResultsPanel.clear();
-        new StatsUpdater().schedule(50);
+      public void onSuccess(String newQueryId) {
+        History.newItem(createHistoryToken(newQueryId, newQueryString, 
+          firstDocumentOnPage));
       }
-    });
+    });    
   }
   
+  protected String createHistoryToken(String queryId, String queryString, 
+                                      int firstDocument) {
+    return "queryId=" + URL.encodeQueryString(queryId) + 
+        "&queryString=" + URL.encodeQueryString(queryString) + 
+        "&firstDoc=" + firstDocument;
+  }
+  
+  /**
+   * Updates the results display (including the feedback label)
+   * @param resultsData
+   */
   protected void updatePage(ResultsData resultsData) {
     int resTotal = resultsData.getResultsTotal();
     int resPartial = resultsData.getResultsPartial();
     StringBuilder textBuilder = new StringBuilder("Documents ");
-    textBuilder.append(firstDocumentOnPage);
+    textBuilder.append(firstDocumentOnPage + 1);
     textBuilder.append(" to ");
     if(firstDocumentOnPage + maxDocumentsOnPage < resPartial) {
       textBuilder.append(firstDocumentOnPage + maxDocumentsOnPage);
@@ -187,7 +326,7 @@
       textBuilder.append(resPartial);
     }
     textBuilder.append(":");
-    resultStatsLabel.setText(textBuilder.toString());
+    feedbackLabel.setText(textBuilder.toString());
 
     if(resultsData.getDocuments() != null){
       // now update the documents display
@@ -201,7 +340,7 @@
         if(docPosition == documentsData.size()) {
           documentsData.add(docData);
           HTMLPanel documentDisplay = buildDocumentDisplay(docData);
-          if(docPosition % 2 == 0) documentDisplay.addStyleName("even");
+//          if(docPosition % 2 == 0) documentDisplay.addStyleName("even");
           searchResultsPanel.add(documentDisplay);
         } else {
           if(documentsData.get(docPosition).documentRank == 
docData.documentRank) {
@@ -213,20 +352,65 @@
         }
       }      
     }
+    
+    // page links
+    pageLinksPanel.clear();
+    int currentPage = firstDocumentOnPage / maxDocumentsOnPage;
+    int firstPage = Math.max(0, currentPage - (maxPages / 2));
+    int maxPage = resultsData.getResultsPartial() / maxDocumentsOnPage;
+    if(resultsData.getResultsPartial() % maxDocumentsOnPage > 0) maxPage++;
+    maxPage = Math.min(maxPage, firstPage + maxPages);
+    for(int pageNo = firstPage; pageNo < maxPage; pageNo++) {
+      Widget pageLink;
+      if(pageNo != currentPage) {
+        pageLink = new InlineHyperlink("" + (pageNo + 1), 
+          createHistoryToken(queryId, queryString, 
+            pageNo * maxDocumentsOnPage));
+      } else {
+        pageLink = new InlineLabel("" + (pageNo + 1));
+      }
+      pageLink.addStyleName("pageLink");
+      pageLinksPanel.add(pageLink);
+    }
   }
   
   private HTMLPanel buildDocumentDisplay(DocumentData docData) {
     HTMLPanel documentDisplay = new HTMLPanel("");
     documentDisplay.setStyleName("hit");
-    HTMLPanel docTitle = new HTMLPanel(docData.documentTitle);
-    docTitle.setStyleName("document-title");
-    documentDisplay.add(docTitle);
+    String documentUri = docData.documentUri;
+    String documentTitle = docData.documentTitle;
+    if(documentTitle == null || documentTitle.trim().length() == 0) {
+      // we got no title to display: use the URI file
+      String [] pathElems = documentUri.split("/");
+      documentTitle = pathElems[pathElems.length -1];
+    }
+    String documentTitleText = "<span title=\"" + documentUri + 
+        "\" class=\"document-title\">" +
+        docData.documentTitle + "</span>";
+    FlowPanel docLinkPanel = new FlowPanel();
+//    docLinkPanel.setStyleName("document-title");
+    if(uriIsLink) {
+      // generate two links: original doc and cached
+      docLinkPanel.add(new Anchor(documentTitleText, true, documentUri));
+      docLinkPanel.add(new InlineLabel(" ("));
+      docLinkPanel.add(new Anchor("cached", false, 
+          "document?documentRank=" + docData.documentRank + 
+          "&queryId=" + queryId));
+      docLinkPanel.add(new InlineLabel(")"));
+    } else {
+      // generate one link: cached, with document name as text
+      docLinkPanel.add(new Anchor(documentTitle, true,
+          "document?documentRank=" + docData.documentRank + 
+          "&queryId=" + queryId));
+    }
+    documentDisplay.add(docLinkPanel);
     if(docData.snippets != null) {
+      StringBuilder snippetsText = new StringBuilder("<div 
class=\"snippets\">");
       // each row is left context, snippet, right context
       for(String[] snippet : docData.snippets) {
-        HTMLPanel snippetPanel = new HTMLPanel("");
-        snippetPanel.setStyleName("snippet");
-        snippetPanel.add(new InlineLabel(snippet[0]));
+        snippetsText.append("<span class=\"snippet\">");
+        snippetsText.append(snippet[0]);
+        snippetsText.append("<span class=\"snippet-text\">");
         String snipText = snippet[1];
         int snipLen = snipText.length();
         if(snipLen > maxSnippetLength) {
@@ -235,21 +419,15 @@
               " ... " + 
               snipText.substring((snipLen + toRemove) / 2);
         }
-        InlineLabel snippetLabel = new InlineLabel(snipText);
-        snippetLabel.setStyleName("snippet-text");
-        snippetPanel.add(snippetLabel);
-        snippetPanel.add(new InlineLabel(snippet[2]));
-        documentDisplay.add(snippetPanel);
+        snippetsText.append(snipText);
+        //close snippet-text span
+        snippetsText.append("</span>");
+        snippetsText.append(snippet[2]);
+        //close snippet span
+        snippetsText.append("</span>");
       }
+      documentDisplay.add(new HTML(snippetsText.toString()));
     }
     return documentDisplay;
   }
-  
-  private native String getIndexId() /*-{
-    return $wnd.indexId;
-  }-*/;
-
-  private native String getUriIsLink() /*-{
-    return $wnd.uriIsLink;
-  }-*/;
 }

Deleted: 
mimir/trunk/mimir-web/src/java/gate/mimir/web/client/GwtRpcException.java
===================================================================
--- mimir/trunk/mimir-web/src/java/gate/mimir/web/client/GwtRpcException.java   
2011-12-07 14:00:02 UTC (rev 14683)
+++ mimir/trunk/mimir-web/src/java/gate/mimir/web/client/GwtRpcException.java   
2011-12-07 14:44:34 UTC (rev 14684)
@@ -1,28 +0,0 @@
-/**
- *  GwtRpcException.java
- * 
- *  Copyright (c) 1995-2010, The University of Sheffield. See the file
- *  COPYRIGHT.txt in the software or at http://gate.ac.uk/gate/COPYRIGHT.txt
- *
- *  This file is part of GATE (see http://gate.ac.uk/), and is free
- *  software, licenced under the GNU Library General Public License,
- *  Version 2, June 1991 (in the distribution as file licence.html,
- *  and also available at http://gate.ac.uk/gate/licence.html).
- *
- *  Valentin Tablan, 01 Dec 2011 
- */
-package gate.mimir.web.client;
-
-import com.google.gwt.user.client.rpc.IsSerializable;
-
-public class GwtRpcException extends Exception implements IsSerializable {
-  private static final long serialVersionUID = 1L;
-
-  public GwtRpcException() {
-    super();
-  }
-
-  public GwtRpcException(String message) {
-    super(message);
-  }
-}

Modified: 
mimir/trunk/mimir-web/src/java/gate/mimir/web/client/GwtRpcService.java
===================================================================
--- mimir/trunk/mimir-web/src/java/gate/mimir/web/client/GwtRpcService.java     
2011-12-07 14:00:02 UTC (rev 14683)
+++ mimir/trunk/mimir-web/src/java/gate/mimir/web/client/GwtRpcService.java     
2011-12-07 14:44:34 UTC (rev 14684)
@@ -17,10 +17,10 @@
 
 public interface GwtRpcService extends RemoteService {
   public String search(java.lang.String indexId, java.lang.String query)
-    throws GwtRpcException;
+    throws MimirSearchException;
 
   public ResultsData getResultsData(String queryId, int firstDocumentRank, 
-      int documentsCount) throws GwtRpcException;
+      int documentsCount) throws MimirSearchException;
 
   public void releaseQuery(java.lang.String queryId);
 }

Modified: 
mimir/trunk/mimir-web/src/java/gate/mimir/web/client/GwtRpcServiceAsync.java
===================================================================
--- 
mimir/trunk/mimir-web/src/java/gate/mimir/web/client/GwtRpcServiceAsync.java    
    2011-12-07 14:00:02 UTC (rev 14683)
+++ 
mimir/trunk/mimir-web/src/java/gate/mimir/web/client/GwtRpcServiceAsync.java    
    2011-12-07 14:44:34 UTC (rev 14684)
@@ -16,11 +16,13 @@
 import com.google.gwt.user.client.rpc.AsyncCallback;
 
 public interface GwtRpcServiceAsync {
+  
   public void search(java.lang.String indexId, java.lang.String query,
               AsyncCallback<String> callback);
 
   void getResultsData(String queryId, int firstDocumentRank,
                       int documentsCount, AsyncCallback<ResultsData> callback);
   
-  public void releaseQuery(java.lang.String queryId, AsyncCallback callback);
+  public void releaseQuery(java.lang.String queryId, 
+                           AsyncCallback<Void> callback);
 }

Copied: 
mimir/trunk/mimir-web/src/java/gate/mimir/web/client/MimirSearchException.java 
(from rev 14674, 
mimir/trunk/mimir-web/src/java/gate/mimir/web/client/GwtRpcException.java)
===================================================================
--- 
mimir/trunk/mimir-web/src/java/gate/mimir/web/client/MimirSearchException.java  
                            (rev 0)
+++ 
mimir/trunk/mimir-web/src/java/gate/mimir/web/client/MimirSearchException.java  
    2011-12-07 14:44:34 UTC (rev 14684)
@@ -0,0 +1,56 @@
+/**
+ *  MimirSearchException.java
+ * 
+ *  Copyright (c) 1995-2010, The University of Sheffield. See the file
+ *  COPYRIGHT.txt in the software or at http://gate.ac.uk/gate/COPYRIGHT.txt
+ *
+ *  This file is part of GATE (see http://gate.ac.uk/), and is free
+ *  software, licenced under the GNU Library General Public License,
+ *  Version 2, June 1991 (in the distribution as file licence.html,
+ *  and also available at http://gate.ac.uk/gate/licence.html).
+ *
+ *  Valentin Tablan, 01 Dec 2011 
+ */
+package gate.mimir.web.client;
+
+import com.google.gwt.user.client.rpc.IsSerializable;
+
+/**
+ * Exceptions that can occur on the server side as an effect of remote GWT RPC
+ * calls (and that can be serialised and sent back to the client).
+ */
+public class MimirSearchException extends Exception implements IsSerializable {
+  
+  private static final long serialVersionUID = 1L;
+
+  public static final int QUERY_ID_NOT_KNOWN = 1;
+  
+  public static final int OTHER = 0;
+  
+  private int errorCode;
+  
+  
+  public MimirSearchException(int errorCode) {
+    super();
+    this.errorCode = errorCode;
+  }
+
+  public MimirSearchException(int errorCode, String message) {
+    super(message);
+    this.errorCode = errorCode;
+  }
+  
+  public MimirSearchException() {
+    errorCode = OTHER;
+  }
+
+  public MimirSearchException(String message) {
+    super(message);
+    errorCode = OTHER;
+  }
+
+  public int getErrorCode() {
+    return errorCode;
+  }
+  
+}

Modified: mimir/trunk/mimir-web/web-app/css/mimir.css
===================================================================
--- mimir/trunk/mimir-web/web-app/css/mimir.css 2011-12-07 14:00:02 UTC (rev 
14683)
+++ mimir/trunk/mimir-web/web-app/css/mimir.css 2011-12-07 14:44:34 UTC (rev 
14684)
@@ -24,7 +24,7 @@
     color: #006193;
     font-weight: bold;
     text-decoration: none;
-} 
+}
 
 h1, .bluebar {
     color: #fff;
@@ -181,19 +181,41 @@
     background: #e3ecf3;
 }
 
+.searchBox {
+       margin: 10px;
+       text-align: center;
+       vertical-align: middle;
+}
+
+.searchButton {
+  margin-left: 5px;
+}
+
+.searchResults {
+       width: 80%;
+}
+
 .hit {
   font-size: medium;
-  margin: 5px;
+  margin-top: 20px;
 }
 
+.hit a {
+       font-weight: normal;
+}
+
 .document-title {
-  font-weight: bold;
+       font-weight:bold;
   color: #006193;
 }
 
+.snippets {
+  /* margin-left: 20px; */
+}
+
 .snippet {
-       margin-left: 20px;
        font-size: small;
+       margin-right: 10px;
 }
 
 .snippet-text {
@@ -206,6 +228,27 @@
     color: black;
 }
 
+.pageLinks {
+  margin-top: 10px;
+  margin-bottom: 10px;
+  padding-top: 5px;
+  padding-bottom: 5px;
+  text-align: center;
+}
+
+.pageLink {
+       margin-left: 5px;
+       color: white;
+       padding-left: 2px;
+       padding-right: 2px;
+}
+
+a.pageLink {
+  color: white;
+  font-weight: normal;
+  border: thin solid white;
+}
+
 /* LIST */
 
 .list table {

This was sent by the SourceForge.net collaborative development platform, the 
world's largest Open Source development site.


------------------------------------------------------------------------------
Cloud Services Checklist: Pricing and Packaging Optimization
This white paper is intended to serve as a reference, checklist and point of 
discussion for anyone considering optimizing the pricing and packaging model 
of a cloud services business. Read Now!
http://www.accelacomm.com/jaw/sfnl/114/51491232/
_______________________________________________
GATE-cvs mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/gate-cvs

Reply via email to