Move | Duplicate| Replicate selected records [+LaunchBar]

I have keyboard shortcuts assigned to this script and two variants of it (edit “Move” in the first line to “Duplicate” or “Replicate” to determine the script’s behaviour).

The built-in equivalents are fine, but I find it quicker to shun the mouse where possible, and prefer to type a few characters to specify the target.



property pMove : "Move"
property pDup : "Duplicate"
property pRep : "Replicate"

property pDefaultVerb : pMove -- Default verb: may be switched to Duplicate (pDup) or Replicate (pRep) at run-time

-- ver 1.0		Allows double dot (..) as short-hand for next group up in the hierarchy
--				(to facilitate moving selected records up one level in the group hierarchy)
-- ver 0.9		Fixed a big involving search strings which include a space
-- ver 0.8		Clarified the Growl message: bold header: [Verb]d [N] records to:, body:[Destination]/n[List of records]
-- ver 0.7		Enhanced the "Group not Found" message for replication to remind user that replication
--				is only possible within a single database.
-- ver 0.6		The default verb (Move) can be overridden by prefixing the search string with "d " or "r "
--				(for "Duplicate" or "Replicate") in LaunchBar or the standard dialog
--				(a single verb letter followed by a single space before the search string itself)
-- ver 0.5		Can get the search string from LaunchBar
-- ver 0.4		Allows target databases (where their names match) as well as target folders
--				(the default incoming group of the database is used as the destination)

property pVer : "0.9"
property pTitle : "  to target group" & "    " & pVer

property pstrDelim : "    "

on run
	set lstSelns to GetDevnSelns()
	if lstSelns ≠ {} then
		set strNames to GetNameList(lstSelns)
		set strCmd to GetSearchString(pDefaultVerb, strNames)
		if strCmd ≠ "" then
			set {strVerb, strFind} to ParseCmd(strCmd)
			if strFind ≠ "" then ProcessSelns(lstSelns, strNames, strVerb, strFind)
		end if
	end if
end run

-- GET THE TARGET GROUP SEARCH STRING FROM LAUNCHBAR
on handle_string(strCmd)
	if strCmd ≠ "" then
		set lstSelns to GetDevnSelns()
		if lstSelns ≠ {} then
			set {strVerb, strFind} to ParseCmd(strCmd)
			if strFind ≠ "" then
				set strNames to GetNameList(lstSelns)
				ProcessSelns(lstSelns, strNames, strVerb, strFind)
			end if
		end if
	end if
end handle_string

on ParseCmd(strCmd)
	set {strVerb, strFind} to {pDefaultVerb, ""}
	set text item delimiters to space
	set lstParts to text items of strCmd
	if length of lstParts > 1 then
		set strFirst to first item of lstParts
		ignoring case
			if strFirst is in {"m", "d", "r"} then
				if strFirst = "m" then
					set strVerb to pMove
				else if strFirst = "d" then
					set strVerb to pDup
				else if strFirst = "r" then
					set strVerb to pRep
				end if
				set strFind to (items 2 thru end of lstParts) as string
			else
				set strFind to strCmd
			end if
		end ignoring
	else
		set strFind to strCmd
	end if
	{strVerb, strFind}
end ParseCmd

