I have been using MacRuby and running through the book MacRuby:The Definitive Guide by Matt Aimonetti.
On the Movies CoreData app example, I've got this code:
def add_image(sender)
movie = movies.selectedObjects.lastObject
return unless movie
image_panel = NSOpenPanel.openPanel
image_panel.canChooseDirectories = false
image_panel.canCreateDirectories = false
image_panel.allowsMultipleSelection = false
image_panel.beginSheetModalForWindow(sender.window, completionHandler: Proc.new{|result|
return if (result == NSCancelButton)
path = image_panel.filename
# use a GUID to avoid conflicts
guid = NSProcessInfo.processInfo.globallyUniqueString
# set the destination path in the support folder
dest_path = applicationFilesDirectory.URLByAppendingPathComponent(guid)
dest_path = dest_path.relativePath
error = Pointer.new(:id)
NSFileManager.defaultManager.copyItemAtPath(path, toPath:dest_path, error:error)
NSApplication.sharedApplication.presentError(error[0]) if error[0]
movie.setValue(dest_path, forKey:"imagePath")
})
end
The app loads fine and runs without issue - I can create new movies in CoreData and delete them, etc. However, when I click the button which calls this function it opens the dialog window fine, but either the "cancel" or "open file" buttons cause a crash here:
#import <Cocoa/Cocoa.h>
#import <MacRuby/MacRuby.h>
int main(int argc, char *argv[])
{
return macruby_main("rb_main.rb", argc, argv); << Thread 1: Program received signal EXC_BAD_ACCESS
}
Any help is appreciated. I thought it had something to do with BridgeSupport but either embedding isn't working or my attempts to do so aren't working. Either way, something else seems borked as the example provided with the book also crashes.
Thanks!
ADDED NOTE:
I went and tested out this code from macruby.org and it worked fine:
def browse(sender)
# Create the File Open Dialog class.
dialog = NSOpenPanel.openPanel
# Disable the selection of files in the dialog.
dialog.canChooseFiles = false
# Enable the selection of directories in the dialog.
dialog.canChooseDirectories = true
# Disable the selection of multiple items in the dialog.
dialog.allowsMultipleSelection = false
# Display the dialog and process the selected folder
if dialog.runModalForDirectory(nil, file:nil) == NSOKButton
# if we had a allowed for the selection of multiple items
# we would have want to loop through the selection
destination_path.stringValue = dialog.filenames.first
end
end
Seems either something is borked in the beginSheetModalForWindow call or I'm missing something, trying to track down what. I can make my modal dialog for file selection work with the above code, but it isn't a sheet attached to the window.
NSOpenPanel is a subclass of NSSavePanel so you can use NSSavePanel's beginSheetModalForWindow:completionHandler:. It is runModalForDirectory that is depreciated. However, "depreciated" doesn't mean, "doesn't work" it means "will stop working in the future."
The error you are getting is pointing to the C code that loads MacRuby itself. This indicates a severe crash that MacRuby could not trap. It just shows in Main.m because that is the only place the debugger manages to trap the stack in which the error occurred. Unfortunately, locating the error at the very top/bottom of the stack like this makes it useless for debugging.
I don't see any obvious problem with Matt Aimonetti's code so I'm going to guess it's a problem with MacRuby handling the block that is passed for the completion handler. That would also explain why the error is not trapped because the block will be in a different address space than the object that defines it.
I would suggest contacting Matt Aimonetti directly via either the book site or the MacRuby mailing list (he's very active there.)
Related
I'm a newcomer to Swift (2.2) and am having a problem with a simple app using Xcode 7.3 and OS X 10.11. In this app, the user clicks a button and selects a file through NSOpenPanel. The code uses the URL selected to get the file's data and name, then processes the data and saves the result somewhere else. With large files, the processing can take several seconds. When processing large files, once the file is selected, the space where the Open File window had been remains blank, covering the app view and everything else, and stays there until the operation is complete. Also, the app's outlets are frozen until the operation finishes. It appears NSOpenPanel isn't handing window control back to the app and the system.
The code goes like this:
#IBAction func processFile(sender: AnyObject) {
var chosenURL: NSURL?
let openPanel = NSOpenPanel()
openPanel.title = "Choose a file"
openPanel.canChooseDirectories = false
openPanel.allowsMultipleSelection = false
if openPanel.runModal() == NSFileHandlingPanelOKButton {
chosenURL = openPanel.URL
}
let dataBytes = NSData(contentsOfURL: chosenURL!)
let fileName = chosenURL!.lastPathCompnent!
// Remaining code processes dataBytes and fileName
I've tried a few variations but get the same result. Searching for "NSOpenPanel won't close" on the 'net usually just brings up examples in Objective-C, which I know nothing of. Any suggestions of how to make NSOpenPanel shut off and have view and control return to the app window?
Following on from Eric D's suggestion, I looked into Grand Central Dispatch and background processes. My first approach was:
dispatch_async(dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0)) {
dataBytes = NSData(contentsOfURL: chosenURL!)
}
That didn't change anything. I found I had to put the whole remaining process (everything from 'let dataBytes…' onwards) within the dispatch closure, with 'dispatch_async(dispatch_get_main_queue())' statements around UI updates. This stopped the window from freezing and blanking, and returned control to the app. Thanks again, Eric.
I am currently switching input sources by running a GUI AppleScript through Alfred, and the GUI script can sometime take up to 1s to complete the change. It gets quite annoying at times.
I have come across Determine OS X keyboard layout (“input source”) in the terminal/a script. And I want to know since we can find out the current input source if there's a way to change input source programatically? I'd tried overwriting the com.apple.HIToolbox.plist but it does not change the input.
(I do realise there's mapping shortcut to input sources available in the system preference, however I prefer mapping keywords with Alfred)
You can do it using the Text Input Services API:
NSArray* sources = CFBridgingRelease(TISCreateInputSourceList((__bridge CFDictionaryRef)#{ (__bridge NSString*)kTISPropertyInputSourceID : #"com.apple.keylayout.French" }, FALSE));
TISInputSourceRef source = (__bridge TISInputSourceRef)sources[0];
OSStatus status = TISSelectInputSource(source);
if (status != noErr)
/* handle error */;
The dictionary in the first line can use other properties for other criteria for picking an input source.
There's also NSTextInputContext. It has a selectedKeyboardInputSource which can be set to an input source ID to select a different input source. The issue there is that you need an instance of NSTextInputContext to work with and one of those exists only when you have a key window with a text view as its first responder.
#Ken Thomases' solution is probably the most robust - but it requires creation of a command-line utility.
A non-GUI-scripting shell scripting / AppleScripting solution is unfortunately not an option: while it is possible to update the *.plist file that reflects the currently selected input source (keyboard layout) - ~/Library/Preferences/com.apple.HIToolbox.plist - the system will ignore the change.
However, the following GUI-scripting solution (based on this), while still involving visible action, is robust and reasonably fast on my machine (around 0.2 seconds):
(If you just wanted to cycle through installed layouts, using a keyboard shortcut defined in System Preferences is probably your best bet; the advantage of this solution is that you can target a specific layout.)
Note the prerequisites mentioned in the comments.
# Example call
my switchToInputSource("Spanish")
# Switches to the specified input source (keyboard layout) using GUI scripting.
# Prerequisites:
# - The application running this script must be granted assisistive access.
# - Showing the Input menu in the menu bar must be turned on
# (System Preferences > Keyboard > Input Sources > Show Input menu in menu bar).
# Parameters:
# name ... input source name, as displayed when you open the Input menu from
# the menu bar; e.g.: "U.S."
# Example:
# my switchToInputSource("Spanish")
on switchToInputSource(name)
tell application "System Events" to tell process "SystemUIServer"
tell (menu bar item 1 of menu bar 1 whose description is "text input")
# !! Sadly, we must *visibly* select (open) the text-input menu-bar extra in order to
# !! populate its menu with the available input sources.
select
tell menu 1
# !! Curiously, using just `name` instead of `(get name)` didn't work: 'Access not allowed'.
click (first menu item whose title = (get name))
end tell
end tell
end tell
end switchToInputSource
Solution using Xcode Command Line Tools
For those, who would like to build #Ken Thomases' solution but without installing Xcode (which is several GiB and is totally useless to spend so much space on unless used seriously) it is possible to build it using the Xcode Command Line Tools.
There are several tutorials on the internet about how to install Xcode Command Line Tools. The point here is only that it takes fraction of the space compared to full-blown Xcode.
Once you have it installed, these are the steps:
Create a file called whatever.m
In whatever.m put the following:
#include <Carbon/Carbon.h>
int main (int argc, const char * argv[]) {
NSArray* sources = CFBridgingRelease(TISCreateInputSourceList((__bridge CFDictionaryRef)#{ (__bridge NSString*)kTISPropertyInputSourceID : #"com.apple.keylayout.French" }, FALSE));
TISInputSourceRef source = (__bridge TISInputSourceRef)sources[0];
OSStatus status = TISSelectInputSource(source);
if (status != noErr)
return -1;
return 0;
}
Replace French with your desired layout.
Save the file
Open terminal in the same folder as whatever.m is
Run this command:
clang -framework Carbon whatever.m -o whatever
Your application is created as whatever in the same folder and can be executed as:
.\whatever
Additionally
I've never created any Objective-C programs, so this may be suboptimal, but I wanted an executable that can take the keyboard layout as a command line parameter. For anyone interested, here's the solution I came up with:
In step 2 use this code:
#import <Foundation/Foundation.h>
#include <Carbon/Carbon.h>
int main (int argc, const char * argv[]) {
NSArray *arguments = [[NSProcessInfo processInfo] arguments];
NSArray* sources = CFBridgingRelease(TISCreateInputSourceList((__bridge CFDictionaryRef)#{ (__bridge NSString*)kTISPropertyInputSourceID : [#"com.apple.keylayout." stringByAppendingString:arguments[1]] }, FALSE));
TISInputSourceRef source = (__bridge TISInputSourceRef)sources[0];
OSStatus status = TISSelectInputSource(source);
if (status != noErr)
return -1;
return 0;
}
In step 6. run this command:
clang -framework Carbon -framework Foundation whatever.m -o whatever
You can now switch to any layout from the command line, e.g.:
./whatever British
Note: it only allows to switch to layouts already configured on your system!
Another option is to use Swift. It can be used in a script-like fashion (no compilation).
Install Xcode Command Line Tools
Create a script from the code below
Run the script using swift script_file_name
Code:
import Carbon
let command = ProcessInfo.processInfo.arguments.dropFirst().last ?? ""
let filter = command == "list" ? nil : [kTISPropertyInputSourceID: command]
guard let cfSources = TISCreateInputSourceList(filter as CFDictionary?, false),
let sources = cfSources.takeRetainedValue() as? [TISInputSource] else {
print("Use \"list\" as an argument to list all enabled input sources.")
exit(-1)
}
if filter == nil { // Print all sources
print("Change input source by passing one of these names as an argument:")
sources.forEach {
let cfID = TISGetInputSourceProperty($0, kTISPropertyInputSourceID)!
print(Unmanaged<CFString>.fromOpaque(cfID).takeUnretainedValue() as String)
}
} else if let firstSource = sources.first { // Select this source
exit(TISSelectInputSource(firstSource))
}
This elaborates on answers by Ken Thomases and sbnc.eu.
On AppleScript you must only take cmd + "space" (or something other, what you use for change keyboard source).
And all what you need:
key code 49 using command down
49 - code of 'space' button in ASCII for AppleScript.
P.S.: don't forget get access for you AppleScript utility in System Preferences.
tell application "System Events"
key code 49 using control down
end tell
Changes layout via keypress
I want to make my own window, using Glade (3.14.2).
At a certain point in my program, I want to
1) Put up the window and let the user do stuff
2) Wait for it to close
3) Get values from the window object
4) Continue on in my code
So basically, I want to treat the window like a modal dialog - but one that I write and control.
I've tried for a few hours. The window appears just fine, as designed in Glade. The user can interact with it.
When the window closes, code that's been connected with signal_connect('destroy') executes.
But the code that invoked the window's show() method... does not continue executing after the window closes.
class GrammarNodeEditor
#this makes the class visual:
include GladeGUI
def initialize(raw_node = nil, &close_block)
#raw_node = raw_node || {type: :Sequence, data: []}
#original_data = #raw_node[:data]
#close_block = close_block
end
def show
puts "GNE Window Opening"
load_glade(__FILE__)
#builder["window1"].title = "Edit/Create Grammar Node"
#builder["window1"].signal_connect('destroy') {|*args|
#close_block.call(self)
puts "GNE WINDOW DESTROY"
}
show_window()
puts "Done showing window"
end
Here is how I invoke it:
rhs_editor = GrammarNodeEditor.new {|obj|
puts "In closeblck, obj is #{obj.inspect}"
#rhs = obj.raw_node
}
puts "About to call show in GR:Init"
rhs_editor.show
puts "Back from calling show in GR:Init"
Here is the output:
About to call show in GR:Init
GNE Window Opening
In closeblck, obj is #<GrammarNodeEditor:0x7b82a88 #raw_node={:type=>:Sequence, :data=>[]}, [more junk here]>
GNE WINDOW DESTROY
The first two lines of output appear after I open the window. The 3rd and 4th appear when I close the window.
Note that "Done showing window" and "Back from calling show in GR:Init" are not printed at all.
Just to make this a little more interesting, I want to be able to do this from within code that puts up another window. My top-level window has a button to create a new Rule. The Rule must be initialized with a Node, and then the Rule must be edited. So first I need to put up a Node-definition window (as shown above) and then, when I have a Node defined, I want to put up a Rule window that uses that Node.
So I think I need to call this code within either the initialize() or the show() method of the GrammarRuleWindow class (another Glade-defined window).
Can someone explain why my puts's aren't being printed, and how to make the control flow go on through them?
Thanks!
...So it turned out the problem was that I had created the window's .glade file directly in Glade, rather than using the VisualRuby IDE.
Creating the .glade in VR adds some stuff to the file that VR needs. Specifically, the file needs to contain the line
<signal name="destroy" handler="destroy_window" swapped="no"/>
before the first <child...> tag.
While setting up an NSAlert object to be displayed as a modal sheet in Xcode 5.0.2, I hit an interesting surprise.
I was planning on using beginSheetModalForWindow:modalDelegate:didEndSelector:contextInfo:
As I started to enter it, Xcode autofilled beginSheetModalForWindow:completionHandler: for me (even though I cannot find this in any NSAlert documentation).
I prefer to use completion handlers rather than delegate/selector as a callback mechanism, so I went ahead and tried it. I was pleasantly surprised to find that it worked perfectly.
Three quick questions before I commit to this.
Am I missing something in the documentation?
Is it "safe" to use this feature if it is undocumented? (i.e. will it magically disappear as mysteriously as it appeared?)
I'd rather not hardcode the response values based on what I'm seeing via logging. Does anybody know the "proper" NS...Button constants?
This call is “safe” but it’s 10.9+ only. Here it is from the header file:
#if NS_BLOCKS_AVAILABLE
- (void)beginSheetModalForWindow:(NSWindow *)sheetWindow completionHandler:(void (^)(NSModalResponse returnCode))handler NS_AVAILABLE_MAC(10_9);
#endif
It appears they just accidentally left it out of the current docs. The headers are generally considered the “truth” in Cocoa, though—they authoritatively tell you what’s deprecated and what’s new. (Unlike in X11, for instance, where the documentation was declared to be correct over the actual implementations or the headers.)
These are the constants you want to use inside your completionHandler block:
/* These are additional NSModalResponse values used by NSAlert's -runModal and -beginSheetModalForWindow:completionHandler:.
By default, NSAlert return values are position dependent, with this mapping:
first (rightmost) button = NSAlertFirstButtonReturn
second button = NSAlertSecondButtonReturn
third button = NSAlertThirdButtonReturn
buttonPosition 3+x = NSAlertThirdButtonReturn + x
Note that these return values do not apply to an NSAlert created via +alertWithMessageText:defaultButton:alternateButton:otherButton:informativeTextWithFormat:, which instead uses the same return values as NSRunAlertPanel. See NSAlertDefaultReturn, etc. in NSPanel.h
*/
enum {
NSAlertFirstButtonReturn = 1000,
NSAlertSecondButtonReturn = 1001,
NSAlertThirdButtonReturn = 1002
};
I am learning PyQt4 (I am using version 4.4.4) and I'm pretty new to Python (Python 2.5). I have a GUI with a QListWidget and a QPushButton. I want a user to be able to click to select an entry in the list and then click the QPushButton and have the selected entry go away (be deleted from the QList). I've been banging my head against this problem for over a week and I would deeply appreciate some help.
Currently, my GUI comes up and I can select different list items (only one at a time right now), but when I click the QPushButton, nothing happens. The selection color goes from blue to grey, but the entry is not removed. No error is shown in Command Prompt (Windows 7).
I have defined a function, remove(),which I am using as the slot for the QPushButton. I believe the QPushButton.connect is defined correctly for a Qt Signal to Python Slot, based on what I've seen of answers to similar problems, but the items are not being deleted. However, the remove function is not even being triggered. I have a print statement within the function, but it is not being called when I click the QPushButton, which is how I know that the function is not being called.
Here is my most recent code: (I read a very rant-y post on meta-SO about big blocks of code, so I've cut this down to the bits I think are relevant: the list creation, the button creation and the remove function, which I'm trying to use as a slot. I've left in comments that indicate what the other sections are, so if you think I've left out something that could help, let me know and I'll add it back in)
class questionGUI(QtGui.QWidget):
#This class is the window of the gui.
def __init__(self):
super(questionGUI,self).__init__()
#Layout
grid = QtGui.QGridLayout()
grid.setSpacing(10)
#Labels Needed
...
#Question List
self.qList = QtGui.QListWidget()
#print self.qList
self.qList.setSelectionMode(QtGui.QAbstractItemView.SingleSelection)
entries = ['[Pick Image] <Default>','[Slider Question] <Default>', '[Comment Box] <Default>']
for i in entries:
item = QtGui.QListWidgetItem(i)
self.qList.addItem(item)
#Type select
...
#Text insert Needed
...
#Buttons Needed
deleteButton = QtGui.QPushButton('Delete Question')
deleteButton.connect(deleteButton,QtCore.SIGNAL('itemClicked(clicked)'),lambda: self.remove)
addQuestionButton = QtGui.QPushButton('Add Question')
...
doneButton = QtGui.QPushButton('Done')
...
#Parameters Needed
...
#Layout Placement and Window dimensions
...
def addQuestion(self):
...
def remove(self):
print 'remove triggered'
print self.qList.currentItem()
self.qList.removeItemWidget(self.qList.currentItem())
...
I tried to post an image, but I don't have enough reputation. If you think an image would be useful, let me know and I can send it to you.
You mixed the signals:
deleteButton.connect(deleteButton,QtCore.SIGNAL('itemClicked(clicked)'),lambda: self.remove)
deleteButton is a QPushButton, but itemClicked(clicked) looks like the signal from QListWidget with wrong signature. Since, QPushButton doesn't have this signal, no connection is made. Qt doesn't raise errors for failed connections, but .connect method has a bool return value indicating success/failure of the attempted connection.
Also, lambda: self.remove as a slot doesn't make sense. Slot should be a callable that is called upon signal emit. Sure, lambda creates a function, but all you do is reference the method self.remove. lambda will be called, self.remove not. Just self.remove as a slot is enough.
You should use clicked() signal (or clicked(bool), if you care about the checked value) from button:
deleteButton.connect(deleteButton, QtCore.SIGNAL('clicked()'), self.remove)
Edit
Another problem: Your remove method doesn't do what you want. removeItemWidget doesn't remove the item, it removes the widget inside the item (if you set one). It's counterpart to setItemWidget.
You should use takeItem to remove items.
def remove(self):
self.qList.takeItem(self.qList.currentRow())