1--[[ Copyright (c) 2010 Peter "Corsix" Cawley 2 3Permission is hereby granted, free of charge, to any person obtaining a copy of 4this software and associated documentation files (the "Software"), to deal in 5the Software without restriction, including without limitation the rights to 6use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 7of the Software, and to permit persons to whom the Software is furnished to do 8so, subject to the following conditions: 9 10The above copyright notice and this permission notice shall be included in all 11copies or substantial portions of the Software. 12 13THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19SOFTWARE. --]] 20 21local lfs = require("lfs") 22local TH = require("TH") 23local iso_fs = TH.iso_fs() 24local lfsext = TH.lfsExt() 25 26--! A tree node representing a directory in the physical file-system. 27class "DirTreeNode" (FileTreeNode) 28 29---@type DirTreeNode 30local DirTreeNode = _G["DirTreeNode"] 31 32function DirTreeNode:DirTreeNode(path) 33 self:FileTreeNode(path) 34end 35 36function DirTreeNode:isValidFile(name) 37 -- Check parent criteria and that it's a directory. 38 if FileTreeNode.isValidFile(self, name) and 39 lfs.attributes(self:childPath(name), "mode") == "directory" then 40 -- Make sure that we are allowed to read the directory. 41 local status, _, dir_obj = pcall(lfs.dir, self:childPath(name)) 42 if status then 43 dir_obj:close() 44 end 45 return status 46 end 47end 48 49function DirTreeNode:getSelectColour(canvas) 50 if self.is_valid_directory then 51 return self.highlight_colour 52 else 53 return canvas:mapRGB(174, 166, 218) 54 end 55end 56 57 58 59 60--! This tree only shows directories and highlights valid TH directories. 61class "InstallDirTreeNode" (DirTreeNode) 62 63---@type InstallDirTreeNode 64local InstallDirTreeNode = _G["InstallDirTreeNode"] 65 66function InstallDirTreeNode:InstallDirTreeNode(path) 67 self:FileTreeNode(path) 68end 69 70function InstallDirTreeNode:createNewNode(path) 71 return InstallDirTreeNode(path) 72end 73 74--! Test whether this file node is a directory or iso file. 75-- 76--!return (bool) true if directory or iso, false otherwise 77function InstallDirTreeNode:isValidFile(name) 78 -- Check parent criteria and that it's a directory. 79 if FileTreeNode.isValidFile(self, name) then 80 return DirTreeNode.isValidFile(self, name) or FileSystem:isIso(name) 81 end 82 return false 83end 84 85 86function InstallDirTreeNode:select() 87 -- Do nothing as an override. getHighlightColour solves this instead. 88end 89 90--! Check whether this node is a valid install selection. 91-- 92-- Sets self.is_valid_directory to true if the selection is valid. 93-- 94--!return (colour) A highlight colour if the node is a valid selection, or nil 95-- otherwise. 96function InstallDirTreeNode:getHighlightColour(canvas) 97 local highlight_colour = self.highlight_colour 98 if highlight_colour == nil then 99 highlight_colour = false 100 101 if self:getLevel() == 0 and not self.has_looked_for_children then 102 -- Assume root-level things are not TH directories, unless we've already 103 -- got a list of their children. 104 highlight_colour = nil 105 elseif self:getChildCount() >= 3 and TheApp:isThemeHospitalPath(self.path) then 106 highlight_colour = canvas:mapRGB(0, 255, 0) 107 self.is_valid_directory = true 108 elseif FileSystem:isIso(self.path) and iso_fs:setRoot(self.path) then 109 highlight_colour = canvas:mapRGB(0, 255, 0) 110 self.is_valid_directory = true 111 end 112 self.highlight_colour = highlight_colour 113 end 114 return highlight_colour or nil 115end 116 117--! Prompter for Theme Hospital install directory 118class "UIDirectoryBrowser" (UIResizable) 119 120---@type UIDirectoryBrowser 121local UIDirectoryBrowser = _G["UIDirectoryBrowser"] 122 123--! Creates a new directory browser window 124--!param ui The active UI to hook into. 125--!param mode Whether the dialog has been opened from the main_menu or somewhere else. Currently 126--! valid are "menu" or "dir_browser". 127--!param instruction The textual instruction what the user should do in the dialog. 128--!param treenode_class What TreeNode subclass the nodes will be built from. E.g. "InstallDirTreeNode" 129--!param callback The function that is called when the user has chosen a directory. Gets 130--! a path string as argument. 131function UIDirectoryBrowser:UIDirectoryBrowser(ui, mode, instruction, treenode_class, callback) 132 self.col_bg = { 133 red = 154, 134 green = 146, 135 blue = 198, 136 } 137 self.col_scrollbar = { 138 red = 164, 139 green = 156, 140 blue = 208, 141 } 142 143 self:UIResizable(ui, 500, 423, self.col_bg, mode == nil and true or false) 144 self.ui = ui 145 self.mode = mode 146 self.instruction = instruction 147 self:setSize(500, 423) 148 self:addColourPanel(0, 0, self.width, self.height, self.col_bg.red, self.col_bg.green, self.col_bg.blue) 149 150 self.modal_class = mode == "menu" and "main menu" or "dir browser" 151 self.resizable = false 152 self.exit_button = self:addBevelPanel(260, 400, 100, 18, self.col_bg) 153 154 if mode ~= nil then 155 self.font = TheApp.gfx:loadFont("QData", "Font01V") 156 self:setDefaultPosition(0.5, 0.25) 157 self.on_top = true 158 self.esc_closes = true 159 self.exit_button:setLabel(_S.install.cancel, self.font):makeButton(0, 0, 100, 18, nil, self.close) 160 else 161 self.font = ui.app.gfx:loadBuiltinFont() 162 self:setDefaultPosition(0.05, 0.5) 163 self:addKeyHandler("global_cancel", self.exit) 164 self:addKeyHandler("global_cancel_alt", self.exit) 165 self.exit_button:setLabel(_S.install.exit, self.font):makeButton(0, 0, 100, 18, nil, self.exit) 166 end 167 168 -- Create the root item (or items, on Windows), and set it as the 169 -- first_visible_node. 170 local root 171 local roots = lfsext.volumes() 172 if #roots > 1 then 173 for k, v in pairs(roots) do 174 roots[k] = _G[treenode_class](v) 175 end 176 root = DummyRootNode(roots) 177 else 178 root = _G[treenode_class](roots[1]) 179 end 180 181 local select_function = function(node) 182 if node.is_valid_directory then 183 callback(node.path) 184 self:close() 185 end 186 end 187 188 local control = TreeControl(root, 5, 55, 490, 340, self.col_bg, self.col_scrollbar) 189 :setSelectCallback(select_function) 190 191 local ok_function = function() 192 if control.selected_node then 193 select_function(control.selected_node) 194 end 195 end 196 self.ok_button = self:addBevelPanel(130, 400, 100, 18, self.col_bg) 197 :setLabel(_S.install.ok, self.font):makeButton(0, 0, 100, 18, nil, ok_function) 198 199 self:addWindow(control) 200end 201 202function UIDirectoryBrowser:exit() 203 self.ui.app:exit() 204end 205 206function UIDirectoryBrowser:close() 207 UIResizable.close(self) 208 if self.mode == "menu" then 209 self.ui:addWindow(UIFolder(self.ui, "menu")) 210 end 211end 212 213function UIDirectoryBrowser:draw(canvas, x, y) 214 UIResizable.draw(self, canvas, x, y) 215 x, y = self.x + x, self.y + y 216 if not self.mode then 217 self.font:drawWrapped(canvas, _S.install.title, x + 5, y + 5, self.width - 10, "center") 218 self.font:drawWrapped(canvas, self.instruction, x + 5, y + 15, self.width - 10) 219 else 220 self.font:drawWrapped(canvas, self.instruction, x + 5, y + 15, self.width - 10) 221 end 222end 223