3 Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU Lesser General Public License as published by
7 the Free Software Foundation; either version 2.1 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU Lesser General Public License for more details.
15 You should have received a copy of the GNU Lesser General Public License along
16 with this program; if not, write to the Free Software Foundation, Inc.,
17 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
25 #include "guiConfigureWorld.h"
26 #include "guiMessageMenu.h"
27 #include <IGUIButton.h>
28 #include <IGUICheckBox.h>
29 #include <IGUIListBox.h>
30 #include <IGUIStaticText.h>
31 #include <IGUITreeView.h>
33 #include "util/string.h"
39 GUI_ID_MOD_TREEVIEW = 101,
40 GUI_ID_ENABLED_CHECKBOX,
43 GUI_ID_DEPENDS_LISTBOX,
44 GUI_ID_RDEPENDS_LISTBOX,
49 #define QUESTIONMARK_STR L"?"
50 #define CHECKMARK_STR L"\411"
51 #define CROSS_STR L"\403"
53 GUIConfigureWorld::GUIConfigureWorld(gui::IGUIEnvironment* env,
54 gui::IGUIElement* parent, s32 id,
55 IMenuManager *menumgr, WorldSpec wspec):
56 GUIModalMenu(env, parent, id, menumgr),
58 m_gspec(findWorldSubgame(m_wspec.path)),
61 //will be initialized in regenerateGUI()
65 m_gamemods = flattenModTree(getModsInPath(m_gspec.gamemods_path));
68 std::string worldmods_path = wspec.path + DIR_DELIM + "worldmods";
69 m_worldmods = flattenModTree(getModsInPath(worldmods_path));
71 // fill m_addontree with add-on mods
72 std::set<std::string> paths = m_gspec.addon_mods_paths;
73 for(std::set<std::string>::iterator it=paths.begin();
74 it != paths.end(); ++it)
76 std::map<std::string,ModSpec> mods = getModsInPath(*it);
77 m_addontree.insert(mods.begin(), mods.end());
81 m_addonmods = flattenModTree(m_addontree);
83 // collect reverse dependencies
84 for(std::map<std::string, ModSpec>::iterator it = m_addonmods.begin();
85 it != m_addonmods.end(); ++it)
87 std::string modname = (*it).first;
88 ModSpec mod = (*it).second;
89 for(std::set<std::string>::iterator dep_it = mod.depends.begin();
90 dep_it != mod.depends.end(); ++dep_it)
92 m_reverse_depends.insert(std::make_pair((*dep_it),modname));
96 m_settings.readConfigFile((m_wspec.path + DIR_DELIM + "world.mt").c_str());
97 std::vector<std::string> names = m_settings.getNames();
99 // mod_names contains the names of mods mentioned in the world.mt file
100 std::set<std::string> mod_names;
101 for(std::vector<std::string>::iterator it = names.begin();
102 it != names.end(); ++it)
104 std::string name = *it;
105 if (name.compare(0,9,"load_mod_")==0)
106 mod_names.insert(name.substr(9));
109 // find new mods (installed but not mentioned in world.mt)
110 for(std::map<std::string, ModSpec>::iterator it = m_addonmods.begin();
111 it != m_addonmods.end(); ++it)
113 std::string modname = (*it).first;
114 ModSpec mod = (*it).second;
115 // a mod is new if it is not a modpack, and does not occur in
117 if(!mod.is_modpack &&
118 mod_names.count(modname) == 0)
119 m_new_mod_names.insert(modname);
121 if(!m_new_mod_names.empty())
123 wchar_t* text = wgettext("Warning: Some mods are not configured yet.\n"
124 "They will be enabled by default when you save the configuration. ");
125 GUIMessageMenu *menu =
126 new GUIMessageMenu(Environment, Parent, -1, m_menumgr, text);
132 // find missing mods (mentioned in world.mt, but not installed)
133 std::set<std::string> missing_mods;
134 for(std::set<std::string>::iterator it = mod_names.begin();
135 it != mod_names.end(); ++it)
137 std::string modname = *it;
138 if(m_addonmods.count(modname) == 0)
139 missing_mods.insert(modname);
141 if(!missing_mods.empty())
143 wchar_t* text = wgettext("Warning: Some configured mods are missing.\n"
144 "Their setting will be removed when you save the configuration. ");
145 GUIMessageMenu *menu =
146 new GUIMessageMenu(Environment, Parent, -1, m_menumgr, text);
148 for(std::set<std::string>::iterator it = missing_mods.begin();
149 it != missing_mods.end(); ++it)
150 m_settings.remove("load_mod_"+(*it));
155 void GUIConfigureWorld::drawMenu()
157 gui::IGUISkin* skin = Environment->getSkin();
160 video::IVideoDriver* driver = Environment->getVideoDriver();
162 video::SColor bgcolor(140,0,0,0);
163 driver->draw2DRectangle(bgcolor, AbsoluteRect, &AbsoluteClippingRect);
165 gui::IGUIElement::draw();
169 void GUIConfigureWorld::regenerateGui(v2u32 screensize)
178 Calculate new sizes and positions
180 core::rect<s32> rect(
181 screensize.X/2 - 580/2,
182 screensize.Y/2 - 300/2,
183 screensize.X/2 + 580/2,
184 screensize.Y/2 + 300/2
188 recalculateAbsolutePosition(false);
190 v2s32 size = rect.getSize();
192 v2s32 topleft = v2s32(10, 10);
199 core::rect<s32> rect(0, 0, 200, 20);
201 //proper text is set below, when a mod is selected
202 m_modname_text = Environment->addStaticText(L"Mod: N/A", rect, false,
206 core::rect<s32> rect(0, 0, 200, 20);
207 rect += v2s32(0, 25) + topleft;
208 wchar_t* text = wgettext("enabled");
210 Environment->addCheckBox(false, rect, this, GUI_ID_ENABLED_CHECKBOX,
213 m_enabled_checkbox->setVisible(false);
216 core::rect<s32> rect(0, 0, 85, 30);
217 rect = rect + v2s32(0, 25) + topleft;
218 wchar_t* text = wgettext("Enable All");
219 m_enableall = Environment->addButton(rect, this, GUI_ID_ENABLEALL,
222 m_enableall->setVisible(false);
225 core::rect<s32> rect(0, 0, 85, 30);
226 rect = rect + v2s32(115, 25) + topleft;
227 wchar_t* text = wgettext("Disable All");
228 m_disableall = Environment->addButton(rect, this, GUI_ID_DISABLEALL, text );
230 m_disableall->setVisible(false);
233 core::rect<s32> rect(0, 0, 200, 20);
234 rect += v2s32(0, 60) + topleft;
235 wchar_t* text = wgettext("depends on:");
236 Environment->addStaticText(text, rect, false, false, this, -1);
240 core::rect<s32> rect(0, 0, 200, 85);
241 rect += v2s32(0, 80) + topleft;
242 m_dependencies_listbox =
243 Environment->addListBox(rect, this, GUI_ID_DEPENDS_LISTBOX, true);
246 core::rect<s32> rect(0, 0, 200, 20);
247 rect += v2s32(0, 175) + topleft;
248 wchar_t* text = wgettext("is required by:");
249 Environment->addStaticText( text, rect, false, false, this, -1);
253 core::rect<s32> rect(0, 0, 200, 85);
254 rect += v2s32(0, 195) + topleft;
255 m_rdependencies_listbox =
256 Environment->addListBox(rect,this, GUI_ID_RDEPENDS_LISTBOX,true);
259 core::rect<s32> rect(0, 0, 340, 250);
260 rect += v2s32(220, 0) + topleft;
261 m_treeview = Environment->addTreeView(rect, this,
262 GUI_ID_MOD_TREEVIEW,true);
263 gui::IGUITreeViewNode* node
264 = m_treeview->getRoot()->addChildBack(L"Add-Ons");
265 buildTreeView(m_addontree, node);
268 core::rect<s32> rect(0, 0, 120, 30);
269 rect = rect + v2s32(330, 270) - topleft;
270 wchar_t* text = wgettext("Cancel");
271 Environment->addButton(rect, this, GUI_ID_CANCEL, text);
275 core::rect<s32> rect(0, 0, 120, 30);
276 rect = rect + v2s32(460, 270) - topleft;
277 wchar_t* text = wgettext("Save");
278 Environment->addButton(rect, this, GUI_ID_SAVE, text);
283 // at start, none of the treeview nodes is selected, so we select
284 // the first element in the treeview of mods manually here.
285 if(m_treeview->getRoot()->hasChilds())
287 m_treeview->getRoot()->getFirstChild()->setExpanded(true);
288 m_treeview->getRoot()->getFirstChild()->setSelected(true);
289 // Because a manual ->setSelected() doesn't cause an event, we
290 // have to do this here:
295 bool GUIConfigureWorld::OnEvent(const SEvent& event)
298 gui::IGUITreeViewNode* selected_node = NULL;
299 if(m_treeview != NULL)
300 selected_node = m_treeview->getSelected();
302 if(event.EventType==EET_KEY_INPUT_EVENT && event.KeyInput.PressedDown)
304 switch (event.KeyInput.Key) {
309 // irrlicht's built-in TreeView gui has no keyboard control,
310 // so we do it here: up/down to select prev/next node,
311 // left/right to collapse/expand nodes, space to toggle
314 if(selected_node != NULL)
316 gui::IGUITreeViewNode* node = selected_node->getNextVisible();
319 node->setSelected(true);
326 if(selected_node != NULL)
328 gui::IGUITreeViewNode* node = selected_node->getPrevSibling();
331 node->setSelected(true);
336 gui::IGUITreeViewNode* parent = selected_node->getParent();
337 if(selected_node == parent->getFirstChild() &&
338 parent != m_treeview->getRoot())
340 parent->setSelected(true);
348 if(selected_node != NULL && selected_node->hasChilds())
349 selected_node->setExpanded(true);
353 if(selected_node != NULL && selected_node->hasChilds())
354 selected_node->setExpanded(false);
358 if(selected_node != NULL && !selected_node->hasChilds() &&
359 selected_node->getText() != NULL)
361 std::string modname = wide_to_narrow(selected_node->getText());
362 bool checked = m_enabled_checkbox->isChecked();
363 m_enabled_checkbox->setChecked(!checked);
364 setEnabled(modname,!checked);
371 if(event.EventType==EET_GUI_EVENT)
373 if(event.GUIEvent.EventType==gui::EGET_ELEMENT_FOCUS_LOST
376 if(!canTakeFocus(event.GUIEvent.Element))
378 dstream<<"GUIConfigureWorld: Not allowing focus change."
380 // Returning true disables focus change
384 if(event.GUIEvent.EventType==gui::EGET_BUTTON_CLICKED){
385 switch(event.GUIEvent.Caller->getID()){
386 case GUI_ID_CANCEL: {
391 for(std::set<std::string>::iterator it = m_new_mod_names.begin();
392 it!= m_new_mod_names.end(); ++it)
394 m_settings.setBool("load_mod_"+(*it),true);
396 std::string worldmtfile = m_wspec.path+DIR_DELIM+"world.mt";
397 m_settings.updateConfigFile(worldmtfile.c_str());
399 // The trailing spaces are because there seems to be a
400 // bug in the text-size calculation. if the trailing
401 // spaces are removed from the message text, the
402 // message gets wrapped and parts of it are cut off:
403 wchar_t* text = wgettext("Configuration saved. ");
404 GUIMessageMenu *menu =
405 new GUIMessageMenu(Environment, Parent, -1, m_menumgr,
412 ModConfiguration modconf(m_wspec.path);
413 if(!modconf.isConsistent())
415 wchar_t* text = wgettext("Warning: Configuration not consistent. ");
416 GUIMessageMenu *menu =
417 new GUIMessageMenu(Environment, Parent, -1, m_menumgr,
425 errorstream<<err.what()<<std::endl;
426 std::wstring text = narrow_to_wide(err.what()) + wgettext("\nCheck debug.txt for details.");
427 GUIMessageMenu *menu =
428 new GUIMessageMenu(Environment, Parent, -1, m_menumgr,
436 case GUI_ID_ENABLEALL: {
437 if(selected_node != NULL && selected_node->getParent() == m_treeview->getRoot())
439 enableAllMods(m_addonmods,true);
441 else if(selected_node != NULL && selected_node->getText() != NULL)
443 std::string modname = wide_to_narrow(selected_node->getText());
444 std::map<std::string, ModSpec>::iterator mod_it = m_addonmods.find(modname);
445 if(mod_it != m_addonmods.end())
446 enableAllMods(mod_it->second.modpack_content,true);
450 case GUI_ID_DISABLEALL: {
451 if(selected_node != NULL && selected_node->getParent() == m_treeview->getRoot())
453 enableAllMods(m_addonmods,false);
455 if(selected_node != NULL && selected_node->getText() != NULL)
457 std::string modname = wide_to_narrow(selected_node->getText());
458 std::map<std::string, ModSpec>::iterator mod_it = m_addonmods.find(modname);
459 if(mod_it != m_addonmods.end())
460 enableAllMods(mod_it->second.modpack_content,false);
466 if(event.GUIEvent.EventType==gui::EGET_CHECKBOX_CHANGED &&
467 event.GUIEvent.Caller->getID() == GUI_ID_ENABLED_CHECKBOX)
469 if(selected_node != NULL && !selected_node->hasChilds() &&
470 selected_node->getText() != NULL)
472 std::string modname = wide_to_narrow(selected_node->getText());
473 setEnabled(modname, m_enabled_checkbox->isChecked());
477 if(event.GUIEvent.EventType==gui::EGET_TREEVIEW_NODE_SELECT &&
478 event.GUIEvent.Caller->getID() == GUI_ID_MOD_TREEVIEW)
485 if(event.GUIEvent.EventType==gui::EGET_LISTBOX_CHANGED &&
486 event.GUIEvent.Caller->getID() == GUI_ID_DEPENDS_LISTBOX)
488 selecting_dep = m_dependencies_listbox->getSelected();
492 if(event.GUIEvent.EventType==gui::EGET_LISTBOX_CHANGED &&
493 event.GUIEvent.Caller->getID() == GUI_ID_RDEPENDS_LISTBOX)
496 selecting_rdep = m_rdependencies_listbox->getSelected();
500 //double click in a dependency listbox: find corresponding
501 //treeviewnode and select it:
502 if(event.GUIEvent.EventType==gui::EGET_LISTBOX_SELECTED_AGAIN)
504 gui::IGUIListBox* box = NULL;
505 if(event.GUIEvent.Caller->getID() == GUI_ID_DEPENDS_LISTBOX)
507 box = m_dependencies_listbox;
508 if(box->getSelected() != selecting_dep)
511 if(event.GUIEvent.Caller->getID() == GUI_ID_RDEPENDS_LISTBOX)
513 box = m_rdependencies_listbox;
514 if(box->getSelected() != selecting_rdep)
517 if(box != NULL && box->getSelected() != -1 &&
518 box->getListItem(box->getSelected()) != NULL)
520 std::string modname =
521 wide_to_narrow(box->getListItem(box->getSelected()));
522 std::map<std::string, gui::IGUITreeViewNode*>::iterator it =
523 m_nodes.find(modname);
524 if(it != m_nodes.end())
526 // select node and make sure node is visible by
527 // expanding all parents
528 gui::IGUITreeViewNode* node = (*it).second;
529 node->setSelected(true);
530 while(!node->isVisible() &&
531 node->getParent() != m_treeview->getRoot())
533 node = node->getParent();
534 node->setExpanded(true);
543 return Parent ? Parent->OnEvent(event) : false;
546 void GUIConfigureWorld::buildTreeView(std::map<std::string, ModSpec> mods,
547 gui::IGUITreeViewNode* node)
549 for(std::map<std::string,ModSpec>::iterator it = mods.begin();
550 it != mods.end(); ++it)
552 std::string modname = (*it).first;
553 ModSpec mod = (*it).second;
554 gui::IGUITreeViewNode* new_node =
555 node->addChildBack(narrow_to_wide(modname).c_str());
556 m_nodes.insert(std::make_pair(modname, new_node));
558 buildTreeView(mod.modpack_content, new_node);
561 // set icon for node: ? for new mods, x for disabled mods,
562 // checkmark for enabled mods
563 if(m_new_mod_names.count(modname) > 0)
565 new_node->setIcon(QUESTIONMARK_STR);
569 bool mod_enabled = true;
570 if(m_settings.exists("load_mod_"+modname))
571 mod_enabled = m_settings.getBool("load_mod_"+modname);
573 new_node->setIcon(CHECKMARK_STR);
575 new_node->setIcon(CROSS_STR);
582 void GUIConfigureWorld::adjustSidebar()
584 gui::IGUITreeViewNode* node = m_treeview->getSelected();
585 std::wstring modname_w;
586 if(node->getText() != NULL)
587 modname_w = node->getText();
590 std::string modname = wide_to_narrow(modname_w);
593 std::map<std::string, ModSpec>::iterator it = m_addonmods.find(modname);
594 if(it != m_addonmods.end())
597 m_dependencies_listbox->clear();
598 m_rdependencies_listbox->clear();
600 // if no mods installed, there is nothing to enable/disable, so we
601 // don't show buttons or checkbox on the sidebar
602 if(node->getParent() == m_treeview->getRoot() && !node->hasChilds())
604 m_disableall->setVisible(false);
605 m_enableall->setVisible(false);
606 m_enabled_checkbox->setVisible(false);
610 // a modpack is not enabled/disabled by itself, only its cotnents
611 // are. so we show show enable/disable all buttons, but hide the
613 if(node->getParent() == m_treeview->getRoot() ||
616 m_enabled_checkbox->setVisible(false);
617 m_disableall->setVisible(true);
618 m_enableall->setVisible(true);
619 m_modname_text->setText((L"Modpack: "+modname_w).c_str());
623 // for a normal mod, we hide the enable/disable all buttons, but show the checkbox.
624 m_disableall->setVisible(false);
625 m_enableall->setVisible(false);
626 m_enabled_checkbox->setVisible(true);
627 m_modname_text->setText((L"Mod: "+modname_w).c_str());
629 // the mod is enabled unless it is disabled in the world.mt settings.
630 bool mod_enabled = true;
631 if(m_settings.exists("load_mod_"+modname))
632 mod_enabled = m_settings.getBool("load_mod_"+modname);
633 m_enabled_checkbox->setChecked(mod_enabled);
635 for(std::set<std::string>::iterator it=mspec.depends.begin();
636 it != mspec.depends.end(); ++it)
638 // check if it is an add-on mod or a game/world mod. We only
639 // want to show add-ons
640 std::string dependency = (*it);
641 if(m_gamemods.count(dependency) > 0)
642 dependency += " (" + m_gspec.id + ")";
643 else if(m_worldmods.count(dependency) > 0)
644 dependency += " (" + m_wspec.name + ")";
645 else if(m_addonmods.count(dependency) == 0)
646 dependency += " (missing)";
647 m_dependencies_listbox->addItem(narrow_to_wide(dependency).c_str());
650 // reverse dependencies of this mod:
651 std::pair< std::multimap<std::string, std::string>::iterator,
652 std::multimap<std::string, std::string>::iterator > rdep =
653 m_reverse_depends.equal_range(modname);
654 for(std::multimap<std::string,std::string>::iterator it = rdep.first;
655 it != rdep.second; ++it)
657 // check if it is an add-on mod or a game/world mod. We only
658 // want to show add-ons
659 std::string rdependency = (*it).second;
660 if(m_addonmods.count(rdependency) > 0)
661 m_rdependencies_listbox->addItem(narrow_to_wide(rdependency).c_str());
665 void GUIConfigureWorld::enableAllMods(std::map<std::string, ModSpec> mods,bool enable)
667 for(std::map<std::string, ModSpec>::iterator it = mods.begin();
668 it != mods.end(); ++it)
670 ModSpec mod = (*it).second;
672 // a modpack, recursively enable all mods in it
673 enableAllMods(mod.modpack_content,enable);
674 else // not a modpack
675 setEnabled(mod.name, enable);
680 void GUIConfigureWorld::enableMod(std::string modname)
682 std::map<std::string, ModSpec>::iterator mod_it = m_addonmods.find(modname);
683 if(mod_it == m_addonmods.end()){
684 errorstream << "enableMod() called with invalid mod name \"" << modname << "\"" << std::endl;
687 ModSpec mspec = mod_it->second;
688 m_settings.setBool("load_mod_"+modname,true);
689 std::map<std::string,gui::IGUITreeViewNode*>::iterator it =
690 m_nodes.find(modname);
691 if(it != m_nodes.end())
692 (*it).second->setIcon(CHECKMARK_STR);
693 m_new_mod_names.erase(modname);
694 //also enable all dependencies
695 for(std::set<std::string>::iterator it=mspec.depends.begin();
696 it != mspec.depends.end(); ++it)
698 std::string dependency = *it;
699 // only enable it if it is an add-on mod
700 if(m_addonmods.count(dependency) > 0)
701 enableMod(dependency);
705 void GUIConfigureWorld::disableMod(std::string modname)
707 std::map<std::string, ModSpec>::iterator mod_it = m_addonmods.find(modname);
708 if(mod_it == m_addonmods.end()){
709 errorstream << "disableMod() called with invalid mod name \"" << modname << "\"" << std::endl;
713 m_settings.setBool("load_mod_"+modname,false);
714 std::map<std::string,gui::IGUITreeViewNode*>::iterator it =
715 m_nodes.find(modname);
716 if(it != m_nodes.end())
717 (*it).second->setIcon(CROSS_STR);
718 m_new_mod_names.erase(modname);
719 //also disable all mods that depend on this one
720 std::pair<std::multimap<std::string, std::string>::iterator,
721 std::multimap<std::string, std::string>::iterator > rdep =
722 m_reverse_depends.equal_range(modname);
723 for(std::multimap<std::string,std::string>::iterator it = rdep.first;
724 it != rdep.second; ++it)
726 std::string rdependency = (*it).second;
727 // only disable it if it is an add-on mod
728 if(m_addonmods.count(rdependency) > 0)
729 disableMod(rdependency);