Repository: beeware/toga

Issue:
open file dialog not implemented for osx
## Expected Behavior
I expected open_file_dialog to work for osx.


## Current Behavior
```
File "<venv>/lib/python3.7/site-packages/toga/window.py", line 275, in open_file_dialog
    return self._impl.open_file_dialog(title, initial_directory, file_types, multiselect)
AttributeError: 'MainWindow' object has no attribute 'open_file_dialog'
```

## Steps to reproduce
`app.main_window.open_file_dialog(...`


## Your Environment
* Python Version
3.7.2

* Operating System and Version (select from the following and list the specific version number; if your OS is not listed, list that as well)

  - [x] macOS - version: 10.14.3
  - [ ] Linux - distro:   - version:
  - [ ] Windows - version:
  - [ ] Other - name:  - version:

* Toga Target (the type of app you are trying to generate)
 
  - [ ] android
  - [x] cocoa
  - [ ] django 
  - [ ] gtk
  - [ ] iOS
  - [ ] tvOS
  - [ ] watchOS
  - [ ] winforms 
  - [ ] win32
  - [ ] Other (please specify)



Discussion:
Are there any solutions or work arounds for this one?
I have that same issue as toejough with more or less the same environmental setup
@ghynes Well the solution for something that isn't implemented is... someone implements it. 😄 

The workaround would be to dig into how the other file dialogs work on Cocoa, and access the same underlying APIs - however, once you've done that, you'd be 90% of the way to implementing a full solution...

Resolving patch:
diff --git a/examples/dialogs/dialogs/app.py b/examples/dialogs/dialogs/app.py
--- a/examples/dialogs/dialogs/app.py
+++ b/examples/dialogs/dialogs/app.py
@@ -24,8 +24,27 @@ def action_open_file_dialog(self, widget):
         try:
             fname = self.main_window.open_file_dialog(
                 title="Open file with Toga",
+                multiselect=False
             )
-            self.label.text = "File to open:" + fname
+            if fname is not None:
+                self.label.text = "File to open:" + fname
+            else:
+                self.label.text = "No file selected!"
+        except ValueError:
+            self.label.text = "Open file dialog was canceled"
+
+    def action_open_file_dialog_multi(self, widget):
+        try:
+            filenames = self.main_window.open_file_dialog(
+                title="Open file with Toga",
+                multiselect=True
+            )
+            if filenames is not None:
+                msg = "Files to open: {}".format(', '.join(filenames))
+                self.label.text = msg
+            else:
+                self.label.text = "No files selected!"
+
         except ValueError:
             self.label.text = "Open file dialog was canceled"
 
@@ -63,6 +82,9 @@ def startup(self):
         btn_info = toga.Button('Info', on_press=self.action_info_dialog, style=btn_style)
         btn_question = toga.Button('Question', on_press=self.action_question_dialog, style=btn_style)
         btn_open = toga.Button('Open File', on_press=self.action_open_file_dialog, style=btn_style)
+        btn_open_multi = toga.Button('Open File (Multiple)',
+                                     on_press=self.action_open_file_dialog_multi,
+                                     style=btn_style)
         btn_save = toga.Button('Save File', on_press=self.action_save_file_dialog, style=btn_style)
         btn_select = toga.Button('Select Folder', on_press=self.action_select_folder_dialog, style=btn_style)
         dialog_btn_box = toga.Box(
@@ -81,8 +103,9 @@ def startup(self):
         btn_clear = toga.Button('Clear', on_press=self.do_clear, style=btn_style)
         btn_box = toga.Box(
             children=[
+                btn_open_multi,
                 btn_do_stuff,
-                btn_clear
+                btn_clear,
             ],
             style=Pack(direction=ROW)
         )
diff --git a/src/cocoa/toga_cocoa/dialogs.py b/src/cocoa/toga_cocoa/dialogs.py
--- a/src/cocoa/toga_cocoa/dialogs.py
+++ b/src/cocoa/toga_cocoa/dialogs.py
@@ -99,3 +99,37 @@ def save_file(window, title, suggested_filename, file_types=None):
     if result == NSFileHandlingPanelOKButton:
         return panel.URL.path
     return None
+
+
+def open_file(window, title, file_types, multiselect):
+    """Cocoa open file dialog implementation.
+
+    We restrict the panel invocation to only choose files. We also allow
+    creating directories but not selecting directories.
+
+    Args:
+        window: The window this dialog belongs to.
+        title: Title of the modal.
+        file_types: Ignored for now.
+        multiselect: Flag to allow multiple file selection.
+    Returns:
+        The file path on success, None otherwise.
+    """
+
+    # Initialize and configure the panel.
+    panel = NSOpenPanel.alloc().init()
+    panel.title = title
+    panel.allowedFileTypes = file_types
+    panel.allowsMultipleSelection = multiselect
+    panel.canChooseDirectories = False
+    panel.canCreateDirectories = False
+    panel.canChooseFiles = True
+
+    # Show modal and return file path on success.
+    result = panel.runModal()
+    if result == NSFileHandlingPanelOKButton:
+        paths = [str(url.path) for url in panel.URLs]
+        filename_or_filenames = (paths if multiselect else
+                                 panel.URL.path)
+        return filename_or_filenames
+    return None
diff --git a/src/cocoa/toga_cocoa/window.py b/src/cocoa/toga_cocoa/window.py
--- a/src/cocoa/toga_cocoa/window.py
+++ b/src/cocoa/toga_cocoa/window.py
@@ -242,3 +242,6 @@ def stack_trace_dialog(self, title, message, content, retry=False):
 
     def save_file_dialog(self, title, suggested_filename, file_types):
         return dialogs.save_file(self.interface, title, suggested_filename, file_types)
+
+    def open_file_dialog(self, title, initial_directory, file_types, multiselect):
+        return dialogs.open_file(self.interface, title, file_types, multiselect)


Test patch: