MBS Plugin Advent calendar 2024

candy cane Monkeybread Monkey as an elf candy cane
Day 16 - Add Entries In Apple Calendar

Our monkey has lots of appointments and uses the Apple Calendar app to keep track of them. Now it would be nice if our monkey could also enter appointments directly from FileMaker into this calendar and we would like to implement this today. The functions from the Calendar section are available for this purpose.

Since our layout is getting pretty full and we don't need to see the information from the last created appointment on the layout all the time, we use a button with a popover and position our fields on this popover. As the fields do not belong to a specific data record, we create these fields as global fields in the Data table. We need the following fields:

Field Type
Appointment_Title Text
Appointment_Start Timestamp
Appointment_End Timestamp
Appointment_All Day Text
Appointment_URL Text
Appointment_Location Text
Appointment_Notes Text

We can fill these fields with information later when we create an appointment. The appointment should then be transferred to the calendar app by clicking on a button. Behind this button is the script that I would now like to introduce to you.

As we have already seen several times, the plugin often works with references in the workspace. This is also the case here. We first create a new calendar entry with Calendar.NewEvent. We can address this entry with the reference that the function returns. Now we can already set the individual information of the event. First of all, this is the title of the event. We set this by specifying the reference and the title using the Calendar.Item.SetTitle function. Now we can set the start and end times with Calendar.Item.SetStartDate and Calendar.Item.SetEndDate. Here we pass the respective timestamp in addition to the reference. We specify whether an appointment is an all-day appointment with Calendar.Item.SetAllDay. If we pass a 1 in the parameters here, the appointment is an all-day appointment. If we enter a 0, it is created as an appointment with a time frame that is described in Start and End Date. If there is an Internet page with further information for this appointment, e.g. for an event, it is a good idea to add this to the appointment with Calendar.Item.SetURL. We can also use Calendar.Item.SetLocation to specify the location where the event is to take place. If the information in the title of the calendar entry is not sufficient or if you have further information about an appointment, you can enter this as a note with Calendar.Item.SetNotes.

In the calendar app, we have the option of having several calendars. For example, public holidays are displayed in a different color than my own appointments or birthdays. This helps to keep an overview. Before we can make an entry in the calendar, we have to decide in which calendar the appointment should be entered. We then set the name of this calendar with Calendar.Item.SetCalendar.

The Calendar.SaveEvent function then saves our calendar entry in the calendar.

Our script is now complete and looks like this:

Set Variable [ $Cal ; Value: MBS("Calendar.NewEvent") ]
Set Variable [ $r ; Value: MBS("Calendar.Item.SetTitle"; $Cal; Data::Appointment_Title) ]
Set Variable [ $r ; Value: MBS("Calendar.Item.SetStartDate"; $Cal; Data::Appointment_Start) ]
Set Variable [ $r ; Value: MBS("Calendar.Item.SetEndDate"; $Cal; Data::Appointment_End) ]
Set Variable [ $r ; Value: MBS("Calendar.Item.SetAllDay"; $Cal; Data::Appointment_All Day) ]
Set Variable [ $r ; Value: MBS("Calendar.Item.SetURL"; $Cal; Data::Appointment_URL) ]
Set Variable [ $r ; Value: MBS("Calendar.Item.SetLocation"; $Cal; Data::Appointment_Location) ]
Set Variable [ $r ; Value: MBS("Calendar.Item.SetNotes"; $Cal; Data::Appointment_Notes) ]
Set Variable [ $r ; Value: MBS("Calendar.Item.SetCalendar"; $Cal; "Test") ]
Set Variable [ $r ; Value: MBS("Calendar.SaveEvent"; $Cal) ]

That's it for today and I hope to see you again tomorrow.

candy cane Monkeybread Monkey as an elf candy cane
Day 17 - User Notifications

Advent is running faster and faster and there are not many days left until Christmas. We want our monkey to keep an eye on the time and so we always want to receive a user notification when FileMaker is opened showing how many days are left until Christmas.

