mirror of
https://github.com/qinyuhang/ShadowsocksX-NG-R.git
synced 2026-10-24 09:16:45 +00:00
1. Add duplicate server profile button 2. Update status bar icon (改进了之前颜色太浅的问题) 3. Fix layout on server preference window 4. Add alert or shake window when profile is not completed
375 lines
14 KiB
Swift
375 lines
14 KiB
Swift
//
|
|
// PreferencesWindowController.swift
|
|
// ShadowsocksX-NG
|
|
//
|
|
// Created by 邱宇舟 on 16/6/6.
|
|
// Copyright © 2016年 qiuyuzhou. All rights reserved.
|
|
//
|
|
|
|
import Cocoa
|
|
|
|
class PreferencesWindowController: NSWindowController
|
|
, NSTableViewDataSource, NSTableViewDelegate {
|
|
|
|
@IBOutlet weak var profilesTableView: NSTableView!
|
|
|
|
@IBOutlet weak var profileBox: NSBox!
|
|
|
|
@IBOutlet weak var hostTextField: NSTextField!
|
|
@IBOutlet weak var portTextField: NSTextField!
|
|
@IBOutlet weak var methodTextField: NSComboBox!
|
|
|
|
@IBOutlet weak var ProtocolTextField: NSComboBox!
|
|
@IBOutlet weak var ProtocolParamTextField: NSTextField!
|
|
@IBOutlet weak var ObfsTextField: NSComboBox!
|
|
@IBOutlet weak var ObfsParamTextField: NSTextField!
|
|
|
|
@IBOutlet weak var duplicateProfileButton: NSButton!
|
|
@IBOutlet weak var passwordTextField: NSTextField!
|
|
@IBOutlet weak var remarkTextField: NSTextField!
|
|
|
|
@IBOutlet weak var otaCheckBoxBtn: NSButton!
|
|
|
|
@IBOutlet weak var copyURLBtn: NSButton!
|
|
|
|
let tableViewDragType: String = "ss.server.profile.data"
|
|
|
|
var defaults: NSUserDefaults!
|
|
var profileMgr: ServerProfileManager!
|
|
|
|
var editingProfile: ServerProfile!
|
|
|
|
override func windowDidLoad() {
|
|
super.windowDidLoad()
|
|
|
|
// Implement this method to handle any initialization after your window controller's window has been loaded from its nib file.
|
|
defaults = NSUserDefaults.standardUserDefaults()
|
|
profileMgr = ServerProfileManager.instance
|
|
|
|
methodTextField.addItemsWithObjectValues([
|
|
"table",
|
|
"rc4",
|
|
"rc4-md5-6",
|
|
"rc4-md5",
|
|
"aes-128-cfb",
|
|
"aes-192-cfb",
|
|
"aes-256-cfb",
|
|
"bf-cfb",
|
|
"camellia-128-cfb",
|
|
"camellia-192-cfb",
|
|
"camellia-256-cfb",
|
|
"cast5-cfb",
|
|
"des-cfb",
|
|
"idea-cfb",
|
|
"rc2-cfb",
|
|
"seed-cfb",
|
|
"salsa20",
|
|
"chacha20",
|
|
"chacha20-ietf"
|
|
])
|
|
ProtocolTextField.addItemsWithObjectValues([
|
|
"origin",
|
|
"verify_simple",
|
|
"verify_sha1",
|
|
"auth_sha1",
|
|
"auth_sha1_v2",
|
|
])
|
|
ObfsTextField.addItemsWithObjectValues([
|
|
"palin",
|
|
"http_simple",
|
|
"tls_simple",
|
|
"random_head",
|
|
"tls1.2_ticket_auth",
|
|
])
|
|
profilesTableView.reloadData()
|
|
updateProfileBoxVisible()
|
|
}
|
|
|
|
override func awakeFromNib() {
|
|
profilesTableView.registerForDraggedTypes([tableViewDragType])
|
|
}
|
|
|
|
@IBAction func addProfile(sender: NSButton) {
|
|
if editingProfile != nil && !editingProfile.isValid(){
|
|
return
|
|
}
|
|
profilesTableView.beginUpdates()
|
|
let profile = ServerProfile()
|
|
profile.remark = "New Server".localized
|
|
profileMgr.profiles.append(profile)
|
|
|
|
let index = NSIndexSet(index: profileMgr.profiles.count-1)
|
|
profilesTableView.insertRowsAtIndexes(index, withAnimation: .EffectFade)
|
|
|
|
self.profilesTableView.scrollRowToVisible(self.profileMgr.profiles.count-1)
|
|
self.profilesTableView.selectRowIndexes(index, byExtendingSelection: false)
|
|
profilesTableView.endUpdates()
|
|
updateProfileBoxVisible()
|
|
}
|
|
|
|
@IBAction func removeProfile(sender: NSButton) {
|
|
let index = profilesTableView.selectedRow
|
|
if index >= 0 {
|
|
profilesTableView.beginUpdates()
|
|
profileMgr.profiles.removeAtIndex(index)
|
|
profilesTableView.removeRowsAtIndexes(NSIndexSet(index: index), withAnimation: .EffectFade)
|
|
profilesTableView.endUpdates()
|
|
}
|
|
updateProfileBoxVisible()
|
|
}
|
|
|
|
@IBAction func ok(sender: NSButton) {
|
|
if editingProfile != nil {
|
|
if !editingProfile.isValid() {
|
|
let numberOfShakes:Int = 8
|
|
let durationOfShake:Float = 0.5
|
|
let vigourOfShake:Float = 0.05
|
|
|
|
let frame:CGRect = (self.window?.frame)!
|
|
let shakeAnimation = CAKeyframeAnimation()
|
|
|
|
let shakePath = CGPathCreateMutable()
|
|
CGPathMoveToPoint(shakePath, nil, NSMinX(frame), NSMinY(frame))
|
|
|
|
for _ in 1...numberOfShakes{
|
|
CGPathAddLineToPoint(shakePath, nil, NSMinX(frame) - frame.size.width * CGFloat(vigourOfShake), NSMinY(frame))
|
|
CGPathAddLineToPoint(shakePath, nil, NSMinX(frame) + frame.size.width * CGFloat(vigourOfShake), NSMinY(frame))
|
|
}
|
|
|
|
CGPathCloseSubpath(shakePath)
|
|
shakeAnimation.path = shakePath
|
|
shakeAnimation.duration = CFTimeInterval(durationOfShake)
|
|
self.window?.animations = ["frameOrigin":shakeAnimation]
|
|
self.window?.animator().setFrameOrigin(self.window!.frame.origin)
|
|
return
|
|
}
|
|
}
|
|
profileMgr.save()
|
|
window?.performClose(nil)
|
|
|
|
NSNotificationCenter.defaultCenter()
|
|
.postNotificationName(NOTIFY_SERVER_PROFILES_CHANGED, object: nil)
|
|
}
|
|
|
|
@IBAction func cancel(sender: NSButton) {
|
|
window?.performClose(self)
|
|
}
|
|
|
|
@IBAction func duplicateProfile(sender: NSButton) {
|
|
//读取当前profile,并且保存
|
|
if editingProfile != nil && !editingProfile.isValid(){
|
|
return
|
|
}
|
|
let index = profilesTableView.selectedRow
|
|
if index >= 0 {
|
|
let profile = profileMgr.profiles[index]
|
|
profilesTableView.beginUpdates()
|
|
profileMgr.profiles.append(profile)
|
|
let index = NSIndexSet(index: profileMgr.profiles.count-1)
|
|
profilesTableView.insertRowsAtIndexes(index, withAnimation: .EffectFade)
|
|
self.profilesTableView.scrollRowToVisible(self.profileMgr.profiles.count-1)
|
|
self.profilesTableView.selectRowIndexes(index, byExtendingSelection: false)
|
|
profilesTableView.endUpdates()
|
|
updateProfileBoxVisible()
|
|
}
|
|
}
|
|
|
|
@IBAction func copyCurrentProfileURL2Pasteboard(sender: NSButton) {
|
|
let index = profilesTableView.selectedRow
|
|
if index >= 0 {
|
|
let profile = profileMgr.profiles[index]
|
|
let ssURL = profile.URL()
|
|
if let url = ssURL {
|
|
// Then copy url to pasteboard
|
|
// TODO Why it not working?? It's ok in objective-c
|
|
let pboard = NSPasteboard.generalPasteboard()
|
|
pboard.clearContents()
|
|
let rs = pboard.writeObjects([url])
|
|
if rs {
|
|
NSLog("copy to pasteboard success")
|
|
} else {
|
|
NSLog("copy to pasteboard failed")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func updateProfileBoxVisible() {
|
|
if profileMgr.profiles.isEmpty {
|
|
profileBox.hidden = true
|
|
} else {
|
|
profileBox.hidden = false
|
|
}
|
|
}
|
|
|
|
func bindProfile(index:Int) {
|
|
NSLog("bind profile \(index)")
|
|
if index >= 0 && index < profileMgr.profiles.count {
|
|
editingProfile = profileMgr.profiles[index]
|
|
|
|
hostTextField.bind("value", toObject: editingProfile, withKeyPath: "serverHost"
|
|
, options: [NSContinuouslyUpdatesValueBindingOption: true])
|
|
portTextField.bind("value", toObject: editingProfile, withKeyPath: "serverPort"
|
|
, options: [NSContinuouslyUpdatesValueBindingOption: true])
|
|
|
|
methodTextField.bind("value", toObject: editingProfile, withKeyPath: "method"
|
|
, options: [NSContinuouslyUpdatesValueBindingOption: true])
|
|
passwordTextField.bind("value", toObject: editingProfile, withKeyPath: "password"
|
|
, options: [NSContinuouslyUpdatesValueBindingOption: true])
|
|
|
|
remarkTextField.bind("value", toObject: editingProfile, withKeyPath: "remark"
|
|
, options: [NSContinuouslyUpdatesValueBindingOption: true])
|
|
|
|
ProtocolTextField.bind("value", toObject: editingProfile, withKeyPath: "ssrProtocol", options: [NSContinuouslyUpdatesValueBindingOption: true])
|
|
|
|
ProtocolParamTextField.bind("value", toObject: editingProfile, withKeyPath: "ssrProtocolParam", options: [NSContinuouslyUpdatesValueBindingOption: true])
|
|
|
|
ObfsTextField.bind("value", toObject: editingProfile, withKeyPath: "ssrObfs", options: [NSContinuouslyUpdatesValueBindingOption: true])
|
|
|
|
ObfsParamTextField.bind("value", toObject: editingProfile, withKeyPath: "ssrObfsParam", options: [NSContinuouslyUpdatesValueBindingOption: true])
|
|
|
|
otaCheckBoxBtn.bind("value", toObject: editingProfile, withKeyPath: "ota"
|
|
, options: [NSContinuouslyUpdatesValueBindingOption: true])
|
|
} else {
|
|
editingProfile = nil
|
|
hostTextField.unbind("value")
|
|
portTextField.unbind("value")
|
|
|
|
methodTextField.unbind("value")
|
|
passwordTextField.unbind("value")
|
|
|
|
ProtocolTextField.unbind("value")
|
|
ProtocolParamTextField.unbind("value")
|
|
ObfsTextField.unbind("value")
|
|
ObfsParamTextField.unbind("value")
|
|
|
|
remarkTextField.unbind("value")
|
|
|
|
otaCheckBoxBtn.unbind("value")
|
|
}
|
|
}
|
|
|
|
func getDataAtRow(index:Int) -> (String, Bool) {
|
|
let profile = profileMgr.profiles[index]
|
|
let isActive = (profileMgr.activeProfileId == profile.uuid)
|
|
if !profile.remark.isEmpty {
|
|
return (profile.remark, isActive)
|
|
} else {
|
|
return (profile.serverHost, isActive)
|
|
}
|
|
}
|
|
|
|
//--------------------------------------------------
|
|
// For NSTableViewDataSource
|
|
|
|
func numberOfRowsInTableView(tableView: NSTableView) -> Int {
|
|
if let mgr = profileMgr {
|
|
return mgr.profiles.count
|
|
}
|
|
return 0
|
|
}
|
|
|
|
func tableView(tableView: NSTableView
|
|
, objectValueForTableColumn tableColumn: NSTableColumn?
|
|
, row: Int) -> AnyObject? {
|
|
|
|
let (title, isActive) = getDataAtRow(row)
|
|
|
|
if tableColumn?.identifier == "main" {
|
|
return title
|
|
} else if tableColumn?.identifier == "status" {
|
|
if isActive {
|
|
return NSImage(named: "NSMenuOnStateTemplate")
|
|
} else {
|
|
return nil
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// Drag & Drop reorder rows
|
|
|
|
func tableView(tableView: NSTableView, pasteboardWriterForRow row: Int) -> NSPasteboardWriting? {
|
|
let item = NSPasteboardItem()
|
|
item.setString(String(row), forType: tableViewDragType)
|
|
return item
|
|
}
|
|
|
|
func tableView(tableView: NSTableView, validateDrop info: NSDraggingInfo, proposedRow row: Int
|
|
, proposedDropOperation dropOperation: NSTableViewDropOperation) -> NSDragOperation {
|
|
if dropOperation == .Above {
|
|
return .Move
|
|
}
|
|
return .None
|
|
}
|
|
|
|
func tableView(tableView: NSTableView, acceptDrop info: NSDraggingInfo
|
|
, row: Int, dropOperation: NSTableViewDropOperation) -> Bool {
|
|
if let mgr = profileMgr {
|
|
var oldIndexes = [Int]()
|
|
info.enumerateDraggingItemsWithOptions([], forView: tableView, classes: [NSPasteboardItem.self], searchOptions: [:]) {
|
|
if let str = ($0.0.item as! NSPasteboardItem).stringForType(self.tableViewDragType), index = Int(str) {
|
|
oldIndexes.append(index)
|
|
}
|
|
}
|
|
|
|
var oldIndexOffset = 0
|
|
var newIndexOffset = 0
|
|
|
|
// For simplicity, the code below uses `tableView.moveRowAtIndex` to move rows around directly.
|
|
// You may want to move rows in your content array and then call `tableView.reloadData()` instead.
|
|
tableView.beginUpdates()
|
|
for oldIndex in oldIndexes {
|
|
if oldIndex < row {
|
|
let o = mgr.profiles.removeAtIndex(oldIndex + oldIndexOffset)
|
|
mgr.profiles.insert(o, atIndex:row - 1)
|
|
tableView.moveRowAtIndex(oldIndex + oldIndexOffset, toIndex: row - 1)
|
|
oldIndexOffset -= 1
|
|
} else {
|
|
let o = mgr.profiles.removeAtIndex(oldIndex)
|
|
mgr.profiles.insert(o, atIndex:row + newIndexOffset)
|
|
tableView.moveRowAtIndex(oldIndex, toIndex: row + newIndexOffset)
|
|
newIndexOffset += 1
|
|
}
|
|
}
|
|
tableView.endUpdates()
|
|
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
//--------------------------------------------------
|
|
// For NSTableViewDelegate
|
|
|
|
func tableView(tableView: NSTableView
|
|
, shouldEditTableColumn tableColumn: NSTableColumn?, row: Int) -> Bool {
|
|
return false
|
|
}
|
|
|
|
func tableView(tableView: NSTableView, shouldSelectRow row: Int) -> Bool {
|
|
if row < 0 {
|
|
editingProfile = nil
|
|
return true
|
|
}
|
|
if editingProfile != nil {
|
|
if !editingProfile.isValid() {
|
|
return false
|
|
}
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
func tableViewSelectionDidChange(notification: NSNotification) {
|
|
if profilesTableView.selectedRow >= 0 {
|
|
bindProfile(profilesTableView.selectedRow)
|
|
} else {
|
|
if !profileMgr.profiles.isEmpty {
|
|
let index = NSIndexSet(index: profileMgr.profiles.count - 1)
|
|
profilesTableView.selectRowIndexes(index, byExtendingSelection: false)
|
|
}
|
|
}
|
|
}
|
|
}
|