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