I will now show you how this works with our user notifications under Windows and Mac.

First we have to determine the number of days left until Christmas. We always start from the current year, so if it is December 30th we want a negative number to come out. First we get the current date in a variable. Then we determine the date of December 25th in the current year by taking the year from the current date. Internally, a date value is seen as a number of days since a specified day. For this reason, we get the exact number of days by subtracting the current date from the Christmas date.

Set Variable [ $CurrentDate ; Value: GetAsNumber ( Get(CurrentDate) ) ]
Set Variable [ $CurrentYear ; Value: Year ( Get(CurrentDate) ) ]
Set Variable [ $ChrismasDate ; Value: GetAsNumber ( Date ( 12 ; 25 ; $CurrentYear ) ) ]
Set Variable [ $DaysUntilChristmas ; Value: $ChrismasDate-$CurrentDate ]

We would like to return individualized results depending on the value. If Christmas is still to come this year, i.e. the number is greater than 0, then we write as the title how many days there are left until Christmas and in the text that the monkey should hurry because there is still a lot to do. On Christmas Day (value 0) we wish him a Merry Christmas. If this is not the case, then the value is less than 0 and we are between Christmas and New Year's Eve and our monkey has missed Christmas.

If [ $DaysUntilChristmas > 0 ]
   Set Variable [ $Title ; Value: 
      $DaysUntilChristmas & " days until Christmas" ]
   Set Variable [ $Text ; Value: 
      "Hurry up! There's still a lot to do!" ]
Else If [ $DaysUntilChristmas = 0 ]
   Set Variable [ $Title ; Value: 
      "HoHo Merry Christmas" ]
   Set Variable [ $Text ; Value: 
      "It's Christmas! Grab your presents and keep up the good work! " ]
Else
   Set Variable [ $Title ; Value: 
      "You missed Christmas! Maybe next year." ]
   Set Variable [ $Text ; Value: 
      "You can take it easy now. " & 
      "There's still plenty of time until next Christmas. " & 
      "Give yourself a break" ]
End If

After we have determined the days and the corresponding titles and texts, we now want to create the notifications. Here we have to make a distinction depending on the operating system, because we have different function areas in the plugin for Mac/iOS and Windows. For Mac and iOS we use the functions from the UNNotification topic and for Windows the functions from the WindowsUserNotification topic.

We decide which part of the program is executed with the FileMaker Get(SystemPlatform) Platform function if its value is -2 it is a Windows operating system and 1 and 3 are Mac or iOS.

Under the Apple operating systems, we first create a new reference with UNNotification.New. We can now fill this with life by first setting the title and text that should be displayed in the notification with UNNotification.SetTitle and UNNotification.SetBody. Of course, we always specify the reference in these functions in addition to the values. If we want a sound to be played when the notification is displayed, you can set this with UNNotification.SetSound. We use the default sound here, but you can also specify a different sound. So that the notification is also displayed, we trigger it with UNNotification.Deliver.

If [ Get(SystemPlatform)= 1 or Get(SystemPlatform) = 3 ]
   # Mac and iOS
   Set Variable [ $UN ; Value: MBS( "UNNotification.New" ) ]
   Set Variable [ $r ; Value: MBS( "UNNotification.SetTitle"; $UN; $Title) ]
   Set Variable [ $r ; Value: MBS("UNNotification.SetBody"; $UN; $Text) ]
   Set Variable [ $r ; Value: MBS("UNNotification.SetSound"; $UN; "default") ]
   Set Variable [ $r ; Value: MBS("UNNotification.Deliver"; $UN) ]

We are now done with the Apple part and come to the Windows version. Here we must first initialize the notification system. Here we have to specify which application we want to use, followed by the company name, the surname and the version information. After we have initialized this, we can also create a new working environment here with WindowsUserNotification.NewNotification. We can then set the content of this notification. Let's start with the icon that should be next to the text. This will be our monkey. Since the WindowsUserNotification.SetImagePath requires a path, we write our monkey file in the temporary folder, as we have already seen in another door and then pass the finished path to the function. Just as with the Mac version, we also want to specify the title and text here. In contrast to the Apple version, here we not have title and text, but can also specify two further subordinate texts if required. To do this, we enter an index in the WindowsUserNotification.SetText function next to the reference and the text, which can be in the range between 0 and 3. The smaller the index, the higher up the text is. We take index 0 for the title and index 1 for the text. Now that we have set the texts, we also want to trigger this notification with WindowsUserNotification.ShowNotification.

Else If [ Get(SystemPlatform)= -2 ]
   # Windows
   Set Variable [ $r ; Value: MBS( "WindowsUserNotification.Initialize"; 
      "FileMaker"; "Test"; "test"; "test"; "1.0") ]
   Set Variable [ $UN ; Value: MBS( "WindowsUserNotification.NewNotification" ) ]

   Set Variable [ $Temp ; Value: MBS("Folders.UserTemporary") ]
   Set Variable [ $Name ; Value: "ChristmasMonkey.png" ]
   Set Variable [ $Path ; Value: MBS("Path.AddPathComponent"; $Temp; $Name) ]
   Set Variable [ $r ; Value: MBS("Files.WriteFile"; $$Overlay_Image; $Path) ]
   Set Variable [ $r ; Value: MBS( "WindowsUserNotification.SetImagePath"; $UN; $Path ) ]

   Set Variable [ $r ; Value: MBS( "WindowsUserNotification.SetText"; $UN; 0; $Title ) ]
   Set Variable [ $r ; Value: MBS( "WindowsUserNotification.SetText"; $UN; 1; $Text ) ]
   Set Variable [ $r ; Value: MBS( "WindowsUserNotification.ShowNotification"; $UN) ]

With both variants, whether Mac or Windows, we have to think about their release, as we have worked with references.

Our monkey can now keep an eye on the days and is well prepared. We'll see you again tomorrow, until then I wish you a relaxing Advent.

1 Like
candy cane Monkeybread Monkey as an elf candy cane
Day 18 - ics-Files

In door 16 we took care of the fact that we can enter appointments in our calendar, but what do we do if our monkey wants to share an appointment with someone, e.g. as an attachment for an appointment confirmation. We can also realize this by assembling an ics file that contains the data for an appointment. As with the business card that we embedded in the QR code, an ics file also has a specific form, where some data is required and some is optional. But let's take a look at the structure of such a file by exporting an existing appointment from the calendar and opening it in an editor. It is an event that takes place on 24th December 2024 from 17:00 to 23:30 and has the title Christmas Eve Dinner. It takes place in Home, has a further description and a link to a website. And this is what it looks like in the editor:

BEGIN:VCALENDAR
CALSCALE:GREGORIAN
PRODID:-//Apple Inc.//macOS 13.4.1//EN
VERSION:2.0
BEGIN:VTIMEZONE
TZID:Europe/Berlin
BEGIN:DAYLIGHT
DTSTART:19810329T020000
RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU
TZNAME:CEST
TZOFFSETFROM:+0100
TZOFFSETTO:+0200
END:DAYLIGHT
BEGIN:STANDARD
DTSTART:19961027T030000
RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU
TZNAME:CET
TZOFFSETFROM:+0200
TZOFFSETTO:+0100
END:STANDARD
END:VTIMEZONE
BEGIN:VEVENT
CREATED:20241212T205429Z
DESCRIPTION:Prepare food on December 23rd
DTEND;TZID=Europe/Berlin:20241224T233000
DTSTAMP:20241212T205735Z
DTSTART;TZID=Europe/Berlin:20241224T170000
LAST-MODIFIED:20241212T205713Z
LOCATION:Home
SEQUENCE:0
SUMMARY:Christmas Dinner
TRANSP:OPAQUE
UID:809C6AEE-B99E-4DBB-A29F-4223314288A3
URL;VALUE=URI:www.xyz.com
BEGIN:VALARM
ACTION:NONE
TRIGGER;VALUE=DATE-TIME:19760401T005545Z
END:VALARM
END:VEVENT
END:VCALENDAR