on ProcessSelns(lstSelns, strNames, strVerb, strFind)
	-- CHECK THAT A KNOWN VERB IS SPECIFIED AT THE TOP OF THE SCRIPT
	if strVerb is not in {pMove, pDup, pRep} then
		tell application id "com.apple.systemevents"
			activate
			display dialog "\"" & strVerb & "\": unknown value for pDefaultVerb" & return & return & "Expected Move | Duplicate | Replicate"
		end tell
		return
	end if
	
	tell application id "com.devon-technologies.thinkpro2"
		-- GET FILTERED SET OF POSSIBLE DESTINATIONS
		if strVerb ≠ pRep then
			-- BOTH OPEN DATABASES WITH MATCHING NAMES
			set lstParent to (incoming group of databases where name contains strFind)
			
			-- AND FOLDERS WITH MATCHING NAMES
			repeat with oDb in databases
				set lstParent to lstParent & (parents of oDb where name contains strFind)
			end repeat
		else -- OR, IN THE CASE OF REPLICATION, IN CURRENT DATABASE 
			set lstParent to (parents of current database where name contains strFind)
		end if
		
		-- ALLOW FOR DOUBLE DOT (..) AS REFERENCE TO NEXT GROUP UP IN THE HIERARCHY
		if strFind = ".." then
			set oSelnParent to first item of (parents of (first item of lstSelns))
			set lstGrandParents to parents of oSelnParent
			if lstGrandParents ≠ {} then set end of lstParent to first item of lstGrandParents
		end if
		
		-- BUILD NUMBERED MENU OF DESTINATION NAMES
		set lngParents to length of lstParent
		if lngParents > 0 then
			set lngDigits to length of (lngParents as string)
			set lstMenu to {}
			repeat with i from 1 to lngParents
				tell item i of lstParent
					set strName to (name of its database) & its location & its name
				end tell
				if strName = "Inbox/Inbox" then set strName to "Global Inbox"
				set end of lstMenu to my PadNum(i, lngDigits) & pstrDelim & strName
			end repeat
			
			-- CHOOSE DESTINATION
			tell application id "com.apple.systemevents"
				activate
				set varTargets to choose from list lstMenu with title strVerb & pTitle with prompt strVerb & ":
			
" & strNames & "

to target group:" default items {first item of lstMenu} without multiple selections allowed
			end tell
			if varTargets is not false then
				
				-- 	RETRIEVE CHOSEN DESTINATION BY NUMERIC INDEX
				set my text item delimiters to space
				set oTarget to item ((first text item of (first item of varTargets)) as integer) of lstParent
				
				-- MOVE | DUPLICATE | REPLICATE SELECTED ITEMS TO DESTINATION
				if strVerb = pMove then
					repeat with oRec in lstSelns
						move record oRec to oTarget
					end repeat
				else if strVerb = pDup then
					repeat with oRec in lstSelns
						duplicate record oRec to oTarget
					end repeat
				else if strVerb = pRep then
					repeat with oRec in lstSelns
						replicate record oRec to oTarget
					end repeat
				end if
				
				-- NOTIFY RESULT BY GROWL (IF INSTALLED) OR WITH DIALOG BOX
				my Announce(oTarget, strVerb, (length of lstSelns), strNames)
				open window for record oTarget
				activate
			end if
		else
			if strVerb ≠ pRep then
				set strRepNote to ""
			else
				set strRepNote to "
				
(If you were attempting to replicate to another open database, note that DevonThink only supports replication within a single database)"
			end if
			tell application id "com.apple.systemevents"
				activate
				display dialog "No open groups match:" & "
	
		" & strFind & strRepNote buttons {"OK"} default button "OK" with title strVerb & pTitle
			end tell
		end if
	end tell
end ProcessSelns

on GetDevnSelns()
	tell application id "com.devon-technologies.thinkpro2"
		return selection
	end tell
end GetDevnSelns

on GetSearchString(strVerb, strNames)
	set strFind to ""
	-- GET  SUB-STRING TO FILTER LIST OF TARGETS
	tell application id "com.apple.systemevents"
		activate
		set strFind to text returned of (display dialog strVerb & ":" & return & return & strNames & return & ¬
			"Enter search string to list target groups:" default answer "" buttons {"Cancel", "OK"} default button "OK" cancel button "Cancel" with title strVerb & pTitle)
	end tell
	return strFind
end GetSearchString

on GetNameList(lstSelns)
	-- BUILD BULLETED LIST OF SELECTION NAMES
	set strNames to ""
	repeat with i from 1 to count of lstSelns
		set strNames to strNames & "• " & name of (item i of lstSelns) & return
	end repeat
	return strNames
end GetNameList

-- REPORT RESULT
on Announce(oGroup, strVerb, lngRecords, strNames)
	tell application id "com.devon-technologies.thinkpro2" to set {strDb, strPath, strFolder} to {name of database, location, name} of oGroup
	set strGrowlTitle to strVerb & "d " & (lngRecords as string) & " records to:"
	set strGrowlBody to strDb & strPath & strFolder & "
	
