diff --git a/assets/resources/application/de.xml b/assets/resources/application/de.xml
index d27d3e1f289..e3f3335c1a4 100644
--- a/assets/resources/application/de.xml
+++ b/assets/resources/application/de.xml
@@ -397,6 +397,7 @@
+
diff --git a/assets/resources/application/en.xml b/assets/resources/application/en.xml
index 98c6f211f6e..4e410aa2094 100644
--- a/assets/resources/application/en.xml
+++ b/assets/resources/application/en.xml
@@ -395,6 +395,7 @@
+
diff --git a/src/org/geometerplus/android/fbreader/ProcessHyperlinkAction.java b/src/org/geometerplus/android/fbreader/ProcessHyperlinkAction.java
index f2c1a378eee..aba80eedc4e 100644
--- a/src/org/geometerplus/android/fbreader/ProcessHyperlinkAction.java
+++ b/src/org/geometerplus/android/fbreader/ProcessHyperlinkAction.java
@@ -24,6 +24,8 @@
import android.net.Uri;
import org.geometerplus.zlibrary.core.resources.ZLResource;
+import org.geometerplus.zlibrary.core.util.ZLHyperlinkStackManager;
+import org.geometerplus.zlibrary.core.util.ZLVisitedLinkManager;
import org.geometerplus.zlibrary.core.network.ZLNetworkException;
import org.geometerplus.zlibrary.text.view.ZLTextView;
@@ -53,14 +55,18 @@ public boolean isEnabled() {
}
public void run() {
- final ZLTextHyperlink hyperlink = Reader.getTextView().getCurrentHyperlink();
+ ZLTextHyperlink hyperlink = Reader.getTextView().getCurrentHyperlink();
if (hyperlink != null) {
switch (hyperlink.Type) {
case FBHyperlinkType.EXTERNAL:
openInBrowser(hyperlink.Id);
break;
case FBHyperlinkType.INTERNAL:
+ case FBHyperlinkType.INTERNAL_VISITED:
+ ZLHyperlinkStackManager.Instance().pushPosition(Reader.getPosition());
+ ZLVisitedLinkManager.Instance().markLinkVisited(hyperlink.Id);
Reader.tryOpenFootnote(hyperlink.Id);
+ hyperlink.Type = FBHyperlinkType.INTERNAL_VISITED;
break;
}
return;
diff --git a/src/org/geometerplus/android/fbreader/SQLiteBooksDatabase.java b/src/org/geometerplus/android/fbreader/SQLiteBooksDatabase.java
index 5dfffe0443e..e83151eeee1 100644
--- a/src/org/geometerplus/android/fbreader/SQLiteBooksDatabase.java
+++ b/src/org/geometerplus/android/fbreader/SQLiteBooksDatabase.java
@@ -30,6 +30,8 @@
import org.geometerplus.zlibrary.core.filesystem.ZLFile;
import org.geometerplus.zlibrary.core.options.ZLStringOption;
import org.geometerplus.zlibrary.core.options.ZLIntegerOption;
+import org.geometerplus.zlibrary.core.util.ZLHyperlinkStackManager;
+import org.geometerplus.zlibrary.core.util.ZLVisitedLinkManager;
import org.geometerplus.zlibrary.core.config.ZLConfig;
import org.geometerplus.zlibrary.text.view.ZLTextPosition;
import org.geometerplus.zlibrary.text.view.ZLTextFixedPosition;
@@ -60,7 +62,7 @@ protected void executeAsATransaction(Runnable actions) {
private void migrate(Context context) {
final int version = myDatabase.getVersion();
- final int currentVersion = 13;
+ final int currentVersion = 14;
if (version >= currentVersion) {
return;
}
@@ -95,6 +97,8 @@ public void run() {
updateTables11();
case 12:
updateTables12();
+ case 13:
+ updateTables13();
}
myDatabase.setTransactionSuccessful();
myDatabase.endTransaction();
@@ -849,6 +853,8 @@ protected boolean deleteFromBookList(long bookId) {
}
myDeleteFromBookListStatement.bindLong(1, bookId);
myDeleteFromBookListStatement.execute();
+ deleteVisitedLinks(bookId);
+ deleteLinkHistory(bookId);
return true;
}
@@ -864,6 +870,108 @@ protected boolean checkBookList(long bookId) {
}
+ private SQLiteStatement myDeleteVisitedLinksStatement;
+ private void deleteVisitedLinks(long bookId) {
+ if (myDeleteVisitedLinksStatement == null) {
+ myDeleteVisitedLinksStatement = myDatabase.compileStatement(
+ "DELETE FROM VisitedLinks WHERE book_id = ?"
+ );
+ }
+
+ myDeleteVisitedLinksStatement.bindLong(1, bookId);
+ myDeleteVisitedLinksStatement.execute();
+ }
+
+ private SQLiteStatement myStoreVisitedLinksStatement;
+ protected void storeVisitedLinks(long bookId) {
+ if (myStoreVisitedLinksStatement == null) {
+ myStoreVisitedLinksStatement = myDatabase.compileStatement(
+ "INSERT OR REPLACE INTO VisitedLinks(book_id, link_id, link) VALUES (?,?,?)"
+ );
+ }
+
+ deleteVisitedLinks(bookId);
+ int linkId = 0;
+ Iterator it = ZLVisitedLinkManager.Instance().getVisitedLinks().iterator();
+ while( it.hasNext()) {
+ String link = it.next();
+ myStoreVisitedLinksStatement.bindLong(1, bookId);
+ myStoreVisitedLinksStatement.bindLong(2, linkId);
+ myStoreVisitedLinksStatement.bindString(3, link);
+ myStoreVisitedLinksStatement.execute();
+ linkId = linkId + 1;
+ }
+ }
+
+ protected void loadVisitedLinks(long bookId) {
+ ZLVisitedLinkManager linkManager = ZLVisitedLinkManager.Instance();
+ linkManager.reset();
+ final Cursor cursor = myDatabase.rawQuery("SELECT link FROM VisitedLinks WHERE book_id = ?", new String[] { "" + bookId });
+ if (!cursor.moveToNext()) {
+ cursor.close();
+ return;
+ }
+ do {
+ String link = cursor.getString(0);
+ linkManager.markLinkVisited(link);
+ } while (cursor.moveToNext());
+ cursor.close();
+ }
+
+ private SQLiteStatement myDeleteLinkHistoryStatement;
+ private void deleteLinkHistory(long bookId) {
+ if (myDeleteLinkHistoryStatement == null) {
+ myDeleteLinkHistoryStatement = myDatabase.compileStatement(
+ "DELETE FROM LinkHistory WHERE book_id = ?"
+ );
+ }
+
+ myDeleteLinkHistoryStatement.bindLong(1, bookId);
+ myDeleteLinkHistoryStatement.execute();
+ }
+
+ private SQLiteStatement myStoreLinkHistoryStatement;
+ protected void storeLinkHistory(long bookId) {
+ if (myStoreLinkHistoryStatement == null) {
+ myStoreLinkHistoryStatement = myDatabase.compileStatement(
+ "INSERT OR REPLACE INTO LinkHistory (book_id, history_id, paragraph, word, char) VALUES (?,?,?,?,?)"
+ );
+ }
+
+ deleteLinkHistory(bookId);
+ int historyId = 0;
+ Iterator iterator = ZLHyperlinkStackManager.Instance().getIterator();
+ while (iterator.hasNext()) {
+ ZLTextPosition position = iterator.next();
+ myStoreLinkHistoryStatement.bindLong(1, bookId);
+ myStoreLinkHistoryStatement.bindLong(2, historyId);
+ myStoreLinkHistoryStatement.bindLong(3, position.getParagraphIndex());
+ myStoreLinkHistoryStatement.bindLong(4, position.getElementIndex());
+ myStoreLinkHistoryStatement.bindLong(5, position.getCharIndex());
+ myStoreLinkHistoryStatement.execute();
+ historyId = historyId + 1;
+ }
+ }
+
+ protected void loadLinkHistory(long bookId) {
+ ZLHyperlinkStackManager stackManager = ZLHyperlinkStackManager.Instance();
+ stackManager.reset();
+ final Cursor cursor = myDatabase.rawQuery("SELECT paragraph, word, char FROM LinkHistory WHERE book_id = ? ORDER BY history_id", new String[] { "" + bookId });
+ if (!cursor.moveToNext()) {
+ cursor.close();
+ return;
+ }
+ do {
+ ZLTextPosition position = new ZLTextFixedPosition(
+ (int)cursor.getLong(0),
+ (int)cursor.getLong(1),
+ (int)cursor.getLong(2)
+ );
+ stackManager.pushPosition(position);
+ } while (cursor.moveToNext());
+ cursor.close();
+ }
+
private void createTables() {
myDatabase.execSQL(
"CREATE TABLE Books(" +
@@ -1134,4 +1242,21 @@ private void updateTables11() {
private void updateTables12() {
myDatabase.execSQL("DELETE FROM Files WHERE parent_id IN (SELECT file_id FROM Files WHERE name LIKE '%.epub')");
}
+
+ private void updateTables13() {
+ myDatabase.execSQL(
+ "CREATE TABLE VisitedLinks(" +
+ "book_id INTEGER NOT NULL REFERENCES Books(book_id)," +
+ "link_id INTEGER NOT NULL," +
+ "link TEXT NOT NULL," +
+ "CONSTRAINT VisitedLinks_Unique UNIQUE (book_id, link_id))");
+ myDatabase.execSQL(
+ "CREATE TABLE LinkHistory(" +
+ "book_id INTEGER NOT NULL REFERENCES Books(book_id)," +
+ "history_id INTEGER NOT NULL," +
+ "paragraph INTEGER NOT NULL," +
+ "word INTEGER NOT NULL," +
+ "char INTEGER NOT NULL," +
+ "CONSTRAINT LinkHistory_Unique UNIQUE (book_id, history_id))");
+ }
}
diff --git a/src/org/geometerplus/android/fbreader/preferences/PreferenceActivity.java b/src/org/geometerplus/android/fbreader/preferences/PreferenceActivity.java
index fcd429fb005..8d9b55f957d 100644
--- a/src/org/geometerplus/android/fbreader/preferences/PreferenceActivity.java
+++ b/src/org/geometerplus/android/fbreader/preferences/PreferenceActivity.java
@@ -221,6 +221,7 @@ protected void onDialogClosed(boolean result) {
colorsScreen.addOption(profile.HighlightingOption, "highlighting");
colorsScreen.addOption(profile.RegularTextOption, "text");
colorsScreen.addOption(profile.HyperlinkTextOption, "hyperlink");
+ colorsScreen.addOption(profile.HyperlinkVisitedTextOption, "hyperlinkVisited");
colorsScreen.addOption(profile.FooterFillOption, "footer");
final Screen marginsScreen = createPreferenceScreen("margins");
diff --git a/src/org/geometerplus/fbreader/bookmodel/BookReader.java b/src/org/geometerplus/fbreader/bookmodel/BookReader.java
index 22904994e69..b3c877e761c 100644
--- a/src/org/geometerplus/fbreader/bookmodel/BookReader.java
+++ b/src/org/geometerplus/fbreader/bookmodel/BookReader.java
@@ -147,6 +147,14 @@ private final void insertEndParagraph(byte kind) {
}
}
+ public final void beginStartOfSectionParagraph() {
+ final ZLTextWritableModel textModel = myCurrentTextModel;
+ if (textModel != null) {
+ textModel.createParagraph(ZLTextParagraph.Kind.START_OF_SECTION_PARAGRAPH);
+ myTextParagraphExists = true;
+ }
+ }
+
public final void insertEndOfSectionParagraph() {
insertEndParagraph(ZLTextParagraph.Kind.END_OF_SECTION_PARAGRAPH);
}
@@ -394,6 +402,15 @@ public final void addImageReference(String ref, short vOffset) {
}
}
+ public final void addPageLink(String ref) {
+ final ZLTextWritableModel textModel = myCurrentTextModel;
+ if (textModel != null) {
+ beginStartOfSectionParagraph();
+ textModel.addPageLink(ref);
+ endParagraph();
+ }
+ }
+
public final void addImage(String id, ZLImage image) {
Model.addImage(id, image);
}
diff --git a/src/org/geometerplus/fbreader/bookmodel/FBHyperlinkType.java b/src/org/geometerplus/fbreader/bookmodel/FBHyperlinkType.java
index 8e23c1da022..f445b513165 100644
--- a/src/org/geometerplus/fbreader/bookmodel/FBHyperlinkType.java
+++ b/src/org/geometerplus/fbreader/bookmodel/FBHyperlinkType.java
@@ -23,4 +23,5 @@ public interface FBHyperlinkType {
byte NONE = 0;
byte INTERNAL = 1;
byte EXTERNAL = 2;
+ byte INTERNAL_VISITED = 3;
}
diff --git a/src/org/geometerplus/fbreader/fbreader/CancelAction.java b/src/org/geometerplus/fbreader/fbreader/CancelAction.java
index 73dfaa8ea3a..aa8fbc74ef4 100644
--- a/src/org/geometerplus/fbreader/fbreader/CancelAction.java
+++ b/src/org/geometerplus/fbreader/fbreader/CancelAction.java
@@ -19,6 +19,8 @@
package org.geometerplus.fbreader.fbreader;
+import org.geometerplus.zlibrary.core.util.ZLHyperlinkStackManager;
+
class CancelAction extends FBAction {
CancelAction(FBReaderApp fbreader) {
super(fbreader);
@@ -28,7 +30,13 @@ public void run() {
if (Reader.getCurrentView() != Reader.BookTextView) {
Reader.showBookTextView();
} else {
- Reader.closeWindow();
+ ZLHyperlinkStackManager hyperlinkStack = ZLHyperlinkStackManager.Instance();
+ if (!hyperlinkStack.emptyStack()) {
+ Reader.gotoPosition(hyperlinkStack.popPosition());
+ } else {
+// Reader.doAction(ActionCode.SHOW_LIBRARY);
+ Reader.closeWindow();
+ }
}
}
}
diff --git a/src/org/geometerplus/fbreader/fbreader/ColorProfile.java b/src/org/geometerplus/fbreader/fbreader/ColorProfile.java
index e3d032d54ef..c5276ac8316 100644
--- a/src/org/geometerplus/fbreader/fbreader/ColorProfile.java
+++ b/src/org/geometerplus/fbreader/fbreader/ColorProfile.java
@@ -59,6 +59,7 @@ public static ColorProfile get(String name) {
public final ZLColorOption HighlightingOption;
public final ZLColorOption RegularTextOption;
public final ZLColorOption HyperlinkTextOption;
+ public final ZLColorOption HyperlinkVisitedTextOption;
public final ZLColorOption FooterFillOption;
private ColorProfile(String name, ColorProfile base) {
@@ -68,6 +69,7 @@ private ColorProfile(String name, ColorProfile base) {
HighlightingOption.setValue(base.HighlightingOption.getValue());
RegularTextOption.setValue(base.RegularTextOption.getValue());
HyperlinkTextOption.setValue(base.HyperlinkTextOption.getValue());
+ HyperlinkVisitedTextOption.setValue(base.HyperlinkVisitedTextOption.getValue());
FooterFillOption.setValue(base.FooterFillOption.getValue());
}
@@ -89,6 +91,8 @@ private ColorProfile(String name) {
createOption(name, "Text", 192, 192, 192);
HyperlinkTextOption =
createOption(name, "Hyperlink", 60, 142, 224);
+ HyperlinkVisitedTextOption =
+ createOption(name, "HyperlinkVisited", 200, 139, 255);
FooterFillOption =
createOption(name, "FooterFillOption", 85, 85, 85);
} else {
@@ -104,6 +108,8 @@ private ColorProfile(String name) {
createOption(name, "Text", 0, 0, 0);
HyperlinkTextOption =
createOption(name, "Hyperlink", 60, 139, 255);
+ HyperlinkVisitedTextOption =
+ createOption(name, "HyperlinkVisited", 200, 139, 255);
FooterFillOption =
createOption(name, "FooterFillOption", 170, 170, 170);
}
diff --git a/src/org/geometerplus/fbreader/fbreader/FBReaderApp.java b/src/org/geometerplus/fbreader/fbreader/FBReaderApp.java
index 9c7630b789a..0832a424553 100644
--- a/src/org/geometerplus/fbreader/fbreader/FBReaderApp.java
+++ b/src/org/geometerplus/fbreader/fbreader/FBReaderApp.java
@@ -25,6 +25,7 @@
import org.geometerplus.zlibrary.core.options.*;
import org.geometerplus.zlibrary.text.hyphenation.ZLTextHyphenator;
+import org.geometerplus.zlibrary.text.view.ZLTextPosition;
import org.geometerplus.fbreader.bookmodel.BookModel;
import org.geometerplus.fbreader.library.Library;
@@ -207,6 +208,8 @@ void openBookInternal(Book book, Bookmark bookmark) {
if (Model != null) {
Model.Book.storePosition(BookTextView.getStartCursor());
+ Model.Book.storeVisitedLinks();
+ Model.Book.storeLinkHistory();
}
BookTextView.setModel(null);
FootnoteView.setModel(null);
@@ -218,6 +221,8 @@ void openBookInternal(Book book, Bookmark bookmark) {
Model = BookModel.createModel(book);
if (Model != null) {
ZLTextHyphenator.Instance().load(book.getLanguage());
+ book.loadLinkHistory();
+ book.loadVisitedLinks();
BookTextView.setModel(Model.BookTextModel);
BookTextView.gotoPosition(book.getStoredPosition());
if (bookmark == null) {
@@ -248,6 +253,15 @@ public void showBookTextView() {
setView(BookTextView);
}
+ public void gotoPosition(ZLTextPosition position) {
+ BookTextView.gotoPosition(position);
+ setView(BookTextView);
+ }
+
+ public ZLTextPosition getPosition() {
+ return BookTextView.getStartCursor();
+ }
+
private Book createBookForFile(ZLFile file) {
if (file == null) {
return null;
@@ -280,6 +294,8 @@ public void openFile(ZLFile file) {
public void onWindowClosing() {
if ((Model != null) && (BookTextView != null)) {
Model.Book.storePosition(BookTextView.getStartCursor());
+ Model.Book.storeVisitedLinks();
+ Model.Book.storeLinkHistory();
}
}
}
diff --git a/src/org/geometerplus/fbreader/fbreader/FBView.java b/src/org/geometerplus/fbreader/fbreader/FBView.java
index d0560433c69..53040600fe8 100644
--- a/src/org/geometerplus/fbreader/fbreader/FBView.java
+++ b/src/org/geometerplus/fbreader/fbreader/FBView.java
@@ -476,6 +476,8 @@ public ZLColor getTextColor(byte hyperlinkType) {
default:
case FBHyperlinkType.NONE:
return profile.RegularTextOption.getValue();
+ case FBHyperlinkType.INTERNAL_VISITED:
+ return profile.HyperlinkVisitedTextOption.getValue();
case FBHyperlinkType.INTERNAL:
case FBHyperlinkType.EXTERNAL:
return profile.HyperlinkTextOption.getValue();
diff --git a/src/org/geometerplus/fbreader/fbreader/TurnPageAction.java b/src/org/geometerplus/fbreader/fbreader/TurnPageAction.java
index 29e546dc78c..663c441aa44 100644
--- a/src/org/geometerplus/fbreader/fbreader/TurnPageAction.java
+++ b/src/org/geometerplus/fbreader/fbreader/TurnPageAction.java
@@ -57,6 +57,7 @@ public boolean isEnabled() {
public void run() {
final ScrollingPreferences preferences = ScrollingPreferences.Instance();
final FBView view = Reader.getTextView();
+ view.checkInvalidCache();
if (view.getAnimationType() != FBView.Animation.none) {
final boolean horizontal = preferences.HorizontalOption.getValue();
if (myForward) {
diff --git a/src/org/geometerplus/fbreader/fbreader/VolumeKeyTurnPageAction.java b/src/org/geometerplus/fbreader/fbreader/VolumeKeyTurnPageAction.java
index d92a1862b5e..8ddc772128c 100755
--- a/src/org/geometerplus/fbreader/fbreader/VolumeKeyTurnPageAction.java
+++ b/src/org/geometerplus/fbreader/fbreader/VolumeKeyTurnPageAction.java
@@ -55,6 +55,7 @@ public void run() {
}
}
final FBView view = Reader.getTextView();
+ view.checkInvalidCache();
if (view.getAnimationType() != FBView.Animation.none) {
final boolean horizontal = preferences.HorizontalOption.getValue();
if (forward) {
diff --git a/src/org/geometerplus/fbreader/formats/html/HtmlReader.java b/src/org/geometerplus/fbreader/formats/html/HtmlReader.java
index e7087b1cc27..6a2a9bf8737 100644
--- a/src/org/geometerplus/fbreader/formats/html/HtmlReader.java
+++ b/src/org/geometerplus/fbreader/formats/html/HtmlReader.java
@@ -259,6 +259,9 @@ public void startElementHandler(byte tag, int offset, ZLHtmlAttributeMap attribu
if (ref.charAt(0) == '#') {
myHyperlinkType = FBTextKind.FOOTNOTE;
ref = ref.substring(1);
+ } else if (ref.charAt(0) == '&') {
+ myHyperlinkType = FBTextKind.INTERNAL_HYPERLINK;
+ ref = ref.substring(1);
} else {
myHyperlinkType = FBTextKind.EXTERNAL_HYPERLINK;
}
diff --git a/src/org/geometerplus/fbreader/formats/oeb/OEBBookReader.java b/src/org/geometerplus/fbreader/formats/oeb/OEBBookReader.java
index 585ef779b70..0a89463c7e3 100644
--- a/src/org/geometerplus/fbreader/formats/oeb/OEBBookReader.java
+++ b/src/org/geometerplus/fbreader/formats/oeb/OEBBookReader.java
@@ -85,6 +85,7 @@ boolean readBook(ZLFile file) {
myModelReader.addHyperlinkLabel(referenceName);
myTOCLabels.put(referenceName, myModelReader.Model.BookTextModel.getParagraphsNumber());
+ myModelReader.addPageLink(referenceName);
reader.readFile(xhtmlFile, referenceName + '#');
myModelReader.insertEndOfSectionParagraph();
}
diff --git a/src/org/geometerplus/fbreader/formats/pdb/MobipocketHtmlBookReader.java b/src/org/geometerplus/fbreader/formats/pdb/MobipocketHtmlBookReader.java
index f537fa24737..28b24e7853f 100644
--- a/src/org/geometerplus/fbreader/formats/pdb/MobipocketHtmlBookReader.java
+++ b/src/org/geometerplus/fbreader/formats/pdb/MobipocketHtmlBookReader.java
@@ -122,7 +122,7 @@ public void startElementHandler(byte tag, int offset, ZLHtmlAttributeMap attribu
}
}
myFileposReferences.add(filePosition);
- // TODO: add hyperlink control
+ attributes.put(new ZLByteBuffer("href"), new ZLByteBuffer("&filepos" + filePosition));
} catch (NumberFormatException e) {
}
}
@@ -182,12 +182,21 @@ public void startDocumentHandler() {
if (length <= 0) {
break;
}
- addImage("" + index, new ZLFileImage(MimeTypes.MIME_IMAGE_AUTO, Model.Book.File, offset, length));
+ addImage("" + (index + 1), new ZLFileImage(MimeTypes.MIME_IMAGE_AUTO, Model.Book.File, offset, length));
}
}
@Override
public void endDocumentHandler() {
+ for (Integer entry: myFileposReferences) {
+ final SortedMap subMap =
+ myPositionToParagraph.tailMap(entry);
+ if (subMap.isEmpty()) {
+ break;
+ }
+ addHyperlinkLabel("filepos" + entry, subMap.get(subMap.firstKey()));
+ }
+
for (Map.Entry entry : myTocEntries.entrySet()) {
final SortedMap subMap =
myPositionToParagraph.tailMap(entry.getKey());
diff --git a/src/org/geometerplus/fbreader/formats/pdb/MobipocketPlugin.java b/src/org/geometerplus/fbreader/formats/pdb/MobipocketPlugin.java
index c9cfb7f167e..032089f2396 100644
--- a/src/org/geometerplus/fbreader/formats/pdb/MobipocketPlugin.java
+++ b/src/org/geometerplus/fbreader/formats/pdb/MobipocketPlugin.java
@@ -22,7 +22,9 @@
import java.io.*;
import org.geometerplus.zlibrary.core.filesystem.ZLFile;
+import org.geometerplus.zlibrary.core.image.ZLFileImage;
import org.geometerplus.zlibrary.core.image.ZLImage;
+import org.geometerplus.zlibrary.core.constants.MimeTypes;
import org.geometerplus.zlibrary.core.encoding.ZLEncodingCollection;
import org.geometerplus.zlibrary.core.language.ZLLanguageUtil;
@@ -202,20 +204,14 @@ public ZLImage readCover(ZLFile file) {
coverIndex = thumbIndex;
}
- // TODO: implement
- /*final MobipocketStream mpStream = new MobipocketStream(file);
-
- int index = pbStream.firstImageLocationIndex(file.path());
- if (index >= 0) {
- std::pair imageLocation = pbStream.imageLocation(pbStream.header(), index + coverIndex);
- if ((imageLocation.first > 0) && (imageLocation.second > 0)) {
- return new ZLFileImage(
- file,
- imageLocation.first,
- imageLocation.second
- );
+ MobipocketStream myMobipocketStream = new MobipocketStream(file);
+ int start = myMobipocketStream.getImageOffset(coverIndex);
+ if (start >= 0) {
+ int len = myMobipocketStream.getImageLength(coverIndex);
+ if (len > 0) {
+ return new ZLFileImage(MimeTypes.MIME_IMAGE_AUTO, file, start, len);
}
- }*/
+ }
return null;
} catch (IOException e) {
return null;
diff --git a/src/org/geometerplus/fbreader/formats/pdb/MobipocketStream.java b/src/org/geometerplus/fbreader/formats/pdb/MobipocketStream.java
index 1c33344ebb2..ba9435db280 100644
--- a/src/org/geometerplus/fbreader/formats/pdb/MobipocketStream.java
+++ b/src/org/geometerplus/fbreader/formats/pdb/MobipocketStream.java
@@ -25,6 +25,7 @@
class MobipocketStream extends PalmDocLikeStream {
private final int myFileSize;
+ private final int myImageStartIndex;
MobipocketStream(ZLFile file) throws IOException {
super(file);
@@ -39,11 +40,14 @@ class MobipocketStream extends PalmDocLikeStream {
}
myBuffer = new byte[maxRecordSize];
myRecordIndex = 0;
+
+ PdbUtil.skip(myBase, 96);
+ myImageStartIndex = (int)PdbUtil.readInt(myBase);
}
int getImageOffset(int index) {
try {
- return myHeader.Offsets[index + myMaxRecordIndex + 1];
+ return myHeader.Offsets[index + myImageStartIndex];
} catch (ArrayIndexOutOfBoundsException e) {
return -1;
}
@@ -51,7 +55,7 @@ int getImageOffset(int index) {
int getImageLength(int index) {
try {
- final int i = index + myMaxRecordIndex + 1;
+ final int i = index + myImageStartIndex;
final int start = myHeader.Offsets[i];
final int end = (i == myHeader.Offsets.length) ? myFileSize : myHeader.Offsets[i + 1];
return end - start;
diff --git a/src/org/geometerplus/fbreader/library/Book.java b/src/org/geometerplus/fbreader/library/Book.java
index 9bafe1d8352..aad0ffe3106 100644
--- a/src/org/geometerplus/fbreader/library/Book.java
+++ b/src/org/geometerplus/fbreader/library/Book.java
@@ -364,6 +364,30 @@ public void storePosition(ZLTextPosition position) {
}
}
+ public void loadVisitedLinks() {
+ if (myId != -1) {
+ BooksDatabase.Instance().loadVisitedLinks(myId);
+ }
+ }
+
+ public void storeVisitedLinks() {
+ if (myId != -1) {
+ BooksDatabase.Instance().storeVisitedLinks(myId);
+ }
+ }
+
+ public void loadLinkHistory() {
+ if (myId != -1) {
+ BooksDatabase.Instance().loadLinkHistory(myId);
+ }
+ }
+
+ public void storeLinkHistory() {
+ if (myId != -1) {
+ BooksDatabase.Instance().storeLinkHistory(myId);
+ }
+ }
+
public void insertIntoBookList() {
if (myId != -1) {
BooksDatabase.Instance().insertIntoBookList(myId);
diff --git a/src/org/geometerplus/fbreader/library/BooksDatabase.java b/src/org/geometerplus/fbreader/library/BooksDatabase.java
index 6ecde9a4812..770577f6024 100644
--- a/src/org/geometerplus/fbreader/library/BooksDatabase.java
+++ b/src/org/geometerplus/fbreader/library/BooksDatabase.java
@@ -104,4 +104,9 @@ protected Bookmark createBookmark(long id, long bookId, String bookTitle, String
protected abstract boolean insertIntoBookList(long bookId);
protected abstract boolean deleteFromBookList(long bookId);
protected abstract boolean checkBookList(long bookId);
+
+ protected abstract void storeVisitedLinks(long bookId);
+ protected abstract void loadVisitedLinks(long bookId);
+ protected abstract void storeLinkHistory(long bookId);
+ protected abstract void loadLinkHistory(long bookId);
}
diff --git a/src/org/geometerplus/zlibrary/core/html/ZLByteBuffer.java b/src/org/geometerplus/zlibrary/core/html/ZLByteBuffer.java
index dadfc12353b..08050e97f35 100644
--- a/src/org/geometerplus/zlibrary/core/html/ZLByteBuffer.java
+++ b/src/org/geometerplus/zlibrary/core/html/ZLByteBuffer.java
@@ -37,6 +37,11 @@ public ZLByteBuffer() {
this(20);
}
+ public ZLByteBuffer(String value) {
+ myLength = value.length();
+ myData = value.getBytes();
+ }
+
ZLByteBuffer(ZLByteBuffer container) {
final int len = container.myLength;
myData = ZLArrayUtils.createCopy(container.myData, len, len);
diff --git a/src/org/geometerplus/zlibrary/core/util/ZLHyperlinkStackManager.java b/src/org/geometerplus/zlibrary/core/util/ZLHyperlinkStackManager.java
new file mode 100644
index 00000000000..fc05a6531ae
--- /dev/null
+++ b/src/org/geometerplus/zlibrary/core/util/ZLHyperlinkStackManager.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2007-2011 Geometer Plus
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+package org.geometerplus.zlibrary.core.util;
+
+import java.util.Iterator;
+import java.util.Stack;
+
+import org.geometerplus.zlibrary.text.view.ZLTextFixedPosition;
+import org.geometerplus.zlibrary.text.view.ZLTextPosition;
+
+public abstract class ZLHyperlinkStackManager {
+ private static ZLHyperlinkStackManager ourInstance;
+ private static Stack positionStack;
+
+ public static ZLHyperlinkStackManager Instance() {
+ return ourInstance;
+ }
+
+ protected ZLHyperlinkStackManager() {
+ ourInstance = this;
+ reset();
+ }
+
+ public void pushPosition(ZLTextPosition position) {
+ ZLTextFixedPosition fixedPosition = new ZLTextFixedPosition(position);
+ positionStack.push(fixedPosition);
+ }
+
+ public boolean emptyStack() {
+ return positionStack.empty();
+ }
+
+ public ZLTextPosition popPosition() {
+ return positionStack.pop();
+ }
+
+ public Iterator getIterator() {
+ return positionStack.iterator();
+ }
+
+ public void reset() {
+ positionStack = new Stack();
+ }
+}
diff --git a/src/org/geometerplus/zlibrary/core/util/ZLVisitedLinkManager.java b/src/org/geometerplus/zlibrary/core/util/ZLVisitedLinkManager.java
new file mode 100644
index 00000000000..feff257e4ed
--- /dev/null
+++ b/src/org/geometerplus/zlibrary/core/util/ZLVisitedLinkManager.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2007-2011 Geometer Plus
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+package org.geometerplus.zlibrary.core.util;
+
+import java.util.Set;
+import java.util.TreeSet;
+
+public abstract class ZLVisitedLinkManager {
+ private static ZLVisitedLinkManager ourInstance;
+ private static Set visitedLinks;
+
+ public static ZLVisitedLinkManager Instance() {
+ return ourInstance;
+ }
+
+ protected ZLVisitedLinkManager() {
+ ourInstance = this;
+ reset();
+ }
+
+ private String stripId(String id) {
+ int index = id.indexOf('#');
+ return index != -1 ? id.substring(0, index) : id;
+ }
+
+ public void markLinkVisited(String id) {
+ visitedLinks.add(stripId(id));
+ }
+
+ public boolean isLinkVisited(String id) {
+ return visitedLinks.contains(stripId(id));
+ }
+
+ public Set getVisitedLinks() {
+ return visitedLinks;
+ }
+
+ public void reset() {
+ visitedLinks = new TreeSet();
+ }
+}
diff --git a/src/org/geometerplus/zlibrary/core/view/ZLView.java b/src/org/geometerplus/zlibrary/core/view/ZLView.java
index 94ba619d3d7..206eaef18b4 100644
--- a/src/org/geometerplus/zlibrary/core/view/ZLView.java
+++ b/src/org/geometerplus/zlibrary/core/view/ZLView.java
@@ -44,6 +44,7 @@ public enum Animation {
}
public abstract Animation getAnimationType();
+ abstract public void checkInvalidCache();
abstract public void paint(ZLPaintContext context, int viewPage);
abstract public void onScrollingFinished(int viewPage);
diff --git a/src/org/geometerplus/zlibrary/text/model/ZLTextParagraph.java b/src/org/geometerplus/zlibrary/text/model/ZLTextParagraph.java
index 5aea9a57af3..9485d53e91c 100644
--- a/src/org/geometerplus/zlibrary/text/model/ZLTextParagraph.java
+++ b/src/org/geometerplus/zlibrary/text/model/ZLTextParagraph.java
@@ -26,6 +26,7 @@ interface Entry {
byte CONTROL = 3;
byte FORCED_CONTROL = 4;
byte FIXED_HSPACE = 5;
+ byte PAGELINK = 6;
}
interface EntryIterator {
@@ -41,6 +42,7 @@ interface EntryIterator {
byte getHyperlinkType();
String getHyperlinkId();
+ String getPageLink();
ZLImageEntry getImageEntry();
short getFixedHSpaceLength();
@@ -58,6 +60,7 @@ interface Kind {
byte AFTER_SKIP_PARAGRAPH = 3;
byte END_OF_SECTION_PARAGRAPH = 4;
byte END_OF_TEXT_PARAGRAPH = 5;
+ byte START_OF_SECTION_PARAGRAPH = 6;
};
byte getKind();
diff --git a/src/org/geometerplus/zlibrary/text/model/ZLTextPlainModel.java b/src/org/geometerplus/zlibrary/text/model/ZLTextPlainModel.java
index a63612ba43a..ce2624b3f8b 100644
--- a/src/org/geometerplus/zlibrary/text/model/ZLTextPlainModel.java
+++ b/src/org/geometerplus/zlibrary/text/model/ZLTextPlainModel.java
@@ -20,9 +20,12 @@
package org.geometerplus.zlibrary.text.model;
import java.util.*;
+
+import org.geometerplus.fbreader.bookmodel.FBHyperlinkType;
import org.geometerplus.zlibrary.core.util.*;
import org.geometerplus.zlibrary.core.image.ZLImageMap;
+import org.geometerplus.zlibrary.ui.android.util.ZLAndroidVisitedLinkManager;
public class ZLTextPlainModel implements ZLTextModel {
protected final String myId;
@@ -58,6 +61,7 @@ final class EntryIteratorImpl implements ZLTextParagraph.EntryIterator {
private byte myHyperlinkType;
private String myHyperlinkId;
+ private String myPageLink;
private ZLImageEntry myImageEntry;
private ZLTextForcedControlEntry myForcedControlEntry;
@@ -107,6 +111,10 @@ public ZLImageEntry getImageEntry() {
return myImageEntry;
}
+ public String getPageLink() {
+ return myPageLink;
+ }
+
public ZLTextForcedControlEntry getForcedControlEntry() {
return myForcedControlEntry;
}
@@ -153,9 +161,21 @@ public void next() {
short labelLength = (short)data[dataOffset++];
myHyperlinkId = new String(data, dataOffset, labelLength);
dataOffset += labelLength;
+ if (myHyperlinkType == FBHyperlinkType.INTERNAL
+ && ZLAndroidVisitedLinkManager.Instance().isLinkVisited(myHyperlinkId)) {
+ myHyperlinkType = FBHyperlinkType.INTERNAL_VISITED;
+ }
}
break;
}
+ case ZLTextParagraph.Entry.PAGELINK:
+ {
+ final short len = (short)data[dataOffset++];
+ final String id = new String(data, dataOffset, len);
+ dataOffset += len;
+ myPageLink = id;
+ break;
+ }
case ZLTextParagraph.Entry.IMAGE:
{
final short vOffset = (short)data[dataOffset++];
diff --git a/src/org/geometerplus/zlibrary/text/model/ZLTextWritableModel.java b/src/org/geometerplus/zlibrary/text/model/ZLTextWritableModel.java
index 45593d03ca8..7752493bab4 100644
--- a/src/org/geometerplus/zlibrary/text/model/ZLTextWritableModel.java
+++ b/src/org/geometerplus/zlibrary/text/model/ZLTextWritableModel.java
@@ -28,6 +28,7 @@ public interface ZLTextWritableModel extends ZLTextModel {
//void addControl(ZLTextForcedControlEntry entry);
void addHyperlinkControl(byte textKind, byte hyperlinkType, String id);
+ void addPageLink(String id);
void addImage(String id, short vOffset);
void addFixedHSpace(short length);
diff --git a/src/org/geometerplus/zlibrary/text/model/ZLTextWritablePlainModel.java b/src/org/geometerplus/zlibrary/text/model/ZLTextWritablePlainModel.java
index f2201acb990..da17981f4f1 100644
--- a/src/org/geometerplus/zlibrary/text/model/ZLTextWritablePlainModel.java
+++ b/src/org/geometerplus/zlibrary/text/model/ZLTextWritablePlainModel.java
@@ -141,6 +141,17 @@ public void addImage(String id, short vOffset) {
myBlockOffset = blockOffset + len;
}
+ public void addPageLink(String id) {
+ final int len = id.length();
+ final char[] block = getDataBlock(2 + len);
+ ++myParagraphLengths[myParagraphsNumber - 1];
+ int blockOffset = myBlockOffset;
+ block[blockOffset++] = (char)ZLTextParagraph.Entry.PAGELINK;
+ block[blockOffset++] = (char)len;
+ id.getChars(0, len, block, blockOffset);
+ myBlockOffset = blockOffset + len;
+ }
+
public void addFixedHSpace(short length) {
final char[] block = getDataBlock(2);
++myParagraphLengths[myParagraphsNumber - 1];
diff --git a/src/org/geometerplus/zlibrary/text/view/ZLTextHyperlink.java b/src/org/geometerplus/zlibrary/text/view/ZLTextHyperlink.java
index 5478cd06a1a..8e734959df3 100644
--- a/src/org/geometerplus/zlibrary/text/view/ZLTextHyperlink.java
+++ b/src/org/geometerplus/zlibrary/text/view/ZLTextHyperlink.java
@@ -20,7 +20,7 @@
package org.geometerplus.zlibrary.text.view;
public class ZLTextHyperlink {
- public final byte Type;
+ public byte Type;
public final String Id;
public static final ZLTextHyperlink NO_LINK = new ZLTextHyperlink((byte)0, null);
diff --git a/src/org/geometerplus/zlibrary/text/view/ZLTextPage.java b/src/org/geometerplus/zlibrary/text/view/ZLTextPage.java
index ba7210184f1..313c2ecd62d 100644
--- a/src/org/geometerplus/zlibrary/text/view/ZLTextPage.java
+++ b/src/org/geometerplus/zlibrary/text/view/ZLTextPage.java
@@ -26,6 +26,7 @@ final class ZLTextPage {
final ZLTextWordCursor EndCursor = new ZLTextWordCursor();
final ArrayList LineInfos = new ArrayList();
int PaintState = PaintStateEnum.NOTHING_TO_PAINT;
+ boolean myInvalidCache = false;
final ZLTextElementAreaVector TextElementMap = new ZLTextElementAreaVector();
diff --git a/src/org/geometerplus/zlibrary/text/view/ZLTextParagraphCursor.java b/src/org/geometerplus/zlibrary/text/view/ZLTextParagraphCursor.java
index f581c6da618..fa214482245 100644
--- a/src/org/geometerplus/zlibrary/text/view/ZLTextParagraphCursor.java
+++ b/src/org/geometerplus/zlibrary/text/view/ZLTextParagraphCursor.java
@@ -214,6 +214,10 @@ public boolean isLast() {
return (Index + 1 >= Model.getParagraphsNumber());
}
+ public boolean isStartOfSection() {
+ return (Model.getParagraph(Index).getKind() == ZLTextParagraph.Kind.START_OF_SECTION_PARAGRAPH);
+ }
+
public boolean isEndOfSection() {
return (Model.getParagraph(Index).getKind() == ZLTextParagraph.Kind.END_OF_SECTION_PARAGRAPH);
}
diff --git a/src/org/geometerplus/zlibrary/text/view/ZLTextSelectionModel.java b/src/org/geometerplus/zlibrary/text/view/ZLTextSelectionModel.java
index 55ca0fee105..9836e8117b1 100644
--- a/src/org/geometerplus/zlibrary/text/view/ZLTextSelectionModel.java
+++ b/src/org/geometerplus/zlibrary/text/view/ZLTextSelectionModel.java
@@ -271,6 +271,7 @@ private void startSelectionScrolling(final boolean forward) {
stopSelectionScrolling();
myScrollingTask = new TimerTask() {
public void run() {
+ myView.checkInvalidCache();
myView.scrollPage(forward, ZLTextView.ScrollingMode.SCROLL_LINES, 1);
myDoUpdate = true;
ZLApplication.Instance().repaintView();
diff --git a/src/org/geometerplus/zlibrary/text/view/ZLTextView.java b/src/org/geometerplus/zlibrary/text/view/ZLTextView.java
index 6c27620ab53..64fceb11740 100644
--- a/src/org/geometerplus/zlibrary/text/view/ZLTextView.java
+++ b/src/org/geometerplus/zlibrary/text/view/ZLTextView.java
@@ -22,6 +22,7 @@
import java.util.*;
import org.geometerplus.zlibrary.core.application.ZLApplication;
+import org.geometerplus.zlibrary.core.util.ZLVisitedLinkManager;
import org.geometerplus.zlibrary.core.view.ZLPaintContext;
import org.geometerplus.zlibrary.core.filesystem.ZLFile;
import org.geometerplus.zlibrary.core.filesystem.ZLResourceFile;
@@ -252,6 +253,14 @@ public synchronized void onScrollingFinished(int viewPage) {
}
}
+ @Override
+ public synchronized void checkInvalidCache() {
+ if (myCurrentPage.myInvalidCache) {
+ clearCaches();
+ myCurrentPage.myInvalidCache = false;
+ }
+ }
+
@Override
public synchronized void paint(ZLPaintContext context, int viewPage) {
myContext = context;
@@ -662,6 +671,12 @@ private void buildInfos(ZLTextPage page, ZLTextWordCursor start, ZLTextWordCurso
do {
resetTextStyle();
final ZLTextParagraphCursor paragraphCursor = result.getParagraphCursor();
+ if (paragraphCursor != null && paragraphCursor.isStartOfSection()) {
+ ZLTextParagraph.EntryIterator iter = paragraphCursor.getParagraph().iterator();
+ iter.next();
+ ZLVisitedLinkManager.Instance().markLinkVisited(iter.getPageLink());
+ page.myInvalidCache = true;
+ }
final int wordIndex = result.getElementIndex();
applyControls(paragraphCursor, 0, wordIndex);
ZLTextLineInfo info = new ZLTextLineInfo(paragraphCursor, wordIndex, result.getCharIndex(), getTextStyle());
diff --git a/src/org/geometerplus/zlibrary/ui/android/library/ZLAndroidApplication.java b/src/org/geometerplus/zlibrary/ui/android/library/ZLAndroidApplication.java
index 418abfb1b46..7d4d757c78e 100644
--- a/src/org/geometerplus/zlibrary/ui/android/library/ZLAndroidApplication.java
+++ b/src/org/geometerplus/zlibrary/ui/android/library/ZLAndroidApplication.java
@@ -29,6 +29,8 @@
import org.geometerplus.zlibrary.ui.android.application.ZLAndroidApplicationWindow;
import org.geometerplus.zlibrary.ui.android.dialogs.ZLAndroidDialogManager;
import org.geometerplus.zlibrary.ui.android.image.ZLAndroidImageManager;
+import org.geometerplus.zlibrary.ui.android.util.ZLAndroidHyperlinkStackManager;
+import org.geometerplus.zlibrary.ui.android.util.ZLAndroidVisitedLinkManager;
public class ZLAndroidApplication extends Application {
private static ZLAndroidApplication ourApplication;
@@ -62,6 +64,8 @@ public void onCreate() {
super.onCreate();
new ZLSQLiteConfig(this);
new ZLAndroidImageManager();
+ new ZLAndroidVisitedLinkManager();
+ new ZLAndroidHyperlinkStackManager();
new ZLAndroidDialogManager();
new ZLAndroidLibrary(this);
}
diff --git a/src/org/geometerplus/zlibrary/ui/android/util/ZLAndroidHyperlinkStackManager.java b/src/org/geometerplus/zlibrary/ui/android/util/ZLAndroidHyperlinkStackManager.java
new file mode 100644
index 00000000000..5a8afed91d9
--- /dev/null
+++ b/src/org/geometerplus/zlibrary/ui/android/util/ZLAndroidHyperlinkStackManager.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2007-2011 Geometer Plus
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+package org.geometerplus.zlibrary.ui.android.util;
+
+import org.geometerplus.zlibrary.core.util.ZLHyperlinkStackManager;
+
+public final class ZLAndroidHyperlinkStackManager extends ZLHyperlinkStackManager {
+}
diff --git a/src/org/geometerplus/zlibrary/ui/android/util/ZLAndroidVisitedLinkManager.java b/src/org/geometerplus/zlibrary/ui/android/util/ZLAndroidVisitedLinkManager.java
new file mode 100644
index 00000000000..cf22edd41fe
--- /dev/null
+++ b/src/org/geometerplus/zlibrary/ui/android/util/ZLAndroidVisitedLinkManager.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2007-2011 Geometer Plus
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+package org.geometerplus.zlibrary.ui.android.util;
+
+import org.geometerplus.zlibrary.core.util.ZLVisitedLinkManager;
+
+public final class ZLAndroidVisitedLinkManager extends ZLVisitedLinkManager {
+}