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