2 * Copyright 2007, 2008, 2009 Electronic Business Systems Ltd.
4 * This file is part of GSS.
6 * GSS is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
11 * GSS is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with GSS. If not, see <http://www.gnu.org/licenses/>.
19 package gr.ebs.gss.client;
21 import gr.ebs.gss.client.dnd.DnDSimpleFocusPanel;
22 import gr.ebs.gss.client.rest.GetCommand;
23 import gr.ebs.gss.client.rest.RestCommand;
24 import gr.ebs.gss.client.rest.RestException;
25 import gr.ebs.gss.client.rest.resource.FileResource;
26 import gr.ebs.gss.client.rest.resource.SearchResource;
27 import gr.ebs.gss.client.rest.resource.UserResource;
29 import java.util.ArrayList;
30 import java.util.Collections;
31 import java.util.Comparator;
32 import java.util.List;
34 import com.google.gwt.core.client.GWT;
35 import com.google.gwt.dom.client.NativeEvent;
36 import com.google.gwt.event.dom.client.ClickEvent;
37 import com.google.gwt.event.dom.client.ClickHandler;
38 import com.google.gwt.http.client.URL;
39 import com.google.gwt.i18n.client.DateTimeFormat;
40 import com.google.gwt.resources.client.ClientBundle;
41 import com.google.gwt.resources.client.ImageResource;
42 import com.google.gwt.user.client.DOM;
43 import com.google.gwt.user.client.DeferredCommand;
44 import com.google.gwt.user.client.Event;
45 import com.google.gwt.user.client.IncrementalCommand;
46 import com.google.gwt.user.client.Window;
47 import com.google.gwt.user.client.ui.AbstractImagePrototype;
48 import com.google.gwt.user.client.ui.Composite;
49 import com.google.gwt.user.client.ui.Grid;
50 import com.google.gwt.user.client.ui.HTML;
51 import com.google.gwt.user.client.ui.HTMLTable.Cell;
52 import com.google.gwt.user.client.ui.HasHorizontalAlignment;
53 import com.google.gwt.user.client.ui.HorizontalPanel;
54 import com.google.gwt.user.client.ui.VerticalPanel;
57 * A composite that displays a list of search results for a particular query on
60 public class SearchResults extends Composite implements ClickHandler {
62 private HTML prevButton = new HTML("<a href='javascript:;'>< Previous</a>", true);
64 private HTML nextButton = new HTML("<a href='javascript:;'>Next ></a>", true);
66 private String showingStats = "";
68 private int startIndex = 0;
71 * A constant that denotes the completion of an IncrementalCommand.
73 public static final boolean DONE = false;
75 private boolean clickControl = false;
77 private boolean clickShift = false;
79 private int firstShift = -1;
81 private ArrayList<Integer> selectedRows = new ArrayList<Integer>();
84 * The context menu for the selected file.
86 final DnDSimpleFocusPanel contextMenu;
89 * Specifies that the images available for this composite will be the ones
90 * available in FileContextMenu.
92 public interface Images extends ClientBundle,FileContextMenu.Images, Folders.Images {
94 @Source("gr/ebs/gss/resources/blank.gif")
95 ImageResource blank();
97 @Source("gr/ebs/gss/resources/asc.png")
100 @Source("gr/ebs/gss/resources/desc.png")
101 ImageResource desc();
105 * A label with the number of files in this folder.
107 private HTML countLabel = new HTML();
110 * The table widget with the file list.
112 private FileTable table;
115 * The navigation bar for paginating the results.
117 private HorizontalPanel navBar = new HorizontalPanel();
120 * The number of files in the search results
122 private int folderFileCount;
125 * Total search results size
127 private long folderTotalSize;
130 * A cache of the files in the list.
132 private List<FileResource> files;
135 * The widget's image bundle.
137 private final Images images;
139 private String sortingProperty = "name";
141 private boolean sortingType = true;
143 private HTML nameLabel;
145 private HTML versionLabel;
147 private HTML sizeLabel;
149 private HTML dateLabel;
151 private HTML ownerLabel;
153 private HTML pathLabel;
155 private HTML searchResults = new HTML("Results for search:");
158 * Construct the file list widget. This entails setting up the widget
159 * layout, fetching the number of files in the current folder from the
160 * server and filling the local file cache of displayed files with data from
161 * the server, as well.
165 public SearchResults(final Images _images) {
167 final GSS app = GSS.get();
168 table = new FileTable(GSS.VISIBLE_FILE_COUNT + 1, 8) {
171 public void onBrowserEvent(Event event) {
172 if (DOM.eventGetType(event) == Event.ONMOUSEDOWN && DOM.eventGetButton(event) == NativeEvent.BUTTON_RIGHT){
173 if (DOM.eventGetCtrlKey(event))
176 clickControl = false;
177 if (DOM.eventGetShiftKey(event)) {
179 if (selectedRows.size() == 1)
180 firstShift = selectedRows.get(0) - startIndex;
181 //event.preventDefault();
185 //event.preventDefault();
187 int ri = table.getRowForEvent2(event);
189 if(!selectedRows.contains(ri-1))
190 onRowClicked(ri, false);
193 if (files == null || files.size() == 0) {
194 if (DOM.eventGetType(event) == Event.ONCONTEXTMENU && selectedRows.size() == 0) {
195 FileContextMenu fm = new FileContextMenu(images, false, true);
196 fm.onEmptyEvent(event);
200 if (DOM.eventGetType(event) == Event.ONCONTEXTMENU && selectedRows.size() != 0) {
201 FileContextMenu fm = new FileContextMenu(images, false, false);
203 } else if (DOM.eventGetType(event) == Event.ONCONTEXTMENU && selectedRows.size() == 0) {
204 FileContextMenu fm = new FileContextMenu(images, false, true);
205 fm.onEmptyEvent(event);
206 } else if (DOM.eventGetType(event) == Event.ONDBLCLICK)
207 if (getSelectedFiles().size() == 1) {
208 FileResource file = getSelectedFiles().get(0);
209 String dateString = RestCommand.getDate();
210 String resource = file.getUri().substring(app.getApiPath().length() - 1, file.getUri().length());
211 String sig = app.getCurrentUserResource().getUsername() + " " +
212 RestCommand.calculateSig("GET", dateString, resource,
213 RestCommand.base64decode(app.getToken()));
214 Window.open(file.getUri() + "?Authorization=" + URL.encodeComponent(sig) + "&Date=" + URL.encodeComponent(dateString), "_blank", "");
215 event.preventDefault();
218 if (DOM.eventGetType(event) == Event.ONCLICK) {
219 if (DOM.eventGetCtrlKey(event))
222 clickControl = false;
223 if (DOM.eventGetShiftKey(event)) {
225 if (selectedRows.size() == 1)
226 firstShift = selectedRows.get(0) - startIndex;
227 //event.preventDefault();
231 //event.preventDefault();
234 super.onBrowserEvent(event);
237 prevButton.addClickHandler(this);
238 nextButton.addClickHandler(this);
240 contextMenu = new DnDSimpleFocusPanel(new HTML(AbstractImagePrototype.create(images.fileContextMenu()).getHTML()));
241 contextMenu.addClickHandler(new FileContextMenu(images, false, false));
242 app.getDragController().makeDraggable(contextMenu);
245 table.setCellSpacing(0);
246 table.setCellPadding(2);
247 table.setWidth("100%");
250 table.addClickHandler(new ClickHandler() {
253 public void onClick(ClickEvent event) {
254 Cell cell = table.getCellForEvent(event);
255 onRowClicked(cell.getRowIndex(),true);
259 // Create the 'navigation' bar at the upper-right.
260 HorizontalPanel innerNavBar = new HorizontalPanel();
261 innerNavBar.setStyleName("gss-ListNavBar");
262 innerNavBar.setSpacing(8);
263 innerNavBar.add(prevButton);
264 innerNavBar.add(countLabel);
265 innerNavBar.add(nextButton);
266 navBar.setHorizontalAlignment(HasHorizontalAlignment.ALIGN_RIGHT);
267 navBar.add(innerNavBar);
268 navBar.setWidth("100%");
269 VerticalPanel vp = new VerticalPanel();
270 vp.add(searchResults);
271 searchResults.addStyleName("gss-searchLabel");
274 table.setStyleName("gss-List");
276 DeferredCommand.addCommand(new IncrementalCommand() {
279 public boolean execute() {
280 return fetchRootFolder();
283 table.sinkEvents(Event.ONCONTEXTMENU);
284 table.sinkEvents(Event.ONMOUSEUP);
285 table.sinkEvents(Event.ONMOUSEDOWN);
286 table.sinkEvents(Event.ONCLICK);
287 table.sinkEvents(Event.ONKEYDOWN);
288 table.sinkEvents(Event.ONDBLCLICK);
289 preventIESelection();
293 public void onClick(ClickEvent event) {
294 if (event.getSource() == nextButton) {
295 // Move forward a page.
297 startIndex += GSS.VISIBLE_FILE_COUNT;
298 if (startIndex >= folderFileCount)
299 startIndex -= GSS.VISIBLE_FILE_COUNT;
302 } else if (event.getSource() == prevButton) {
305 startIndex -= GSS.VISIBLE_FILE_COUNT;
314 * Retrieve the root folder for the current user.
316 * @return true if the retrieval was successful
318 protected boolean fetchRootFolder() {
319 UserResource user = GSS.get().getCurrentUserResource();
326 public void onRowClicked(int row, boolean toggleSelection) {
327 // Select the row that was clicked (-1 to account for header row).
328 if (row > folderFileCount)
331 GWT.log("ROW is:" + row + " fs:" + firstShift, null);
332 if (firstShift == -1)
334 else if (row > firstShift) {
336 for (int i = firstShift; i < row; i++) {
337 selectedRows.add(startIndex + i);
340 GSS.get().setCurrentSelection(getSelectedFiles());
341 //contextMenu.setFiles(getSelectedFiles());
342 table.setWidget(row, 0, contextMenu);
343 } else if (row != -1 && row == firstShift) {
344 selectedRows.add(row);
345 selectedRows.add(row - 1);
347 styleRow(row - 1, true);
348 } else if (row < firstShift) {
349 GWT.log("ROW is:" + row + " fs:" + firstShift, null);
352 for (int i = firstShift; i >= row - 1; i--) {
353 selectedRows.add(startIndex + i);
356 GSS.get().setCurrentSelection(getSelectedFiles());
357 table.setWidget(row, 0, contextMenu);
358 //contextMenu.setFiles(getSelectedFiles());
362 selectRow(row - 1, toggleSelection);
367 * Initializes the table so that it contains enough rows for a full page of
370 private void initTable() {
372 nameLabel = new HTML("Name");
373 nameLabel.addClickHandler(new ClickHandler() {
375 public void onClick(ClickEvent event) {
380 versionLabel = new HTML("Version");
381 versionLabel.addClickHandler(new ClickHandler() {
383 public void onClick(ClickEvent event) {
384 sortFiles("version");
388 sizeLabel = new HTML("Size");
389 sizeLabel.addClickHandler(new ClickHandler() {
391 public void onClick(ClickEvent event) {
396 dateLabel = new HTML("Last modified");
397 dateLabel.addClickHandler(new ClickHandler() {
399 public void onClick(ClickEvent event) {
404 ownerLabel = new HTML("Owner");
405 ownerLabel.addClickHandler(new ClickHandler() {
407 public void onClick(ClickEvent event) {
413 pathLabel = new HTML("Path");
414 pathLabel.addClickHandler(new ClickHandler() {
416 public void onClick(ClickEvent event) {
421 // Create the header row.
422 table.setText(0, 0, "");
423 // table.setText(0, 1, "Name");
424 table.setWidget(0, 1, nameLabel);
425 table.setWidget(0, 2, ownerLabel);
426 // table.setText(0, 3, "Version");
427 table.setWidget(0, 3, pathLabel);
428 table.setWidget(0, 4, versionLabel);
429 // table.setText(0, 4, "Size");
430 table.setWidget(0, 5, sizeLabel);
431 // table.setText(0, 5, "Last modified");
432 table.setWidget(0, 6, dateLabel);
433 table.setWidget(0, 7, navBar);
434 table.getRowFormatter().setStyleName(0, "gss-ListHeader");
436 // Initialize the rest of the rows.
437 for (int i = 1; i < GSS.VISIBLE_FILE_COUNT + 1; ++i) {
438 table.setText(i, 0, "");
439 table.setText(i, 1, "");
440 table.setText(i, 2, "");
441 table.setText(i, 3, "");
442 table.setText(i, 4, "");
443 table.setText(i, 5, "");
444 table.setText(i, 6, "");
445 table.getCellFormatter().setWordWrap(i, 0, false);
446 table.getCellFormatter().setWordWrap(i, 1, false);
447 table.getCellFormatter().setWordWrap(i, 2, false);
448 table.getCellFormatter().setWordWrap(i, 3, false);
449 table.getCellFormatter().setWordWrap(i, 4, false);
450 table.getCellFormatter().setWordWrap(i, 5, false);
451 table.getCellFormatter().setWordWrap(i, 6, false);
452 table.getCellFormatter().setHorizontalAlignment(i, 4, HasHorizontalAlignment.ALIGN_CENTER);
454 prevButton.setVisible(false);
455 nextButton.setVisible(false);
459 * Selects the given row (relative to the current page).
461 * @param row the row to be selected
463 private void selectRow(final int row, boolean toggleSelection) {
464 if (row < folderFileCount) {
466 if (selectedRows.contains(row) && toggleSelection) {
467 int i = selectedRows.indexOf(startIndex + row);
468 selectedRows.remove(i);
469 styleRow(row, false);
471 for (int r : selectedRows) {
472 int prow = r - startIndex;
473 table.setWidget(prow + 1, 0, AbstractImagePrototype.create(images.document()).createImage());
475 selectedRows.add(startIndex + row);
480 selectedRows.add(startIndex + row);
483 if (selectedRows.size() == 1)
484 GSS.get().setCurrentSelection(files.get(selectedRows.get(0)));
486 GSS.get().setCurrentSelection(getSelectedFiles());
487 //contextMenu.setFiles(getSelectedFiles());
488 table.setWidget(row + 1, 0, contextMenu);
492 public List<FileResource> getSelectedFiles() {
493 List<FileResource> result = new ArrayList();
494 for (int i : selectedRows)
495 result.add(files.get(i));
500 * Make the specified row look like selected or not, according to the
501 * <code>selected</code> flag.
506 void styleRow(final int row, final boolean selected) {
507 if (row != -1 && row >= 0)
509 table.getRowFormatter().addStyleName(row + 1, "gss-SelectedRow");
511 table.getRowFormatter().removeStyleName(row + 1, "gss-SelectedRow");
515 * Update the display of the file list.
517 void update(boolean sort) {
518 int count = folderFileCount;
519 int max = startIndex + GSS.VISIBLE_FILE_COUNT;
524 if (sort && files != null && files.size() != 0) {
528 Collections.sort(files, new Comparator<FileResource>() {
531 public int compare(FileResource arg0, FileResource arg1) {
532 AbstractImagePrototype descPrototype = AbstractImagePrototype.create(images.desc());
533 AbstractImagePrototype ascPrototype = AbstractImagePrototype.create(images.asc());
535 if (sortingProperty.equals("version")) {
536 versionLabel.setHTML("Version " + descPrototype.getHTML());
537 return arg0.getVersion().compareTo(arg1.getVersion());
538 } else if (sortingProperty.equals("owner")) {
539 ownerLabel.setHTML("Owner " + descPrototype.getHTML());
540 return arg0.getOwner().compareTo(arg1.getOwner());
541 } else if (sortingProperty.equals("date")) {
542 dateLabel.setHTML("Last modified " + descPrototype.getHTML());
543 return arg0.getModificationDate().compareTo(arg1.getModificationDate());
544 } else if (sortingProperty.equals("size")) {
545 sizeLabel.setHTML("Size " + descPrototype.getHTML());
546 return arg0.getContentLength().compareTo(arg1.getContentLength());
547 } else if (sortingProperty.equals("name")) {
548 nameLabel.setHTML("Name " + descPrototype.getHTML());
549 return arg0.getName().compareTo(arg1.getName());
550 } else if (sortingProperty.equals("path")) {
551 pathLabel.setHTML("Path " + descPrototype.getHTML());
552 return arg0.getUri().compareTo(arg1.getUri());
554 nameLabel.setHTML("Name " + descPrototype.getHTML());
555 return arg0.getName().compareTo(arg1.getName());
557 else if (sortingProperty.equals("version")) {
558 versionLabel.setHTML("Version " + ascPrototype.getHTML());
559 return arg1.getVersion().compareTo(arg0.getVersion());
560 } else if (sortingProperty.equals("owner")) {
561 ownerLabel.setHTML("Owner " + ascPrototype.getHTML());
562 return arg1.getOwner().compareTo(arg0.getOwner());
563 } else if (sortingProperty.equals("date")) {
564 dateLabel.setHTML("Last modified " + ascPrototype.getHTML());
565 return arg1.getModificationDate().compareTo(arg0.getModificationDate());
566 } else if (sortingProperty.equals("size")) {
567 sizeLabel.setHTML("Size " + ascPrototype.getHTML());
568 return arg1.getContentLength().compareTo(arg0.getContentLength());
569 } else if (sortingProperty.equals("name")) {
570 nameLabel.setHTML("Name " + ascPrototype.getHTML());
571 return arg1.getName().compareTo(arg0.getName());
572 } else if (sortingProperty.equals("path")) {
573 pathLabel.setHTML("Path " + ascPrototype.getHTML());
574 return arg1.getUri().compareTo(arg0.getUri());
576 nameLabel.setHTML("Name " + ascPrototype.getHTML());
577 return arg1.getName().compareTo(arg0.getName());
584 // Show the selected files.
586 for (; i < GSS.VISIBLE_FILE_COUNT + 1; ++i) {
587 // Don't read past the end.
588 // if (i > folderFileCount)
590 if (startIndex + i > folderFileCount)
592 // Add a new row to the table, then set each of its columns to the
594 table.setWidget(i, 0, AbstractImagePrototype.create(images.document()).createImage());
595 FileResource fileHeader = files.get(startIndex + i - 1);
596 table.getRowFormatter().addStyleName(i, "gss-fileRow");
597 table.setHTML(i, 1, fileHeader.getName());
598 table.setText(i, 2, fileHeader.getOwner());
599 table.setText(i, 3, fileHeader.getPath());
600 table.setText(i, 4, String.valueOf(fileHeader.getVersion()));
601 table.setText(i, 5, String.valueOf(fileHeader.getFileSizeAsString()));
602 final DateTimeFormat formatter = DateTimeFormat.getFormat("d/M/yyyy h:mm a");
603 table.setText(i, 6, formatter.format(fileHeader.getModificationDate()));
604 folderTotalSize += fileHeader.getContentLength();
607 // Clear any remaining slots.
608 for (; i < GSS.VISIBLE_FILE_COUNT + 1; ++i) {
609 table.setHTML(i, 0, " ");
610 table.setHTML(i, 1, " ");
611 table.setHTML(i, 2, " ");
612 table.setHTML(i, 3, " ");
613 table.setHTML(i, 4, " ");
614 table.setHTML(i, 5, " ");
615 table.setHTML(i, 6, " ");
616 table.setHTML(i, 7, " ");
619 if (folderFileCount == 0) {
620 showingStats = "no files";
621 prevButton.setVisible(false);
622 nextButton.setVisible(false);
623 } else if (folderFileCount < GSS.VISIBLE_FILE_COUNT) {
624 if (folderFileCount == 1)
625 showingStats = "1 file";
627 showingStats = folderFileCount + " files";
628 showingStats += " (" + FileResource.getFileSizeAsString(folderTotalSize) + ")";
629 prevButton.setVisible(false);
630 nextButton.setVisible(false);
632 showingStats = "" + (startIndex + 1) + " - " + max + " of " + count + " files" + " (" + FileResource.getFileSizeAsString(folderTotalSize) + ")";
633 prevButton.setVisible(startIndex != 0);
634 nextButton.setVisible(startIndex + GSS.VISIBLE_FILE_COUNT < count);
636 updateCurrentlyShowingStats();
640 * update status panel with currently showing file stats
642 public void updateCurrentlyShowingStats() {
643 GSS.get().getStatusPanel().updateCurrentlyShowing(showingStats);
647 * Adjust the height of the table by adding and removing rows as necessary.
649 * @param newHeight the new height to reach
651 void resizeTableHeight(final int newHeight) {
652 GWT.log("Panel: " + newHeight + ", parent: " + table.getParent().getOffsetHeight(), null);
653 // Fill the rest with empty slots.
654 if (newHeight > table.getOffsetHeight())
655 while (newHeight > table.getOffsetHeight()) {
656 // table.setHTML(table.getRowCount(), 5, " ");
657 table.resizeRows(table.getRowCount() + 1);
658 GWT.log("Table: " + table.getOffsetHeight() + ", rows: " + table.getRowCount(), null);
661 while (newHeight < table.getOffsetHeight()) {
662 // table.setHTML(table.getRowCount(), 5, " ");
663 table.resizeRows(table.getRowCount() - 1);
664 GWT.log("Table: " + table.getOffsetHeight() + ", rows: " + table.getRowCount(), null);
669 * Update the file cache with data from the server.
671 public void updateFileCache(String query) {
672 final GSS app = GSS.get();
676 app.showLoadingIndicator();
677 if (query == null || query.trim().equals("")) {
678 searchResults.setHTML("You must specify a query");
679 setFiles(new ArrayList());
681 app.hideLoadingIndicator();
683 searchResults.setHTML("Search results for " + query);
685 GetCommand<SearchResource> eg = new GetCommand<SearchResource>(SearchResource.class,
686 app.getApiPath() + "search/" + URL.encodeComponent(query), null) {
689 public void onComplete() {
690 SearchResource s = getResult();
691 setFiles(s.getFiles());
696 public void onError(Throwable t) {
697 if(t instanceof RestException)
698 app.displayError("Unable to perform search:"+((RestException)t).getHttpStatusText());
700 app.displayError("System error performing search:"+t.getMessage());
705 DeferredCommand.addCommand(eg);
710 * Fill the file cache with data.
713 * @param filePaths the files to set
715 private void setFiles(List<FileResource> _files) {
717 Collections.sort(files, new Comparator<FileResource>() {
720 public int compare(FileResource arg0, FileResource arg1) {
721 return arg0.getName().compareTo(arg1.getName());
725 folderFileCount = files.size();
726 GWT.log("File count:" + folderFileCount, null);
729 private void sortFiles(final String sortProperty) {
730 if (sortProperty.equals(sortingProperty))
731 sortingType = !sortingType;
733 sortingProperty = sortProperty;
739 private void clearLabels() {
740 nameLabel.setText("Name");
741 versionLabel.setText("Version");
742 sizeLabel.setText("Size");
743 dateLabel.setText("Last modified");
744 ownerLabel.setText("Owner");
745 pathLabel.setText("Path");
749 * Retrieve the table.
758 * Does the list contains the requested filename
763 public boolean contains(String fileName) {
764 for (int i = 0; i < files.size(); i++)
765 if (files.get(i).getName().equals(fileName))
770 public void clearSelectedRows() {
771 for (int r : selectedRows) {
772 int row = r - startIndex;
773 styleRow(row, false);
774 table.setWidget(row + 1, 0, AbstractImagePrototype.create(images.document()).createImage());
776 selectedRows.clear();
777 Object sel = GSS.get().getCurrentSelection();
778 if (sel instanceof FileResource || sel instanceof List)
779 GSS.get().setCurrentSelection(null);
782 public static native void preventIESelection() /*-{
783 $doc.body.onselectstart = function () { return false; };
786 public static native void enableIESelection() /*-{
787 if ($doc.body.onselectstart != null)
788 $doc.body.onselectstart = null;
791 public void selectAllRows() {
793 int count = folderFileCount;
796 int max = startIndex + GSS.VISIBLE_FILE_COUNT;
800 for (; i < GSS.VISIBLE_FILE_COUNT + 1; ++i) {
801 // Don't read past the end.
802 // if (i > folderFileCount)
804 if (startIndex + i > folderFileCount)
806 selectedRows.add(startIndex + i - 1);
807 styleRow(i - 1, true);
809 GSS.get().setCurrentSelection(getSelectedFiles());
810 //contextMenu.setFiles(getSelectedFiles());
811 table.setWidget(i - 1, 0, contextMenu);