Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ While some features like the clipboard, menus or file dialogs are not yet availa
- GTK: Support disabled menu items. ([#897] by [@jneem])
- X11: Support individual window closing. ([#900] by [@xStrom])
- X11: Support `Application::quit`. ([#900] by [@xStrom])
- GTK: Support file filters in open/save dialogs. ([#903] by [@jneem])

### Visual

Expand Down Expand Up @@ -154,6 +155,7 @@ While some features like the clipboard, menus or file dialogs are not yet availa
[#897]: https://github.com/xi-editor/druid/pull/897
[#898]: https://github.com/xi-editor/druid/pull/898
[#900]: https://github.com/xi-editor/druid/pull/900
[#903]: https://github.com/xi-editor/druid/pull/903
[#909]: https://github.com/xi-editor/druid/pull/909

## [0.5.0] - 2020-04-01
Expand Down
43 changes: 41 additions & 2 deletions druid-shell/src/platform/gtk/dialog.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,20 @@

use std::ffi::OsString;

use gtk::{FileChooserAction, FileChooserExt, NativeDialogExt, ResponseType, Window};
use gtk::{FileChooserAction, FileChooserExt, FileFilter, NativeDialogExt, ResponseType, Window};

use crate::dialog::{FileDialogOptions, FileDialogType};
use crate::dialog::{FileDialogOptions, FileDialogType, FileSpec};
use crate::Error;

fn file_filter(fs: &FileSpec) -> FileFilter {
let ret = FileFilter::new();
ret.set_name(Some(fs.name));
for ext in fs.extensions {
ret.add_pattern(&format!("*.{}", ext));
}
ret
}

pub(crate) fn get_file_dialog_path(
window: &Window,
ty: FileDialogType,
Expand All @@ -45,6 +54,36 @@ pub(crate) fn get_file_dialog_path(

dialog.set_select_multiple(options.multi_selection);

let mut found_default_filter = false;
if let Some(file_types) = &options.allowed_types {
for f in file_types {
let filter = file_filter(f);
dialog.add_filter(&filter);

if let Some(default) = &options.default_type {
if default == f {
// Note that we're providing the same FileFilter object to
// add_filter and set_filter, because gtk checks them for
// identity, not structural equality.
dialog.set_filter(&filter);
found_default_filter = true;
}
}
}
}

if let Some(default_file_type) = &options.default_type {
if options.allowed_types.is_some() && !found_default_filter {
// It's ok to set a default file filter without providing a list of
// allowed filters, but it's not ok (or at least, doesn't work in gtk)
// to provide a default filter that isn't in the (present) list
// of allowed filters.
log::warn!("default file type not found in allowed types");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This warning should be removed, because there is an explictly documented behavior for this case.

If it's None or not present in allowed_types then the first entry in allowed_types will be used as default.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But it seems unreasonable for the user to provide a default that is not even available.
If this happens, it most definitely seems to be an accident and should log a warning (on all platforms), at least I think so.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess that's reasonable. We can keep the warning then.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that (and I can change this if it isn't desired druid behavior), if allowed_types is empty then the default type is allowed to be present: it applies a filter that cannot be changed.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to be clear, my previous comment is about gtk's behavior.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So if I interpret that code correctly, on Windows Some(default_type) is ignored if allowed_types is None. On mac the default type seems to be ignored all together.

I'm not sure what the 'correct' behavior would be (at least not the mac one).
Maybe we should allow having default be Some while allowed is None?

} else if !found_default_filter {
dialog.set_filter(&file_filter(default_file_type));
}
}

let result = dialog.run();

let result = match result {
Expand Down
106 changes: 106 additions & 0 deletions druid/examples/open_save.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
// Copyright 2020 The xi-editor Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use druid::widget::{Align, Button, Flex, TextBox};
use druid::{
AppDelegate, AppLauncher, Command, DelegateCtx, Env, FileDialogOptions, FileInfo, FileSpec,
LocalizedString, Target, Widget, WindowDesc,
};

struct Delegate;

pub fn main() {
let main_window = WindowDesc::new(ui_builder)
.title(LocalizedString::new("open-save-demo").with_placeholder("Opening/Saving Demo"));
let data = "Type here.".to_owned();
AppLauncher::with_window(main_window)
.delegate(Delegate)
.use_simple_logger()
.launch(data)
.expect("launch failed");
}

fn ui_builder() -> impl Widget<String> {
let rs = FileSpec::new("Rust source", &["rs"]);
let txt = FileSpec::new("Text file", &["txt"]);
let other = FileSpec::new("Bogus file", &["foo", "bar", "baz"]);
let save_dialog_options = FileDialogOptions::new()
.allowed_types(vec![rs, txt, other])
.default_type(txt);
let open_dialog_options = save_dialog_options.clone();

let input = TextBox::new();
let save = Button::new("Save").on_click(move |ctx, _, _| {
ctx.submit_command(
Command::new(
druid::commands::SHOW_SAVE_PANEL,
save_dialog_options.clone(),
),
None,
)
});
let open = Button::new("Open").on_click(move |ctx, _, _| {
ctx.submit_command(
Command::new(
druid::commands::SHOW_OPEN_PANEL,
open_dialog_options.clone(),
),
None,
)
});

let mut col = Flex::column();
col.add_child(input);
col.add_spacer(8.0);
col.add_child(save);
col.add_child(open);
Align::centered(col)
}

impl AppDelegate<String> for Delegate {
fn command(
&mut self,
_ctx: &mut DelegateCtx,
_target: Target,
cmd: &Command,
data: &mut String,
_env: &Env,
) -> bool {
match cmd.selector {
druid::commands::SAVE_FILE => {
if let Ok(file_info) = cmd.get_object::<FileInfo>() {
if let Err(e) = std::fs::write(file_info.path(), &data[..]) {
println!("Error writing file: {}", e);
}
}
true
}
druid::commands::OPEN_FILE => {
if let Ok(file_info) = cmd.get_object::<FileInfo>() {
match std::fs::read_to_string(file_info.path()) {
Ok(s) => {
let first_line = s.lines().next().unwrap_or("");
*data = first_line.to_owned();
}
Err(e) => {
println!("Error opening file: {}", e);
}
}
}
true
}
_ => false,
}
}
}
1 change: 1 addition & 0 deletions druid/examples/wasm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ impl_example!(layout);
impl_example!(lens);
impl_example!(list);
impl_example!(multiwin);
impl_example!(open_save);
impl_example!(panels.unwrap());
impl_example!(parse);
impl_example!(scroll_colors);
Expand Down