Skip to content

Commit

Permalink
Add system dialogs
Browse files Browse the repository at this point in the history
  • Loading branch information
dpjudas committed May 9, 2024
1 parent f8b1271 commit cdb7fb5
Show file tree
Hide file tree
Showing 12 changed files with 890 additions and 0 deletions.
8 changes: 8 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ set(ZWIDGET_SOURCES
src/widgets/listview/listview.cpp
src/widgets/tabwidget/tabwidget.cpp
src/window/window.cpp
src/systemdialogs/folder_browse_dialog.cpp
src/systemdialogs/open_file_dialog.cpp
src/systemdialogs/save_file_dialog.cpp
)

set(ZWIDGET_INCLUDES
Expand Down Expand Up @@ -63,6 +66,9 @@ set(ZWIDGET_INCLUDES
include/zwidget/widgets/listview/listview.h
include/zwidget/widgets/tabwidget/tabwidget.h
include/zwidget/window/window.h
include/zwidget/systemdialogs/folder_browse_dialog.h
include/zwidget/systemdialogs/open_file_dialog.h
include/zwidget/systemdialogs/save_file_dialog.h
)

set(ZWIDGET_WIN32_SOURCES
Expand Down Expand Up @@ -97,6 +103,7 @@ source_group("src\\widgets\\checkboxlabel" REGULAR_EXPRESSION "${CMAKE_CURRENT_S
source_group("src\\widgets\\listview" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/src/widgets/listview/.+")
source_group("src\\widgets\\tabwidget" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/src/widgets/tabwidget/.+")
source_group("src\\window" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/src/window/.+")
source_group("src\\systemdialogs" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/src/systemdialogs/.+")
source_group("include" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/include/zwidget/.+")
source_group("include\\core" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/include/zwidget/core/.+")
source_group("include\\widgets" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/include/zwidget/widgets/.+")
Expand All @@ -116,6 +123,7 @@ source_group("include\\widgets\\tabwidget" REGULAR_EXPRESSION "${CMAKE_CURRENT_S
source_group("include\\window" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/include/zwidget/window/.+")
source_group("include\\window\\win32" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/include/zwidget/window/win32/.+")
source_group("include\\window\\sdl2" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/include/zwidget/window/sdl2/.+")
source_group("include\\systemdialogs" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/include/zwidget/systemdialogs/.+")

include_directories(include include/zwidget src)

Expand Down
2 changes: 2 additions & 0 deletions include/zwidget/core/widget.h
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,8 @@ class Widget : DisplayWindowHost

static Size GetScreenSize();

void* GetNativeHandle();

protected:
virtual void OnPaintFrame(Canvas* canvas);
virtual void OnPaint(Canvas* canvas) { }
Expand Down
30 changes: 30 additions & 0 deletions include/zwidget/systemdialogs/folder_browse_dialog.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@

#pragma once

#include <memory>
#include <string>

class Widget;

/// \brief Displays the system folder browsing dialog
class BrowseFolderDialog
{
public:
/// \brief Constructs an browse folder dialog.
static std::unique_ptr<BrowseFolderDialog> Create(Widget*owner);

virtual ~BrowseFolderDialog() = default;

/// \brief Get the full path of the directory selected.
virtual std::string SelectedPath() const = 0;

/// \brief Sets the initial directory that is displayed.
virtual void SetInitialDirectory(const std::string& path) = 0;

/// \brief Sets the text that appears in the title bar.
virtual void SetTitle(const std::string& title) = 0;

/// \brief Shows the file dialog.
/// \return true if the user clicks the OK button of the dialog that is displayed, false otherwise.
virtual bool Show() = 0;
};
55 changes: 55 additions & 0 deletions include/zwidget/systemdialogs/open_file_dialog.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@

#pragma once

#include <memory>
#include <string>
#include <vector>

class Widget;

/// \brief Displays the system open file dialog.
class OpenFileDialog
{
public:
/// \brief Constructs an open file dialog.
static std::unique_ptr<OpenFileDialog> Create(Widget* owner);

virtual ~OpenFileDialog() = default;

/// \brief Get the full path of the file selected.
///
/// If multiple files are selected, this returns the first file.
virtual std::string Filename() const = 0;

/// \brief Gets an array that contains one file name for each selected file.
virtual std::vector<std::string> Filenames() const = 0;

/// \brief Sets if multiple files can be selected or not.
/// \param multiselect = When true, multiple items can be selected.
virtual void SetMultiSelect(bool multiselect) = 0;

/// \brief Sets a string containing the full path of the file selected.
virtual void SetFilename(const std::string &filename) = 0;

/// \brief Sets the default extension to use.
virtual void SetDefaultExtension(const std::string& extension) = 0;

/// \brief Add a filter that determines what types of files are displayed.
virtual void AddFilter(const std::string &filter_description, const std::string &filter_extension) = 0;

/// \brief Clears all filters.
virtual void ClearFilters() = 0;

/// \brief Sets a default filter, on a 0-based index.
virtual void SetFilterIndex(int filter_index) = 0;

/// \brief Sets the initial directory that is displayed.
virtual void SetInitialDirectory(const std::string &path) = 0;

/// \brief Sets the text that appears in the title bar.
virtual void SetTitle(const std::string &title) = 0;

/// \brief Shows the file dialog.
/// \return true if the user clicks the OK button of the dialog that is displayed, false otherwise.
virtual bool Show() = 0;
};
46 changes: 46 additions & 0 deletions include/zwidget/systemdialogs/save_file_dialog.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@

#pragma once

#include <memory>
#include <string>
#include <vector>

class Widget;

/// \brief Displays the system save file dialog.
class SaveFileDialog
{
public:
/// \brief Constructs a save file dialog.
static std::unique_ptr<SaveFileDialog> Create(Widget *owner);

virtual ~SaveFileDialog() = default;

/// \brief Get the full path of the file selected.
virtual std::string Filename() const = 0;

/// \brief Sets a string containing the full path of the file selected.
virtual void SetFilename(const std::string &filename) = 0;

/// \brief Sets the default extension to use.
virtual void SetDefaultExtension(const std::string& extension) = 0;

/// \brief Add a filter that determines what types of files are displayed.
virtual void AddFilter(const std::string &filter_description, const std::string &filter_extension) = 0;

/// \brief Clears all filters.
virtual void ClearFilters() = 0;

/// \brief Sets a default filter, on a 0-based index.
virtual void SetFilterIndex(int filter_index) = 0;

/// \brief Sets the initial directory that is displayed.
virtual void SetInitialDirectory(const std::string &path) = 0;

/// \brief Sets the text that appears in the title bar.
virtual void SetTitle(const std::string &title) = 0;

/// \brief Shows the file dialog.
/// \return true if the user clicks the OK button of the dialog that is displayed, false otherwise.
virtual bool Show() = 0;
};
2 changes: 2 additions & 0 deletions include/zwidget/window/window.h
Original file line number Diff line number Diff line change
Expand Up @@ -165,4 +165,6 @@ class DisplayWindow

virtual std::string GetClipboardText() = 0;
virtual void SetClipboardText(const std::string& text) = 0;

virtual void* GetNativeHandle() = 0;
};
6 changes: 6 additions & 0 deletions src/core/widget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -733,6 +733,12 @@ Size Widget::GetScreenSize()
return DisplayWindow::GetScreenSize();
}

void* Widget::GetNativeHandle()
{
Widget* w = Window();
return w ? w->DispWindow->GetNativeHandle() : nullptr;
}

void Widget::SetStyleClass(const std::string& themeClass)
{
if (StyleClass != themeClass)
Expand Down
188 changes: 188 additions & 0 deletions src/systemdialogs/folder_browse_dialog.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@

#include "systemdialogs/folder_browse_dialog.h"
#include "core/widget.h"
#include "window/window.h"
#include <stdexcept>

#if defined(WIN32)
#include <Shlobj.h>

namespace
{
static std::string from_utf16(const std::wstring& str)
{
if (str.empty()) return {};
int needed = WideCharToMultiByte(CP_UTF8, 0, str.data(), (int)str.size(), nullptr, 0, nullptr, nullptr);
if (needed == 0)
throw std::runtime_error("WideCharToMultiByte failed");
std::string result;
result.resize(needed);
needed = WideCharToMultiByte(CP_UTF8, 0, str.data(), (int)str.size(), &result[0], (int)result.size(), nullptr, nullptr);
if (needed == 0)
throw std::runtime_error("WideCharToMultiByte failed");
return result;
}

static std::wstring to_utf16(const std::string& str)
{
if (str.empty()) return {};
int needed = MultiByteToWideChar(CP_UTF8, 0, str.data(), (int)str.size(), nullptr, 0);
if (needed == 0)
throw std::runtime_error("MultiByteToWideChar failed");
std::wstring result;
result.resize(needed);
needed = MultiByteToWideChar(CP_UTF8, 0, str.data(), (int)str.size(), &result[0], (int)result.size());
if (needed == 0)
throw std::runtime_error("MultiByteToWideChar failed");
return result;
}

template<typename T>
class ComPtr
{
public:
ComPtr() { Ptr = nullptr; }
ComPtr(const ComPtr& other) { Ptr = other.Ptr; if (Ptr) Ptr->AddRef(); }
ComPtr(ComPtr&& move) { Ptr = move.Ptr; move.Ptr = nullptr; }
~ComPtr() { reset(); }
ComPtr& operator=(const ComPtr& other) { if (this != &other) { if (Ptr) Ptr->Release(); Ptr = other.Ptr; if (Ptr) Ptr->AddRef(); } return *this; }
void reset() { if (Ptr) Ptr->Release(); Ptr = nullptr; }
T* get() { return Ptr; }
static IID GetIID() { return __uuidof(T); }
void** InitPtr() { return (void**)TypedInitPtr(); }
T** TypedInitPtr() { reset(); return &Ptr; }
operator T* () const { return Ptr; }
T* operator ->() const { return Ptr; }
T* Ptr;
};
}

class BrowseFolderDialogImpl : public BrowseFolderDialog
{
public:
BrowseFolderDialogImpl(Widget *owner) : owner(owner)
{
}

Widget *owner = nullptr;

std::string selected_path;
std::string initial_directory;
std::string title;

bool Show() override
{
std::wstring title16 = to_utf16(title);
std::wstring initial_directory16 = to_utf16(initial_directory);

HRESULT result;
ComPtr<IFileOpenDialog> open_dialog;

result = CoCreateInstance(CLSID_FileOpenDialog, nullptr, CLSCTX_ALL, open_dialog.GetIID(), open_dialog.InitPtr());
throw_if_failed(result, "CoCreateInstance(FileOpenDialog) failed");

result = open_dialog->SetTitle(title16.c_str());
throw_if_failed(result, "IFileOpenDialog.SetTitle failed");

result = open_dialog->SetOptions(FOS_PICKFOLDERS | FOS_FORCEFILESYSTEM | FOS_PATHMUSTEXIST);
throw_if_failed(result, "IFileOpenDialog.SetOptions((FOS_PICKFOLDERS) failed");

if (initial_directory16.length() > 0)
{
LPITEMIDLIST item_id_list = nullptr;
SFGAOF flags = 0;
result = SHParseDisplayName(initial_directory16.c_str(), nullptr, &item_id_list, SFGAO_FILESYSTEM, &flags);
throw_if_failed(result, "SHParseDisplayName failed");

ComPtr<IShellItem> folder_item;
result = SHCreateShellItem(nullptr, nullptr, item_id_list, folder_item.TypedInitPtr());
ILFree(item_id_list);
throw_if_failed(result, "SHCreateItemFromParsingName failed");

/* This code requires Windows Vista or newer:
ComPtr<IShellItem> folder_item;
result = SHCreateItemFromParsingName(initial_directory16.c_str(), nullptr, folder_item.GetIID(), folder_item.InitPtr());
throw_if_failed(result, "SHCreateItemFromParsingName failed");
*/

if (folder_item)
{
result = open_dialog->SetFolder(folder_item);
throw_if_failed(result, "IFileOpenDialog.SetFolder failed");
}
}

if (owner && owner->Window())
result = open_dialog->Show((HWND)owner->Window()->GetNativeHandle());
else
result = open_dialog->Show(0);

if (SUCCEEDED(result))
{
ComPtr<IShellItem> chosen_folder;
result = open_dialog->GetResult(chosen_folder.TypedInitPtr());
throw_if_failed(result, "IFileOpenDialog.GetResult failed");

WCHAR *buffer = nullptr;
result = chosen_folder->GetDisplayName(SIGDN_FILESYSPATH, &buffer);
throw_if_failed(result, "IShellItem.GetDisplayName failed");

std::wstring output_directory16;
if (buffer)
{
try
{
output_directory16 = buffer;
}
catch (...)
{
CoTaskMemFree(buffer);
throw;
}
}

CoTaskMemFree(buffer);
selected_path = from_utf16(output_directory16);
return true;
}
else
{
return false;
}
}

std::string BrowseFolderDialog::SelectedPath() const override
{
return selected_path;
}

void BrowseFolderDialog::SetInitialDirectory(const std::string& path) override
{
initial_directory = path;
}

void BrowseFolderDialog::SetTitle(const std::string& newtitle) override
{
title = newtitle;
}

void throw_if_failed(HRESULT result, const std::string &error)
{
if (FAILED(result))
throw std::runtime_error(error);
}
};

std::unique_ptr<BrowseFolderDialog> BrowseFolderDialog::Create(Widget* owner)
{
return std::make_unique<BrowseFolderDialogImpl>(owner);
}

#else

std::unique_ptr<BrowseFolderDialog> BrowseFolderDialog::Create(Widget* owner)
{
return {};
}

#endif
Loading

0 comments on commit cdb7fb5

Please sign in to comment.