# EnvironmentPrefs

- `Download example`

This `NSPreferencePane` can be used to edit the default environment for the current user. It also is a simple example of a localized application.

## Sources

### ShellEnv.py

    """
    EnvironmentPane - PreferencePane for changing the global user environment

    This PreferencePane provides an easy to use UI for changeing
    ~/.MacOSX/environment.plist. This plist is loaded by the loginwindow application
    when the user logs in and is used to initialize the "shell" environment.

    Note that these variables are also available outside of the Terminal, but not
    when the user logs in using SSH.

    TODO:
    - Undo
    """
    import os

    import Cocoa
    import objc
    import PreferencePanes
    from objc import super
    from PyObjCTools import AppHelper

    # Uncomment this during development, you'll get exception tracebacks when
    # the Python code fails.
    # objc.setVerbose(1)

    # Location of the environment.plist
    ENVPLIST = "~/.MacOSX/environment.plist"

    # Template for new keys
    NEWTMPL = Cocoa.NSLocalizedString("New_Variable_%d", "")

    class EnvironmentPane(PreferencePanes.NSPreferencePane):
        """
        The 'model/controller' for the "Shell Environment" preference pane
        """

        deleteButton = objc.IBOutlet()
        mainTable = objc.IBOutlet()

        # __slots__ = (
        #    'environ',  # The actual environment, as a NSMutableDictionary
        #    'keys',     # The list of keys, in the right order for the tableView
        #    'changed',  # True if we should save before exitting
        # )

        def initWithBundle_(self, bundle):
            # Our bundle has been loaded, initialize the instance variables.
            # We don't load the environment.plist yet, do that when we're
            # actually selected. That way we can easier pick up manual changes.

            self = super(EnvironmentPane, self).initWithBundle_(bundle)
            if self is None:
                return None

            self.keys = ()
            self.environ = None
            self.changed = False

            return self

        def mainViewDidLoad(self):
            self.deleteButton.setEnabled_(False)

        def didSelect(self):
            # We are the selected preference pane. Load the environment.plist.

            self.environ = Cocoa.NSMutableDictionary.dictionaryWithContentsOfFile_(
                os.path.expanduser(ENVPLIST)
            )
            if self.environ is None:
                self.environ = Cocoa.NSMutableDictionary.dictionary()
            self.keys = list(self.environ.keys())
            self.keys.sort()
            self.changed = False
            self.mainTable.reloadData()

        def shouldUnselect(self):
            # The user wants to select another preference pane. If we have
            # unsaved changes ask if they should be saved right now.

            if self.changed:
                Cocoa.NSBeginAlertSheet(
                    Cocoa.NSLocalizedString("Save changes?", ""),
                    Cocoa.NSLocalizedString("Cancel", ""),
                    Cocoa.NSLocalizedString("Don't Save", ""),
                    Cocoa.NSLocalizedString("Save", ""),
                    self.mainView().window(),
                    self,
                    None,
                    "sheetDidDismiss:returnCode:contextInfo:",
                    0,
                    Cocoa.NSLocalizedString(
                        "There are unsaved changed, should these be saved?", ""
                    ),
                )
                return PreferencePanes.NSUnselectLater
            return PreferencePanes.NSUnselectNow

        @AppHelper.endSheetMethod
        def sheetDidDismiss_returnCode_contextInfo_(self, sheet, code, info):
            # Sheet handler for saving unsaved changes.

            if code == Cocoa.NSAlertDefaultReturn:  # 'Cancel'
                self.replyToShouldUnselect_(PreferencePanes.NSUnselectCancel)
                return

            elif code == Cocoa.NSAlertAlternateReturn:  # 'Don't Save'
                pass

            elif code == Cocoa.NSAlertOtherReturn:  # 'Save'
                r = self.saveEnvironment()
                if not r:
                    self.runAlertSheet(
                        Cocoa.NSLocalizedString("Cannot save changes", ""),
                        Cocoa.NSLocalizedString(
                            "It was not possible to save your changes", ""
                        ),
                    )
                    self.replyToShouldUnselect_(PreferencePanes.NSUnselectCancel)
                    return

            self.keys = ()
            self.environ = None
            self.changed = False
            self.replyToShouldUnselect_(PreferencePanes.NSUnselectNow)

        def saveEnvironment(self):
            """
            Save the data to environment.plist
            """
            fname = os.path.expanduser(ENVPLIST)
            dname = os.path.dirname(fname)
            if not os.path.isdir(dname):
                try:
                    os.mkdir(dname)
                except os.error:
                    return False

            if not self.environ.writeToFile_atomically_(fname, True):
                return False

            return True

        @objc.IBAction
        def deleteVariable_(self, sender):
            """
            Delete the selected variable
            """
            r = self.mainTable.selectedRow()

            envname = self.keys[r]
            del self.environ[envname]
            self.keys.remove(envname)
            self.mainTable.reloadData()
            self.changed = True

        @objc.IBAction
        def newVariable_(self, sender):
            """
            Add a new variable
            """
            i = 0
            name = NEWTMPL % (i,)
            while name in self.environ:
                i += 1
                name = NEWTMPL % (i,)
            self.environ[name] = Cocoa.NSLocalizedString("New Value", "")
            self.keys = list(self.environ.keys())
            self.keys.sort()
            self.mainTable.reloadData()
            self.changed = True

        def numberOfRowsInTableView_(self, aView):
            """ Return the number of environment variables """
            return len(self.keys)

        def tableView_objectValueForTableColumn_row_(self, aView, aCol, rowIndex):
            """ Get the name of value of an environment variable """
            name = aCol.identifier()
            envname = self.keys[rowIndex]

            if name == "name":
                return envname
            elif name == "value":
                return self.environ[envname]

        def tableView_setObjectValue_forTableColumn_row_(
            self, aView, value, aCol, rowIndex
        ):
            """ Change the name or value of an environment variable """
            if self.environ is None:
                aView.reloadData()
                return

            name = aCol.identifier()
            envname = self.keys[rowIndex]

            if name == "name":
                if value != envname:
                    if value in self.environ:
                        self.runAlertSheet(
                            Cocoa.NSLocalizedString("Name exists", ""),
                            Cocoa.NSLocalizedString("The name %s is already used", "")
                            % (value,),
                        )
                        aView.reloadData()
                        return

                    val = self.environ[envname]
                    del self.environ[envname]
                    self.environ[value] = val
                    self.keys = list(self.environ.keys())
                    self.keys.sort()
                    aView.reloadData()
                    self.changed = True

            elif name == "value":
                val = self.environ[envname]
                if val != value:
                    self.environ[envname] = value
                    self.changed = True

        def tableViewSelectionDidChange_(self, notification):
            """ The delete button should only be active if a row is selected """
            if self.mainTable.numberOfSelectedRows() == 0:
                self.deleteButton.setEnabled_(False)
            else:
                self.deleteButton.setEnabled_(True)

        @objc.python_method
        def runAlertSheet(self, title, message):
            """ Run an alertsheet without callbacks """
            Cocoa.NSBeginAlertSheet(
                title,
                Cocoa.NSLocalizedString("OK", ""),
                None,
                None,
                self.mainView().window(),
                self,
                None,
                None,
                0,
                message,
            )

    objc.removeAutoreleasePool()

### setup.py

    """
    Script for building the example.

    Usage:
        python3 setup.py py2app
    """
    from setuptools import setup

    infoPlist = {
        "CFBundleName": "Shell Environment",
        "CFBundleGetInfoString": "Shell Environment Panel",
        "CFBundleVersion": "0.1",
        "CFBundleShortVersionString": "0.1",
        "NSPrefPaneIconLabel": "Shell Environment",
        "NSPrefPaneIconFile": "ShellEnv.icns",
        "NSPrincipalClass": "EnvironmentPane",
        "NSMainNibFile": "EnvironmentPane",
    }

    setup(
        name="Shell Environment",
        plugin=["ShellEnv.py"],
        data_files=["English.lproj", "Dutch.lproj", "ShellEnv.icns"],
        options={"py2app": {"extension": ".prefPane", "plist": infoPlist}},
        setup_requires=[
            "py2app",
            "pyobjc-framework-Cocoa",
            "pyobjc-framework-PreferencePanes",
        ],
    )