1require 'osx/cocoa' 2include OSX 3 4OSX.require_framework 'PreferencePanes' 5OSX.load_bridge_support_file File.expand_path('../Security.bridgesupport', __FILE__) 6 7require File.expand_path('../passenger_pane_config', __FILE__) 8require File.expand_path('../shared_passenger_behaviour', __FILE__) 9require File.expand_path('../PassengerApplication', __FILE__) 10 11if RUBY_VERSION == "1.8.7" && OSX::RUBYCOCOA_VERSION == "0.13.2" 12 class OSX::NSArray 13 def count 14 oc_count 15 end 16 end 17end 18 19class PrefPanePassenger < NSPreferencePane 20 class << self 21 attr_accessor :sharedInstance 22 end 23 24 include SharedPassengerBehaviour 25 26 ib_outlet :installPassengerWarning 27 ib_outlet :authorizationView 28 ib_outlet :applicationsTableView 29 ib_outlet :applicationsController 30 31 kvc_accessor :applications, :authorized, :dirty_apps, :revertable_apps 32 33 def mainViewDidLoad 34 self.class.sharedInstance = self 35 setup_authorization_view! 36 setup_applications_table_view! 37 38 OSX::NSNotificationCenter.defaultCenter.objc_send( 39 :addObserver, self, 40 :selector, 'paneWillBecomeActive:', 41 :name, OSX::NSApplicationWillBecomeActiveNotification, 42 :object, nil 43 ) 44 end 45 46 def paneWillBecomeActive(notification = nil) 47 willSelect 48 end 49 50 def willSelect 51 @dropping_directories = @dirty_apps = @revertable_apps = false 52 setup_passenger_warning! 53 @applicationsController.content.empty? ? load_appications! : reload_appications! 54 end 55 56 def applicationMarkedDirty(app) 57 self.revertable_apps = @applicationsController.content.any? { |app| app.revertable? } 58 self.dirty_apps = true 59 end 60 61 def apply(sender = nil) 62 if authorize! 63 @applicationsController.content.each { |app| app.apply if app.dirty? } 64 self.dirty_apps = self.revertable_apps = false 65 else 66 p "Unable to #{action} because authorization failed." 67 end 68 end 69 70 def revert(sender = nil) 71 @applicationsController.content.each { |app| app.revert if app.revertable? } 72 self.dirty_apps = self.revertable_apps = false 73 end 74 75 def restart(sender = nil) 76 @applicationsController.content.each { |app| app.restart unless app.new_app? } 77 end 78 79 def remove(sender = nil) 80 apps = @applicationsController.selectedObjects 81 existing_apps = apps.reject { |app| app.new_app? } 82 PassengerApplication.removeApplications(existing_apps) unless existing_apps.empty? 83 @applicationsController.removeObjects apps 84 end 85 86 def rbSetValue_forKey(value, key) 87 super 88 browse if !@dropping_directories and key == 'applications' and !value.empty? and value.last.new_app? 89 end 90 91 def showPassengerHelp(sender) 92 OSX::HelpHelper.openHelpPage File.expand_path('../English.lproj/PassengerPaneHelp/PassengerPaneHelp.html', __FILE__) 93 end 94 95 # Select application directory panel 96 97 def browse(sender = nil) 98 panel = NSOpenPanel.openPanel 99 panel.canChooseDirectories = true 100 panel.canChooseFiles = false 101 panel.objc_send( 102 :beginSheetForDirectory, path_for_browser, 103 :file, nil, 104 :types, nil, 105 :modalForWindow, mainView.window, 106 :modalDelegate, self, 107 :didEndSelector, 'openPanelDidEnd:returnCode:contextInfo:', 108 :contextInfo, nil 109 ) 110 end 111 112 def openPanelDidEnd_returnCode_contextInfo(panel, button, contextInfo) 113 app = @applicationsController.selectedObjects.first 114 if button == OSX::NSOKButton 115 app.setValue_forKey(panel.filename, 'path') 116 else 117 remove if app.new_app? and !app.dirty? 118 end 119 end 120 121 # Applications NSTableView dataSource drag and drop methods 122 123 def tableView_validateDrop_proposedRow_proposedDropOperation(tableView, info, row, operation) 124 return OSX::NSDragOperationNone unless @authorized 125 126 files = info.draggingPasteboard.propertyListForType(OSX::NSFilenamesPboardType) 127 if files.all? { |f| File.directory? f } 128 @applicationsTableView.setDropRow_dropOperation(@applicationsController.content.count, OSX::NSTableViewDropAbove) 129 OSX::NSDragOperationGeneric 130 else 131 OSX::NSDragOperationNone 132 end 133 end 134 135 def tableView_acceptDrop_row_dropOperation(tableView, info, row, operation) 136 apps = info.draggingPasteboard.propertyListForType(OSX::NSFilenamesPboardType).map { |path| PassengerApplication.alloc.initWithPath(path) } 137 @dropping_directories = true 138 @applicationsController.addObjects apps 139 @dropping_directories = false 140 end 141 142 def tableView_writeRowsWithIndexes_toPasteboard(tableView, rows, pboard) 143 config_paths = @applicationsController.content.objectsAtIndexes(rows).map { |app| app.config_path } 144 pboard.declareTypes_owner([OSX::NSFilenamesPboardType], self) 145 pboard.setPropertyList_forType(config_paths, OSX::NSFilenamesPboardType) 146 true 147 end 148 149 # SFAuthorizationView: TODO this should actualy move to the SecurityHelper, but for some reason in prototyping it didn't work, try again when everything is cleaned up. 150 151 def authorizationViewDidAuthorize(authorizationView = nil) 152 OSX::SecurityHelper.sharedInstance.authorizationRef = @authorizationView.authorization.authorizationRef 153 self.authorized = true 154 end 155 156 def authorizationViewDidDeauthorize(authorizationView = nil) 157 OSX::SecurityHelper.sharedInstance.deauthorize 158 self.authorized = false 159 end 160 161 # When the pane wants to be unselected 162 163 def shouldUnselect 164 if @dirty_apps and !@applicationsController.content.empty? 165 alert = OSX::NSAlert.alloc.init 166 alert.messageText = 'This service has unsaved changes' 167 alert.informativeText = 'Would you like to apply your changes before closing the Passenger preference pane?' 168 alert.addButtonWithTitle 'Apply' 169 alert.addButtonWithTitle 'Cancel' 170 alert.addButtonWithTitle 'Don���t Apply' 171 alert.objc_send( 172 :beginSheetModalForWindow, mainView.window, 173 :modalDelegate, self, 174 :didEndSelector, 'unsavedChangesAlertDidEnd:returnCode:contextInfo:', 175 :contextInfo, nil 176 ) 177 return OSX::NSUnselectLater 178 end 179 OSX::NSUnselectNow 180 end 181 182 APPLY = OSX::NSAlertFirstButtonReturn 183 CANCEL = OSX::NSAlertSecondButtonReturn 184 DONT_APPLY = OSX::NSAlertThirdButtonReturn 185 186 def unsavedChangesAlertDidEnd_returnCode_contextInfo(alert, returnCode, contextInfo) 187 alert.window.orderOut(self) 188 case returnCode 189 when CANCEL 190 replyToShouldUnselect false 191 return 192 when APPLY 193 apply 194 when DONT_APPLY 195 @applicationsController.removeObjects @applicationsController.content.select { |app| app.new_app? } 196 revert 197 end 198 replyToShouldUnselect true 199 end 200 201 private 202 203 def authorize! 204 result = @authorizationView.authorization.objc_send( 205 :permitWithRight, OSX::KAuthorizationRightExecute, 206 :flags, (OSX::KAuthorizationFlagPreAuthorize | OSX::KAuthorizationFlagExtendRights | OSX::KAuthorizationFlagInteractionAllowed) 207 ) == 0 208 authorizationViewDidAuthorize if result 209 result 210 end 211 212 def setup_authorization_view! 213 @authorized = false 214 @authorizationView.string = OSX::KAuthorizationRightExecute 215 @authorizationView.delegate = self 216 @authorizationView.updateStatus self 217 @authorizationView.autoupdate = true 218 end 219 220 def setup_applications_table_view! 221 @applications = [].to_ns 222 @applicationsTableView.dataSource = self 223 @applicationsTableView.registerForDraggedTypes [OSX::NSFilenamesPboardType] 224 @applicationsTableView.setDraggingSourceOperationMask_forLocal(OSX::NSDragOperationGeneric, false) 225 end 226 227 def load_appications! 228 unless (existing_apps = PassengerApplication.existingApplications).empty? 229 @applicationsController.addObjects existing_apps 230 @applicationsController.selectedObjects = [existing_apps.last] 231 end 232 end 233 234 def reload_appications! 235 @applicationsController.content.each { |app| app.reload! } 236 end 237 238 def passenger_installed? 239 `#{PassengerPaneConfig::HTTPD_BIN} -t -D DUMP_MODULES 2>&1`.include? 'passenger_module' 240 end 241 242 def path_for_browser 243 app = @applicationsController.selectedObjects.first 244 app.nil? ? OSX.NSHomeDirectory : app.path 245 end 246 247 MODRAILS_URL = 'http://www.modrails.com' 248 def setup_passenger_warning! 249 if passenger_installed? 250 @installPassengerWarning.hidden = true 251 else 252 unless @setup_passenger_warning 253 text_field = @installPassengerWarning.subviews.first 254 255 link_str = OSX::NSMutableAttributedString.alloc.initWithString(MODRAILS_URL) 256 range = OSX::NSMakeRange(0, MODRAILS_URL.length) 257 link_str.addAttribute_value_range OSX::NSLinkAttributeName, MODRAILS_URL, range 258 link_str.addAttribute_value_range OSX::NSForegroundColorAttributeName, OSX::NSColor.blueColor, range 259 link_str.addAttribute_value_range OSX::NSUnderlineStyleAttributeName, OSX::NSSingleUnderlineStyle, range 260 261 text_parts = text_field.stringValue.split(MODRAILS_URL) 262 263 str = OSX::NSMutableAttributedString.alloc.initWithString(text_parts.first) 264 str.appendAttributedString link_str 265 str.appendAttributedString OSX::NSAttributedString.alloc.initWithString(text_parts.last) 266 str.addAttribute_value_range OSX::NSFontAttributeName, OSX::NSFont.systemFontOfSize(11), OSX::NSMakeRange(0, str.length) 267 268 text_field.attributedStringValue = str 269 @setup_passenger_warning = true 270 end 271 272 @installPassengerWarning.hidden = false 273 end 274 end 275end