PDF viewer navigation improvements

Hi all,

I’ve almost entirely found myself using the DTP built-in PDF viewer, but there are some very annoying navigation-based issues I’d like to raise, and hopefully have addressed.

The Move tool has a bug where up/down movement is reversed. I think this is just a silly bug. To reproduce, select the Move tool and drag the document around. Pretty annoying. (The macOS mouse Natural scrolling setting doesn’t affect this.) I’m using DTP 3.8.2.

Now for the feature requests:

  1. Navigation by the mouse is my number 1 request. I would like to have middle-click+drag to pan the document, Command+Scroll-wheel to zoom in and out. I browse very detailed schematics and zooming in and out and panning should feel like an extension of my arm. Extra bonus points for very smooth, responsive, and natural zooming.
  2. Search highlighter would let me type in a search term and specify a highlight color, and it’d just highlight all the instances in the document. Super bonus points for the existing “Enable operators and wildcards” setting, and more than one search term. It really helps understand the document when I can easily identify labels across the page. I often use the standard search tool for this, and there are two issues: a) it jumps to the first search term instead of just displaying what it found, and b) the highlight color is sometimes very hard to see. An option to flash the found locations in the document would help that (I can imagine a little bell icon button that violently flashes the result highlights for a second).
  3. Go to page currently exists as a shortcut (Command+Option+Shift+P), but I’d really like a quick interactive page indicator somewhere. Also, it’d be amazing if a little label would pop up while you’re scrolling and tell you what page you’re on.

What I really like about the DTP viewer is that it loads and scrolls and searches very quickly. The fewer milliseconds it takes to load, the better I keep my focus.

Thanks for considering!

The next release will change this (assuming that nobody prefers the current behaviour).

Clicking on the page number in the navigation bar is another possibility (and the next release will show the page number also in the editing bar).

Thanks. Nobody likes the current behavior. :grin:

Are the “navigation by mouse” requests reasonable to add?

I didn’t even see the navigation bar, since the editing bar is always showing for me; I clicked around until I figured out what you were talking about. Honestly, the extra click to toggle the navigation bar isn’t going to kill me. Unless you move the page number to a permanent area (like beside the <> back/forward buttons), I wouldn’t bother duplicating the control. Thanks for pointing it out.

Most requests are reasonable and might be added to a future release. But of course our to-do list is long.

That’s why the editing bar will soon show the page number too (as we got a similar request last week).

You could try this script.

If you want to provide multiple search terms delimit them with a pipe |

Term1|Term2|Term3

Note: This script is not tested. I quickly modified another script but this other script was written for a quite different use case. It might work or not. Please test with duplicates.

-- Add highlight annotations

use AppleScript version "2.4"
use framework "Foundation"
use framework "Quartz"
use scripting additions

tell application id "DNtp"
	try
		set theRecords to selected records whose type = PDF document
		if theRecords = {} then error "Please select some PDF records"
		
		set theDialog to display dialog "Separate Search Terms with |" default answer "" buttons {"Cancel", "Ok"} default button 2
		if button returned of theDialog = "Cancel" then return
		set theSearchTerms_string to text returned of theDialog
		if theSearchTerms_string = "" then error "Please provide a serach term"
		set theSearchTerms to script "tid"'s tid(theSearchTerms_string, "|")
		
		set theHighlightDescriptions to my getHighlightDescriptions()
		set theChooseFromListItems to theHighlightDescriptions as list
		set theChoice to choose from list theChooseFromListItems with prompt "Choose a highlight color" default items (item 1 of theChooseFromListItems) with title ""
		if theChoice is false then return
		set theHighlightColorIndex to my getHighlightColorIndex(theHighlightDescriptions, theChoice)
		
		show progress indicator "Adding highlight annotations... " steps (count theRecords) with cancel button
		
		repeat with thisRecord in theRecords
			step progress indicator "... " & (name of thisRecord) as string
			my addHighlightAnnotations(path of thisRecord, theSearchTerms, theHighlightColorIndex)
		end repeat
		
		hide progress indicator
	on error error_message number error_number
		hide progress indicator
		if the error_number is not -128 then display alert "DEVONthink" message error_message as warning
		return
	end try
