1 /** @file directoryarraywidget.cpp Widget for an array of native directories.
2 *
3 * @authors Copyright (c) 2016-2017 Jaakko Keränen <jaakko.keranen@iki.fi>
4 *
5 * @par License
6 * LGPL: http://www.gnu.org/licenses/lgpl.html
7 *
8 * <small>This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU Lesser General Public License as published by
10 * the Free Software Foundation; either version 3 of the License, or (at your
11 * option) any later version. This program is distributed in the hope that it
12 * will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
13 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
14 * General Public License for more details. You should have received a copy of
15 * the GNU Lesser General Public License along with this program; if not, see:
16 * http://www.gnu.org/licenses</small>
17 */
18
19 #include "de/DirectoryArrayWidget"
20 #include "de/BaseGuiApp"
21 #include "de/BaseWindow"
22
23 #include <de/Config>
24 #include <de/Garbage>
25 #include <de/NativePath>
26 #include <de/TextValue>
27 #include <de/ToggleWidget>
28 #include <QFileDialog>
29
30 namespace de {
31
32 static String const CFG_LAST_FOLDER("resource.latestDirectory");
33
DENG2_PIMPL_NOREF(DirectoryArrayWidget)34 DENG2_PIMPL_NOREF(DirectoryArrayWidget)
35 {};
36
DirectoryArrayWidget(Variable & variable,String const & name)37 DirectoryArrayWidget::DirectoryArrayWidget(Variable &variable, String const &name)
38 : VariableArrayWidget(variable, name)
39 , d(new Impl)
40 {
41 addButton().setText(tr("Add Folder..."));
42 addButton().setActionFn([this] ()
43 {
44 // Use a native dialog to select the IWAD folder.
45 DENG2_BASE_GUI_APP->beginNativeUIMode();
46
47 QFileDialog dlg(nullptr, tr("Select Folder"),
48 Config::get().gets(CFG_LAST_FOLDER, "."), "");
49 dlg.setFileMode(QFileDialog::Directory);
50 dlg.setReadOnly(true);
51 //dlg.setNameFilter("*.wad");
52 dlg.setLabelText(QFileDialog::Accept, tr("Select"));
53 if (dlg.exec())
54 {
55 String dir = dlg.selectedFiles().at(0);
56 Config::get().set(CFG_LAST_FOLDER, dir.fileNamePath());
57 elementsMenu().items() << makeItem(TextValue(dir));
58 setVariableFromWidget();
59 }
60
61 DENG2_BASE_GUI_APP->endNativeUIMode();
62 });
63
64 updateFromVariable();
65 }
66
labelForElement(Value const & value) const67 String DirectoryArrayWidget::labelForElement(Value const &value) const
68 {
69 return NativePath(value.asText()).pretty();
70 }
71
72 static const String RECURSE_TOGGLE_NAME("recurse-toggle");
73
74 /**
75 * Controller that syncs state between Config.resource.recurseFolders and the toggles
76 * in the DirectoryArrayWidget items. Destroys itself after the item widget is deleted.
77 */
78 struct RecurseToggler
79 : DENG2_OBSERVES(ToggleWidget, Toggle)
80 , DENG2_OBSERVES(Widget, Deletion)
81 , DENG2_OBSERVES(ui::Item, Change)
82 , DENG2_OBSERVES(ChildWidgetOrganizer, WidgetUpdate)
83 {
84 DirectoryArrayWidget *owner;
85 ToggleWidget * tog;
86 const ui::Item * item;
87
RecurseTogglerde::RecurseToggler88 RecurseToggler(DirectoryArrayWidget *owner, LabelWidget &element, const ui::Item &item)
89 : owner(owner)
90 , item(&item)
91 {
92 tog = &element.guiFind(RECURSE_TOGGLE_NAME)->as<ToggleWidget>();
93 item.audienceForChange() += this;
94 element.audienceForDeletion() += this;
95 tog->audienceForToggle() += this;
96 owner->elementsMenu().organizer().audienceForWidgetUpdate() += this;
97 }
98
recursedde::RecurseToggler99 static Variable &recursed()
100 {
101 return Config::get("resource.recursedFolders");
102 }
103
keyde::RecurseToggler104 TextValue key() const
105 {
106 return {item->data().toString()};
107 }
108
fetchde::RecurseToggler109 void fetch()
110 {
111 if (recursed().value().contains(key()))
112 {
113 tog->setActive(recursed().value().element(key()).isTrue());
114 }
115 }
116
toggleStateChangedde::RecurseToggler117 void toggleStateChanged(ToggleWidget &toggle) override
118 {
119 recursed().value().setElement(key(), new NumberValue(toggle.isActive()));
120 emit owner->arrayChanged();
121 }
122
widgetBeingDeletedde::RecurseToggler123 void widgetBeingDeleted(Widget &) override
124 {
125 item->audienceForChange() -= this;
126 // tog is already gone
127 trash(this);
128 }
129
itemChangedde::RecurseToggler130 void itemChanged(const ui::Item &) override
131 {
132 fetch();
133 }
134
widgetUpdatedForItemde::RecurseToggler135 void widgetUpdatedForItem(GuiWidget &, const ui::Item &) override
136 {
137 fetch();
138 }
139 };
140
elementCreated(LabelWidget & element,const ui::Item & item)141 void DirectoryArrayWidget::elementCreated(LabelWidget &element, const ui::Item &item)
142 {
143 element.setSizePolicy(ui::Fixed, ui::Expand);
144 element.setAlignment(ui::AlignLeft);
145 element.setTextLineAlignment(ui::AlignLeft);
146 element.setMaximumTextWidth(rule().width());
147 element.rule().setInput(Rule::Width, rule().width() - margins().width());
148
149 // Add a toggle for configuration recurse mode.
150 auto *tog = new ToggleWidget(ToggleWidget::DefaultFlags, RECURSE_TOGGLE_NAME);
151 element.add(tog);
152 tog->setText("Subdirs");
153 tog->setActive(true); // recurse is on by default
154 tog->set(Background());
155 tog->setFont("small");
156 tog->margins().setLeft("unit").setRight("gap").setTop("unit").setBottom("unit");
157 tog->setSizePolicy(ui::Expand, ui::Expand);
158 tog->rule()
159 .setInput(Rule::Right, element.rule().right() - rule("gap"))
160 .setMidAnchorY(element.rule().midY());
161 element.margins().setRight(tog->rule().width() + rule("gap"));
162
163 new RecurseToggler(this, element, item); // deletes itself
164 }
165
166 } // namespace de
167