Don't panic, you won't need as much information as you see here to create the appointment, but let's take a look at which of it is relevant for us.

The content of the appointment starts with BEGIN:VCALENDAR and ends with END:VCALENDAR, with the information about the appointment in between.

The next line: CALSCALE:GREGORIAN specifies, as you have probably already guessed, the type of calendar. In our case, the Gregorian calendar. Then comes the version. This must be present in every date specification so that it is known how the individual details are to be interpreted. This can simply remain at 2.0.

The PRODID key describes who created the file. In the event you see here, you can see that the calendar app created this event. In our example, we pass the company name afterwards. Then we get down to the details, the data for the actual appointment. Here, our area is always enclosed by BEGIN:VEVENT and END:VEVENT. Such an area must appear at least once in our appointment. Now let's take a look at what's inside this area. Our example appointment is an appointment that has a start and end time. The end time has the key DTEND. We can see that in addition to the character string containing the date and time, the time zone is also specified. In our case, the time that applies in my case, as I live in Germany, is Berlin. If you want to find out more about the individual time zones, you can find this information on the website timezones.de, for example.

Now comes the timestamp and it has a very specific structure. First comes a 4-digit year, then the month, which takes up 2 digits, and the day, which also has 2 digits. This is followed by a T that separates the time from the date. The individual entries for the time are each two characters long and are made up of hours, minutes and seconds. The timestamp therefore has the structure: yyyymmddThhmmss. August 19, 2025 at 9:00 a.m. would therefore receive the following entry: 20250819T090000.

We also have the entry DTSTART in this date. This determines the start time of the event and has the same structure. We see something similar in the time stamps, which tell us when the event was created (CREATED), when it was last modified (LAST-MODIFIED), and when this file was exported or created (DTSTAMP). DTSTAMP is a time specification that must be included in the date. In contrast to the start and end time, we find a Z at the end of the time stamp.

Now we come to a few more useful keys. First, we have the LOCATION key to which we can pass a location or address. Under the DESCRIPTION key, you can describe the appointment further. This content is then displayed as a note for the appointment. You can also use the URL key to specify a URL that belongs to the organizer's or event's website, for example. Another important key is SUMMARY. This describes the title of our event.

The keys do not have to remain in this order, but can be swapped, as we do in our example, so that you can find the order that makes the most sense for you.

We have already created our fields at door 16 and so we only have to create a button in the popover that starts the script that we now want to write. The beginning of our appointment is always the same and we put it together as text. The content of our file will later be in the variable $Content

Set Variable [ $Company ; Value: "Monkey the Elf" ]
Set Variable [ $Start ; Value: 
	"BEGIN:VCALENDAR¶CALSCALE:GREGORIAN¶VERSION:2.0¶METHOD:PUBLISH¶PRODID:" 
	& $Company & "¶BEGIN:VEVENT" ]
Set Variable [ $Content ; Value: $Start ]

Now we can enter the title of our calendar entry. This has the key SUMMARY

Set Variable [ $Title ; Value: "¶SUMMARY:" & Data::Appointment_Title & "¶" ]
Set Variable [ $Content ; Value: $Content  &  $Title ]

The description, the URL and the location of an event are not mandatory, which is why we first check whether these values have been set in our database and only then we add the key with the corresponding value.

If [ Data::Appointment_Notes ≠ "" ]
	Set Variable [ $Description ; Value: "DESCRIPTION:" & Data::Appointment_Notes & "¶" ]
	Set Variable [ $Content ; Value: $Content & $Description ]
