How Can I Show Progress/Countdown to Next Action In The Menubar Systemwide Applescript - macos

I created an Applescript that every 600 seconds retrieve the last line of a log file and add it to a Google Sheet spreadsheet.
This is extremely useful, but it is starting to annoy me not knowing when the next action will be performed.
So now I'm trying to display the progress (or countdown in seconds) to the next action in the menubar, so I can, kind of, be aware that the focus application will change.
Can please someone point me in the right direction?
--- Added Information ---
Now, instead of showing a countdown, I want to just simply show how many steps left, and it's still not working. This is a test code I'm working on:
set progress total steps to 12
set progress completed steps to 0
set progress description to "Running..."
set progress additional description to "ETA: 120 Seconds"
repeat with a from 1 to 12
delay 10
set progress completed steps to a
set ETA to a * 10
set progress additional description to "ETA: " & ETA & " seconds"
end repeat
What I'm doing wrong?

Based on the link red_menace gave in comments and the screenshot you provided, here's a script that (I think) will give the results you want. It's not an automator workflow. This is an AppleScript stay-open application. Copy it into Script Editor and save is as an application: choose "Application" from the File Format pulldown menu on the save screen, and make sure you check the Stay open after run handler checkbox. then run the application like normal and see the demo.
use framework "AppKit"
use framework "Foundation"
use scripting additions
property ca : current application
property NSStatusBar : class "NSStatusBar"
property NSMenu : class "NSMenu"
property NSMenuItem : class "NSMenuItem"
property NSImage : class "NSImage"
property NSProgressIndicator : class "NSProgressIndicator"
property NSView : class "NSView"
property NSTextField : class "NSTextField"
property idle_time : 1
global status_bar_item, progress_views, idx
on run
set progress_views to {}
set status_bar_item to NSStatusBar's systemStatusBar's statusItemWithLength:(ca's NSSquareStatusItemLength)
set status_bar_item's |menu| to NSMenu's alloc's initWithTitle:""
set status_bar_item's |menu|'s minimumWidth to 220
my setImage(ca's NSImageNameSmartBadgeTemplate)
my addToStatusMenu(my progress_view_obj("Number 1"))
my addToStatusMenu(my progress_view_obj("Number 2"))
set idx to 0
end run
on idle
set idx to idx + 1
repeat with this_obj in progress_views
set rand_numb to (random number from 1 to 9)
(this_obj's progress_indicator's incrementBy:rand_numb)
this_obj's changeLabel("Adding: " & rand_numb)
end repeat
if idx > 20 then
quit
end if
return idle_time
end idle
on quit
-- remove status item and quit
NSStatusBar's systemStatusBar's removeStatusItem:status_bar_item
continue quit
end quit
on addToStatusMenu(obj)
set statusMenu to status_bar_item's |menu|
statusMenu's addItem:(obj's menu_item)
set end of progress_views to obj
end addToStatusMenu
on progress_view_obj(label_value)
script prog_view_obj
-- text field
property progress_indicator : missing value
property menu_item : missing value
property label_field : missing value
global content_view
-- make label field
set label_field to NSTextField's labelWithString:label_value
set label_field's translatesAutoresizingMaskIntoConstraints to false
-- progress indicator
set progress_indicator to NSProgressIndicator's alloc's initWithFrame:(ca's NSMakeRect(0, 0, 200, 20))
set progress_indicator's indeterminate to false
progress_indicator's setStyle:(ca's NSProgressIndicatorStyleBar)
progress_indicator's startAnimation:me
set progress_indicator's translatesAutoresizingMaskIntoConstraints to false
-- content view for menu item
set content_view to NSView's alloc's initWithFrame:(ca's NSMakeRect(0, 0, 200, 100))
set content_view's translatesAutoresizingMaskIntoConstraints to false
content_view's addSubview:label_field
content_view's addSubview:progress_indicator
-- constraints to arrange elements
--set view_height_constraint to content_view's heightAnchor's constraintEqualToConstant:100
--view_height_constraint's setActive:true
set lf_left_margin to content_view's leadingAnchor's constraintEqualToAnchor:(label_field's leadingAnchor) |constant|:-10
lf_left_margin's setActive:true
set pi_left_margin to content_view's leadingAnchor's constraintEqualToAnchor:(progress_indicator's leadingAnchor) |constant|:-10
pi_left_margin's setActive:true
set pi_right_margin to content_view's trailingAnchor's constraintEqualToAnchor:(progress_indicator's trailingAnchor) |constant|:10
pi_left_margin's setActive:true
set top_spacing_const to content_view's topAnchor's constraintEqualToAnchor:(label_field's topAnchor) |constant|:-10
set mid_spacing_const to label_field's bottomAnchor's constraintEqualToAnchor:(progress_indicator's topAnchor) |constant|:-10
set bott_spacing_const to content_view's bottomAnchor's constraintGreaterThanOrEqualToAnchor:(progress_indicator's bottomAnchor) |constant|:10
top_spacing_const's setActive:true
mid_spacing_const's setActive:true
bott_spacing_const's setActive:true
content_view's updateConstraints()
-- create and flesh out menu item
set menu_item to NSMenuItem's alloc's init()
set menu_item's view to content_view
content_view's updateConstraints()
on changeLabel(label_value)
set label_field's stringValue to label_value
end changeLabel
on changeMinValue(val)
set progress_indicator's minValue to val
end changeMinValue
on changeMaxValue(val)
set progress_indicator's maxValue to val
end changeMaxValue
on changeValue(val)
set progress_indicator's doubleValue to val
end changeValue
on currentValue()
return progress_indicator's doubleValue() as integer
end currentValue
end script
run prog_view_obj
return prog_view_obj
end progress_view_obj
on setImage(imageName)
status_bar_item's button's setImageScaling:(ca's NSImageScaleProportionallyDown)
status_bar_item's button's setImage:(NSImage's imageNamed:imageName)
status_bar_item's button's setImagePosition:(ca's NSImageLeft)
end setImage
You shouldn't need to change anything except in the run and idle handlers — the first to set up the menus and the second to increment the progress bars or update the labels as needed. You can change how frequently the indicators update by increasing or decreasing the idle-time property (it's currently set to run the idle loop once a second). You'll only need to get into the big, messy handler if you need to change the layout or size of the menu items.
Technical point: I've put each progress view inside a script object (script prog_view_obj) to make it easier to work with. That may take some time for you to wrap your head around — it's tricky AppleScript — but at any rate, there's a bunch of convenience handlers at the end of the script object definition that you may find useful.

Related

How to create splash screen with animated .gif image?

I would like to know if there is any way that when opening my App, a splash screen with an animated GIF image is presented and soon after this screen closes and opens the main screen of the app, what I have done so far is what is in the gif below, but to work I need to use this display alert , or otherwise I don't see the progress bar being loaded, that is, it already opens in the second tab of my tab view.
Example of what I've done so far.
script AppDelegate
property parent : class "NSObject"
-- IBOutlets
property theWindow : missing value
property gifImg : missing value
property indicatorProgress : missing value
property myTabView : missing value
property myLabel : missing value
on applicationWillFinishLaunching_(aNotification)
myTabView's selectTabViewItemAtIndex:0
loadGifScreen()
end applicationWillFinishLaunching_
on applicationShouldTerminateAfterLastWindowClosed_(sender)
return true
end applicationShouldTerminate_
on loadGifScreen()
display alert "" giving up after 2
set c to 0
repeat 100 times
set c to c + 1
delay 0.04
tell indicatorProgress to setDoubleValue:c
if c > 99 then
exit repeat
end if
end repeat
myTabView's selectTabViewItemAtIndex:1
end loadGifScreen
end script

AppleScript - How do I detect if a specific button is clicked on GUI and terminate the script?

I have a script that manipulates a GUI. At some point in the process, a progress indicator appears in the GUI for a few mins. If the user clicks the GUI button button "Stop" of sheet 1 (so not a dialog button), I'd like the script to display dialog followed by error number -128. How do I do this? Here's what I tried...
repeat while progress indicator 1 of sheet 1 exists
try
set button_returned to button returned of button "Stop" of sheet 1
if button_returned is "Stop" then
display dialog "Operation cancelled"
error number -128
end if
end try
end repeat
Note: I use repeat while progress indicator 1 of sheet 1 exists to pause the script whilst the progress indicator is up.
Save this example as usual app to see the correct workaround to solve your problem:
global itemCount
set processName to name of current application
set theList to {"Marlow", "Maddie", "Sammy", "Stuey", "Jessie", "Tolstoy", "Marlow", "Maddie", "Sammy", "Stuey"}
set itemCount to count of theList
set progress total steps to count of theList
repeat with i from 1 to itemCount
set thisItem to item i of theList
set progress description to "Item " & i & " of " & itemCount
set progress additional description to thisItem
-- The delay is simply to simulate processing time
-- so you can see the progress bar in action.
-- Exclude this from your code and put your real do stuff.
delay 1
set progress completed steps to i
end repeat
on quit
if progress completed steps < itemCount then
display dialog "Operation cancelled"
end if
end quit

How can I avoid hard coding the handlers for each menu item?

I have a stay open AppleScript app that creates a menu with many menu items. The handlers that is mapped are repetitive and only differs in its index. How can I refactor this code to dry it up?
on makeMenus(selectedIndex as integer)
newMenu's removeAllItems() -- remove existing menu items
set menuItems to {"POC1", "POC2"}
repeat with i from 1 to number of items in menuItems
set this_item to item i of menuItems
if i is equal to selectedIndex then set this_item to this_item & " " & (emoji's CHECK)
set thisMenuItem to (current application's NSMenuItem's alloc()'s initWithTitle:this_item action:("someAction" & (i as text) & ":") keyEquivalent:"")
(newMenu's addItem:thisMenuItem)
(thisMenuItem's setTarget:me)
end repeat
-- Create the Quit menu item separately
set thisMenuItem to (current application's NSMenuItem's alloc()'s initWithTitle:"Quit" action:("quitAction:") keyEquivalent:"")
(newMenu's addItem:thisMenuItem)
(thisMenuItem's setTarget:me)
end makeMenus
The repetitive handler is:
on someAction1:sender
updateStatus(1)
end someAction1:
on someAction2:sender
updateStatus(2)
end someAction2:
If I can derive the menu item name or index from a parameter in the someAction handler, that would let me have a single handler to take care of all the menu items.
The sender argument of an action handler will be the object that called the action. In this case the sender will be the particular menuItem, so a common action handler can use a property such as the sender's title.
Note that since it will be a Cocoa object, you will need to coerce the title to an AppleScript string, for example:
on doAction:sender
set menuTitle to (sender's title) as text
if menuTitle is "this" then
doThis()
else if menuTitle is "that" then
doThat()
else
doOther()
end if
end doAction:

Problem with date picker showing (main thread issue?)

Working with an AppleScript file from https://github.com/bat-tomr/dialog-node ...
# 06/04/16 09:35:02
# Author: Shane Stanley
# Adapted by Christopher Stone
use AppleScript version "2.4"
use scripting additions
use framework "Foundation"
use framework "AppKit"
my datePicker()
on datePicker()
set theApp to path to frontmost application as text
if not (current application's NSThread's isMainThread()) as boolean then
display alert "This script must be run from the main thread." buttons {"Cancel"} as critical
error number -128
end if
-- create a view
set theView to current application's NSView's alloc()'s initWithFrame:(current application's NSMakeRect(0, 0, 100, 175))
-- create date picker
set datePicker to current application's NSDatePicker's alloc()'s initWithFrame:(current application's NSMakeRect(0, 0, 100, 100))
-- set style: choices are NSTextFieldAndStepperDatePickerStyle, NSClockAndCalendarDatePickerStyle, or NSTextFieldDatePickerStyle
#datePicker's setDatePickerStyle:(current application's NSClockAndCalendarDatePickerStyle)
datePicker's setDatePickerStyle:(current application's NSTextFieldAndStepperDatePickerStyle)
#datePicker's setDatePickerStyle:(current application's NSTextFieldDatePickerStyle)
-- set elements: choices include NSHourMinuteDatePickerElementFlag, NSHourMinuteSecondDatePickerElementFlag, NSTimeZoneDatePickerElementFlag, NSYearMonthDatePickerElementFlag, and NSEraDatePickerElementFlag
datePicker's setDatePickerElements:((current application's NSYearMonthDayDatePickerElementFlag) + (current application's NSHourMinuteSecondDatePickerElementFlag as integer))
-- set initial date
#datePicker's setDateValue:(current application's NSDate's |date|())
--set theCal var to a new(empty) instance of the calendar
set theCal to current application's class "NSCalendar"'s currentCalendar()
-- unpack the components of theCal to a variable
set theComponents to theCal's components:254 fromDate:(current application's NSDate's |date|())
theComponents's setSecond:0
theComponents's setMinute:0
theComponents's setHour:12
theComponents's setYear:2015
theComponents's setMonth:4
theComponents's setDay:1
datePicker's setDateValue:(theCal's dateFromComponents:theComponents)
-- get the size it needs
set theSize to datePicker's fittingSize()
--resize the picker and view accordingly
theView's setFrameSize:theSize
datePicker's setFrameSize:theSize
-- add the picker to the view
theView's setSubviews:{datePicker}
-- create an alert
set theAlert to current application's NSAlert's alloc()'s init()
-- set up alert
tell theAlert
its setMessageText:"Pick a date and time"
its setInformativeText:"Any date"
its addButtonWithTitle:"OK"
its addButtonWithTitle:"Cancel"
its setAccessoryView:theView
end tell
-- show alert in modal loop
set returnCode to theAlert's runModal()
if returnCode = (current application's NSAlertSecondButtonReturn) then error number -128
-- retrieve date
#set theDate to datePicker's dateValue() as date
# Shane Stanley
# http://macscripter.net/viewtopic.php?pid=119633#p119633
set theDate to datePicker's dateValue()
#set theCal to current application's class "NSCalendar"'s currentCalendar()
-- unpack the components of theDate to a variable
set theComponents to theCal's components:254 fromDate:theDate
set theDate to current date
set day of theDate to 1 -- important
set seconds of theDate to theComponents's |second|()
set year of theDate to theComponents's |year|()
set month of theDate to theComponents's |month|()
set day of theDate to theComponents's |day|()
set hours of theDate to theComponents's hour()
set minutes of theDate to theComponents's minute()
return (theDate as text)
#return (theDate)
end datePicker
The date picker does not display (at least on Mojave). It does display, however, if I put a display alert before runModal (but not otherwise).
I understand this may have to do with a need for performSelectorOnMainThread in latter Mac OSX versions, but if that is the case, I was not able to add it such that the code would work. Any ideas?
Here you go... Not much to say really. I basically just rewrote it because I can't stand messy code:
# 06/04/16 09:35:02
# Author: Shane Stanley
# Adapted by Christopher Stone
# Fixed & Rewritten by CJK
--------------------------------------------------------------------------------
use framework "AppKit"
use scripting additions
property this : a reference to the current application
property nil : a reference to missing value
property _1 : a reference to reference
property NSAlert : a reference to NSAlert of this
property NSDatePicker : a reference to NSDatePicker of this
property NSView : a reference to NSView of this
property NSAlertSecondButtonReturn : 1001
property NSHourMinuteSecondDatePickerElementFlag : 14
property NSTextFieldAndStepperDatePickerStyle : 0
property NSYearMonthDayDatePickerElementFlag : 224
--------------------------------------------------------------------------------
property date : missing value
--------------------------------------------------------------------------------
on run
its performSelectorOnMainThread:("showDatePicker:") withObject:{¬
NSTextFieldAndStepperDatePickerStyle, ¬
NSYearMonthDayDatePickerElementFlag + ¬
NSHourMinuteSecondDatePickerElementFlag} ¬
waitUntilDone:true
return my date
end run
on showDatePicker:params
local params
set {PickerStyle, PickerElements} to params
tell (current date) to set ¬
[dateFrom, day, its month, day, year, time] to ¬
[it, 1, 4, 1, 2015, 12 * hours + 0 * minutes]
tell NSDatePicker's alloc()
initWithFrame_({{0, 0}, {100, 100}})
setDatePickerStyle_(PickerStyle)
setDatePickerElements_(PickerElements)
setDateValue_(dateFrom)
set fittingSize to fittingSize()
setFrameSize_(fittingSize)
set View to NSView's alloc()
View's initWithFrame:{{0, 0}, {100, 175}}
View's setFrameSize:fittingSize
View's addSubview:it
tell NSAlert's alloc()
init()
setMessageText_("Pick a date and time")
setInformativeText_("Any date")
addButtonWithTitle_("OK")
addButtonWithTitle_("Cancel")
setAccessoryView_(View)
runModal()
end tell
set my date to dateValue() as date
end tell
end showDatePicker:
---------------------------------------------------------------------------❮END❯
System info: AppleScript version: 2.7 System version: 10.13.6

Why won't Photoshop revert to earlier history state in script?

I've written an Applescript to automate watermarking and resizing images for my company. Everything generally works fine — the script saves the initial history state to a variable, resizes the image, adds the appropriate watermark, saves off a jpeg, then reverts to the initial history state for another resize and watermark loop.
The problem is when I try not to use a watermark and only resize by setting the variable wmColor to "None" or "None for all". It seems that after resizing and saving off a jpeg, Photoshop doesn't like it when I try to revert to the initial history state. This is super annoying, since clearly a resize should count as a history step, and I don't want to rewrite the script to implement multiple open/close operations on the original file. Does anyone know what might be going on? This is the line that's generating the problem (it's in both the doBig and doSmall methods, and throws an error every time I ask it just to do an image resize and change current history state:
set current history state of current document to initialState
and here's the whole script:
property type_list : {"JPEG", "TIFF", "PNGf", "8BPS", "BMPf", "GIFf", "PDF ", "PICT"}
property extension_list : {"jpg", "jpeg", "tif", "tiff", "png", "psd", "bmp", "gif", "jp2", "pdf", "pict", "pct", "sgi", "tga"}
property typeIDs_list : {"public.jpeg", "public.tiff", "public.png", "com.adobe.photoshop-image", "com.microsoft.bmp", "com.compuserve.gif", "public.jpeg-2000", "com.adobe.pdf", "com.apple.pict", "com.sgi.sgi-image", "com.truevision.tga-image"}
global myFolder
global wmYN
global wmColor
global nameUse
global rootName
global nameCount
property myFolder : ""
-- This droplet processes files dropped onto the applet
on open these_items
-- FILTER THE DRAGGED-ON ITEMS BY CHECKING THEIR PROPERTIES AGAINST THE LISTS ABOVE
set wmColor to null
set nameCount to 0
set nameUse to null
if myFolder is not "" then
set myFolder to choose folder with prompt "Choose where to put your finished images" default location myFolder -- where you're going to store the jpgs
else
set myFolder to choose folder with prompt "Choose where to put your finished images" default location (path to desktop)
end if
repeat with i from 1 to the count of these_items
set totalFiles to count of these_items
set this_item to item i of these_items
set the item_info to info for this_item without size
if folder of the item_info is true then
process_folder(this_item)
else
try
set this_extension to the name extension of item_info
on error
set this_extension to ""
end try
try
set this_filetype to the file type of item_info
on error
set this_filetype to ""
end try
try
set this_typeID to the type identifier of item_info
on error
set this_typeID to ""
end try
if (folder of the item_info is false) and (alias of the item_info is false) and ((this_filetype is in the type_list) or (this_extension is in the extension_list) or (this_typeID is in typeIDs_list)) then
-- THE ITEM IS AN IMAGE FILE AND CAN BE PROCESSED
process_item(this_item)
end if
end if
end repeat
end open
-- this sub-routine processes folders
on process_folder(this_folder)
set these_items to list folder this_folder without invisibles
repeat with i from 1 to the count of these_items
set this_item to alias ((this_folder as Unicode text) & (item i of these_items))
set the item_info to info for this_item without size
if folder of the item_info is true then
process_folder(this_item)
else
try
set this_extension to the name extension of item_info
on error
set this_extension to ""
end try
try
set this_filetype to the file type of item_info
on error
set this_filetype to ""
end try
try
set this_typeID to the type identifier of item_info
on error
set this_typeID to ""
end try
if (folder of the item_info is false) and (alias of the item_info is false) and ((this_filetype is in the type_list) or (this_extension is in the extension_list) or (this_typeID is in typeIDs_list)) then
-- THE ITEM IS AN IMAGE FILE AND CAN BE PROCESSED
process_item(this_item)
end if
end if
end repeat
end process_folder
-- this sub-routine processes files
on process_item(this_item)
set this_image to this_item as text
tell application id "com.adobe.photoshop"
set saveUnits to ruler units of settings
set display dialogs to never
open file this_image
if wmColor is not in {"None for all", "White for all", "Black for all"} then
set wmColor to choose from list {"None", "None for all", "Black", "Black for all", "White", "White for all"} with prompt "What color should the watermark be?" default items "White for all" without multiple selections allowed and empty selection allowed
end if
if wmColor is false then
error number -128
end if
if nameUse is not "Just increment this for all" then
set nameBox to display dialog "What should I call these things?" default answer ("image") with title "Choose the name stem for your images" buttons {"Cancel", "Just increment this for all", "OK"} default button "Just increment this for all"
set nameUse to button returned of nameBox -- this will determine whether or not to increment stem names
set rootName to text returned of nameBox -- this will be the root part of all of your file names
set currentName to rootName
else
set nameCount to nameCount + 1
set currentName to rootName & (nameCount as text)
end if
set thisDocument to current document
set initialState to current history state of thisDocument
set ruler units of settings to pixel units
end tell
DoSmall(thisDocument, currentName, initialState)
DoBig(thisDocument, currentName, initialState)
tell application id "com.adobe.photoshop"
close thisDocument without saving
set ruler units of settings to saveUnits
end tell
end process_item
to DoSmall(thisDocument, currentName, initialState)
tell application id "com.adobe.photoshop"
set initWidth to width of thisDocument
if initWidth < 640 then
resize image thisDocument width 640 resample method bicubic smoother
else if initWidth > 640 then
resize image thisDocument width 640 resample method bicubic sharper
end if
set myHeight to height of thisDocument
set myWidth to width of thisDocument
if wmColor is in {"White", "White for all"} then
set wmFile to (path to resource "water_250_white.png" in bundle path to me) as text
else if wmColor is in {"Black", "Black for all"} then
set wmFile to (path to resource "water_250_black.png" in bundle path to me) as text
end if
if wmColor is not in {"None", "None for all"} then
open file wmFile
set wmDocument to current document
set wmHeight to height of wmDocument
set wmWidth to width of wmDocument
duplicate current layer of wmDocument to thisDocument
close wmDocument without saving
translate current layer of thisDocument delta x (myWidth - wmWidth - 10) delta y (myHeight - wmHeight - 10)
set opacity of current layer of thisDocument to 20
end if
set myPath to (myFolder as text) & (currentName) & "_640"
set myOptions to {class:JPEG save options, embed color profile:false, quality:12}
save thisDocument as JPEG in file myPath with options myOptions appending lowercase extension
set current history state of current document to initialState
end tell
end DoSmall
to DoBig(thisDocument, currentName, initialState)
tell application id "com.adobe.photoshop"
set initWidth to width of thisDocument
if initWidth < 1020 then
resize image thisDocument width 1020 resample method bicubic smoother
else if initWidth > 1020 then
resize image thisDocument width 1020 resample method bicubic sharper
end if
set myHeight to height of thisDocument
set myWidth to width of thisDocument
if wmColor is in {"White", "White for all"} then
set wmFile to (path to resource "water_400_white.png" in bundle path to me) as text
else if wmColor is in {"Black", "Black for all"} then
set wmFile to (path to resource "water_400_black.png" in bundle path to me) as text
end if
if wmColor is not in {"None", "None for all"} then
open file wmFile
set wmDocument to current document
set wmHeight to height of wmDocument
set wmWidth to width of wmDocument
duplicate current layer of wmDocument to thisDocument
close wmDocument without saving
translate current layer of thisDocument delta x (myWidth - wmWidth - 16) delta y (myHeight - wmHeight - 16)
set opacity of current layer of thisDocument to 20
end if
set myPath to (myFolder as text) & (currentName) & "_1020"
set myOptions to {class:JPEG save options, embed color profile:false, quality:12}
save thisDocument as JPEG in file myPath with options myOptions appending lowercase extension
set current history state of current document to initialState
end tell
end DoBig
If you choose a color : save document "Ducky.tif" as JPEG in file ..... the current document will be document "Ducky.tif".
If you choose "None" or "None for all" : save document "Ducky.tif" as JPEG in file ...... the current document will be document "image_640".
So the variable initialState = history state 2 of document "Ducky.tif" give an error, because this document no longer exists.
To leaving original open, here's a solution , use copying true in your save command.
save thisDocument as JPEG in file myPath with options myOptions appending lowercase extension with copying

Resources