diff --git a/soitool/inline_editable_soi_view.py b/soitool/inline_editable_soi_view.py
index e56d498a095c42df4ccf139e0225b010747fdb97..621d06e8608a6e7ed93ec38e2d0f876e9bba3602 100644
--- a/soitool/inline_editable_soi_view.py
+++ b/soitool/inline_editable_soi_view.py
@@ -78,6 +78,7 @@ class InlineEditableSOIView(QScrollArea):
         # add listeners to react properly to SOI changes
         self.soi.add_reorganization_listener(self.update_pages)
         self.soi.add_new_module_listener(self.ensure_proxies)
+        self.soi.add_update_property_listener(self.update_pages)
 
         # self.launch_auto_zoom()
 
diff --git a/soitool/setup_settings.py b/soitool/setup_settings.py
index 9e19b468cea539fc23027de3799ee1a50fa6b646..1b8b1ff0d65194e0026f1b6c24de963095dbce34 100644
--- a/soitool/setup_settings.py
+++ b/soitool/setup_settings.py
@@ -16,6 +16,11 @@ from PySide2.QtWidgets import (
     QComboBox,
     QGroupBox,
 )
+from soitool.soi import (
+    STRING_TO_ALGORITHM_RECTPACK_BIN,
+    STRING_TO_ALGORITHM_RECTPACK_PACK,
+    STRING_TO_ALGORITHM_INITIAL_SORT,
+)
 
 
 class Setup(QDialog):  # pylint: disable = R0902
@@ -120,28 +125,25 @@ class Setup(QDialog):  # pylint: disable = R0902
 
         # placement algorithm
         self.layout_setup.addWidget(self.label_algorithm)
-        self.rbutton_bin1 = QRadioButton("BFF")
-        self.rbutton_bin2 = QRadioButton("BBF")
-        self.rbutton_pack1 = QRadioButton("MaxRectsBI")
-        self.rbutton_pack2 = QRadioButton("SkylinBl")
-        self.rbutton_pack3 = QRadioButton("GuillotineBssfSas")
-        self.rbutton_sort1 = QRadioButton("none")
-        self.rbutton_sort2 = QRadioButton("area")
-        self.rbutton_sort3 = QRadioButton("width")
-        self.rbutton_sort4 = QRadioButton("height")
+        self.rbuttons_option_bin = [
+            QRadioButton(name) for name in STRING_TO_ALGORITHM_RECTPACK_BIN
+        ]
+        self.rbuttons_option_pack = [
+            QRadioButton(name) for name in STRING_TO_ALGORITHM_RECTPACK_PACK
+        ]
+        self.rbuttons_option_sort = [
+            QRadioButton(name) for name in STRING_TO_ALGORITHM_INITIAL_SORT
+        ]
 
         self.layout_alg_button_bin.addWidget(QLabel("Bin"))
-        self.layout_alg_button_bin.addWidget(self.rbutton_bin1)
-        self.layout_alg_button_bin.addWidget(self.rbutton_bin2)
+        for rbutton in self.rbuttons_option_bin:
+            self.layout_alg_button_bin.addWidget(rbutton)
         self.layout_alg_button_pack.addWidget(QLabel("Pack"))
-        self.layout_alg_button_pack.addWidget(self.rbutton_pack1)
-        self.layout_alg_button_pack.addWidget(self.rbutton_pack2)
-        self.layout_alg_button_pack.addWidget(self.rbutton_pack3)
+        for rbutton in self.rbuttons_option_pack:
+            self.layout_alg_button_pack.addWidget(rbutton)
         self.layout_alg_button_sort.addWidget(QLabel("Sort"))
-        self.layout_alg_button_sort.addWidget(self.rbutton_sort1)
-        self.layout_alg_button_sort.addWidget(self.rbutton_sort2)
-        self.layout_alg_button_sort.addWidget(self.rbutton_sort3)
-        self.layout_alg_button_sort.addWidget(self.rbutton_sort4)
+        for rbutton in self.rbuttons_option_sort:
+            self.layout_alg_button_sort.addWidget(rbutton)
 
         self.group_algorithm_bin.setLayout(self.layout_alg_button_bin)
         self.group_algorithm_pack.setLayout(self.layout_alg_button_pack)
@@ -169,43 +171,48 @@ class Setup(QDialog):  # pylint: disable = R0902
 
     def save(self):
         """Save and update the SOI with the given changes."""