End If
# 
If [ Data::Appointment_URL ≠ "" ]
	Set Variable [ $URL ; Value: "URL;VALUE=URI:" & Data::Appointment_URL & "¶" ]
	Set Variable [ $Content ; Value: $Content & $URL ]
End If
# 
If [ Data::Appointment_Location ≠ "" ]
	Set Variable [ $Location ; Value: "LOCATION:" & Data::Appointment_Location & "¶" ]
	Set Variable [ $Content ; Value: $Content & $Location ]
End If

Now we come to the part that is a bit tricky, because we have to differentiate between an all-day appointment and an appointment with a start and end time. This is because the composition of the time differs slightly here. If the Appointment_All Day field is set to 1, we jump to the TimeAllday script that compiles the all-day time.

If [ Data::Appointment_All Day = 1 ]
	Perform Script [ Specified: From list ; "TimeAllday" ; Parameter:    ]

We compose the date as we discussed earlier: four characters year, two characters month and two characters day. We get these values from the date field using the corresponding FileMaker functions. But be careful: If we have a day or a month with a value less than 10, the function only returns one character and we have to add the preceding zero.

Set Variable [ $Year ; Value: Year ( Data::Appointment_Start ) ]
# 
If [ Length ( Month ( Data::Appointment_Start ) ) ≠ 2 ]
	Set Variable [ $Month ; Value: "0" & Month ( Data::Appointment_Start ) ]
Else
	Set Variable [ $Month ; Value: Month ( Data::Appointment_Start ) ]
End If
# 
If [ Length ( Day ( Data::Appointment_Start ) ) ≠ 2 ]
	Set Variable [ $Day ; Value: "0" & Day ( Data::Appointment_Start ) ]
Else
	Set Variable [ $Day ; Value: Day ( Data::Appointment_Start ) ]
End If
Set Variable [ $StartDay ; Value: $Year & $Month & $Day ]
Set Variable [ $Beginning ; Value: "DTSTART;VALUE=DATE:" & $StartDay ]
Set Variable [ $Time ; Value: $Beginning  & "¶" ]

Now we have to differentiate whether it is an all-day appointment lasting several days or whether it is only one day with an all-day appointment. To do this, we subtract the end date from the start date and obtain the day difference. If we get a value greater than 0 here, it is an all-day event that lasts several days. If we only specify the end date in this case, this is often interpreted incorrectly and, for example, one day too little is displayed for the event in the calendar. For this reason, we would like to specify the DURATION in this case. This is made up as follows: First we write a P and then comes the duration of the event in days or weeks. We can see whether it is days or weeks by the character after the duration. If we enter weeks, there is a W, if we enter days, as in our case, there is a D. You can also combine the times by writing them down one after the other. For example, the duration of 2 weeks and 3 days would be described with this expression: P2W3D.

1 Like

Because our Appointment_Start and Appointment_End fields are of the timestamp type, if we simply subtract them from each other we would get the seconds between the two times, but we need the days. We get these if we subtract two date values from each other. For this reason, we convert the two time points into dates and then subtract them from each other.

Set Variable [ $DayDifference ; Value: Date ( Month ( Data::Appointment_End ) ; 
	Day ( Data::Appointment_End ) ; Year ( Data::Appointment_End ) ) 
	- Date ( $Month ; $Day ; $Year) ]

If the difference is greater than 0, then our appointment is for several days and we want to specify the duration.

If [ $DayDifference>0 ]
	Set Variable [ $TimeDifference ; Value: "DURATION:P" & $DayDifference+1 & "D" ]
	Set Variable [ $Time ; Value: $Time & $TimeDifference ]

If the difference is zero, then the appointment will only take place for the whole day on this one day and we will also enter the start date as the end date.

Else If [ $DayDifference=0 ]
	Set Variable [ $EndDay ; Value: "DTEND;VALUE=DATE:" & $StartDay ]
	Set Variable [ $Time ; Value: $Time & $EndDay ]

