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 { +}