" & strNames
	
	tell application id "com.apple.systemevents"
		-- USE GROWL IF IT'S RUNNING
		if (count of (every process whose name is "GrowlHelperApp")) > 0 then
			tell application id "com.Growl.GrowlHelperApp"
				register as application "houthakker scripts" all notifications {pTitle} default notifications {pTitle} icon of application "DEVONthink Pro"
				notify with name pTitle title strGrowlTitle application name "houthakker scripts" description strGrowlBody
			end tell
		else
			-- (OR USE A STANDARD DIALOG)
			tell application id "com.apple.systemevents"
				activate
				display dialog strReport & "
				
(This script works best if Growl is installed)" buttons {"OK"} with title pTitle
			end tell
		end if
	end tell
end Announce

-- GET A DIGIT STRING OF MINIMUM WIDTH (LEFT-PADDING WITH ZEROS WHERE NEEDED)
on PadNum(lngNum, lngDigits)
	set strNum to lngNum as string
	set lngGap to (lngDigits - (length of strNum))
	repeat while lngGap > 0
		set strNum to "0" & strNum
		set lngGap to lngGap - 1
	end repeat
	strNum
end PadNum

Very nice. A few notes:

It’s not possible to replicate items between databases, only within a database. If the script is configured to replicate, it should probably not present target choices outside the current database.

The script might be handier if we didn’t have to change the code to change its mode. Maybe using a dialog such as this:

property pVerb : "null"
set pVerb to the button returned of (display dialog "What do you want to do" buttons {"Move", "Duplicate", "Replicate"} default button "Duplicate")

This is not elegant because there’s no Cancel option (AppleScript limits buttons to 3). A better solution could be made with AppleScript Studio - but I’m too lazy to do that. :confused:

I notice that if the parent of the group where the source document is located satisfies the search criteria, it isn’t shown on the list. For example, a document in a subgroup of the Global Inbox. If the search criteria is “Inbox”, the Global Inbox isn’t offered as a choice.

Good point. Script amended above to offer only destinations in the current database if the verb is “Replicate”

I can see the argument for making this one single script with an additional stage of choosing the verb. Personally I find it faster to have three different scripts with three different keyboard shortcuts, and thereby skip an extra dialog, but anyone is very welcome to tailor the source to their own work-flow :wink:

I think you may find that there is no problem with listing or moving (etc) to parents, but you are quite right that the Global Inbox is a special case. Under the hood/bonnet It’s actually a database in its own right (“Inbox”), rather than a folder named “Global Inbox”.

I don’t seem to find myself moving material back into the Global Inbox, so I haven’t added a work-around to make it figure in lists of folders filtered by the presence of the string “inbox” in their name. Could certainly be done if it seemed useful …

[Edited note: see below]

Ver 0.4 (updated at the start of this thread) will now list the Global Inbox (or any other open database) as a potential destination (as long as its name contains the string typed by the user).

When a database (rather than a specific group) is chosen as the destination, the target group, usually the Inbox of the database, will be determined by the value of that database’s incoming group property.

Thanks to Korm for prompting this edit.

I have edited the code at the top of this thread so that it can get its search string from LaunchBar

From Ver 0.6 (at the start of this thread)

The default verb (e.g “Move” - defined by pVerb at the top of the script) can now be over-ridden by prefixing the search string with a single letter code and a space.

  • m (move)
  • d (duplicate)
  • r (replicate)

(This works both with the standard dialog and with strings supplied to LaunchBar).

If the default verb is “Move”, using "d " for duplicate before the string works (in Launchbar), but using the "r " prefix generates an error message that no such group is open. If I change the default verb to “replicate” the "d " and "m " prefixes work as expected. The only problem seems to come if default verb is Move and I try the r prefix

Thank you for your report - I appreciate your taking the time.

When replication is the verb the group search is limited to the current database. (DevonThink doesn’t support replication spanning across different databases).

I can quite understand that it must have been puzzling to simply get a “not found” message when you tried to specify a replication target in another open database, and in Ver 0.7 above, I have added a line to the message, reminding the user that replication is only possible within a single database.

(The "r " prefix works fine, of course, if your search string matches any groups within the same database as the selected record(s))

DOH! Of course (he says slapping his forehead)! I was using the script, which is FANTASTIC, to handle items from the global inbox, and those can’t be replicated.

Sorry to have taken your time with something that should have been obvious - even as I was writing the post, I was saying to myself, this script is so terrific that the author can’t possibly have made a mistake this simple, and of course, you didn’t!

Thanks again for this wonderful addition to my workflow.

