diff options
author | Jon Nermut <jon.nermut@gmail.com> | 2018-01-20 17:08:43 +1100 |
---|---|---|
committer | jan iversen <jani@libreoffice.org> | 2018-01-20 18:58:28 +0100 |
commit | a468fef9ac977e812e83e3cce462b75d8d24c64d (patch) | |
tree | 5170a58b7bf05a655a418e63b750c289201d6d6b /ios/LibreOfficeLight | |
parent | a41ec55c8c86f962392cd64edc382a6eae113cfd (diff) |
iOS: keep track of doc part
- implement swipe left/right and tap gestures for presentations
- move some classes to their own files
Change-Id: I3ddd3e17ec809c87097d5515f08038bbc969764f
Reviewed-on: https://gerrit.libreoffice.org/48231
Reviewed-by: jan iversen <jani@libreoffice.org>
Tested-by: jan iversen <jani@libreoffice.org>
Diffstat (limited to 'ios/LibreOfficeLight')
6 files changed, 489 insertions, 363 deletions
diff --git a/ios/LibreOfficeLight/LibreOfficeLight.xcodeproj/project.pbxproj b/ios/LibreOfficeLight/LibreOfficeLight.xcodeproj/project.pbxproj index 48174b80e271..315d4d18151b 100644 --- a/ios/LibreOfficeLight/LibreOfficeLight.xcodeproj/project.pbxproj +++ b/ios/LibreOfficeLight/LibreOfficeLight.xcodeproj/project.pbxproj @@ -33,6 +33,8 @@ 39B091CE1E5F0BB800682A59 /* unorc in Resources */ = {isa = PBXBuildFile; fileRef = 39B08B9C1E5F0BB600682A59 /* unorc */; }; 39E950531FC9842000D82C49 /* source in Resources */ = {isa = PBXBuildFile; fileRef = 39E950521FC9842000D82C49 /* source */; }; 39EF4E2F1FA500C9001914AC /* PropertiesController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39EF4E2E1FA500C9001914AC /* PropertiesController.swift */; }; + FC31D01E2012F65500E7F402 /* DocumentHolder.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC31D01D2012F65500E7F402 /* DocumentHolder.swift */; }; + FC31D0202012F6D300E7F402 /* RenderCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC31D01F2012F6D300E7F402 /* RenderCache.swift */; }; FCAB1CB82009DB6900F1CC34 /* DocumentOverlaysView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCAB1CB72009DB6900F1CC34 /* DocumentOverlaysView.swift */; }; FCC2E3FA2004A01500CEB504 /* Document.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCC2E3F62004A01400CEB504 /* Document.swift */; }; FCC2E3FC2004A01500CEB504 /* LibreOfficeKitWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCC2E3F82004A01400CEB504 /* LibreOfficeKitWrapper.swift */; }; @@ -77,6 +79,14 @@ 39E950521FC9842000D82C49 /* source */ = {isa = PBXFileReference; lastKnownFileType = folder; name = source; path = ../source; sourceTree = "<group>"; }; 39EE81531FA644E800B73AB8 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; }; 39EF4E2E1FA500C9001914AC /* PropertiesController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PropertiesController.swift; sourceTree = "<group>"; }; + FC31D00E2012EE4A00E7F402 /* LibreOfficeKit.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LibreOfficeKit.h; sourceTree = "<group>"; }; + FC31D00F2012EE4A00E7F402 /* LibreOfficeKit.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = LibreOfficeKit.hxx; sourceTree = "<group>"; }; + FC31D0102012EE4A00E7F402 /* LibreOfficeKitEnums.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LibreOfficeKitEnums.h; sourceTree = "<group>"; }; + FC31D0112012EE4A00E7F402 /* LibreOfficeKitGtk.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LibreOfficeKitGtk.h; sourceTree = "<group>"; }; + FC31D0122012EE4A00E7F402 /* LibreOfficeKitInit.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LibreOfficeKitInit.h; sourceTree = "<group>"; }; + FC31D0132012EE4A00E7F402 /* LibreOfficeKitTypes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LibreOfficeKitTypes.h; sourceTree = "<group>"; }; + FC31D01D2012F65500E7F402 /* DocumentHolder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DocumentHolder.swift; sourceTree = "<group>"; }; + FC31D01F2012F6D300E7F402 /* RenderCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RenderCache.swift; sourceTree = "<group>"; }; FCAB1CB72009DB6900F1CC34 /* DocumentOverlaysView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DocumentOverlaysView.swift; sourceTree = "<group>"; }; FCC2E3F62004A01400CEB504 /* Document.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Document.swift; sourceTree = "<group>"; }; FCC2E3F82004A01400CEB504 /* LibreOfficeKitWrapper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LibreOfficeKitWrapper.swift; sourceTree = "<group>"; }; @@ -194,13 +204,29 @@ name = Resources; sourceTree = SOURCE_ROOT; }; + FC31D00D2012EE4A00E7F402 /* LibreOfficeKit */ = { + isa = PBXGroup; + children = ( + FC31D00E2012EE4A00E7F402 /* LibreOfficeKit.h */, + FC31D00F2012EE4A00E7F402 /* LibreOfficeKit.hxx */, + FC31D0102012EE4A00E7F402 /* LibreOfficeKitEnums.h */, + FC31D0112012EE4A00E7F402 /* LibreOfficeKitGtk.h */, + FC31D0122012EE4A00E7F402 /* LibreOfficeKitInit.h */, + FC31D0132012EE4A00E7F402 /* LibreOfficeKitTypes.h */, + ); + name = LibreOfficeKit; + path = ../../include/LibreOfficeKit; + sourceTree = "<group>"; + }; FCC2E3F52004A01400CEB504 /* LOKit */ = { isa = PBXGroup; children = ( FCC2E4042004B74000CEB504 /* AsyncUtil.swift */, FCC2E3F62004A01400CEB504 /* Document.swift */, + FC31D01D2012F65500E7F402 /* DocumentHolder.swift */, FCC2E3F82004A01400CEB504 /* LibreOfficeKitWrapper.swift */, FCC2E3F92004A01400CEB504 /* LOKitThread.swift */, + FC31D01F2012F6D300E7F402 /* RenderCache.swift */, FCC2E4022004B72700CEB504 /* Util.swift */, ); path = LOKit; @@ -306,7 +332,9 @@ files = ( FCC2E4032004B72700CEB504 /* Util.swift in Sources */, 392ED9B31E5E4B03005C8435 /* ViewPrintManager.swift in Sources */, + FC31D01E2012F65500E7F402 /* DocumentHolder.swift in Sources */, FCAB1CB82009DB6900F1CC34 /* DocumentOverlaysView.swift in Sources */, + FC31D0202012F6D300E7F402 /* RenderCache.swift in Sources */, 399648471E5B87DC00E73E83 /* ViewProperties.swift in Sources */, FCC2E3FC2004A01500CEB504 /* LibreOfficeKitWrapper.swift in Sources */, 39284DB31FA5F207006F43E4 /* DocumentActions.swift in Sources */, diff --git a/ios/LibreOfficeLight/LibreOfficeLight/DocumentController.swift b/ios/LibreOfficeLight/LibreOfficeLight/DocumentController.swift index a15889985f31..3decec85410a 100755 --- a/ios/LibreOfficeLight/LibreOfficeLight/DocumentController.swift +++ b/ios/LibreOfficeLight/LibreOfficeLight/DocumentController.swift @@ -61,9 +61,11 @@ class DocumentController: UIViewController, MenuDelegate, UIDocumentBrowserViewC override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) - let res = Bundle.main.url(forResource: "example", withExtension: "odt") + //let res = Bundle.main.url(forResource: "example", withExtension: "odt") //let res = Bundle.main.url(forResource: "example2", withExtension: "docx") + let res = Bundle.main.url(forResource: "testdata/1", withExtension: "pptx") + if let exampleDoc = res { self.doOpen(exampleDoc) diff --git a/ios/LibreOfficeLight/LibreOfficeLight/DocumentTiledView.swift b/ios/LibreOfficeLight/LibreOfficeLight/DocumentTiledView.swift index 20ca23178f5c..f0a36878c4b3 100644 --- a/ios/LibreOfficeLight/LibreOfficeLight/DocumentTiledView.swift +++ b/ios/LibreOfficeLight/LibreOfficeLight/DocumentTiledView.swift @@ -18,9 +18,7 @@ class DocumentTiledLayer : CATiledLayer } } - - - +/// The main tiled view, which sits inside the scroll view public class DocumentTiledView: UIView { var myScale: CGFloat @@ -33,16 +31,11 @@ public class DocumentTiledView: UIView var drawCount = 0 - - // Create a new view with the desired frame and scale. public init(frame: CGRect, document: DocumentHolder, scale: CGFloat) { - - self.document = document - myScale = scale initialSize = frame.size var size = document.sync { $0.getDocumentSizeAsCGSize() } @@ -67,12 +60,28 @@ public class DocumentTiledView: UIView if let tiledLayer = self.layer as? CATiledLayer { + // these are all tweakable parameters, that give different behaviour to the tiled view tiledLayer.levelsOfDetail = 4 tiledLayer.levelsOfDetailBias = 7 tiledLayer.tileSize = CGSize(width: 1024.0, height: 1024.0) //tiledLayer.tileSize = CGSize(width: 512.0, height: 512.0) } + let tap = UITapGestureRecognizer(target: self, action: #selector(onTap) ) + tap.numberOfTapsRequired = 1 + self.addGestureRecognizer(tap) + + if (document.isPresentation) // only for preso atm + { + // add swipe left/right gestures on a preso + let swipeRight = UISwipeGestureRecognizer(target: self, action: #selector(onSwipeRight)) + swipeRight.direction = .right + self.addGestureRecognizer(swipeRight) + + let swipeLeft = UISwipeGestureRecognizer(target: self, action: #selector(onSwipeLeft)) + swipeLeft.direction = .left + self.addGestureRecognizer(swipeLeft) + } } required public init?(coder aDecoder: NSCoder) @@ -80,6 +89,14 @@ public class DocumentTiledView: UIView fatalError("init(coder:) has not been implemented") } + func incrementPart(amount: Int) + { + document?.incrementPart(amount: Int32(amount)) + document?.async { _ in + runOnMain { self.setNeedsDisplay() } + } + } + public func twipsToPixels(rect: CGRect) -> CGRect { return rect.applying(CGAffineTransform(scaleX: 1.0/initialScaleFactor, y: 1.0/initialScaleFactor )) @@ -196,8 +213,32 @@ public class DocumentTiledView: UIView } -// override func pressesBegan -// { -// -// } +} + +/// Gesture handlers +public extension DocumentTiledView +{ + @objc func onTap(_ sender: UITapGestureRecognizer) + { + if (document?.isPresentation ?? false) + { + incrementPart(amount: 1) + } + } + + @objc func onSwipeRight(_ sender: UISwipeGestureRecognizer) + { + if (document?.isPresentation ?? false) + { + incrementPart(amount: -1) + } + } + + @objc func onSwipeLeft(_ sender: UISwipeGestureRecognizer) + { + if (document?.isPresentation ?? false) + { + incrementPart(amount: 1) + } + } } diff --git a/ios/LibreOfficeLight/LibreOfficeLight/LOKit/DocumentHolder.swift b/ios/LibreOfficeLight/LibreOfficeLight/LOKit/DocumentHolder.swift new file mode 100644 index 000000000000..a380cc45edd0 --- /dev/null +++ b/ios/LibreOfficeLight/LibreOfficeLight/LOKit/DocumentHolder.swift @@ -0,0 +1,327 @@ +// +// This file is part of the LibreOffice project. +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. +// + +import Foundation +import UIKit +import QuartzCore + +/** + * Holds the document object so to enforce access in a thread safe way. + */ +public class DocumentHolder +{ + private let doc: Document + + public weak var delegate: DocumentUIDelegate? = nil + public weak var searchDelegate: SearchDelegate? = nil + + private let cache = RenderCache() + + public let documentType: LibreOfficeKitDocumentType + public let documentSize: CGSize + public let views: Int32 + public let parts: Int32 + + public private(set) var currentPart: Int32 = 0 + + init(doc: Document) + { + self.doc = doc + self.documentType = doc.getDocumentType() + documentSize = doc.getDocumentSizeAsCGSize() + views = doc.getViewsCount() + parts = doc.getParts() + + doc.registerCallback() { + [weak self] typ, payload in + self?.onDocumentEvent(type: typ, payload: payload) + } + } + + public var isPresentation: Bool + { + return documentType == LOK_DOCTYPE_PRESENTATION + } + public var isText: Bool + { + return documentType == LOK_DOCTYPE_TEXT + } + public var isDrawing: Bool + { + return documentType == LOK_DOCTYPE_DRAWING + } + public var isSpeadsheet: Bool + { + return documentType == LOK_DOCTYPE_SPREADSHEET + } + + /// Gives async access to the document + public func async(_ closure: @escaping (Document) -> ()) + { + LOKitThread.instance.async + { + closure(self.doc) + } + self.invokeHandlers() + } + + public func invokeHandlers() + { + LOKitThread.instance.async + { + self.doc.invokeHandlers() + } + } + + /// Gives sync access to the document - blocks until the closure runs. + /// Careful of deadlocks. + public func sync<R>( _ closure: @escaping (Document) -> R ) -> R + { + return LOKitThread.instance.sync + { + self.invokeHandlers() + return closure(self.doc) + } + } + + /// Paints a tile and return synchronously, using a cached version if it can + public func paintTileToImage(canvasSize: CGSize, + tileRect: CGRect) -> UIImage? + { + if let cached = cache.get(part: currentPart, canvasSize: canvasSize, tileRect: tileRect) + { + return cached + } + + let img = sync { + $0.paintTileToImage(canvasSize: canvasSize, tileRect: tileRect) + } + if let image = img + { + cache.add(cachedRender: CachedRender(part: currentPart, canvasSize: canvasSize, tileRect: tileRect, image: image)) + } + + return img + } + + private func onDocumentEvent(type: LibreOfficeKitCallbackType, payload: String?) + { + print("onDocumentEvent type:\(type) payload:\(payload ?? "")") + + switch type + { + case LOK_CALLBACK_INVALIDATE_TILES: + runOnMain { + self.delegate?.invalidateTiles( rects: decodeRects(payload) ) + } + case LOK_CALLBACK_INVALIDATE_VISIBLE_CURSOR: + runOnMain { + self.delegate?.invalidateVisibleCursor( rects: decodeRects(payload) ) + } + case LOK_CALLBACK_TEXT_SELECTION: + runOnMain { + self.delegate?.textSelection( rects: decodeRects(payload) ) + } + case LOK_CALLBACK_TEXT_SELECTION_START: + runOnMain { + self.delegate?.textSelectionStart( rects: decodeRects(payload) ) + } + case LOK_CALLBACK_TEXT_SELECTION_END: + runOnMain { + self.delegate?.textSelectionEnd( rects: decodeRects(payload) ) + } + + case LOK_CALLBACK_SEARCH_NOT_FOUND: + runOnMain { + self.searchDelegate?.searchNotFound() + } + case LOK_CALLBACK_SEARCH_RESULT_SELECTION: + runOnMain { + self.searchResults(payload: payload) + } + + case LOK_CALLBACK_SET_PART: + if let p = payload, let newPart = Int32(p) + { + self.currentPart = newPart + // TODO: callback? + } + + default: + print("onDocumentEvent type:\(type) not handled!") + } + } + + private func searchResults(payload: String?) + { + if let d = payload, let data = d.data(using: .utf8) + { + let decoder = JSONDecoder() + do + { + let searchResults = try decoder.decode(SearchResults.self, from: data ) + self.searchDelegate?.searchResultSelection(searchResults: searchResults) + } + catch + { + print("Error decoding payload: \(error)") + } + } + } + + public func search(searchString: String, forwardDirection: Bool = true, from: CGPoint) + { + var rootJson = JSONObject() + addProperty(&rootJson, "SearchItem.SearchString", "string", searchString); + addProperty(&rootJson, "SearchItem.Backward", "boolean", String(!forwardDirection) ); + addProperty(&rootJson, "SearchItem.SearchStartPointX", "long", String(describing: from.x) ); + addProperty(&rootJson, "SearchItem.SearchStartPointY", "long", String(describing: from.y) ); + addProperty(&rootJson, "SearchItem.Command", "long", "0") // String.valueOf(0)); // search all == 1 + + if let jsonStr = encode(json: rootJson) + { + async { + $0.postUnoCommand(command: ".uno:ExecuteSearch", arguments: jsonStr, notifyWhenFinished: true) + } + } + } + + public func incrementPart(amount: Int32) + { + async { + document in + let currentPart = document.getPart() + let numParts = document.getParts() + let newPart = currentPart + amount + if (newPart < numParts && newPart > 0) + { + document.setPart(nPart: newPart) + } + } + } +} + +public typealias JSONObject = Dictionary<String, AnyObject> +public func addProperty( _ json: inout JSONObject, _ parentValue: String, _ type: String, _ value: String) +{ + var child = JSONObject(); + child["type"] = type as AnyObject + child["value"] = value as AnyObject + json[parentValue] = child as AnyObject +} + +func encode(json: JSONObject) -> String? +{ + if let data = try? JSONSerialization.data(withJSONObject: json, options: .prettyPrinted) + { + return String(data: data, encoding: String.Encoding.utf8) + } + return nil +} + +/// Decodes a series of rectangles in the form: "x, y, width, height; x, y, width, height" +public func decodeRects(_ payload: String?) -> [CGRect]? +{ + guard var pl = payload else { return nil } + pl = pl.trimmingCharacters(in: .whitespacesAndNewlines ) + if pl == "EMPTY" || pl.count == 0 + { + return nil + } + var ret = [CGRect]() + for rectStr in pl.split(separator: ";") + { + let coords = rectStr.split(separator: ",").flatMap { Double($0.trimmingCharacters(in: .whitespacesAndNewlines)) } + if coords.count == 4 + { + let rect = CGRect(x: coords[0], + y: coords[1], + width: coords[2], + height: coords[3]) + ret.append( rect ) + } + } + return ret +} + +// MARK :- Delegates + +public protocol DocumentUIDelegate: class +{ + func invalidateTiles(rects: [CGRect]? ) + func invalidateVisibleCursor(rects: [CGRect]? ) + + func textSelection(rects: [CGRect]? ) + func textSelectionStart(rects: [CGRect]? ) + func textSelectionEnd(rects: [CGRect]? ) +} + +public protocol SearchDelegate: class +{ + func searchNotFound() + func searchResultSelection(searchResults: SearchResults) +} + +/** + Encodes this example json: + { + "searchString": "Office", + "highlightAll": "true", + "searchResultSelection": [ + { + "part": "0", + "rectangles": "1951, 10743, 627, 239" + }, + { + "part": "0", + "rectangles": "5343, 9496, 627, 287" + }, + { + "part": "0", + "rectangles": "1951, 9256, 627, 239" + }, + { + "part": "0", + "rectangles": "6502, 5946, 626, 287" + }, + { + "part": "0", + "rectangles": "6686, 5658, 627, 287" + }, + { + "part": "0", + "rectangles": "4103, 5418, 573, 239" + }, + { + "part": "0", + "rectangles": "1951, 5418, 627, 239" + }, + { + "part": "0", + "rectangles": "4934, 1658, 1586, 559" + } + ] + } + */ +public struct SearchResults: Codable +{ + public var searchString: String? + public var highlightAll: String? + public var searchResultSelection: Array<PartAndRectangles>? +} + +public struct PartAndRectangles: Codable +{ + public var part: String? + public var rectangles: String? + + public var rectsAsCGRects: [CGRect]? { + return decodeRects(self.rectangles) + } +} + diff --git a/ios/LibreOfficeLight/LibreOfficeLight/LOKit/LOKitThread.swift b/ios/LibreOfficeLight/LibreOfficeLight/LOKit/LOKitThread.swift index e8f60e0f2119..8e3607612a6b 100644 --- a/ios/LibreOfficeLight/LibreOfficeLight/LOKit/LOKitThread.swift +++ b/ios/LibreOfficeLight/LibreOfficeLight/LOKit/LOKitThread.swift @@ -119,280 +119,6 @@ public class LOKitThread } } - -open class CachedRender -{ - open let canvasSize: CGSize - open let tileRect: CGRect - open let image: UIImage - - public init(canvasSize: CGSize, tileRect: CGRect, image: UIImage) - { - self.canvasSize = canvasSize - self.tileRect = tileRect - self.image = image - } -} - -class RenderCache -{ - let CACHE_LOWMEM = 4 - let CACHE_NORMAL = 20 - - var cachedRenders: [CachedRender] = [] - var hasReceivedMemoryWarning = false - - let lock = NSRecursiveLock() - - func emptyCache() - { - lock.lock(); defer { lock.unlock() } - - cachedRenders.removeAll() - - } - - func pruneCache() - { - lock.lock(); defer { lock.unlock() } - - let max = hasReceivedMemoryWarning ? CACHE_LOWMEM : CACHE_NORMAL - while cachedRenders.count > max - { - cachedRenders.remove(at: 0) - } - } - - func add(cachedRender: CachedRender) - { - lock.lock(); defer { lock.unlock() } - - cachedRenders.append(cachedRender) - pruneCache() - } - - func get(canvasSize: CGSize, tileRect: CGRect) -> UIImage? - { - lock.lock(); defer { lock.unlock() } - - if let cr = cachedRenders.first(where: { $0.canvasSize == canvasSize && $0.tileRect == tileRect }) - { - return cr.image - } - return nil - } - -} - -/** - * Holds the document object so to enforce access in a thread safe way. - */ -public class DocumentHolder -{ - private let doc: Document - - public weak var delegate: DocumentUIDelegate? = nil - public weak var searchDelegate: SearchDelegate? = nil - - private let cache = RenderCache() - - init(doc: Document) - { - self.doc = doc - doc.registerCallback() { - [weak self] typ, payload in - self?.onDocumentEvent(type: typ, payload: payload) - } - } - - /// Gives async access to the document - public func async(_ closure: @escaping (Document) -> ()) - { - LOKitThread.instance.async - { - closure(self.doc) - } - self.invokeHandlers() - } - - public func invokeHandlers() - { - LOKitThread.instance.async - { - self.doc.invokeHandlers() - } - } - - /// Gives sync access to the document - blocks until the closure runs. - /// Careful of deadlocks. - public func sync<R>( _ closure: @escaping (Document) -> R ) -> R - { - return LOKitThread.instance.sync - { - self.invokeHandlers() - return closure(self.doc) - } - } - - /// Paints a tile and return synchronously, using a cached version if it can - public func paintTileToImage(canvasSize: CGSize, - tileRect: CGRect) -> UIImage? - { - if let cached = cache.get(canvasSize: canvasSize, tileRect: tileRect) - { - return cached - } - - let img = sync { - $0.paintTileToImage(canvasSize: canvasSize, tileRect: tileRect) - } - if let image = img - { - cache.add(cachedRender: CachedRender(canvasSize: canvasSize, tileRect: tileRect, image: image)) - } - - return img - } - - - private func onDocumentEvent(type: LibreOfficeKitCallbackType, payload: String?) - { - print("onDocumentEvent type:\(type) payload:\(payload ?? "")") - - switch type - { - case LOK_CALLBACK_INVALIDATE_TILES: - runOnMain { - self.delegate?.invalidateTiles( rects: decodeRects(payload) ) - } - case LOK_CALLBACK_INVALIDATE_VISIBLE_CURSOR: - runOnMain { - self.delegate?.invalidateVisibleCursor( rects: decodeRects(payload) ) - } - case LOK_CALLBACK_TEXT_SELECTION: - runOnMain { - self.delegate?.textSelection( rects: decodeRects(payload) ) - } - case LOK_CALLBACK_TEXT_SELECTION_START: - runOnMain { - self.delegate?.textSelectionStart( rects: decodeRects(payload) ) - } - case LOK_CALLBACK_TEXT_SELECTION_END: - runOnMain { - self.delegate?.textSelectionEnd( rects: decodeRects(payload) ) - } - - case LOK_CALLBACK_SEARCH_NOT_FOUND: - runOnMain { - self.searchDelegate?.searchNotFound() - } - case LOK_CALLBACK_SEARCH_RESULT_SELECTION: - runOnMain { - self.searchResults(payload: payload) - } - - - default: - print("onDocumentEvent type:\(type) not handled!") - } - } - - private func searchResults(payload: String?) - { - if let d = payload, let data = d.data(using: .utf8) - { - let decoder = JSONDecoder() - - do - { - let searchResults = try decoder.decode(SearchResults.self, from: data ) - - /* - if let srs = searchResults.searchResultSelection - { - for par in srs - { - print("\(par.rectsAsCGRects)") - } - } - */ - - self.searchDelegate?.searchResultSelection(searchResults: searchResults) - } - catch - { - print("Error decoding payload: \(error)") - } - - } - } - - public func search(searchString: String, forwardDirection: Bool = true, from: CGPoint) - { - var rootJson = JSONObject() - - addProperty(&rootJson, "SearchItem.SearchString", "string", searchString); - addProperty(&rootJson, "SearchItem.Backward", "boolean", String(!forwardDirection) ); - addProperty(&rootJson, "SearchItem.SearchStartPointX", "long", String(describing: from.x) ); - addProperty(&rootJson, "SearchItem.SearchStartPointY", "long", String(describing: from.y) ); - addProperty(&rootJson, "SearchItem.Command", "long", "0") // String.valueOf(0)); // search all == 1 - - if let jsonStr = encode(json: rootJson) - { - async { - $0.postUnoCommand(command: ".uno:ExecuteSearch", arguments: jsonStr, notifyWhenFinished: true) - } - } - } - - -} - -public typealias JSONObject = Dictionary<String, AnyObject> -public func addProperty( _ json: inout JSONObject, _ parentValue: String, _ type: String, _ value: String) -{ - var child = JSONObject(); - child["type"] = type as AnyObject - child["value"] = value as AnyObject - json[parentValue] = child as AnyObject -} - -func encode(json: JSONObject) -> String? -{ - //let encoder = JSONEncoder() - - if let data = try? JSONSerialization.data(withJSONObject: json, options: .prettyPrinted) - { - return String(data: data, encoding: String.Encoding.utf8) - } - return nil -} - -/// Decodes a series of rectangles in the form: "x, y, width, height; x, y, width, height" -public func decodeRects(_ payload: String?) -> [CGRect]? -{ - guard var pl = payload else { return nil } - pl = pl.trimmingCharacters(in: .whitespacesAndNewlines ) - if pl == "EMPTY" || pl.count == 0 - { - return nil - } - var ret = [CGRect]() - for rectStr in pl.split(separator: ";") - { - let coords = rectStr.split(separator: ",").flatMap { Double($0.trimmingCharacters(in: .whitespacesAndNewlines)) } - if coords.count == 4 - { - let rect = CGRect(x: coords[0], - y: coords[1], - width: coords[2], - height: coords[3]) - ret.append( rect ) - } - } - return ret -} - /** * Delegate methods for global events emitted from LOKit. * Mostly dispatched on the main thread unless noted. @@ -405,85 +131,9 @@ public protocol LOKitUIDelegate: class public protocol ProgressDelegate: class { func statusIndicatorStart() - func statusIndicatorFinish() - func statusIndicatorSetValue(value: Double) } -public protocol DocumentUIDelegate: class -{ - func invalidateTiles(rects: [CGRect]? ) - - func invalidateVisibleCursor(rects: [CGRect]? ) - - func textSelection(rects: [CGRect]? ) - func textSelectionStart(rects: [CGRect]? ) - func textSelectionEnd(rects: [CGRect]? ) -} -public protocol SearchDelegate: class -{ - func searchNotFound() - - func searchResultSelection(searchResults: SearchResults) -} - -/** - Encodes this example json: - { - "searchString": "Office", - "highlightAll": "true", - "searchResultSelection": [ - { - "part": "0", - "rectangles": "1951, 10743, 627, 239" - }, - { - "part": "0", - "rectangles": "5343, 9496, 627, 287" - }, - { - "part": "0", - "rectangles": "1951, 9256, 627, 239" - }, - { - "part": "0", - "rectangles": "6502, 5946, 626, 287" - }, - { - "part": "0", - "rectangles": "6686, 5658, 627, 287" - }, - { - "part": "0", - "rectangles": "4103, 5418, 573, 239" - }, - { - "part": "0", - "rectangles": "1951, 5418, 627, 239" - }, - { - "part": "0", - "rectangles": "4934, 1658, 1586, 559" - } - ] - } -*/ -public struct SearchResults: Codable -{ - public var searchString: String? - public var highlightAll: String? - public var searchResultSelection: Array<PartAndRectangles>? -} - -public struct PartAndRectangles: Codable -{ - public var part: String? - public var rectangles: String? - - public var rectsAsCGRects: [CGRect]? { - return decodeRects(self.rectangles) - } -} diff --git a/ios/LibreOfficeLight/LibreOfficeLight/LOKit/RenderCache.swift b/ios/LibreOfficeLight/LibreOfficeLight/LOKit/RenderCache.swift new file mode 100644 index 000000000000..f217db0414a6 --- /dev/null +++ b/ios/LibreOfficeLight/LibreOfficeLight/LOKit/RenderCache.swift @@ -0,0 +1,78 @@ +// +// This file is part of the LibreOffice project. +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. +// + +import Foundation +import UIKit + + +open class CachedRender +{ + open let part: Int32 + open let canvasSize: CGSize + open let tileRect: CGRect + open let image: UIImage + + public init(part: Int32, canvasSize: CGSize, tileRect: CGRect, image: UIImage) + { + self.canvasSize = canvasSize + self.tileRect = tileRect + self.image = image + self.part = part + } +} + +class RenderCache +{ + let CACHE_LOWMEM = 4 + let CACHE_NORMAL = 20 + + var cachedRenders: [CachedRender] = [] + var hasReceivedMemoryWarning = false + + let lock = NSRecursiveLock() + + func emptyCache() + { + lock.lock(); defer { lock.unlock() } + cachedRenders.removeAll() + } + + func pruneCache() + { + lock.lock(); defer { lock.unlock() } + + let max = hasReceivedMemoryWarning ? CACHE_LOWMEM : CACHE_NORMAL + while cachedRenders.count > max + { + cachedRenders.remove(at: 0) + } + } + + func add(cachedRender: CachedRender) + { + lock.lock(); defer { lock.unlock() } + + cachedRenders.append(cachedRender) + pruneCache() + } + + func get(part: Int32, canvasSize: CGSize, tileRect: CGRect) -> UIImage? + { + lock.lock(); defer { lock.unlock() } + + if let cr = cachedRenders.first(where: { + $0.canvasSize == canvasSize + && $0.tileRect == tileRect + && $0.part == part + }) + { + return cr.image + } + return nil + } +} |