If the day difference is neither greater than 0 nor equal to zero, then it must be less than 0, i.e. our start time is after the specified end time. In this case, we simply want to swap the data in our database and we call the same time script again and process the result that this call returns.

Else
	Set Variable [ $S ; Value: Data::Appointment_End ]
	Set Variable [ $E ; Value: Data::Appointment_Start ]
	Set Field [ Data::Appointment_Start ; $S ]
	Set Field [ Data::Appointment_End ; $E ]
	Perform Script [ Specified: From list ; "TimeAllday" ; Parameter:    ]
	Set Variable [ $Time ; Value: Get(ScriptResult) ]
	# result
End If

Our script now returns the string with the time information to the calling main script.

Exit Script [ Text Result: $Time ]

We have now seen this for all-day appointments, but if it is not an all-day appointment, then we call the TimeLimited script.

Else
	Perform Script [ Specified: From list ; "TimeLimited" ; Parameter:    ]
End If

Here, not only the date is entered in the specific timestamp, but also the time. The principle is exactly the same as before. First we have the date in the structure YYYYMMDD and then comes the time with HHMMSS. As the seconds are not so important for an appointment, we simply leave them out and write 00 for the seconds instead. We have our DTSTART key again. We then add the desired time zone TZID=Europe/Berlin to this, and then our date is put together. This is followed by a separating T and then the hours, minutes and our 00 for the seconds.

# TimeLimited 

Set Variable [ $Year_Start ; Value: Year ( Data::Appointment_Start ) ]
# 
If [ Length ( Month ( Data::Appointment_Start ) ) ≠ 2 ]
	Set Variable [ $Month_Start ; Value: "0" & Month ( Data::Appointment_Start ) ]
Else
	Set Variable [ $Month_Start ; Value: Month ( Data::Appointment_Start ) ]
End If
# 
If [ Length ( Day ( Data::Appointment_Start ) ) ≠ 2 ]
	Set Variable [ $Day_Start ; Value: "0" & Day ( Data::Appointment_Start ) ]
Else
	Set Variable [ $Day_Start ; Value: Day ( Data::Appointment_Start ) ]
End If
If [ Length ( Hour ( Data::Appointment_Start ) ) ≠ 2 ]
	Set Variable [ $Hour_Start ; Value: "0" & Hour( Data::Appointment_Start ) ]
Else
	Set Variable [ $Hour_Start ; Value: Hour ( Data::Appointment_Start ) ]
End If
# 
If [ Length ( Minute ( Data::Appointment_Start ) ) ≠ 2 ]
	Set Variable [ $Minute_Start ; Value: "0" & Minute ( Data::Appointment_Start ) ]
Else
	Set Variable [ $Minute_Start ; Value: Minute ( Data::Appointment_Start ) ]
End If
Set Variable [ $Beginning ; Value: "DTSTART;TZID=Europe/Berlin:" & $Year_Start 
	& $Month_Start & $Day_Start & "T" & $Hour_Start & $Minute_Start & "00" ]
Set Variable [ $Time ; Value:  $Beginning&"¶" ]

We do exactly the same for DTEND. And, as seen in the other script, we also return our time as text to our main script. The time is then added to the previous content here.

Set Variable [ $Time ; Value: Get(ScriptResult) ]
Set Variable [ $Content ; Value: $Content & $Time & "¶" ]

Now we still need the time at which we exported the appointment, or in our case created it (DTSTAMP). This timestamp has the following form: yymmddThhmmssZ Here, too, we neglect the seconds and enter them as 00.

…
Set Variable [ $CurrentTime ; Value: "DTSTAMP:" & $Year_Current & $Month_Current 
	& $Day_Current & "T" & $Hour_Current & $Minute_Current & "00Z" ]
Set Variable [ $Content ; Value: $Content & $CurrentTime & "¶" ]

