I've made a Finder extension to add a menu to Finder's Context menu for any file. I'd like to access this file when the user selects this custom menu, obviously this file they select could be anywhere in the file system and outside the allowed sandbox areas.
func accessFile(url: URL, userID: String, completion: #escaping ([String:Any]?, Error?) -> Void){
var bookmarks = NSKeyedUnarchiver.unarchiveObject(withFile: bookmarksPath) as? [URL: Data]
print("Testing if we have access to file")
// 1. Test if I have access to a file
let directoryURL = url.deletingLastPathComponent()
let data = bookmarks?[directoryURL]
if data == nil{
print("have not asked for access yet or directory is not saved")
// 2. If I do not, open a open dialog, and get permission
let openPanel = NSOpenPanel()
openPanel.allowsMultipleSelection = false
openPanel.canChooseDirectories = true
openPanel.canCreateDirectories = false
openPanel.canChooseFiles = false
openPanel.prompt = "Grant Access"
openPanel.directoryURL = directoryURL
openPanel.begin { result in
guard result == .OK, let url = openPanel.url else {return}
// 3. obtain bookmark data of folder URL and store it to keyed archive
do{
let data = try url.bookmarkData(options: .withSecurityScope, includingResourceValuesForKeys: nil, relativeTo: nil)
}catch{
print(error)
}
bookmarks?[url] = data
NSKeyedArchiver.archiveRootObject(bookmarks, toFile: bookmarksPath)
// 4. start using the fileURL via:
url.startAccessingSecurityScopedResource()
// < do whatever to file >
url.stopAccessingSecurityScopedResource()
}
}else{
// We have accessed this directory before, get data from bookmarks
print("we have access already")
let directoryURL = url.deletingLastPathComponent()
guard let data = bookmarks?[directoryURL]! else { return }
var isStale = false
let newURL = try? URL(resolvingBookmarkData: data, options: .withSecurityScope, relativeTo: nil, bookmarkDataIsStale: &isStale)
// 3. Now again I start using file URL and upload:
newURL?.startAccessingSecurityScopedResource()
// < do whatever to file >
newURL?.stopAccessingSecurityScopedResource()
}
}
Currently it always asks for permission, so the bookmark is not getting saved
I'm not 100% sure if this is the source of your problem, but I don't see where you are using the isStale value. If it it comes back true from URL(resolvingBookmarkData:...), you have to remake/resave the bookmark. So in your else block you need some code like this:
var isStale = false
let newURL = try? URL(
resolvingBookmarkData: data,
options: .withSecurityScope,
relativeTo: nil,
bookmarkDataIsStale: &isStale
)
if let url = newURL, isStale
{
do
{
data = try url.bookmarkData(
options: .withSecurityScope,
includingResourceValuesForKeys: nil,
relativeTo: nil
)
}
catch { fatalError("Remaking bookmark failed") }
// Resave the bookmark
bookmarks?[url] = data
NSKeyedArchiver.archiveRootObject(bookmarks, toFile: bookmarksPath)
}
newURL?.startAccessingSecurityScopedResource()
// < do whatever to file >
newURL?.stopAccessingSecurityScopedResource()
data will, of course, need to be var instead of let now.
Also remember that stopAccessingSecurityScopedResource() has to be called on main thread, so if you're not sure accessFile is being called on the main thread, you might want to do that explicitly:
DispatchQueue.main.async {
newURL?.stopAccessingSecurityScopedResource()
}
You'd want to do that in both places you call it.
I like to write an extension on URL to make it a little nicer:
extension URL
{
func withSecurityScopedAccess<R>(code: (URL) throws -> R) rethrows -> R
{
self.startAccessingSecurityScopedResource()
defer {
DispatchQueue.main.async {
self.stopAccessingSecurityScopedResource()
}
}
return try code(self)
}
}
So then I can write:
url.withSecurityScopedAccess { url in
// Do whatever with url
}
Whether you use the extension or not, explicitly calling stopAccessingSecurityScopedResource() on DispatchQueue.main does mean that access won't be stopped until the next main run loop iteration. That's normally not a problem, but if you start and stop the access for the same URL multiple times in a single run loop iteration, it might not work, because it will call startAccessingSecurityScopedResource() multiple time without stopAccessingSecurityScopedResource() in between, and the on the next iteration it would call stopAccessingSecurityScopedResource() multiple times as the queued tasks are executed. I have no idea if URL maintains a security access count that would allow that to be safe, or just a flag, in which case it wouldn't be.
Let's make some issues visible by removing the bookmark and NSOPenPanel code:
var bookmarks = NSKeyedUnarchiver.unarchiveObject(withFile: bookmarksPath) as? [URL: Data]
// bookmarks is an optional and can be nil (when the file doesn't exist)
let data = bookmarks?[directoryURL]
if data == nil {
// NSOpenPanel
do {
let data = try openPanelUrl.bookmarkData(options: .withSecurityScope, includingResourceValuesForKeys: nil, relativeTo: nil)
// this data is a local data, the other data didn't change
} catch {
print(error)
}
// bookmarks and data are still nil
bookmarks?[openPanelUrl] = data
NSKeyedArchiver.archiveRootObject(bookmarks, toFile: bookmarksPath)
// use url
} else {
// get the bookmark data again
guard let data = bookmarks?[directoryURL]! else { return }
// get url from data and use it
}
I would do something like:
var bookmarks: [URL: Data]
if let savedBookmarks = NSKeyedUnarchiver.unarchiveObject(withFile: bookmarksPath) as? [URL: Data] {
bookmarks = savedBookmarks
}
else {
bookmarks = [:]
}
// bookmarks is a dictionary and can be saved
if let data = bookmarks[directoryURL] {
// get url from data and use it
}
else {
// NSOpenPanel
do {
if let newData = try openPanelUrl.bookmarkData(options: .withSecurityScope, includingResourceValuesForKeys: nil, relativeTo: nil) {
// bookmarks and newData are not nil
bookmarks[openPanelUrl] = newData
NSKeyedArchiver.archiveRootObject(bookmarks, toFile: bookmarksPath)
// use url
}
} catch {
print(error)
}
}
I'm very new to coding so this is difficult for me to figure out how to do, I've tried looking docs n google but without help!
I got 3 buttons where I want one of them to change the new,vmax container with a new container having data specified in the button func, but I can't seem to figure out how I on the button can change the container content or the whole container
This is my code for now
package main
import (
"fmt"
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/app"
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/theme"
"fyne.io/fyne/v2/widget"
)
var Menu = []string{"Home", "App-Status"}
func main() {
var W fyne.Window
a := app.New()
a.Settings().SetTheme(theme.DarkTheme())
W = a.NewWindow("Application-OutSight")
W.Resize(fyne.NewSize(640, 460))
text := widget.NewLabel("Welcome to This App")
// start container with welcome text
contentcontainer := container.NewMax(text)
split := (container.NewHSplit(
menuBar(Menu),
contentcontainer,
))
split.Offset = 0.2
W.SetContent(split)
W.ShowAndRun()
}
func menuBar(Menu []string) *widget.List {
listView := widget.NewList(func() int {
return len(Menu)
},
func() fyne.CanvasObject {
return widget.NewLabel("template")
},
func(id widget.ListItemID, o fyne.CanvasObject) {
o.(*widget.Label).SetText(Menu[id])
})
listView.OnSelected = func(id widget.ListItemID) {
if id == 0 {
//when i click here i want to change the start container to this container but not the sidebare as shown on picture only the container with the welcome text
somevaluefunction()
anothervaluefunction()
contentcontainer = container.NewMax(somevaluefunction(), anothervaluefunction())
// return or refresh the container in main with this new one
} else if id == 1 {
fmt.Println("app")
}
if id == 2 {
fmt.Println("exit")
}
}
return listView
}
You could change split.Objects[1] or put a named container inside there and set it’s content directly (avoiding the [1] to access second item of a split.
Scroll collapses everytime i use list widget inside scroll container, if i use label widget then scroll container is full width and full height but when i use list widget it just collapses.
Not Working (Scroll collapses)
func ShowListDialog(win fyne.Window, messages []string){
list := widget.NewList(
func() int {
return len(messages)
},
func() fyne.CanvasObject {
return widget.NewLabel("label")
},
func(i widget.ListItemID, o fyne.CanvasObject) {
o.(*widget.Label).SetText(messages[i])
},
)
d := dialog.NewCustom("Messages", "Close" , container.NewScroll(list), win)
d.Resize(fyne.NewSize(500, 400))
d.Show()
}
Working for label (scroll has full width&height)
func ShowLabelDialog(win fyne.Window, message string){
d := dialog.NewCustom("Message", "Close", container.NewScroll(widget.NewLabel(message)), win)
d.Resize(fyne.NewSize(500, 400))
d.Show()
}
The list widget already contains a scroll - removing the outer one should resolve your issue.
I am using following code to create and show a window with GUI components as label, entry and button:
// modified from: https://github.com/andlabs/ui/wiki/Getting-Started
package main
import ("github.com/andlabs/ui")
func makewinfn() {
var name = ui.NewEntry()
var button = ui.NewButton("Greet")
var greeting = ui.NewLabel("")
box := ui.NewVerticalBox()
box.Append(ui.NewLabel("Enter your name:"), false)
box.Append(name, false)
box.Append(button, false)
box.Append(greeting, false)
mywindow := ui.NewWindow("MyTitle", 200, 100, false)
mywindow.SetChild(box)
button.OnClicked( func (*ui.Button) {greeting.SetText("Hello, " + name.Text() + "!") } )
mywindow.OnClosing( func (*ui.Window) bool { ui.Quit(); return true } )
mywindow.Show()
}
func main() {
ui.Main(makewinfn)
// HOW TO CREATE AND SHOW ANOTHER SUCH WINDOW HERE ?
// ui.Main(makewinfn) // this opens window only after first is closed.
}
It works well, but as I mentioned in title and as commented in code above, how can I open two such windows simultaneously from main function?
Repeating ui.Main(makewinfn) in main function leads to second window opening only after first is closed.
Thanks for your help.
In UI libraries usually the components or widgets you build the interface with have parents, and usually a single component may have at most one parent.
So if you want 2 windows, having the same components, you still have to create those components in 2 instances, because a component cannot be added to 2 different parents (in 2 different windows).
So simplest would be to move the component and window creation logic into a function, and call that twice from the function you pass to ui.Main():
func createWindow() {
var name = ui.NewEntry()
var button = ui.NewButton("Greet")
var greeting = ui.NewLabel("")
box := ui.NewVerticalBox()
box.Append(ui.NewLabel("Enter your name:"), false)
box.Append(name, false)
box.Append(button, false)
box.Append(greeting, false)
mywindow := ui.NewWindow("MyTitle", 200, 100, false)
mywindow.SetChild(box)
button.OnClicked( func (*ui.Button) {greeting.SetText("Hello, " + name.Text() + "!") } )
mywindow.OnClosing( func (*ui.Window) bool { ui.Quit(); return true } )
mywindow.Show()
}
func makewinfn() {
createWindow()
createWindow()
}
Using this createWindow() function of course is not a requirement, you could have a loop in makewinfn() with 2 iterations, each which could create a window.
The above example creates 2 identical windows, but they will be "independent". If you enter a text in one of them and click on its button, the result will only be seen in its containing / parent window. This is possible because each component has been created twice.
If you wan to customize the windows, you could pass a parameter to createWindow() so the window and its content could be customized / personalized based on its value. For example:
func createWindow(id string) {
var name = ui.NewEntry()
var button = ui.NewButton("Greet " + id)
var greeting = ui.NewLabel("")
box := ui.NewVerticalBox()
box.Append(ui.NewLabel("Enter your name " + id + ":"), false)
box.Append(name, false)
box.Append(button, false)
box.Append(greeting, false)
mywindow := ui.NewWindow("MyTitle " + id, 200, 100, false)
mywindow.SetChild(box)
button.OnClicked( func (*ui.Button) {greeting.SetText("Hello, " + name.Text() + "!") } )
mywindow.OnClosing( func (*ui.Window) bool { ui.Quit(); return true } )
mywindow.Show()
}
func makewinfn() {
createWindow("one")
createWindow("two")
}
I don't know Go but assuming the GUI works like any other language I've used, in your makewinfn function you can simply create more windows by calling ui.NewWindow() again.
func makewinfn() {
var name = ui.NewEntry()
var button = ui.NewButton("Greet")
var greeting = ui.NewLabel("")
box := ui.NewVerticalBox()
box.Append(ui.NewLabel("Enter your name:"), false)
box.Append(name, false)
box.Append(button, false)
box.Append(greeting, false)
mywindow := ui.NewWindow("MyTitle", 200, 100, false)
mywindow.SetChild(box)
button.OnClicked( func (*ui.Button) {greeting.SetText("Hello, " + name.Text() + "!") } )
mywindow.OnClosing( func (*ui.Window) bool { ui.Quit(); return true } )
mywindow.Show()
myOtherWindow := ui.NewWindow("MyOtherTitle", 200, 100, false)
myOtherWindow.Show()
}
I need help writing a shiny app that will spit out a separate datatable after entering into each respective input and clicking the actionbutton. I got the app to simply output to the datatable, but it will overwrite upon a new entry. I checked the reference section of R Shiny and it seems I need to use insertUI() but I don't really understand Shiny very well to figure it out. Please help. Thanks.
library(shiny)
library(rhandsontable)
#UI
ui <-shinyUI(fluidPage(
titlePanel('Drug Prep'),
sidebarPanel(textInput('expid','Experiment ID'),
textInput('nsc','NSC Number'),
textInput('solvent','Solvent'),
numericInput('stkcon','Stock Conc(mg/ml)',0),
numericInput('Repeat','Repeat',0),
textAreaInput('instruct','Instructions',height='50px'),
numericInput('amt','Amt to weigh(mg)',0),
numericInput('vehicle','Tot_vol of vehicle(ml)',0),
hr(),
numericInput('grp','Group',0),
numericInput('micedose','Mice Dose/vial',0),
numericInput('dose','Dose(mg/kg)',0),
numericInput('doseconc','Dose Concentration(mg/ml)',0),
numericInput('numvial','No. of Vials',0),
numericInput('volA','Vol of Vehicle_A(ml)',0),
numericInput('volB','Vol of Vehicle_B(ml)',0),
numericInput('volC','Vol of Vehicle_C(ml)',0),
br(),
actionButton('save','Save')
),
mainPanel(
textOutput('nsc'),
verbatimTextOutput('instruct'),
uiOutput("hot"),
hr(),
uiOutput('hot2')
)
)
)
#SERVER
server=function(input, output) {
global <- reactiveValues()
observeEvent(input$save, {
global$data[[length(global$data) + 1]] = data.frame(ExpID=input$expid,NSC=input$nsc,Stock.conc=input$stkcon,
Repeats=input$Repeat,Amt=input$amt,vol=input$vehicle,
stringsAsFactors = F)
})
observeEvent(input$save, {
global$ditto[[length(global$ditto) + 1]] = data.frame(group=input$grp,No_of_mice_dosed=input$micedose,dose=input$dose,dose_conc=input$doseconc,
No_vial=input$numvial,vol_Vehicle_A=input$volA,vol_Vehicle_B=input$volB,vol_Vehicle_C=input$volC,
tot_vol=input$volA+input$volB+input$volC,stringsAsFactors = F)
})
observeEvent(input$save, {
for(nr in 1:length(global$data)){
local({
df <- global$data[[nr]]
output[[paste0("hot", nr)]] <- renderRHandsontable(rhandsontable(df))
})
}
})
observeEvent(input$save, {
for(nr in 1:length(global$ditto)){
local({
df<-global$ditto[[nr]]
output[[paste0('hot2',nr)]] <- renderRHandsontable(rhandsontable(df))
})
}
})
output$instruct<-renderText({input$save
paste(isolate(input$nsc),isolate(input$instruct),sep='\n')
})
output$hot <- renderUI({
lapply(1:length(global$data), function(nr) rHandsontableOutput(paste0("hot", nr)))
})
output$hot2<-renderUI({
lapply(1:length(global$ditto), function(nr) rHandsontableOutput(paste0("hot2", nr)))
})
}
shinyApp(ui = ui, server=server)
I want the app to minic this excel spreadsheet, notice how the amount of columns differ for each table (submission).
I wasn't 100% clear whether you wanted to add a new entry to the table or you want to do something else like replacing the table. Below you will find an example of adding new rows you your table. I also added a check so that no duplicates are entered using duplicated function.
library(shiny)
library(rhandsontable)
#UI
ui <-shinyUI(fluidPage(
titlePanel('Drug Prep'),
sidebarPanel(textInput('expid','Experiment ID'),
textInput('nsc','NSC Number'),
textInput('solvent','Solvent'),
numericInput('stkcon','Stock Conc(mg/ml)',0),
numericInput('Repeat','Repeat',0),
textAreaInput('instruct','Instructions',height='50px'),
numericInput('amt','Amt to weigh(mg)',0),
numericInput('vehicle','Tot_vol of vehicle(ml)',0),
hr(),
numericInput('grp','Group',0),
numericInput('micedose','Mice Dose/vial',0),
numericInput('dose','Dose(mg/kg)',0),
numericInput('doseconc','Dose Concentration(mg/ml)',0),
numericInput('numvial','No. of Vials',0),
numericInput('volA','Vol of Vehicle_A(ml)',0),
numericInput('volB','Vol of Vehicle_B(ml)',0),
numericInput('volC','Vol of Vehicle_C(ml)',0),
br(),
actionButton('save','Save')
),
mainPanel(
textOutput('nsc'),
verbatimTextOutput('instruct'),
rHandsontableOutput("hot"),
br(),
rHandsontableOutput('hot2')),
hr()
)
)
#SERVER
server=function(input, output, session) {
output$instruct <- renderText({input$save
paste(isolate(input$nsc),isolate(input$instruct),sep='\n')
})
mydf <- reactiveValues()
observeEvent(input$save,{
data <- data.frame(ExpID=input$expid,NSC=input$nsc,Stock.conc=input$stkcon,
Repeats=input$Repeat,Amt=input$amt,vol=input$vehicle,
stringsAsFactors = F)
mydf$df <- rbind(mydf$df,data)
mydf$df <- mydf$df[!duplicated(mydf$df), ]
data2 <- data.frame(group=input$grp,No_of_mice_dosed=input$micedose,dose=input$dose,dose_conc=input$doseconc,
No_vial=input$numvial,vol_Vehicle_A=input$volA,vol_Vehicle_B=input$volB,vol_Vehicle_C=input$volC,
tot_vol=input$volA+input$volB+input$volC,stringsAsFactors = F)
mydf$df2 <- rbind(mydf$df2,data2)
mydf$df2 <- mydf$df2[!duplicated(mydf$df2), ]
})
output$hot <- renderRHandsontable({
if(is.null(mydf$df)){
return()
}
rhandsontable(mydf$df)
})
output$hot2 <- renderRHandsontable({
if(is.null(mydf$df2)){
return()
}
rhandsontable(mydf$df2)
})
}
shinyApp(ui = ui, server=server)