001    /*
002      Copyright (C) 2001-2003 Laurent Martelli <laurent@aopsys.com>
003      
004      This program is free software; you can redistribute it and/or modify
005      it under the terms of the GNU Lesser General Public License as
006      published by the Free Software Foundation; either version 2 of the
007      License, or (at your option) any later version.
008    
009      This program is distributed in the hope that it will be useful,
010      but WITHOUT ANY WARRANTY; without even the implied warranty of
011      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
012      GNU Lesser General Public License for more details.
013    
014      You should have received a copy of the GNU Lesser General Public License
015      along with this program; if not, write to the Free Software
016      Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA */
017    
018    package org.objectweb.jac.aspects.gui.web;
019    
020    import java.io.PrintWriter;
021    import java.net.URLEncoder;
022    import org.apache.log4j.Logger;
023    import org.objectweb.jac.aspects.gui.*;
024    import org.objectweb.jac.core.NameRepository;
025    import org.objectweb.jac.core.rtti.ClassRepository;
026    import org.objectweb.jac.core.rtti.CollectionItem;
027    import org.objectweb.jac.core.rtti.FieldItem;
028    
029    /**
030     * Base class for collection views (list, table, etc)
031     */
032    public abstract class AbstractCollection extends AbstractView
033        implements CollectionListener, CollectionView, HTMLViewer
034    {
035        static Logger loggerEvents = Logger.getLogger("gui.events");
036        static Logger loggerEditor = Logger.getLogger("gui.editor");
037    
038        protected CollectionItem collection;
039        protected Object substance; 
040        protected CollectionModel model;
041        protected TableSorter sorter;
042        protected TableFilter filter;
043    
044        protected boolean autoUpdate = true;
045        protected boolean isEditor = true;
046    
047        /* index of selected row (-1 if no row if selection is empty) */
048        int selected = -1;
049    
050        /* number of rows to display on each page */
051        int rowsPerPage = 10;
052        protected void setRowsPerPage(int rowsPerPage) {
053            this.rowsPerPage = rowsPerPage;
054            split = rowsPerPage>0;
055        }
056    
057        /* do we split the collection in multiple pages */
058        boolean split;
059    
060        /* do we show a column with row numbers */
061        boolean showRowNumbers;
062       
063        /* current offset */
064        int startIndex = 0;
065    
066        protected org.objectweb.jac.aspects.gui.CollectionItemView itemView;
067    
068        protected boolean viewableItems; // shortcut for itemView.isViewableItems()
069    
070        protected ObjectChooser rowsPerPageChooser;
071    
072        public AbstractCollection(ViewFactory factory, DisplayContext context,
073                                  CollectionItem collection, Object substance,
074                                  CollectionModel model, 
075                                  org.objectweb.jac.aspects.gui.CollectionItemView itemView) 
076        {
077            super(factory,context);
078    
079            this.collection = collection;
080            this.substance = substance;
081            this.model = model;
082            setRowsPerPage(GuiAC.getNumRowsPerPage(collection));
083            this.startIndex = GuiAC.getStartIndex(collection);
084            this.showRowNumbers = GuiAC.isShowRowNumbers(collection);
085            this.itemView = itemView;
086            if (itemView!=null)
087                this.viewableItems = itemView.isViewableItems();
088            init();
089            sort();
090    
091            int[] availableNumRowsPerPage = GuiAC.getAvailableNumRowsPerPage(collection);
092            if (availableNumRowsPerPage!=null) {
093                ComboBoxModel comboModel = new ComboBoxModel();
094                for (int i=0; i<availableNumRowsPerPage.length; i++) {
095                    int n = availableNumRowsPerPage[i];
096                    comboModel.addObject(new Integer(n), n!=0?(""+n):GuiAC.getLabelAll());
097                }
098                rowsPerPageChooser = new ObjectChooser(null,null,comboModel,false);
099                rowsPerPageChooser.setLabel(label+"_rowsPerPageChooser");
100                comboModel.setSelectedItem(""+availableNumRowsPerPage[0]);
101                setRowsPerPage(availableNumRowsPerPage[0]);
102            }
103        }
104    
105        public AbstractCollection() {
106        }
107    
108        public void setSubstance(Object substance) {
109            this.substance = substance;
110        }
111    
112        public Object getSubstance() {
113            return substance;
114        }
115    
116        public CollectionModel getCollectionModel() {
117            return model;
118        }
119    
120        public boolean isEditor() {
121            return isEditor;
122        }
123    
124        public void setEditor(boolean isEditor) {
125            this.isEditor = isEditor;
126        }
127    
128        public void setAutoUpdate(boolean autoUpdate) {
129            this.autoUpdate = autoUpdate;
130        }
131    
132        /**
133         * Generate HTML code for a collection's event (add, remove, ...)
134         * @param out write HTML to this writer
135         * @param event name of the associated event
136         * @param icon name of icon resource to display
137         * @param label text to display
138         */
139        void genCollectionEvent(PrintWriter out,
140                                String event,
141                                String icon,
142                                String label) 
143        {
144            JacRequest request = WebDisplay.getRequest();
145            if (request.isIEUserAgent()) {
146                out.println(
147                    "<table class=\"method\"><td>"+
148                    iconElement(ResourceManager.getResource(icon),"")+
149                    eventURL(label,event,"").toString()+
150                    "</td></table>");
151            } else {
152                out.println(
153                    eventURL(" "+label,event,"")
154                    .add(0,iconElement(ResourceManager.getResource(icon),"").addCssClass("first"))
155                    .cssClass("method").toString());
156            }
157        }
158    
159        /**
160         * Sorts the collection with the column index stored in the context
161         * if any.
162         */
163        public abstract void sort();
164    
165        /**
166         * Initialization to be performed before sort()
167         */
168        protected void init() {}
169    
170        public void onView(Object object) {
171            loggerEvents.debug(toString()+".onView("+object+")");
172            selected = model.indexOf(object);
173    
174            // used for CollectionItemView
175            CollectionPosition extra = new CollectionPosition(
176                this,
177                collection,selected,substance);
178    
179            EventHandler.get().onSelection(context,collection,object,
180                                           null,extra,true);
181        }
182    
183        // HTMLViewer interface
184    
185            protected void genHeader(PrintWriter out) {
186                    genHeader(out, true);
187            }
188            
189        /**
190         * Generate HTML for adder, remover, prev/next buttons, ...
191         * @param out print HTML code to this writer 
192         */
193        protected void genHeader(PrintWriter out, boolean div) {
194            if (collection.getSubstance(substance)==null)
195                return;
196    
197            boolean addable = GuiAC.isAddable(substance,collection);
198            boolean removable = GuiAC.isRemovable(substance,collection);
199            boolean refreshable = showRefreshButton();
200            if (!(addable || refreshable ||
201                  (model.getRowCount()>0 && removable) ||
202                  (startIndex>0) ||
203                  (split && startIndex+rowsPerPage<model.getRowCount()))) {
204                return;
205            }
206            if (div) {
207                out.println("<div class=\"methods\">");
208            }
209            if (addable && isEditor) {
210                if (GuiAC.isAutoCreate(collection)) {
211                    genCollectionEvent(out,"onAddToCollection","new_icon",GuiAC.getLabelAdd());
212                    /*
213                      genCollectionEvent(out,"onAddToCollection","new_icon","add new");
214                      genCollectionEvent(out,"onAddExistingToCollection","new_icon","add existing");
215                    */
216                } else {
217                    genCollectionEvent(out,"onAddToCollection","new_icon",GuiAC.getLabelAdd());
218                }
219            }
220            if (startIndex>0) {
221                out.println("<a href=\""+eventURL("onFirst")+"\">"+
222                            iconElement(ResourceManager.getResource("first_icon"),"first")+
223                            "</a>");
224                out.println("<a href=\""+eventURL("onPrevious")+"\">"+
225                            iconElement(ResourceManager.getResource("previous_icon"),"previous")+
226                            "</a>");
227            }
228            if (rowsPerPage>0 && model.getRowCount()>0 && 
229                (startIndex>0 || startIndex+rowsPerPage<model.getRowCount())) {
230                out.println("["+(startIndex+1)+"-"+
231                            Math.min(startIndex+rowsPerPage,model.getRowCount())+"/"+
232                            model.getRowCount()+"]");
233            }
234            if (split && startIndex+rowsPerPage<model.getRowCount()) {
235                out.println("<a href=\""+eventURL("onNext")+"\">"+
236                            iconElement(ResourceManager.getResource("next_icon"),"next")+
237                            "</a>");
238                out.println("<a href=\""+eventURL("onLast")+"\">"+
239                            iconElement(ResourceManager.getResource("last_icon"),"last")+
240                            "</a>");
241            }
242            if (split && model.getRowCount()>rowsPerPage) {         
243                out.println("["+(startIndex/rowsPerPage+1)+"/"+
244                            (int)Math.ceil((float)model.getRowCount()/(float)rowsPerPage)+"]");
245            }
246    
247            if (rowsPerPageChooser!=null) {
248                out.print("Display ");
249                rowsPerPageChooser.genHTML(out);
250                out.print(" rows");
251            }
252            if (showRefreshButton())
253                genEventAndActionButton(out,"onRefreshCollection");
254    
255                    if (div) {
256                out.println("</div>");
257                    }
258        }
259    
260        /**
261         * Tells whether a refesh button must be shown
262         */
263        protected boolean showRefreshButton() {
264            return rowsPerPageChooser!=null;
265        }
266    
267        /**
268         * Gets object at a given index
269         */
270        protected Object getObject(int index) {
271            return model.getObject(index);
272        }
273    
274        // CollectionView interface
275    
276        public void setSelected(int index) {
277            selected = index;      
278        }
279    
280        public void setField(FieldItem field) {
281            collection = (CollectionItem)field;
282        }
283    
284        public FieldItem getField() {
285            return collection;
286        }
287    
288        public void setValue(Object value) {
289        }
290    
291        public void updateModel(Object substance) {
292        }
293    
294        // CollectionListener interface
295    
296        public void onView(int index) {
297            loggerEvents.debug(toString()+".onView("+index+")");
298            selected = index;
299    
300            // used for CollectionItemView
301            CollectionPosition extra = new CollectionPosition(
302                this,
303                collection,index,substance);
304    
305            EventHandler.get().onSelection(context,collection,getObject(index),
306                                           null,extra,true);
307            loggerEvents.debug("  ending "+toString()+".onView("+index+"), selected="+selected);
308        }
309    
310        public void onViewObject(String name) {
311            Object object = NameRepository.get().getObject(name);
312            EventHandler.get().onSelection(context,collection,object,
313                                           null,null,true);
314        }
315    
316        public void onRemove(int index) {
317            loggerEvents.debug(toString()+".onRemove("+index+")");
318            Object toRemove = getObject(index);
319          
320            EventHandler.get()
321                .onRemoveFromCollection(
322                    context, 
323                    new RemoveEvent(
324                        this,
325                        collection.getSubstance(substance), 
326                        collection, 
327                        toRemove), 
328                    false);
329        }
330    
331        public void onTableInvoke(int index,String methodName) {
332            loggerEvents.debug(toString()+".onTableInvoke("+index+")");
333            selected = index;
334            EventHandler.get().onInvoke(
335                context,
336                new InvokeEvent(
337                    this,
338                    getObject(index),
339                    ClassRepository.get().getClass(getObject(index)).getMethod(methodName)));
340        }
341       
342        public void onAddToCollection() {
343            EventHandler.get().onAddToCollection(
344                context,
345                new AddEvent(
346                    this,
347                    collection.getSubstance(substance),
348                    collection,
349                    null),
350                false);
351            sort();
352        }
353    
354        public void onAddExistingToCollection() {
355            EventHandler.get().onAddToCollection(
356                context,
357                new AddEvent(this,collection.getSubstance(substance),collection,null),
358                true);
359            sort();
360        }
361    
362        public void onRemoveFromCollection() {
363            EventHandler.get().onRemoveFromCollection(
364                context,
365                new RemoveEvent(this,collection.getSubstance(substance),collection,null),
366                true);
367            sort();
368        }
369    
370        public void onNext() {
371            if (startIndex+rowsPerPage<model.getRowCount())
372                startIndex += rowsPerPage;
373            context.getDisplay().refresh();
374        }
375    
376        public void onLast() {
377            startIndex = model.getRowCount() - model.getRowCount()%rowsPerPage;
378            if (startIndex==model.getRowCount())
379                startIndex -= rowsPerPage;
380            context.getDisplay().refresh();
381        }
382    
383        public void onPrevious() {
384            startIndex -= rowsPerPage;
385            if (startIndex<0)
386                startIndex = 0;
387            context.getDisplay().refresh();
388        }
389    
390        public void onFirst() {
391            startIndex = 0;
392            context.getDisplay().refresh();
393        }
394    
395        public void onRefreshCollection() {
396            if (rowsPerPageChooser!=null) {
397                rowsPerPageChooser.readValue(
398                    ((WebDisplay)context.getDisplay())
399                            .getRequest().getParameter(rowsPerPageChooser.getLabel()));
400                setRowsPerPage(((Number)rowsPerPageChooser.getValue()).intValue());
401            }
402            context.getDisplay().refresh();
403        }
404        
405        /**
406         * Ensures that startIndex is not ouf of range
407         */
408        protected void checkRange() {
409            if (startIndex>=model.getRowCount()) {
410                if (split) {
411                    startIndex = model.getRowCount() - model.getRowCount()%rowsPerPage;
412                    if (startIndex==model.getRowCount())
413                        startIndex = 0;
414                } else
415                    startIndex = 0;
416            }
417        }
418    
419        /**
420         * Returns an HTML link to remove the element at a given position.
421         */
422        protected String removeLink(int position) {
423            // We should use eventURL
424            return "<a href=\""+eventURL("onRemove")+
425                "&index="+position+"\">"+
426                iconElement(ResourceManager.getResource("remove_icon"),"remove")+"</a>";
427        }
428    
429        protected String viewLink(int position) {
430            // We should use eventURL
431            return "<a href=\""+eventURL("onView")+
432                "&index="+position+"\">"+
433                iconElement(ResourceManager.getResource("view_icon"),"view")+"</a>";
434        }
435    
436        /**
437         * Build an HTML link with an image showing if a column is used to
438         * sort the collection
439         * @param column the index of the column 
440         * @param text additional text to put in the link
441         * @return the HTML code of the link
442         */
443        protected String sortLink(int column,String text) {
444            SortCriteria criteria = sorter.getSortCriteria(column);
445            String iconResource;
446            if (criteria==null) {
447                iconResource = "small_down_icon";
448            } else {
449                if (criteria.isAscending()) 
450                    iconResource = "small_down_icon_selected";
451                else 
452                    iconResource = "small_up_icon_selected";
453            }
454            return 
455                "<a href=\""+eventURL("onHeaderClick")+"&col="+column+"\""
456                + " title=\"sort\">"
457                + iconElement(ResourceManager.getResource(iconResource),"sort")
458                + " " + text
459                + "</a>";
460        }
461    
462        // When disabled == true, readValue() does nothing
463        boolean disabled = false;
464        public boolean isEnabled() {
465            return !disabled;
466        }
467        public void setEnabled(boolean enabled) {
468            loggerEditor.debug((enabled?"enable ":"disable ")+this);
469            this.disabled = !enabled;
470        }
471    }