In the content of our appointment, the final boundaries of our areas are still missing. We will add these.

Set Variable [ $Content ; Value: $Content & "END:VEVENT¶END:VCALENDAR" ]

We have now compiled our text and want to save this content in a path on the desktop. First, we create this path as we have already shown in other doors.

Set Variable [ $Desk ; Value: MBS("Folders.UserDesktop") ]
Set Variable [ $Path ; Value: MBS("Path.AddPathComponent"; $Desk; "Appointment" & ".ics") ]

Then we use the functions from the BinaryFile component to create a text file of type ics. We then use "BinaryFile.Create" to create such a text file by specifying the path we have just determined. We write the content to the file using the "BinaryFile.WriteText" function. In the parameters of this function, in addition to the reference number obtained from the previous function and the content stored in the $Content variable, we also specify the encoding of the text. We select UTF-8. With "BinaryFile.Close" we close the file again and it can be used.

Set Variable [ $File ; Value: MBS("BinaryFile.Create"; $Path) ]
Set Variable [ $r ; Value: MBS("BinaryFile.WriteText"; $File; $Content; "UTF-8") ]
Set Variable [ $r ; Value: MBS("BinaryFile.Close"; $File) ]

Now we can create our appointment files with the push of a button. I hope to see you again tomorrow for the next door.

1 Like
candy cane Monkeybread Monkey as an elf candy cane
Day 19 - Hot Keys

In Door 17 we got to know the UserNotifications, which we display once every time the solution is opened. But our little monkey doesn't think that's enough, so he wants to be able to call it up more often. However, he doesn't want an additional button on the layout, but would like to start the script automatically with a key combination. The HotKey functions under Mac and Windows are available in the plugin for this purpose. We can use them to define hotkeys to start scripts or calculations.

With the HotKey.Register function, we can register a specific key combination by first entering the virtual key code of the key to watch in the parameters. This can be the function keys (F), for example. So that the keys cannot fire if only this key is pressed, as otherwise normal use would not be possible, we also enter so-called keyboard modifiers. These can be the additional keys command, shift, alpha, option, control, RightShift, RightOption and RightControl. We can also specify other optional parameters in this function. For example, we can specify whether a hotkey should be exclusively assignable for this solution, which would prohibit other applications that also want to register this combination, because this combination is already assigned and can only be used again when this hotkey is released. Then we can also define the script in the parameters that is to be started when this combination is pressed. To do this, we specify the name of the database in which the script is located and the script name.

Set Variable [ $HotKey ; Value: MBS( "HotKey.Register" ; "F3"; "Shift" ; 0 ; Get(FileName) ; "HotKey" ) ]

There is also another way to set this script, but I will show you that later.

In other parameters of the HotKey.Register function, we can also specify whether FileMaker must be in front so that the key combination calls the script (local hotkey) or whether the script is executed independently (global hotkey). In addition, we can also assign a name and a tag to the hotkey for better identification. The function returns a reference to the hotkey.

In our example, we have decided to specify only the key combination (Shift+F3) in the register function, because we can also set the script with the HotKey.SetScript function. First we enter the reference of the HotKey and then the name of the database in which the script that we want to call up is located. In our case, we define the hotkey in the same solution as the script and so we can use Get(FileName). We then enter the script name. If this script requires a parameter to work, we can specify this as an optional parameter. Before we register a new hotkey, we release all old hotkeys with HotKey.UnregisterAll so that we don't have to deal with old ones. Our script looks like this.

Set Variable [ $r ; Value: MBS( "HotKey.UnregisterAll" ) ]
Set Variable [ $HotKey ; Value: MBS("HotKey.Register"; "F3"; "Shift") ]
Set Variable [ $r ; Value: MBS("HotKey.SetScript"; $HotKey; Get(FileName); "UserNotification") ]