-        self.soi.title = self.edit_title.text()
-        self.soi.description = self.edit_description.text()
-        self.soi.version = self.edit_version.text()
-        self.soi.date = self.edit_date.text()
-        self.soi.valid_from = self.edit_valid_from.text()
-        self.soi.valid_to = self.edit_valid_to.text()
-        self.soi.classification = (
-            self.edit_classification.currentText().lower()
-        )
-
-        # find which radiobutton that is checked
+        # Dict will contain all changes to make
+        property_changes = {}
+        property_changes["title"] = self.edit_title.text()
+        property_changes["description"] = self.edit_description.text()
+        property_changes["version"] = self.edit_version.text()
+        property_changes["date"] = self.edit_date.text()
+        property_changes["valid_from"] = self.edit_valid_from.text()
+        property_changes["valid_to"] = self.edit_valid_to.text()
+        property_changes[
+            "classification"
+        ] = self.edit_classification.currentText()
+
+        # Find which radiobutton that is checked
         if self.rbutton_landscape.isChecked():
-            self.soi.orientation = "landscape"
+            property_changes["orientation"] = "landscape"
         else:
-            self.soi.orientation = "portrait"
+            property_changes["orientation"] = "portrait"
 
         if self.rbutton_auto.isChecked():
-            self.soi.placement_strategy = "auto"
+            property_changes["placement_strategy"] = "auto"
         else:
-            self.soi.placement_strategy = "manual"
+            property_changes["placement_strategy"] = "manual"
 
-        # loop through groupbox to find checked radiobuttons
+        # Loop through groupbox to find checked radiobuttons
         for i in self.group_algorithm_bin.findChildren(QRadioButton):
             if i.isChecked():
-                self.soi.algorithm_bin = i.text()
+                property_changes["algorithm_bin"] = i.text()
                 break
 
         for i in self.group_algorithm_pack.findChildren(QRadioButton):
             if i.isChecked():
-                self.soi.algorithm_pack = i.text()
+                property_changes["algorithm_pack"] = i.text()
                 break
 
         for i in self.group_algorithm_sort.findChildren(QRadioButton):
             if i.isChecked():
-                self.soi.algorithm_sort = i.text()
+                property_changes["algorithm_sort"] = i.text()
                 break
 
+        # Pass changes as unpacked variable list
+        self.soi.update_properties(**property_changes)
+
         self.accept()
 
     def get_from_soi(self):
@@ -216,11 +223,9 @@ class Setup(QDialog):  # pylint: disable = R0902
         self.edit_date.setText(self.soi.date)
         self.edit_valid_from.setText(self.soi.valid_from)
         self.edit_valid_to.setText(self.soi.valid_to)
-        self.edit_classification.setCurrentText(
-            self.soi.classification.upper()
-        )
+        self.edit_classification.setCurrentText(self.soi.classification)
 
-        # check radiobuttons according to current SOI settings
+        # Check radiobuttons according to current SOI settings
         if self.soi.orientation == "landscape":
             self.rbutton_landscape.setChecked(True)
         else:
diff --git a/soitool/soi.py b/soitool/soi.py
index 1394c197930cf1d12415a66482c15343565d5cb6..b9b7a724ea755598b95cccf10dabf4fc64475ab3 100644
--- a/soitool/soi.py
+++ b/soitool/soi.py
@@ -188,6 +188,12 @@ class SOI:
     updating the widget positions based on the "meta" positions, using the
     formulas above.
 
+    ## Note about properties
+
+    To ensure other users of SOI are properly notified, all property updates
+    should happen through member functions. `update_properties` for general
+    properties, and `add_module` for modules and attachments.
+
     Parameters
     ----------
     title : string
@@ -307,21 +313,22 @@ class SOI:
         self.modules = modules
         self.attachments = attachments
 
-        # the following properties are relevant when self.placement_strategy
+        # The following properties are relevant when self.placement_strategy
         # is "auto". They are used by rectpack
         self.algorithm_bin = algorithm_bin
         self.algorithm_pack = algorithm_pack
         self.algorithm_sort = algorithm_sort
 
-        # prepare listener lists: list of functions to call after certain
+        # Prepare listener lists: list of functions to call after certain
         # events happen
         self.reorganization_listeners = []
         self.new_module_listeners = []
+        self.update_property_listeners = []
 
         self.reorganize()
 
     def add_reorganization_listener(self, function):
-        """Add function to list of functions to be called after reorganization.
+        """Add to list of functions to be called after reorganization.
 
         This is useful for users of this class to handle changes to the SOI.
         As an example a class displaying an SOI can be updated after changes.
@@ -329,13 +336,21 @@ class SOI:
         self.reorganization_listeners.append(function)
 
     def add_new_module_listener(self, function):