Good! I’m glad it’s useful.

Ver 1.0 (above):

  • Improves the Growl notification,
  • corrects handling of search strings which include a space,
  • and introduces double period (…) as a shorthand search string, referring to the next group up in the hierarchy. (For quickly moving the selected record(s) up one level, to the group enclosing their current group).

I am dying to see a way to use Launchbar with DTPO.
However, I am unable to copy the code and save it for some reason, and I have never learnt scripting myself. Is it possible to ask for some guidance on how to save this script and use it with Launchbar?

PS: Thanks for excellent scripts on previous occasions!

Scripts needs to be added to LaunchBar’s index, (and I prefer to give them two-letter abbreviations).

To see the LaunchBar index, run LaunchBar itself and choose Index > Show Index

If the sidebar does not include a folder in which you would like to save the script e.g.
/Users/[username]/Library/Scripts
then you can add such a folder with the plus icon at the bottom of LaunchBar’s sidebar.

INSTALLATION

  1. Save the script file (from Applescript Editor) to a name like DTMove.scpt, in the LaunchBar-indexed folder of your choice,
  2. Update LaunchBar’s index of that folder, (Update button, or Index > Update Index),
  3. Consult “Assigning Abbreviations” in LaunchBar help.

USAGE

  1. Select record(s) in DevonThink,
  2. Select script in LaunchBar, and tap spacebar, to enter parameter(s),
  3. Type part of the name of a destination group, optionally preceded by d or r and a space if you want to duplicate or replicate the records rather than moving them,
  4. Hit enter, and confirm the choice of target group if the script finds more than one match.

Thanks a lot for your detailed description.

It turned out, however, that my trouble was caused by me using Lion and the new default setting that hides the library and in fact prohibits saving to my script folders. After realising that I felt a bit less green… I found after some googling that one has to add the command <chflags nohidden ~/Library/> in Terminal before you can save to script folders in ~Library.
The two first times I tried the script it caused Launchbar to crash, but it has worked fine now for some time. Seems Launchbar must get accustomed to it, somehow, but I am running a first version of Lion as mentioned.
One comment from using it that could be of interest to you is that if you replicate a record one or several times with the script and then move the same record (not typing r+space before target group), all replicates previously added by the script are deleted.

Thanks again for your great work and for your help!

This is a wonderful idea, but I need a little help using it, I’m afraid. I know these are dumb questions, but since I don’t use LaunchBar, I have to ask:

How and where do I install the script?
Do I need to change it because I don’t use LaunchBar?

Thanks–and thanks for doing this!!!

P.S.: Still on Snow Leopard, if that makes a difference.

Use of LaunchBar is entirely optional - the script will work unmodified without it.

You can experiment with running it from the Applescript Editor to see how it works, and then install it in any script folder.

See Help > DevonThink Pro Office Help > Scripts & Third-Party apps for the various installation options.

Done! Thanks so much for this.

Thanks for this great script! However, after I move something into a specific group the group window will open. Is it possible to tell the script not to do that?

I still like the growl notification as a result dialog but I don’t need the destination group window opening.

Thanks for your help!

You should be able to do this by searching for the point in the script which calls the notification, and commenting out the line open window for record oTarget by preceding it with a couple of hyphens, so that it reads like this:

-- NOTIFY RESULT BY GROWL (IF INSTALLED) OR WITH DIALOG BOX
my Announce(oTarget, strVerb, (length of lstSelns), strNames)
-- open window for record oTarget

@houthakker
Many thanks. That worked great!

@houthakker-

Simply awesome! I love using LaunchBar, and I don’t like the process for moving/duplicating/replicating within DTPO, so this is a big win-win for me. Much thanks, and appreciation for your fine-tuning.

I have a couple of quick questions.

I am on Lion. When I move/duplicate/replicate a file, it works perfectly. However:

  1. The new group window doesn’t open.
  2. DTP loses focus, so even though ‘Devonthink…’ is still visible in the top left corner, I have to click once within the window to be able to take any action after that, and
  3. I don’t get a Growl notification.

Is there anything I need to check or modify within the script to change these items? I have noticed that when the dialog box opens for the search term, it is labeled as version 0.9, if that is helpful at all.

Also, do you use LB to search within DTP, by any chance?

Cheers!