If you are now desperately testing the key combination over and over again and it does not do what you are supposed to do, then this may be because the F keys are normally already assigned tasks. If you now also press the fn key, it works. If you do not want to press this key additionally, you can set your Mac so that you have to press the fn key if you want to use the F keys in their actual functionality, but not when you call up a key combination for FileMaker.

That concludes this door and we'll see again tomorrow for the 20th door.

candy cane Monkeybread Monkey as an elf candy cane
Day 20 - Scan and Recognize

In this day and age, there are still people who like to receive things in printed form, and Christmas is no exception. Some people still fill out their wish lists and send them by mail. But we also have ways to make this easier. Here I would like to make a distinction between Mac and Windows. Today I'll show you the Windows part. First, we want to digitalize the piece of paper. To do this, we first scan it with functions from the WIA component and then run the Windows text recognition over it.

So let's start with our script. First we check whether we are really on a Windows system. If this is the case, we initialize the WIA service for scanning with WIA.Initialize. Then we want to display a dialog with WIA.SelectDeviceDialog with which we can select the scanner with which we can scan the paper. If we always want to specify a fixed scanner to scan the document, we can also set this scanner with WIA.SetCurrentDevice. Now we want to scan our document and call WIA.Scan. Here we specify whether the document is to be scanned via a flatbed or feeder. In the next step, we then check whether the function was executed without errors and returns OK. If this is the case, we would now like to have the scanned document. To do this, we use the WIA.Image function and specify the number of images we want in the parameters. In our case, we have only scanned one image and therefore we want to work with the first one. Here again, the count starts from 0 and we write a 0. However, the function does not return an image, but a path where the image was saved. With Files.ReadFile we can read this file, specifying the path as a container value, and write it to the WishlistIn field.

# Scann and Read

If [ Get(SystemPlatform)= -2 ]
	Set Variable [ $r ; Value: MBS( "WIA.Initialize" ) ]
	Set Variable [ $Printer ; Value: MBS( "WIA.SelectDeviceDialog"; "Sanner") ]
	Set Variable [ $r ; Value: MBS("WIA.Scan"; "Flatbed") ]
	If [ $r="OK" ]
		Set Variable [ $paths ; Value: MBS( "WIA.Image"; 0) ]
		Set Variable [ $img ; Value: MBS("Files.ReadFile"; $paths) ]
		Set Field [ Giftee::WishlistIn ; $img ]
	Else
		Show Custom Dialog [ "Scan Result" ; $r ]
		Exit Script [ Text Result:    ]
	End If

Now, of course, we also want to know what is in the document. We can use Windows text recognition for this. This is built into Windows 10 and 11 by default and can be used with the WindowsOCR component using the plugin. With WindowsOCR.Available we can use the text recognition with the currently used operating system. With WindowsOCR.New we then create a new working environment and receive a reference again. Next, we call the WindowsOCR.Recognize function, which searches for text on an image. In the parameters we enter the reference of the working environment and the container with the image. If we need the text from an image that is located in a container instead of an image that is located as a file on a path, we use the WindowsOCR.RecognizeFile function. Now we have several possibilities to retrieve our result. On the one hand, we can use WindowsOCR.Result to return a JSON that contains information about the text found, such as the positions of the individual words. However, the simple text is sufficient for us. To do this, we call the WindowsOCR.Text function. We can then write the text in our Presents field.

If [ MBS("WindowsOCR.Available") ]
	Set Variable [ $ocr ; Value: MBS("WindowsOCR.New") ]
	Set Variable [ $r ; Value: MBS("WindowsOCR.Recognize"; $ocr; Giftee::WishlistIn) ]
	Set Variable [ $text ; Value: MBS("WindowsOCR.Text"; $ocr) ]
	Set Field [ Giftee::Presents ; $text ]
Else
	Show Custom Dialog [ "Windows OCR not available" ; "You need Windows 10 or 11" ]
End If

That brings us to the end of this door and I'll see you again tomorrow for another door.