Spec-Zone .ru
спецификации, руководства, описания, API
001/*
002 * Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved.
003 */
004package javafx.scene.web;
005
006import com.sun.webkit.BackForwardList;
007import com.sun.webkit.WebPage;
008import com.sun.webkit.event.WCChangeEvent;
009import com.sun.webkit.event.WCChangeListener;
010import java.net.URL;
011import java.util.Date;
012import javafx.beans.property.IntegerProperty;
013import javafx.beans.property.ReadOnlyIntegerProperty;
014import javafx.beans.property.ReadOnlyIntegerWrapper;
015import javafx.beans.property.ReadOnlyObjectProperty;
016import javafx.beans.property.ReadOnlyObjectWrapper;
017import javafx.beans.property.SimpleIntegerProperty;
018import javafx.collections.FXCollections;
019import javafx.collections.ObservableList;
020
021/**
022 * The {@code WebHistory} class represents a session history associated with
023 * a {@link WebEngine} instance.
024 * 
025 * A single instance of {@code WebHistory} for a particular web engine can be
026 * obtained through the {@link WebEngine#getHistory()} method.
027 * 
028 * The history is basically a list of entries. Each entry represents a visited page
029 * and it provides access to relevant page info, such as URL, title, and the date
030 * the page was last visited. Entries in the list are arranged in the order
031 * in which the corresponding pages were visited from oldest to newest. The list can
032 * be obtained by using the {@link #getEntries()} method.
033 * 
034 * The history and the corresponding list of entries change as {@code WebEngine} navigates
035 * across the web. The list may expand or shrink depending on browser actions. These
036 * changes can be listened to by the {@link javafx.collections.ObservableList}
037 * API that the list exposes.
038 * 
039 * The index of the history entry associated with the currently visited page 
040 * is represented by the {@link #currentIndexProperty}. The current index can be
041 * used to navigate to any entry in the history by using the {@link #go(int)} method. 
042 * 
043 * The {@link #maxSizeProperty()} sets the maximum history size, which is the size of the
044 * history list.
045 *
046 * @since 2.2
047 */
048public final class WebHistory {
049    /**
050     * The {@code Entry} class represents a single entry in the session history.
051     * An entry instance is associated with the visited page.
052     *
053     * @since 2.2
054     */
055    public final class Entry {
056        private final URL url;
057        private final ReadOnlyObjectWrapper<String> title = new ReadOnlyObjectWrapper(this, "title");
058        private final ReadOnlyObjectWrapper<Date> lastVisitedDate = new ReadOnlyObjectWrapper(this, "lastVisitedDate");
059        private final BackForwardList.Entry peer;
060        
061        private Entry(final BackForwardList.Entry entry) {
062            this.url = entry.getURL();
063            this.title.set(entry.getTitle());
064            this.lastVisitedDate.set(entry.getLastVisitedDate());
065            this.peer = entry;
066            
067            entry.addChangeListener(new WCChangeListener() {
068                @Override
069                public void stateChanged(WCChangeEvent e) {
070                    String _title = entry.getTitle();
071                    // null title is acceptable
072                    if (_title == null || !_title.equals(getTitle())) {
073                        title.set(_title);
074                    }
075                    
076                    Date _date = entry.getLastVisitedDate();
077                    // null date is not acceptable
078                    if (_date != null && !_date.equals(getLastVisitedDate())) {
079                        lastVisitedDate.set(_date);
080                    }
081                }
082            });
083        }
084         
085        /** 
086         * Returns the URL of the page. 
087         * 
088         * @return the url of the page 
089         */ 
090        public String getUrl() {
091            assert url != null;
092            return url.toString();
093        }
094         
095        /** 
096         * Defines the title of the page. 
097         */ 
098        public ReadOnlyObjectProperty<String> titleProperty() {
099            return title.getReadOnlyProperty();
100        }
101         
102        public String getTitle() {
103            return title.get();
104        }
105         
106        /** 
107         * Defines the {@link java.util.Date} the page was last visited. 
108         */ 
109        public ReadOnlyObjectProperty<Date> lastVisitedDateProperty() {
110            return lastVisitedDate.getReadOnlyProperty();
111        }
112         
113        public Date getLastVisitedDate() {
114            return lastVisitedDate.get();
115        }
116        
117        boolean isPeer(BackForwardList.Entry entry) {
118            return peer == entry;
119        }
120        
121        @Override
122        public String toString() {
123            return "[url: " + getUrl()
124                 + ", title: " + getTitle()
125                 + ", date: " + getLastVisitedDate()
126                 + "]";
127        }
128    }
129    
130    private final BackForwardList bfl; // backend history impl
131    
132    private final ObservableList<Entry> list;
133    private final ObservableList<Entry> ulist; // unmodifiable wrapper
134    
135    WebHistory(WebPage page) {
136        this.list = FXCollections.<Entry>observableArrayList();
137        this.ulist = FXCollections.unmodifiableObservableList(list);
138        this.bfl = page.createBackForwardList();
139        
140        setMaxSize(getMaxSize()); // init default
141        
142        this.bfl.addChangeListener(new WCChangeListener() {
143            @Override
144            public void stateChanged(WCChangeEvent e) {
145                // 1. Size has increased
146                //    - one new entry is appended.
147                //    - currentIndex is set to the new entry.
148                if (bfl.size() > list.size()) {
149                    assert (bfl.size() == list.size() + 1);
150                    list.add(new Entry(bfl.getCurrentEntry()));
151                    
152                    WebHistory.this.setCurrentIndex(list.size() - 1);
153                    return;
154                }
155                
156                // 2. Size hasn't changed
157                if (bfl.size() == list.size()) {
158                    if (list.size() == 0) {
159                        return; // no changes
160                    }                    
161                    assert (list.size() > 0);
162                    BackForwardList.Entry last = bfl.get(list.size() - 1);
163                    BackForwardList.Entry first = bfl.get(0);
164                    
165                    // - currentIndex may change
166                    if (list.get(list.size() - 1).isPeer(last)) {
167                        WebHistory.this.setCurrentIndex(bfl.getCurrentIndex());
168                        return;
169                    
170                    // - first entry is removed.
171                    // - one new entry is appended.
172                    // - currentIndex is set to the new entry.
173                    } else if (!list.get(0).isPeer(first)) {
174                        list.remove(0);
175                        list.add(new Entry(last));
176                        WebHistory.this.setCurrentIndex(bfl.getCurrentIndex());                        
177                        return;
178                    }
179                }
180                                
181                // 3. Size has decreased or hasn't changed (due to maxSize or navigation)
182                //    - one or more entries are popped.
183                //    - one new entry may be appended.
184                //    - currentIndex may be set to the new entry.
185                assert (bfl.size() <= list.size());                
186                list.remove(bfl.size(), list.size()); // no-op if equals
187                int lastIndex = list.size() - 1;
188                if (lastIndex >= 0 && !list.get(lastIndex).isPeer(bfl.get(lastIndex))) {
189                    list.remove(lastIndex);
190                    list.add(new Entry(bfl.get(lastIndex)));
191                }
192                WebHistory.this.setCurrentIndex(bfl.getCurrentIndex());
193            }
194        });
195    }
196    
197    private final ReadOnlyIntegerWrapper currentIndex =
198            new ReadOnlyIntegerWrapper(this, "currentIndex");
199
200    /**
201     * Defines the index of the current {@code Entry} in the history.
202     * The current entry is the entry associated with the currently loaded page.
203     * The index belongs to the range of (<tt>index &gt;= 0 && index &lt; getEntries().size()</tt>)
204     */
205    public ReadOnlyIntegerProperty currentIndexProperty() {
206        return currentIndex.getReadOnlyProperty();
207    }
208     
209    public int getCurrentIndex() {
210        return currentIndexProperty().get();
211    }
212    
213    private void setCurrentIndex(int value) {
214        currentIndex.set(value);
215    }
216    
217    private IntegerProperty maxSize;
218     
219    /**
220     * Defines the maximum size of the history list.
221     * If the list reaches its maximum and a new entry is added,
222     * the first entry is removed from the history.
223     * <p>
224     * The value specified for this property can not be negative, otherwise
225     * {@code IllegalArgumentException} is thrown.
226     * 
227     * @defaultValue 100
228     */
229    public IntegerProperty maxSizeProperty()  {
230        if (maxSize == null) {
231            maxSize = new SimpleIntegerProperty(this, "maxSize", 100) {
232                @Override
233                public void set(int value) {
234                    if (value < 0) {
235                        throw new IllegalArgumentException("value cannot be negative.");
236                    }
237                    super.set(value);
238                }
239            };
240        }
241        return maxSize;
242    }
243     
244    public void setMaxSize(int value) {
245        maxSizeProperty().set(value);
246        bfl.setMaximumSize(value);
247    }
248     
249    public int getMaxSize() {
250        return maxSizeProperty().get();
251    }
252
253    /**
254     * Returns an unmodifiable observable list of all entries in the history.
255     * 
256     * @return list of all history entries
257     */
258    public ObservableList<Entry> getEntries() {
259        return ulist;
260    }
261
262    /**
263     * Navigates the web engine to the URL defined by the {@code Entry} object
264     * within the specified position relative to the current entry. A negative
265     * {@code offset} value specifies the position preceding to the current entry,
266     * and a positive {@code offset} value specifies the position following the
267     * current entry. For example, -1 points to the previous entry, and 1 points
268     * to the next entry, corresponding to pressing a web browser's 'back'
269     * and 'forward' buttons, respectively.
270     *
271     * The zero {@code offset} value is silently ignored (no-op).
272     *
273     * The effective entry position should belong to the rage of [0..size-1].
274     * Otherwise, {@code IndexOutOfBoundsException} is thrown.
275     *
276     * @param offset a negative value specifies a position preceding the
277     *        current entry, a positive value specifies a position following
278     *        the current entry, zero value causes no effect
279     * @throws IndexOutOfBoundsException if the effective entry position is out
280     *           of range
281     */
282    public void go(int offset) throws IndexOutOfBoundsException {
283        if (offset == 0)
284            return;
285        
286        int index = getCurrentIndex() + offset;
287        if (index < 0 || index >= list.size()) {
288            throw new IndexOutOfBoundsException("the effective index " + index
289                                                + " is out of the range [0.."
290                                                + (list.size() - 1) + "]");
291        }
292        bfl.setCurrentIndex(index);
293    }
294}