end tell

on addHighlightAnnotations(thePath, theSearchTerms, theHighlightColorIndex)
	try
		set thePDF to current application's PDFDocument's alloc()'s initWithURL:(current application's |NSURL|'s fileURLWithPath:thePath)
		set theSearchTerms to current application's NSMutableArray's arrayWithArray:theSearchTerms
		
		set theSelectionsArray to current application's NSMutableArray's new()
		
		repeat with i from 0 to ((theSearchTerms's |count|()) - 1)
			set thisSearchTerm to (theSearchTerms's objectAtIndex:i)
			set theResultSelections to (thePDF's findString:thisSearchTerm withOptions:0)
			if theResultSelections's |count|() ≠ 0 then
				set theSelectionsArray to my addSelectionToSelectionsArray(thisSearchTerm, theResultSelections, theSelectionsArray)
			else
				set thisSearchTerm_Components to (thisSearchTerm's componentsSeparatedByString:space)
				repeat with j from 0 to ((thisSearchTerm_Components's |count|()) - 1)
					set theseSearchTerm_Components_1 to (thisSearchTerm_Components's subarrayWithRange:{0, (thisSearchTerm_Components's |count|()) - j})
					set thisSearchTerm_Part_1 to (theseSearchTerm_Components_1's componentsJoinedByString:space)
					set theResultSelections to (thePDF's findString:thisSearchTerm_Part_1 withOptions:0)
					if theResultSelections's |count|() ≠ 0 then
						set theSelectionsArray to my addSelectionToSelectionsArray(thisSearchTerm_Part_1, theResultSelections, theSelectionsArray)
						set thisLocation to (theseSearchTerm_Components_1's |count|())
						set thisLength to (thisSearchTerm_Components's |count|()) - thisLocation
						set theseSearchTerm_Components_2 to (thisSearchTerm_Components's subarrayWithRange:{thisLocation, thisLength})
						set thisSearchTerm_Part_2 to (theseSearchTerm_Components_2's componentsJoinedByString:space)
						set theResultSelections to (thePDF's findString:thisSearchTerm_Part_2 withOptions:0)
						if theResultSelections's |count|() ≠ 0 then
							set theSelectionsArray to my addSelectionToSelectionsArray(thisSearchTerm_Part_2, theResultSelections, theSelectionsArray)
						end if
						exit repeat
					end if
				end repeat
			end if
		end repeat
		
		set theDeeperLookArray to current application's NSMutableArray's new()
		
		repeat with i from 0 to ((theSearchTerms's |count|()) - 1)
			set thisSearchTerm to (theSearchTerms's objectAtIndex:i)
			repeat with j from 0 to ((theSearchTerms's |count|()) - 1)
				set thatSearchTerm to (theSearchTerms's objectAtIndex:j)
				if (thisSearchTerm's containsString:thatSearchTerm) and not (thisSearchTerm's isEqualTo:thatSearchTerm) then
					(theDeeperLookArray's addObject:thatSearchTerm)
				end if
			end repeat
		end repeat
		
		if theHighlightColorIndex < 1 or theHighlightColorIndex > 7 then error "No valid HighlightColor index. Valid: 1-7"
		if current application's id = "com.devon-technologies.think3" then
			set theDefaults to current application's NSUserDefaults's standardUserDefaults()
		else
			set theDefaults to current application's NSUserDefaults's alloc()'s initWithSuiteName:"com.devon-technologies.think3"
		end if
		set theDictionary to (theDefaults's dictionaryRepresentation())'s dictionaryWithValuesForKeys:{"HighlightColor-0", "HighlightColor-1", "HighlightColor-2", "HighlightColor-3", "HighlightColor-4", "HighlightColor-5", "HighlightColor-6"}
		set theColorDictionary to theDictionary's objectForKey:("HighlightColor-" & ((theHighlightColorIndex - 1) as string))
		set theRed to current application's NSNumber's numberWithDouble:((theColorDictionary's valueForKey:"red"))
		set theGreen to current application's NSNumber's numberWithDouble:((theColorDictionary's valueForKey:"green"))
		set theBlue to current application's NSNumber's numberWithDouble:((theColorDictionary's valueForKey:"blue"))
		set theAlpha to current application's NSNumber's numberWithDouble:((theColorDictionary's valueForKey:"alpha"))
		set theHighlightColor to current application's NSColor's colorWithCalibratedRed:theRed green:theGreen blue:theBlue alpha:theAlpha
		set theDocumentAuthor to (theDefaults's dictionaryRepresentation())'s stringForKey:"DocumentAuthor"
		set theAnnotationSubtype to current application's PDFAnnotationSubtypeHighlight
		set theAnnotationProperties to current application's NSMutableDictionary's new()
		(theAnnotationProperties's setObject:theHighlightColor forKey:(current application's PDFAnnotationKeyColor))
		
		repeat with i from 0 to ((theSelectionsArray's |count|()) - 1)
			set thisItem to (theSelectionsArray's objectAtIndex:i)
			set thisSearchTerm to (thisItem's valueForKey:"SearchTerm")
			set thisResultSelection_BoundsForPage to (thisItem's valueForKey:"Selection_BoundsForPage")
			set thisResultSelection_Lines_QuadPointsArray to (thisItem's valueForKey:"Selection_Lines_QuadPoints")
			set createAnnotation to true
			
			if (theDeeperLookArray's containsObject:thisSearchTerm) then
				set thisResultSelection_Page_Label to (thisItem's valueForKey:"Selection_Page_Label")
				set thisSelectionsArray_filtered to (theSelectionsArray's filteredArrayUsingPredicate:(current application's NSPredicate's predicateWithFormat:("self.Selection_Page_Label = " & quoted form of (thisResultSelection_Page_Label as string) & " AND " & "self.SearchTerm CONTAINS " & quoted form of (thisSearchTerm as string) & " AND " & "!self.SearchTerm = " & quoted form of (thisSearchTerm as string))))
				repeat with j from 0 to ((thisSelectionsArray_filtered's |count|()) - 1)
					set thatItem to (thisSelectionsArray_filtered's objectAtIndex:j)
					set thatResultSelection_BoundsForPage to (thatItem's valueForKey:"Selection_BoundsForPage")
					set thatResultSelection_Lines_QuadPointsArray to (thatItem's valueForKey:"Selection_Lines_QuadPoints")
					set intersectsBoundsForPage to current application's NSIntersectsRect(thisResultSelection_BoundsForPage, thatResultSelection_BoundsForPage)
					if intersectsBoundsForPage then
						repeat with k from 0 to ((thisResultSelection_Lines_QuadPointsArray's |count|()) - 1) by 4
							set thisResultSelection_Line_QuadPoints to (thisResultSelection_Lines_QuadPointsArray's subarrayWithRange:{k, 4})
							set thisResultSelection_Line_Bounds to my makeRect(thisResultSelection_Line_QuadPoints)
							repeat with l from 0 to ((thatResultSelection_Lines_QuadPointsArray's |count|()) - 1) by 4
								set thatResultSelection_Line_QuadPoints to (thatResultSelection_Lines_QuadPointsArray's subarrayWithRange:{l, 4})
								set thatResultSelection_Line_Bounds to my makeRect(thatResultSelection_Line_QuadPoints)
								set intersectsBoundsForLine to current application's NSIntersectsRect(thisResultSelection_Line_Bounds, thatResultSelection_Line_Bounds)
								if intersectsBoundsForLine then
									set createAnnotation to false
									exit repeat
								end if
							end repeat
							if intersectsBoundsForLine then exit repeat
						end repeat
						if intersectsBoundsForLine then exit repeat
					end if
				end repeat
			end if
			
			if createAnnotation then
				set thisDate to (current application's NSDate's |date|())
				(theAnnotationProperties's setObject:thisDate forKey:(current application's PDFAnnotationKeyDate))
				set thisAnnotation to (current application's PDFAnnotation's alloc()'s initWithBounds:(thisResultSelection_BoundsForPage) forType:theAnnotationSubtype withProperties:theAnnotationProperties)
				set thisAnnotation_QuadPoints to current application's NSMutableArray's new()
				repeat with i from 0 to ((thisResultSelection_Lines_QuadPointsArray's |count|()) - 1)
					(thisAnnotation_QuadPoints's addObject:(current application's NSValue's valueWithPoint:(thisResultSelection_Lines_QuadPointsArray's objectAtIndex:i)))
				end repeat
				(thisAnnotation's setValue:(thisAnnotation_QuadPoints) forAnnotationKey:(current application's PDFAnnotationKeyQuadPoints))
				if not (theDocumentAuthor's isEqualTo:"") then (thisAnnotation's setUserName:theDocumentAuthor)
				set thisResultSelection_Page to (thisItem's valueForKey:"Selection_Page")
				(thisResultSelection_Page's addAnnotation:thisAnnotation)
			end if
		end repeat
		
		thePDF's writeToFile:thePath
		
	on error error_message number error_number
		activate
		if the error_number is not -128 then display alert "Error: Handler \"addHighlightAnnotations\"" message error_message as warning
		error number -128
	end try
end addHighlightAnnotations



on getHighlightDescriptions()
	try
		if current application's id = "com.devon-technologies.think3" then
			set theDefaults to current application's NSUserDefaults's standardUserDefaults()
		else
			set theDefaults to current application's NSUserDefaults's alloc()'s initWithSuiteName:"com.devon-technologies.think3"
		end if
		set theDictionary to (theDefaults's dictionaryRepresentation())'s dictionaryWithValuesForKeys:{"HighlightDescription-0", "HighlightDescription-1", "HighlightDescription-2", "HighlightDescription-3", "HighlightDescription-4", "HighlightDescription-5", "HighlightDescription-6"}
		set theDictionary_Keys to theDictionary's allKeys()
		set theDictionary_Values to theDictionary's allValues()
		set theArray to current application's NSMutableArray's arrayWithArray:{}
		repeat with i from 0 to ((theDictionary_Keys's |count|()) - 1)
			(theArray's addObject:{|Key|:(theDictionary_Keys's objectAtIndex:i), |Value|:(theDictionary_Values's objectAtIndex:i)})
		end repeat
		set theHighlightDescriptions to ((theArray's sortedArrayUsingDescriptors:{current application's NSSortDescriptor's sortDescriptorWithKey:"Key" ascending:true selector:"compare:"})'s valueForKey:"Value")
	on error error_message number error_number
		activate
		if the error_number is not -128 then display alert "Error: Handler \"getHighlightDescriptions\"" message error_message as warning
		error number -128
	end try
end getHighlightDescriptions

on getHighlightColorIndex(theHighlightDescriptions, theChoice)
	try
		set theHighlightColorIndex to (theHighlightDescriptions's indexOfObject:(item 1 of theChoice)) + 1
	on error error_message number error_number
		activate
		if the error_number is not -128 then display alert "Error: Handler \"getHighlightColorIndex\"" message error_message as warning
		error number -128
	end try
end getHighlightColorIndex

on addSelectionToSelectionsArray(thisSearchTerm, theResultSelections, theSelectionsArray)
	try
		repeat with i from 0 to ((theResultSelections's |count|()) - 1)
			set thisResultSelection to (theResultSelections's objectAtIndex:i)
			set thisResultSelection_Pages to thisResultSelection's pages()
			repeat with j from 0 to ((thisResultSelection_Pages's |count|()) - 1)
				set thisResultSelection_Page to (thisResultSelection_Pages's objectAtIndex:j)
				set thisResultSelection_BoundsForPage to (thisResultSelection's boundsForPage:thisResultSelection_Page)
				set thisResultSelection_Lines to thisResultSelection's selectionsByLine
				set thisResultSelection_Lines_QuadPointsArray to current application's NSMutableArray's new()
				repeat with k from 0 to ((thisResultSelection_Lines's |count|()) - 1)
					set thisResultSelection_Line to (thisResultSelection_Lines's objectAtIndex:k)
					set thisResultSelection_Line_Page to (thisResultSelection_Line's pages())'s firstObject()
					if (thisResultSelection_Line_Page's isEqualTo:thisResultSelection_Page) then
						set thisResultSelection_Line_BoundsForPage to (thisResultSelection_Line's boundsForPage:thisResultSelection_Line_Page)
						set thisResultSelection_Line_BoundsForPage to current application's NSRect's NSInsetRect(thisResultSelection_Line_BoundsForPage, -1, -1) -- DEVONthink recognizes text more reliably
						set MinX to current application's NSRect's NSMinX(thisResultSelection_Line_BoundsForPage)
						set MinY to current application's NSRect's NSMinY(thisResultSelection_Line_BoundsForPage)
						set MaxX to current application's NSRect's NSMaxX(thisResultSelection_Line_BoundsForPage)
						set MaxY to current application's NSRect's NSMaxY(thisResultSelection_Line_BoundsForPage)
						(thisResultSelection_Lines_QuadPointsArray's addObject:{MinX, MaxY})
						(thisResultSelection_Lines_QuadPointsArray's addObject:{MaxX, MaxY})
						(thisResultSelection_Lines_QuadPointsArray's addObject:{MinX, MinY})
						(thisResultSelection_Lines_QuadPointsArray's addObject:{MaxX, MinY})
					end if
				end repeat
				set thisResultSelection_Page_Label to thisResultSelection_Page's label()
				(theSelectionsArray's addObject:{Selection_Page:thisResultSelection_Page, Selection_Page_Label:thisResultSelection_Page_Label, Selection_BoundsForPage:thisResultSelection_BoundsForPage, Selection_Lines_QuadPoints:thisResultSelection_Lines_QuadPointsArray, SearchTerm:thisSearchTerm})
			end repeat
		end repeat
		return theSelectionsArray
	on error error_message number error_number
		activate
		if the error_number is not -128 then display alert "Error: Handler \"addSelectionToSelectionsArray\"" message error_message as warning
		error number -128
	end try
end addSelectionToSelectionsArray

on makeRect(theSelection_QuadPoints)
	try
		set theSelection_QuadPoints to (theSelection_QuadPoints as list)
		set MinX to theSelection_QuadPoints's item 1's item 1
		set MinY to theSelection_QuadPoints's item 3's item 2
		set theWidth to (theSelection_QuadPoints's item 2's item 1) - MinX
		set theHeight to (theSelection_QuadPoints's item 1's item 2) - MinY
		set theRect to current application's NSRect's NSMakeRect(MinX, MinY, theWidth, theHeight)
	on error error_message number error_number
		activate
		if the error_number is not -128 then display alert "Error: Handler \"makeRect\"" message error_message as warning
		error number -128
	end try
end makeRect

on addSearchTerm(theNameWithoutExtension, theSearchTerm, theArray)
	try
		(theArray's addObject:{NameWithoutExtension:theNameWithoutExtension, SearchTerm:theSearchTerm})
		return theArray
	on error error_message number error_number
		activate
		if the error_number is not -128 then display alert "Error: Handler \"addSearchTerm\"" message error_message as warning
		error number -128
	end try
end addSearchTerm

on tid(theInput, theDelimiter)
	set d to AppleScript's text item delimiters
	set AppleScript's text item delimiters to theDelimiter
	if class of theInput = text then
		set theOutput to text items of theInput
	else if class of theInput = list then
		set theOutput to theInput as text
	end if
	set AppleScript's text item delimiters to d
	return theOutput
end tid

1 Like