-        """Add function to list of functions to be called after added module.
+        """Add to list of functions to be called after added module.
 
         This is useful for users of this class to handle changes to the SOI.
         As an example a class displaying an SOI can be updated after changes.
         """
         self.new_module_listeners.append(function)
 
+    def add_update_property_listener(self, function):
+        """Add to list of functions to be called after properties change.
+
+        This is useful for users of this class to handle changes to the SOI.
+        As an example a class displaying an SOI can be updated after changes.
+        """
+        self.update_property_listeners.append(function)
+
     def update_module_widget_position(self, module):
         """Update position of module widget based on meta position.
 
@@ -524,3 +539,80 @@ class SOI:
         # call listener functions
         for listener in self.new_module_listeners:
             listener()
+
+    # Ignoring pylint "Too many branches" error as this function is a special
+    # case where the if-elif-elif-...-else makes sense
+    def update_properties(self, **kwargs):  # pylint: disable=R0912
+        """Update properties given as input, and call listeners.
+
+        This function exists solely because there are listeners on the
+        properties. A "cleaner" way to achieve the same goal would be to
+        create a "setter" for each property and call the listeners there, but
+        for bulk changes this would get unwieldy. Another way to achieve the
+        goal of calling listeners after changes to properties would be to
+        create a separate function that the user is expected to call after
+        changing the properties directly, but this would put unnecessary
+        responsibility on the user.
+
+        All properties except "modules" and "attachements" can be updated
+        using this function.
+
+        If a change is made that affects placement of modules this function
+        will call `reorganize`
+
+        Parameters
+        ----------
+        **kwargs : key, value pairs of properties to update
+            Accepts both a normal dict, and passing arguments as normal:
+            `update_properties({'title': 'my title'})` and
+            update_properties(title='my title')`. Accepted keys are properties
+            of the SOI class, except 'modules' and 'attachements'. Explanation
+            of **kwargs:
+            https://stackoverflow.com/a/1769475/3545896
+        """
+        # For every key, value pair passed in kwargs, update corresponding
+        # property
+        for key, value in kwargs.items():
+            if key == "title":
+                self.title = value
+            elif key == "description":
+                self.description = value
+            elif key == "version":
+                self.version = value
+            elif key == "date":
+                self.date = value
+            elif key == "valid_from":
+                self.valid_from = value
+            elif key == "valid_to":
+                self.valid_to = value
+            elif key == "icon":
+                self.icon = value
+            elif key == "classification":
+                self.classification = value
+            elif key == "orientation":
+                self.orientation = value
+            elif key == "placement_strategy":
+                self.placement_strategy = value
+            elif key == "algorithm_bin":
+                self.algorithm_bin = value
+            elif key == "algorithm_pack":
+                self.algorithm_pack = value
+            elif key == "algorithm_sort":
+                self.algorithm_sort = value
+            else:
+                raise ValueError(
+                    f"Unknown property name {key} passed with value {value}"
+                )
+
+        for listener in self.update_property_listeners:
+            listener()
+
+        # If any properties relating to module placement were touched,
+        # reorganize
+        if (
+            "placement_strategy" in kwargs
+            or "algorithm_bin" in kwargs
+            or "algorithm_pack" in kwargs
+            or "algorithm_sort" in kwargs
+        ):
+            self.reorganize()
diff --git a/test/test_soi.py b/test/test_soi.py
index 9e1a4d2ea85ea4668ea246c2189308b442704663..659d693f484418bc048d34ddc05aa9587fb8eb28 100644
--- a/test/test_soi.py
+++ b/test/test_soi.py
@@ -361,3 +361,28 @@ class TestSOI(unittest.TestCase):
             )
         except ModuleNameTaken:
             self.fail("Exception should not be raised when name is unique")
+
+    def test_update_properties(self):
+        """Test updating properties and corresponding listener."""
+        test_title = "a test title"
+
+        listener_called = False
+
+        def listener_update_properties():
+            # nonlocal is required to access variable in nesting function's
+            # scope
+            nonlocal listener_called
+            listener_called = True
+
+        self.soi.add_update_property_listener(listener_update_properties)
+        self.soi.update_properties(title=test_title)
+        self.assertTrue(listener_called)
+
+        self.assertEqual(self.soi.title, test_title)
+
+    def test_update_properties_invalid_key(self):
+        """Test update_properties properly throws exception if invalid key."""
+        self.assertRaises(
+            ValueError,
+            lambda: self.soi.update_properties(invalid_key="garbage"),
+        )