Browse code

modified ui with tab removed, and disk partitioning

Change-Id: I4b459f3bc2c81fa6b48cbf110f61be0667086144
Reviewed-on: http://photon-jenkins.eng.vmware.com:8082/1625
Tested-by: gerrit-photon <photon-checkins@vmware.com>
Reviewed-by: suezzelur <anishs@vmware.com>

yangyao2 authored on 2016/11/03 14:36:58
Showing 25 changed files
1 1
old mode 100644
2 2
new mode 100755
3 3
old mode 100644
4 4
new mode 100755
5 5
old mode 100644
6 6
new mode 100755
... ...
@@ -20,7 +20,7 @@ class ConfirmWindow(Window):
20 20
                         ('No',  self.exit_function, False)
21 21
                     ]
22 22
         self.menu = Menu(menu_starty,  maxx, items, can_navigate_outside = False, horizontal=True)
23
-        super(ConfirmWindow, self).__init__(height, width, maxy, maxx, 'Confirm', False, self.menu)
23
+        super(ConfirmWindow, self).__init__(height, width, maxy, maxx, 'Confirm', False, self.menu, items=[])
24 24
         self.addstr(0,0, message)
25 25
 
26 26
     def exit_function(self, yes):
27 27
old mode 100644
28 28
new mode 100755
... ...
@@ -19,6 +19,11 @@ class Device(object):
19 19
         return Device.wrap_devices_from_list(devices_list)
20 20
 
21 21
     @staticmethod
22
+    def refresh_devices_bytes():
23
+        devices_list = subprocess.check_output(['lsblk', '-S', '--bytes', '-I', '8', '-n', '--output', 'NAME,SIZE,MODEL'], stderr=open(os.devnull, 'w'))
24
+        return Device.wrap_devices_from_list(devices_list)
25
+
26
+    @staticmethod
22 27
     def wrap_devices_from_list(list):
23 28
         devices = []
24 29
         deviceslines = list.splitlines()
25 30
old mode 100644
26 31
new mode 100755
27 32
old mode 100644
28 33
new mode 100755
... ...
@@ -64,7 +64,7 @@ class Installer(object):
64 64
             self.progress_width = self.width - self.progress_padding
65 65
             self.starty = (self.maxy - self.height) / 2
66 66
             self.startx = (self.maxx - self.width) / 2
67
-            self.window = Window(self.height, self.width, self.maxy, self.maxx, 'Installing Photon', False)
67
+            self.window = Window(self.height, self.width, self.maxy, self.maxx, 'Installing Photon', False, items =[])
68 68
             self.progress_bar = ProgressBar(self.starty + 3, self.startx + self.progress_padding / 2, self.progress_width)
69 69
 
70 70
         signal.signal(signal.SIGINT, self.exit_gracefully)
... ...
@@ -231,6 +231,7 @@ class Installer(object):
231 231
             fsck = 2
232 232
 
233 233
             if 'mountpoint' in partition and partition['mountpoint'] == '/':
234
+                options = options + ',barrier,noatime,noacl,data=ordered'
234 235
                 fsck = 1
235 236
             
236 237
             if partition['filesystem'] == 'swap':
237 238
old mode 100644
238 239
new mode 100755
... ...
@@ -21,6 +21,7 @@ import random
21 21
 import urllib
22 22
 import urllib2
23 23
 import modules.commons
24
+from partitionISO import PartitionISO
24 25
 from diskpartitioner import DiskPartitioner
25 26
 from packageselector import PackageSelector
26 27
 from custompackageselector import CustomPackageSelector
... ...
@@ -227,10 +228,12 @@ class IsoInstaller(object):
227 227
             install_config = {'iso_system': False}
228 228
             license_agreement = License(self.maxy, self.maxx)
229 229
             select_disk = SelectDisk(self.maxy, self.maxx, install_config)
230
+            select_partition = PartitionISO(self.maxy, self.maxx, install_config)
230 231
             package_selector = PackageSelector(self.maxy, self.maxx, install_config, options_file)
231 232
 
232 233
             self.alpha_chars = range(65, 91)
233 234
             self.alpha_chars.extend(range(97,123))
235
+            partition_accepted_chars = list(range(48, 58))
234 236
             hostname_accepted_chars = list(self.alpha_chars)
235 237
             # Adding the numeric chars
236 238
             hostname_accepted_chars.extend(range(48, 58))
... ...
@@ -246,7 +249,8 @@ class IsoInstaller(object):
246 246
                     self.validate_hostname, # validation function of the input
247 247
                     None, # post processing of the input field
248 248
                     'Choose the hostname for your system', 'Hostname:', 2, install_config,
249
-                    random_hostname)
249
+                    random_hostname,
250
+                    True)
250 251
             root_password_reader = WindowStringReader(
251 252
                     self.maxy, self.maxx, 10, 70, 
252 253
                     'password', 
... ...
@@ -290,6 +294,8 @@ class IsoInstaller(object):
290 290
             items = items + [
291 291
                     (license_agreement.display, False),
292 292
                     (select_disk.display, True),
293
+                    (select_partition.display, False),
294
+                    (select_disk.guided_partitions, False),
293 295
                     (package_selector.display, True),
294 296
                     (hostname_reader.get_user_string, True),
295 297
                     (root_password_reader.get_user_string, True),
296 298
old mode 100644
297 299
new mode 100755
298 300
old mode 100644
299 301
new mode 100755
... ...
@@ -22,7 +22,7 @@ class License(object):
22 22
         self.text_height = self.win_height - 6
23 23
         self.text_width = self.win_width - 6
24 24
 
25
-        self.window = Window(self.win_height, self.win_width, self.maxy, self.maxx, 'Welcome to the Photon installer', False)
25
+        self.window = Window(self.win_height, self.win_width, self.maxy, self.maxx, 'Welcome to the Photon installer', False, items=[])
26 26
 
27 27
     def display(self, params):
28 28
         accept_decline_items =  [
29 29
old mode 100644
30 30
new mode 100755
... ...
@@ -10,7 +10,7 @@ from action import Action
10 10
 from sets import Set
11 11
 
12 12
 class Menu(Action):
13
-    def __init__(self, starty, maxx, items, height = 0, selector_menu = False, can_navigate_outside = True, horizontal = False, default_selected = 0):
13
+    def __init__(self, starty, maxx, items, height = 0, selector_menu = False, can_navigate_outside = True, horizontal = False, default_selected = 0, save_sel = False, tab_enable = True):
14 14
         self.can_navigate_outside = can_navigate_outside
15 15
         self.horizontal = horizontal
16 16
         self.horizontal_padding = 10
... ...
@@ -20,6 +20,8 @@ class Menu(Action):
20 20
         self.items_strings = []
21 21
         self.width = self.lengthen_items()
22 22
         self.num_items = len(self.items)
23
+        self.save_sel = save_sel
24
+        self.tab_enable=tab_enable
23 25
         if height == 0 or height > self.num_items:
24 26
             self.height = self.num_items
25 27
         else:
... ...
@@ -61,6 +63,9 @@ class Menu(Action):
61 61
         self.panel.hide()
62 62
         curses.panel.update_panels()
63 63
 
64
+    def can_save_sel(self, can_save_sel):
65
+        self.save_sel = can_save_sel
66
+
64 67
     def lengthen_items(self):
65 68
         width = 0
66 69
         for item in self.items:
... ...
@@ -179,13 +184,31 @@ class Menu(Action):
179 179
                 else:
180 180
                     self.selected_items.add(self.position)
181 181
             elif key in [ord('\t')] and self.can_navigate_outside:
182
+                if not self.tab_enable:
183
+                    continue
182 184
                 self.refresh(False)
183
-                return ActionResult(False, None)
185
+                if self.save_sel:
186
+                    return ActionResult(False, {'diskIndex': self.position})
187
+                else:
188
+                    return ActionResult(False, None)
184 189
             
185 190
             elif key == curses.KEY_UP or key == curses.KEY_LEFT:
191
+                if not self.tab_enable and key==curses.KEY_LEFT:
192
+                    if self.save_sel:
193
+                        return ActionResult(False, {'diskIndex': self.position, 'direction':-1})
194
+                    elif self.selector_menu:
195
+                        result = self.items[self.position][1](self.selected_items)
196
+                    else:
197
+                        result = self.items[self.position][1](self.items[self.position][2])
198
+                    return ActionResult(False, {'direction': -1})
186 199
                 self.navigate(-1)
187 200
 
188 201
             elif key == curses.KEY_DOWN or key == curses.KEY_RIGHT:
202
+                if not self.tab_enable and key==curses.KEY_RIGHT:
203
+                    if self.save_sel:
204
+                        return ActionResult(False, {'diskIndex': self.position, 'direction':1})
205
+                    else:
206
+                        return ActionResult(False, {'direction': 1})
189 207
                 self.navigate(1)
190 208
 
191 209
             elif key == curses.KEY_NPAGE:
192 210
old mode 100644
193 211
new mode 100755
194 212
old mode 100644
195 213
new mode 100755
... ...
@@ -155,7 +155,7 @@ class OstreeInstaller(Installer):
155 155
 
156 156
 
157 157
         deployment_fstab = os.path.join(deployment, "etc/fstab")
158
-        self.run("echo \"/dev/sda3    /        ext4   defaults   1 1  \" >> {} ".format(deployment_fstab), "Adding / mount point in fstab")
158
+        self.run("echo \"/dev/sda3    /        ext4   defaults,barrier,noatime,noacl,data=ordered 1 1  \" >> {} ".format(deployment_fstab), "Adding / mount point in fstab")
159 159
         self.run("echo \"/dev/sda2    /boot    ext4   defaults   1 2  \" >> {} ".format(deployment_fstab), "Adding /boot mount point in fstab")
160 160
         self.run("mount --bind {} {}".format(deployment, self.photon_root))
161 161
         self.progress_bar.update_loading_message("Starting post install modules")
162 162
old mode 100644
163 163
new mode 100755
164 164
old mode 100644
165 165
new mode 100755
166 166
old mode 100644
167 167
new mode 100755
168 168
old mode 100644
169 169
new mode 100755
... ...
@@ -28,7 +28,7 @@ class PackageSelector(object):
28 28
 
29 29
         self.load_package_list(options_file)
30 30
 
31
-        self.window = Window(self.win_height, self.win_width, self.maxy, self.maxx, 'Select Installation', True, self.package_menu)
31
+        self.window = Window(self.win_height, self.win_width, self.maxy, self.maxx, 'Select Installation', True, self.package_menu, can_go_next=True, position=1)
32 32
 
33 33
     @staticmethod
34 34
     def get_packages_to_install(options, config_type, output_data_path):
... ...
@@ -74,7 +74,7 @@ class PackageSelector(object):
74 74
                 visible_options_cnt = visible_options_cnt + 1
75 75
 
76 76
 
77
-        self.package_menu = Menu(self.menu_starty,  self.maxx, self.package_menu_items, default_selected = default_selected)
77
+        self.package_menu = Menu(self.menu_starty,  self.maxx, self.package_menu_items, default_selected = default_selected, tab_enable=False)
78 78
 
79 79
     def exit_function(self,  selected_item_params):
80 80
         self.install_config['type'] = selected_item_params[0];
81 81
new file mode 100755
... ...
@@ -0,0 +1,196 @@
0
+from window import Window 
1
+from windowstringreader import WindowStringReader
2
+from textpane import TextPane
3
+from readmultext import ReadMulText
4
+from confirmwindow import ConfirmWindow
5
+from actionresult import ActionResult
6
+from device import Device
7
+
8
+class PartitionISO(object):
9
+    def __init__(self, maxy, maxx, install_config):
10
+        self.maxx = maxx
11
+        self.maxy = maxy
12
+        self.win_width = maxx - 4
13
+        self.win_height = maxy - 4
14
+        self.install_config = install_config
15
+        self.path_checker = []
16
+
17
+        self.win_starty = (self.maxy - self.win_height) / 2
18
+        self.win_startx = (self.maxx - self.win_width) / 2
19
+
20
+        self.text_starty = self.win_starty + 4
21
+        self.text_height = self.win_height - 6
22
+        self.text_width = self.win_width - 6
23
+        self.install_config['partitionsnumber'] = 0
24
+        self.devices = Device.refresh_devices_bytes()
25
+        self.has_slash = False
26
+        self.has_remain = False
27
+        self.has_empty = False
28
+
29
+        self.disk_size = []
30
+        for index, device in enumerate(self.devices):
31
+            self.disk_size.append((device.path, int(device.size) / 1048576))     
32
+
33
+        self.window = Window(self.win_height, self.win_width, self.maxy, self.maxx, 'Welcome to the Photon installer', False, items=[], can_go_next=False)
34
+        Device.refresh_devices()
35
+
36
+    def display(self, params):
37
+        if 'skipPrevs' in self.install_config and self.install_config['skipPrevs'] == True:
38
+            self.delete()
39
+            return ActionResult(False, {'goBack':True})
40
+        if 'autopartition' in self.install_config and self.install_config['autopartition'] == True:
41
+            return ActionResult(True, None)
42
+        if 'delete_partition' in self.install_config and self.install_config['delete_partition'] == True:
43
+            self.delete()
44
+            self.install_config['delete_partition']=False
45
+
46
+        self.device_index = self.install_config['diskindex']
47
+
48
+        self.disk_buttom_items = []
49
+        self.disk_buttom_items.append(('<Next>', self.next))
50
+        self.disk_buttom_items.append(('<Create New>', self.create_function))
51
+        self.disk_buttom_items.append(('<Delete All>', self.delete_function))
52
+        self.disk_buttom_items.append(('<Go Back>', self.go_back))
53
+
54
+        self.text_items = []
55
+        self.text_items.append(('Disk', 20))
56
+        self.text_items.append(('Size', 5))
57
+        self.text_items.append(('Type', 5))
58
+        self.text_items.append(('Mountpoint', 20))
59
+        self.table_space = 5
60
+
61
+        title = 'Current partitions:\n'
62
+        self.window.addstr(0, (self.win_width - len(title)) / 2, title)
63
+
64
+        info = "Unpartitioned space: "+str(self.disk_size[self.device_index][1])+ " MB, Total size: "+ str(int(self.devices[self.device_index].size)/ 1048576)+" MB"
65
+
66
+        self.text_pane = TextPane(self.text_starty, self.maxx, self.text_width, 
67
+            "EULA.txt", self.text_height, self.disk_buttom_items, 
68
+            partition = True, popupWindow = True, install_config = self.install_config, 
69
+            text_items = self.text_items, table_space=self.table_space, default_start=1, info=info, size_left=str(self.disk_size[self.device_index][1]))
70
+
71
+        self.window.set_action_panel(self.text_pane)
72
+
73
+        return self.window.do_action()
74
+
75
+    def validate_partition(self, pstr):
76
+        if not pstr:
77
+            return ActionResult(False, None)
78
+        sizedata = pstr[0]
79
+        mtdata = pstr[2]
80
+        typedata = pstr[1]
81
+        devicedata = self.devices[self.device_index].path
82
+
83
+        #no empty fields unless swap
84
+        if typedata == 'swap' and (len(mtdata)!=0 or len(typedata) == 0 or len(devicedata) == 0):
85
+            return False, "invalid swap data "
86
+
87
+        if typedata != 'swap' and (len(sizedata) == 0 or len(mtdata) == 0 or len(typedata) == 0 or len(devicedata) == 0):
88
+            if not self.has_empty and len(mtdata) != 0 and len(typedata) != 0 and len(devicedata) != 0:
89
+                self.has_empty = True
90
+            else:
91
+                return False, "Input cannot be empty"
92
+
93
+        if typedata !='swap' and typedata!='ext3' and typedata!='ext4':
94
+            return False, "Invalid type"
95
+
96
+        if len(mtdata)!=0 and mtdata[0] !='/':
97
+            return False, "Invalid path"
98
+
99
+        if mtdata in self.path_checker:
100
+            return False, "Path already existed"
101
+        #validate disk: must be one of the existing disks
102
+        i = self.device_index
103
+
104
+        #valid size: must not exceed memory limit
105
+        curr_size = self.disk_size[i][1]
106
+        if len(sizedata)!=0:
107
+            try:
108
+                int(sizedata)
109
+            except ValueError:
110
+                return False, "invalid device size"
111
+
112
+            if int(curr_size) - int(sizedata) < 0:
113
+                return False, "invalid device size"
114
+            #if valid, update the size and return true
115
+            new_size = (self.disk_size[i][0], int(curr_size)- int(sizedata))
116
+            self.disk_size[i] =new_size
117
+
118
+        if mtdata=="/":
119
+            self.has_slash=True
120
+
121
+        self.path_checker.append(mtdata)
122
+        return True, None
123
+
124
+    def create_function(self):
125
+        self.window.hide_window()
126
+
127
+        self.install_config['partition_disk'] = self.devices[self.device_index].path
128
+        self.partition_items = []
129
+        self.partition_items.append(('Size in MB: '+str(self.disk_size[self.device_index][1])+' available'))
130
+        self.partition_items.append(('Type: (ext3, ext4, swap)'))
131
+        self.partition_items.append(('Mountpoint:'))
132
+        self.create_window = ReadMulText(
133
+                self.maxy, self.maxx, 0,
134
+                self.install_config,
135
+                str(self.install_config['partitionsnumber']) + 'partition_info',
136
+                self.partition_items,
137
+                None,
138
+                None,
139
+                None,
140
+                self.validate_partition,   #validation function of the input
141
+                None,
142
+                True,
143
+                )
144
+        result = self.create_window.do_action()
145
+        if result.success:
146
+            self.install_config['partitionsnumber'] = self.install_config['partitionsnumber'] + 1
147
+
148
+        #parse the input in install config
149
+        return self.display(False)
150
+
151
+    def delete_function(self):
152
+        self.delete()
153
+        return self.display(False)
154
+
155
+    def go_back(self):
156
+        self.delete()
157
+        self.window.hide_window()
158
+        self.text_pane.hide()
159
+        return ActionResult(False, {'goBack':True})
160
+
161
+    def next(self):
162
+        if self.install_config['partitionsnumber'] == 0:
163
+            window_height=9
164
+            window_width=40
165
+            window_starty=(self.maxy-window_height)/2+5
166
+            confirm_window=ConfirmWindow(window_height,window_width,self.maxy, self.maxx, window_starty, 'Partition information cannot be empty', info=True)
167
+            confirm_window.do_action()
168
+            return self.display(False)
169
+        #must have /
170
+        if not self.has_slash:
171
+            window_height=9
172
+            window_width=40
173
+            window_starty=(self.maxy-window_height)/2+5
174
+            confirm_window=ConfirmWindow(window_height,window_width,self.maxy, self.maxx, window_starty, 'Missing /', info=True)
175
+            confirm_window.do_action()
176
+            return self.display(False)
177
+
178
+        self.window.hide_window()
179
+        self.text_pane.hide()
180
+        return ActionResult(True, {'goNext':True})
181
+
182
+    def delete(self):
183
+        for i in range(int(self.install_config['partitionsnumber'])):
184
+            self.install_config[str(i)+'partition_info'+str(0)] = ''
185
+            self.install_config[str(i)+'partition_info'+str(1)] = ''
186
+            self.install_config[str(i)+'partition_info'+str(2)] = ''
187
+            self.install_config[str(i)+'partition_info'+str(3)] = ''
188
+        del self.disk_size[:]
189
+        for index, device in enumerate(self.devices):
190
+            self.disk_size.append((device.path, int(device.size) / 1048576))
191
+        del self.path_checker[:]
192
+        self.has_slash = False
193
+        self.has_remain = False
194
+        self.has_empty = False
195
+        self.install_config['partitionsnumber'] = 0
0 196
old mode 100644
1 197
new mode 100755
... ...
@@ -10,7 +10,7 @@ import math
10 10
 from curses import panel
11 11
 
12 12
 class ProgressBar(object):
13
-    def __init__(self, starty, startx, width):
13
+    def __init__(self, starty, startx, width, new_win=False):
14 14
         self.timer = None
15 15
         self.loadding_timer = None
16 16
         self.timer_lock = threading.Lock()
... ...
@@ -26,6 +26,20 @@ class ProgressBar(object):
26 26
         self.window.bkgd(' ', curses.color_pair(2)) #defaultbackground color
27 27
         self.progress = 0
28 28
 
29
+        self.new_win = new_win
30
+        self.x=startx
31
+        self.y=starty
32
+
33
+        if new_win:
34
+            self.contentwin = curses.newwin(7, width+2)
35
+            self.contentwin.bkgd(' ', curses.color_pair(2)) #Default Window color
36
+            self.contentwin.erase()
37
+            self.contentwin.box()
38
+            self.contentpanel = curses.panel.new_panel(self.contentwin)
39
+            self.contentpanel.move(starty-1, startx-1)
40
+            self.contentpanel.hide()
41
+
42
+
29 43
         self.panel = panel.new_panel(self.window)
30 44
         self.panel.move(starty, startx)
31 45
         self.panel.hide()
... ...
@@ -93,6 +107,11 @@ class ProgressBar(object):
93 93
         self.render_progress()
94 94
 
95 95
     def show(self):
96
+        if self.new_win:
97
+            self.contentpanel.top()
98
+            self.contentpanel.move(self.y-1, self.x-1)
99
+            self.contentpanel.show()
100
+
96 101
         self.refresh()
97 102
         self.panel.top()
98 103
         self.panel.show()
... ...
@@ -135,6 +154,8 @@ class ProgressBar(object):
135 135
                 self.loadding_timer.cancel()
136 136
                 self.loadding_timer = None
137 137
 
138
+        if self.new_win:
139
+            self.contentpanel.hide()
138 140
         self.panel.hide()
139 141
         panel.update_panels()
140 142
 
141 143
new file mode 100755
... ...
@@ -0,0 +1,211 @@
0
+#! /usr/bin/python2
1
+#
2
+#    Copyright (C) 2015 vmware inc.
3
+#
4
+#    Author: Yang Yao <yaoyang@vmware.com>
5
+
6
+import curses
7
+import sys
8
+from actionresult import ActionResult
9
+from action import Action
10
+from window import Window
11
+from confirmwindow import ConfirmWindow
12
+
13
+class ReadMulText(Action):
14
+    def __init__(self, maxy, maxx, y, install_config, field, display_string, confirmation_error_msg, 
15
+        echo_char, accepted_chars, validation_fn, conversion_fn, can_cancel, default_string = None):
16
+        self.maxy = maxy
17
+        self.maxx = maxx
18
+        self.y = y
19
+        self.install_config = install_config
20
+        self.field = field
21
+        self.horizontal_padding = 10
22
+        self.confirmation_error_msg = confirmation_error_msg
23
+        self.echo_char = echo_char
24
+        self.accepted_chars = accepted_chars
25
+        self.validation_fn = validation_fn
26
+        self.conversion_fn = conversion_fn
27
+        self.default_string = default_string
28
+        self.display_string = display_string
29
+        self.textwin_width = maxx - self.horizontal_padding -2
30
+        self.textwin_width = self.textwin_width*2/3
31
+        self.visible_text_width = self.textwin_width - 1
32
+        self.position = 0
33
+        self.height = len(self.display_string) * 4 + 2
34
+        self.menu_pos = 0
35
+
36
+        self.textwin = curses.newwin(self.height, self.textwin_width + 2)#self.textwin_width)
37
+        self.textwin.bkgd(' ', curses.color_pair(2))
38
+        self.textwin.keypad(1)
39
+
40
+        self.shadowwin = curses.newwin(self.height, self.textwin_width + 2)
41
+        self.shadowwin.bkgd(' ', curses.color_pair(0)) #Default shadow color
42
+
43
+        self.panel = curses.panel.new_panel(self.textwin)
44
+        self.panel.move((maxy-self.height)/2, (maxx - self.textwin_width) / 2 -1)
45
+        self.panel.hide()
46
+        self.shadowpanel = curses.panel.new_panel(self.shadowwin)
47
+        self.shadowpanel.move((maxy-self.height)/2+1, (maxx - self.textwin_width) / 2)
48
+        self.shadowpanel.hide()
49
+        curses.panel.update_panels()
50
+
51
+        self.init_text()
52
+        self.textwin.box()
53
+        self.maxlength = 255
54
+
55
+        #initialize the accepted characters
56
+        if accepted_chars:
57
+            self.accepted_chars = accepted_chars
58
+        else:
59
+            self.accepted_chars = range(32, 127)
60
+
61
+    def hide(self):
62
+        return
63
+    
64
+    def init_text(self):
65
+        self.shadowpanel.show()
66
+        curses.panel.update_panels()
67
+
68
+        self.x = 0;
69
+        #initialize the ----
70
+        dashes = '_' * self.textwin_width 
71
+        cury = self.y+1
72
+        self.str = []
73
+
74
+        for string in self.display_string:
75
+            self.textwin.addstr(cury, 1, string)
76
+            self.textwin.addstr(cury+1, 1, dashes)
77
+            cury = cury + 4
78
+            self.str.append('')
79
+
80
+        #remove the error messages
81
+        spaces = ' ' * self.textwin_width
82
+        #self.textwin.addstr(self.y + 2, 0, spaces)
83
+        self.update_menu()
84
+
85
+    def do_action(self):
86
+        self.init_text()
87
+        curses.curs_set(1)
88
+
89
+        if self.default_string != None:
90
+            self.textwin.addstr(self.y, 0, self.default_string)
91
+            self.str = self.default_string
92
+
93
+        while True:
94
+            if len(self.str[self.position]) > self.visible_text_width:
95
+                curs_loc = self.visible_text_width + 1
96
+            else:
97
+                curs_loc = len(self.str[self.position]) +1
98
+            ch = self.textwin.getch(self.y+2+self.position*4, curs_loc)
99
+
100
+            update_text = False
101
+            if ch in [curses.KEY_ENTER, ord('\n')]:
102
+                if self.menu_pos==1:
103
+                    curses.curs_set(0)
104
+                    self.shadowpanel.hide()
105
+                    return ActionResult(False, None)
106
+                if self.confirmation_error_msg:
107
+                    if self.str != self.install_config[self.field]:
108
+                        curses.curs_set(0)
109
+                        conf_message_height = 8
110
+                        conf_message_width = 48
111
+                        conf_message_button_y = (self.maxy - conf_message_height) / 2 + 5
112
+                        confrim_window = ConfirmWindow(conf_message_height, conf_message_width, self.maxy, self.maxx, conf_message_button_y, self.confirmation_error_msg, True)
113
+                        confrim_window.do_action()
114
+                        return ActionResult(False, {'goBack': True})
115
+                    self.set_field()
116
+                else:
117
+                    if not self.validate_input():
118
+                        continue
119
+                    self.set_field()
120
+                curses.curs_set(0)
121
+                self.shadowpanel.hide()
122
+                return ActionResult(True, None)
123
+            elif ch == curses.KEY_UP:
124
+                self.refresh(-1)
125
+
126
+            elif ch == curses.KEY_DOWN:
127
+                self.refresh(1)
128
+
129
+            elif ch in [ord('\t')]:
130
+                self.refresh(1, reset=True)
131
+
132
+            elif ch ==curses.KEY_LEFT:
133
+                self.menu_refresh(1)
134
+
135
+            elif ch == curses.KEY_RIGHT:
136
+                self.menu_refresh(-1)
137
+
138
+            elif ch == curses.KEY_BACKSPACE:
139
+                # Handle the backspace case
140
+                self.str[self.position] = self.str[self.position][:len(self.str[self.position]) - 1]
141
+                update_text = True
142
+
143
+            elif len(self.str[self.position]) < self.maxlength and ch in self.accepted_chars:
144
+                self.str[self.position] += chr(ch)
145
+                update_text = True
146
+
147
+            if update_text:
148
+                self.update_text()
149
+
150
+    def menu_refresh(self, n):
151
+        self.menu_pos+=n
152
+        if self.menu_pos<0:
153
+            self.menu_pos=0
154
+        elif self.menu_pos>=1:
155
+            self.menu_pos=1
156
+        self.update_menu()
157
+
158
+    def update_menu(self):
159
+        if self.menu_pos==1:
160
+            self.textwin.addstr(self.height-2, 5, '<Cancel>', curses.color_pair(3))
161
+        else:
162
+            self.textwin.addstr(self.height-2, 5, '<Cancel>')
163
+        if self.menu_pos==0:
164
+            self.textwin.addstr(self.height-2, self.textwin_width-len('<OK>')-5, '<OK>',curses.color_pair(3))
165
+        else:
166
+            self.textwin.addstr(self.height-2, self.textwin_width-len('<OK>')-5, '<OK>')
167
+
168
+
169
+
170
+    def update_text(self):
171
+        if len(self.str[self.position]) > self.visible_text_width:
172
+            text = self.str[self.position][-self.visible_text_width:]
173
+        else:
174
+            text = self.str[self.position]
175
+        if self.echo_char:
176
+            text = self.echo_char * len(text)
177
+
178
+        text = text + '_' * (self.visible_text_width - len(self.str[self.position]))
179
+        self.textwin.addstr(self.y+2+self.position*4, 1, text)
180
+
181
+    def refresh(self, n, reset=False):
182
+        self.position += n 
183
+        if self.position < 0:
184
+            self.position = 0
185
+        elif self.position >= len(self.display_string):
186
+            if reset:
187
+                self.position=0
188
+            else:
189
+                self.position = len(self.display_string)-1
190
+
191
+    def set_field(self):
192
+        i = 0
193
+        for string in self.display_string:
194
+            if self.conversion_fn:
195
+                self.install_config[self.field+str(i)] = self.conversion_fn(self.str[i])
196
+            else:
197
+                self.install_config[self.field+str(i)] = self.str[i]
198
+            i = i + 1
199
+
200
+    def validate_input(self):
201
+        if self.validation_fn:
202
+            success, err = self.validation_fn(self.str)
203
+            if not success:
204
+                spaces = ' ' * (int(self.textwin_width) - len(self.display_string[0]))
205
+                self.textwin.addstr(self.y + 1, len(self.display_string[0]), spaces)
206
+                self.textwin.addstr(self.y + 1, len(self.display_string[0]), err, curses.color_pair(4))
207
+            return success
208
+        else:
209
+            return True
210
+
0 211
old mode 100644
1 212
new mode 100755
... ...
@@ -11,7 +11,7 @@ from action import Action
11 11
 from confirmwindow import ConfirmWindow
12 12
 
13 13
 class ReadText(Action):
14
-    def __init__(self, maxy, maxx, textwin, y, install_config, field, confirmation_error_msg, echo_char, accepted_chars, validation_fn, conversion_fn, default_string = None):
14
+    def __init__(self, maxy, maxx, textwin, y, install_config, field, confirmation_error_msg, echo_char, accepted_chars, validation_fn, conversion_fn, default_string = None, tab_enabled=True):
15 15
         self.textwin = textwin
16 16
         self.maxy = maxy
17 17
         self.maxx = maxx
... ...
@@ -26,10 +26,14 @@ class ReadText(Action):
26 26
         self.default_string = default_string
27 27
         self.textwin_width = self.textwin.getmaxyx()[1] - 1
28 28
         self.visible_text_width = self.textwin_width - 1
29
+        self.tab_enabled=tab_enabled
29 30
 
30 31
         self.init_text()
31 32
         self.maxlength = 255
32 33
 
34
+        if not tab_enabled:
35
+            self.textwin.keypad(1)
36
+
33 37
         #initialize the accepted characters
34 38
         if accepted_chars:
35 39
             self.accepted_chars = accepted_chars
... ...
@@ -50,13 +54,23 @@ class ReadText(Action):
50 50
         spaces = ' ' * self.textwin_width
51 51
         self.textwin.addstr(self.y + 2, 0, spaces)
52 52
 
53
-    def do_action(self):
54
-        self.init_text()
55
-        curses.curs_set(1)
56
-
57
-        if self.default_string != None:
58
-            self.textwin.addstr(self.y, 0, self.default_string)
59
-            self.str = self.default_string
53
+    def do_action(self, returned=False, go_back=False):
54
+        if returned:
55
+            if len(self.str) > self.visible_text_width:
56
+                text = self.str[-self.visible_text_width:]
57
+            else:
58
+                text = self.str
59
+            if self.echo_char:
60
+                text = self.echo_char * len(text)
61
+            # Add the dashes
62
+            text = text + '_' * (self.visible_text_width - len(self.str))
63
+            self.textwin.addstr(self.y, 0, text)
64
+        if not returned:
65
+            curses.curs_set(1)
66
+            self.init_text()
67
+            if self.default_string != None:
68
+                self.textwin.addstr(self.y, 0, self.default_string)
69
+                self.str = self.default_string
60 70
 
61 71
         while True:
62 72
             if len(self.str) > self.visible_text_width:
... ...
@@ -67,9 +81,11 @@ class ReadText(Action):
67 67
 
68 68
             update_text = False
69 69
             if ch in [curses.KEY_ENTER, ord('\n')]:
70
+                curses.curs_set(0)
71
+                if go_back:
72
+                    return ActionResult(False, {'goBack': True})
70 73
                 if self.confirmation_error_msg:
71 74
                     if self.str != self.install_config[self.field]:
72
-                        curses.curs_set(0)
73 75
                         conf_message_height = 8
74 76
                         conf_message_width = 48
75 77
                         conf_message_button_y = (self.maxy - conf_message_height) / 2 + 5
... ...
@@ -83,10 +99,14 @@ class ReadText(Action):
83 83
                     self.set_field()
84 84
                 curses.curs_set(0)
85 85
                 return ActionResult(True, None)
86
+            elif ch ==curses.KEY_LEFT and not self.tab_enabled:
87
+                return ActionResult(False, {'direction': -1})
88
+            elif ch ==curses.KEY_RIGHT and not self.tab_enabled:
89
+                return ActionResult(False, {'direction': 1})
86 90
             elif ch in [ord('\t')]:
87 91
                 curses.curs_set(0)
88 92
                 return ActionResult(False, None)
89
-            elif ch == 127:
93
+            elif ch == curses.KEY_BACKSPACE: #originally ==127
90 94
                 # Handle the backspace case
91 95
                 self.str = self.str[:len(self.str) - 1]
92 96
 
93 97
old mode 100644
94 98
new mode 100755
... ...
@@ -29,30 +29,61 @@ class SelectDisk(object):
29 29
         self.menu_height = 5
30 30
         self.progress_padding = 5
31 31
         self.progress_width = self.win_width - self.progress_padding
32
-        self.progress_bar = ProgressBar(self.win_starty + 6, self.win_startx + self.progress_padding / 2, self.progress_width)
32
+        self.progress_bar = ProgressBar(self.win_starty + 6, self.win_startx + self.progress_padding / 2, self.progress_width, new_win=True)
33 33
 
34
-        self.window = Window(self.win_height, self.win_width, self.maxy, self.maxx, 'Setup your disk', True)
34
+        self.disk_buttom_items = []
35
+        self.disk_buttom_items.append(('<Custom>', self.custom_function, False))
36
+        self.disk_buttom_items.append(('<Auto>', self.auto_function, False))
37
+
38
+        self.window = Window(self.win_height, self.win_width, self.maxy, self.maxx, 'Select a disk', True, items = self.disk_buttom_items, menu_helper = self.save_index, position = 2, tab_enabled=False)
39
+        self.partition_window = Window(self.win_height, self.win_width, self.maxy, self.maxx, 'Partition', True)
35 40
         self.devices = Device.refresh_devices()
36 41
 
37
-    def guided_partitions(self, device_index):
42
+    def guided_partitions(self, params):
43
+        if not 'diskindex' in self.install_config:
44
+            return ActionResult(False, None);
45
+
46
+        device_index = self.install_config['diskindex']
47
+
38 48
         menu_height = 9
39 49
         menu_width = 40
40 50
         menu_starty = (self.maxy - menu_height) / 2 + 5
51
+        self.install_config['delete_partition'] = True
41 52
         confrim_window = ConfirmWindow(menu_height, menu_width, self.maxy, self.maxx, menu_starty, 'This will erase the disk.\nAre you sure?')
42 53
         confirmed = confrim_window.do_action().result['yes']
43 54
 
44
-        if not confirmed:
45
-            return ActionResult(confirmed, None)
55
+        if confirmed == False:
56
+            self.install_config['skipPrevs'] = True
57
+            return ActionResult(False, {'goBack':True})
46 58
 
59
+        self.install_config['skipPrevs'] = False
47 60
         self.progress_bar.initialize('Partitioning...')
48 61
         self.progress_bar.show()
49 62
         self.progress_bar.show_loading('Partitioning')
50 63
 
51 64
         # Do the partitioning
52
-        self.window.clearerror()
53
-        partitions_data = modules.commons.partition_disk(self.devices[device_index].path, modules.commons.default_partitions)
65
+        if 'partitionsnumber' in self.install_config:
66
+                if (int(self.install_config['partitionsnumber']) == 0):
67
+                    partitions_data = modules.commons.partition_disk(self.devices[device_index].path, modules.commons.default_partitions)
68
+                else:
69
+                    partitions = []
70
+                    for i in range (int (self.install_config['partitionsnumber'])):
71
+                        if len(self.install_config[str(i)+'partition_info'+str(0)])==0:
72
+                            sizedata=0
73
+                        else:
74
+                            sizedata = int(self.install_config[str(i)+'partition_info'+str(0)])
75
+                        mtdata = self.install_config[str(i)+'partition_info'+str(2)]
76
+                        typedata = self.install_config[str(i)+'partition_info'+str(1)]
77
+
78
+                        partitions = partitions + [
79
+                                    {"mountpoint": mtdata, "size": sizedata, "filesystem": typedata},
80
+                                    ]
81
+                    partitions_data = modules.commons.partition_disk(self.devices[device_index].path, partitions)
82
+        else: 
83
+            partitions_data = modules.commons.partition_disk(self.devices[device_index].path, modules.commons.default_partitions)
84
+
54 85
         if partitions_data == None:
55
-            self.window.adderror('Partitioning failed, you may try again')
86
+            self.partition_window.adderror('Partitioning failed, you may try again')
56 87
         else:
57 88
             self.install_config['disk'] = partitions_data
58 89
 
... ...
@@ -60,7 +91,9 @@ class SelectDisk(object):
60 60
         return ActionResult(partitions_data != None, None)
61 61
 
62 62
     def display(self, params):
63
-        self.window.addstr(0, 0, 'First, we will setup your disks.\n\nWe have detected {0} disks, choose disk to be auto-partitioned:'.format(len(self.devices)))
63
+        if 'skipPrevs' in self.install_config:
64
+            self.install_config['skipPrevs'] = False
65
+        self.window.addstr(0, 0, 'Please select a disk and a method how to partition it:\nAuto - single partition for /, no swap partition.\nCustom - for customized partitioning')
64 66
 
65 67
         self.disk_menu_items = []
66 68
 
... ...
@@ -70,15 +103,27 @@ class SelectDisk(object):
70 70
                 self.disk_menu_items.append(
71 71
                         (
72 72
                             '{2} - {1} @ {0}'.format(device.path, device.size, device.model), 
73
-                            self.guided_partitions, 
73
+                            self.save_index, 
74 74
                             index
75 75
                         )
76 76
                     )
77 77
 
78
-        self.disk_menu = Menu(self.menu_starty,  self.maxx, self.disk_menu_items, self.menu_height)
78
+        self.disk_menu = Menu(self.menu_starty,  self.maxx, self.disk_menu_items, self.menu_height, tab_enable=False)
79
+        self.disk_menu.can_save_sel(True)
79 80
 
80 81
         self.window.set_action_panel(self.disk_menu)
81 82
         return self.window.do_action()
82 83
 
84
+    def save_index(self, device_index):
85
+        self.install_config['diskindex'] = device_index
86
+        return ActionResult(True, None)
87
+
88
+    def auto_function(self, params):    #default is no partition
89
+        self.install_config['autopartition'] = True
90
+        self.install_config['partitionsnumber'] = 0
91
+        return ActionResult(True, None)
83 92
 
93
+    def custom_function(self, params):  #custom minimize partition number is 1
94
+        self.install_config['autopartition'] = False
95
+        return ActionResult(True, None)
84 96
 
85 97
old mode 100644
86 98
new mode 100755
... ...
@@ -9,18 +9,29 @@ from actionresult import ActionResult
9 9
 from action import Action
10 10
 
11 11
 class TextPane(Action):
12
-    def __init__(self, starty, maxx, width, text_file_path, height, menu_items):
12
+    def __init__(self, starty, maxx, width, text_file_path, height, menu_items, partition = False, popupWindow = False, install_config = {}, text_items = [], table_space = 0, default_start = 0, info=[], size_left=[]):
13 13
         self.head_position = 0  #This is the start of showing
14
-        self.menu_position = 0
14
+        self.menu_position = default_start
15 15
         self.lines = []
16
-        self.menu_items = menu_items;
16
+        self.menu_items = menu_items
17
+        self.text_items = text_items
18
+        self.table_space = table_space
19
+        self.info=info
20
+        self.size_left=size_left
17 21
 
18 22
         self.width = width
19 23
         
20
-        self.read_file(text_file_path, self.width - 3);
21
-
24
+        if partition == False:
25
+            self.read_file(text_file_path, self.width - 3);
26
+        else:
27
+            self.install_config=install_config
28
+            self.partition();
29
+        
22 30
         self.num_items = len(self.lines)
23
-        self.text_height = height - 2
31
+        if self.info:
32
+            self.text_height = height - 4
33
+        else:
34
+            self.text_height = height - 2
24 35
 
25 36
         # Check if we need to add a scroll bar
26 37
         if self.num_items > self.text_height:
... ...
@@ -29,7 +40,10 @@ class TextPane(Action):
29 29
             self.show_scroll = False
30 30
 
31 31
         # Some calculation to detitmine the size of the scroll filled portion
32
-        self.filled = int(round(self.text_height * self.text_height / float(self.num_items)))
32
+        if (self.num_items == 0):
33
+            self.filled = 0
34
+        else:
35
+            self.filled = int(round(self.text_height * self.text_height / float(self.num_items)))
33 36
         if self.filled == 0:
34 37
             self.filled += 1
35 38
         for i in [1, 2]:
... ...
@@ -38,6 +52,7 @@ class TextPane(Action):
38 38
 
39 39
         self.window = curses.newwin(height, self.width)
40 40
         self.window.bkgd(' ', curses.color_pair(2))
41
+        self.popupWindow = popupWindow
41 42
 
42 43
         self.window.keypad(1)
43 44
         self.panel = curses.panel.new_panel(self.window)
... ...
@@ -46,6 +61,37 @@ class TextPane(Action):
46 46
         self.panel.hide()
47 47
         curses.panel.update_panels()
48 48
 
49
+    def partition(self):
50
+        if self.install_config == {}:
51
+            return
52
+
53
+        tstring = ''
54
+        #calculate the start index for each item and draw the title
55
+        for index, item in enumerate(self.text_items):
56
+            tstring = tstring + item[0] + ' '*(item[1] - len(item[0])) + ' '*self.table_space
57
+
58
+        self.lines.append(tstring)
59
+        #draw the table
60
+        for i in range (int (self.install_config['partitionsnumber'])):
61
+            pdisk = self.install_config['partition_disk']
62
+            if len(pdisk) > self.text_items[0][1]:
63
+                pdisk = pdisk[-self.text_items[0][1]:]
64
+            psize = self.install_config[str(i)+'partition_info'+str(0)]
65
+            if len(psize) > self.text_items[1][1]:
66
+                psize = psize[-self.text_items[1][1]:]
67
+            if len(psize)==0:
68
+                psize='<'+self.size_left+'>'
69
+            ptype = self.install_config[str(i)+'partition_info'+str(1)]
70
+            if len(ptype) > self.text_items[2][1]:
71
+                ptype = ptype[-self.text_items[2][1]:]
72
+            pmountpoint = self.install_config[str(i)+'partition_info'+str(2)]
73
+            if len(pmountpoint) > self.text_items[3][1]:
74
+                pmountpoint = pmountpoint[-self.text_items[3][1]:]
75
+            pstring = pdisk + ' '*(self.text_items[0][1] - len(pdisk)) + ' ' * self.table_space + psize + ' '*(self.text_items[1][1] - len(psize)) + ' ' * self.table_space + ptype + ' '*(self.text_items[2][1] - len(ptype) + self.table_space) + pmountpoint
76
+            self.lines.append(pstring)
77
+
78
+
79
+
49 80
     def read_file(self, text_file_path, line_width):
50 81
         with open(text_file_path, "r") as f:
51 82
             for line in f:
... ...
@@ -73,11 +119,12 @@ class TextPane(Action):
73 73
                 self.lines.append(line + ' ' * (line_width - len(line)))
74 74
 
75 75
     def navigate(self, n):
76
-        self.head_position += n
77
-        if self.head_position < 0:
78
-            self.head_position = 0
79
-        elif self.head_position > (len(self.lines) - self.text_height + 1):
80
-            self.head_position = len(self.lines) - self.text_height + 1
76
+        if self.show_scroll:
77
+            self.head_position += n
78
+            if self.head_position < 0:
79
+                self.head_position = 0
80
+            elif self.head_position > (len(self.lines) - self.text_height + 1):
81
+                self.head_position = len(self.lines) - self.text_height + 1
81 82
 
82 83
     def navigate_menu(self, n):
83 84
         self.menu_position += n
... ...
@@ -134,10 +181,16 @@ class TextPane(Action):
134 134
                 mode = curses.color_pair(3)
135 135
             else:
136 136
                 mode = curses.color_pair(2)
137
-
138
-            self.window.addstr(self.text_height + 1, xpos - len(item[0]) - 4, item[0], mode)
137
+            if self.info:
138
+                self.window.addstr(self.text_height + 3, xpos - len(item[0]) - 4, item[0], mode)
139
+            else:
140
+                self.window.addstr(self.text_height + 1, xpos - len(item[0]) - 4, item[0], mode)
139 141
             xpos = xpos - len(item[0]) - 4
140 142
 
143
+        if self.info:
144
+            self.window.addstr(self.text_height+1, 5, self.info)
145
+
146
+
141 147
         self.render_scroll_bar()
142 148
 
143 149
         self.window.refresh()
... ...
@@ -160,7 +213,6 @@ class TextPane(Action):
160 160
             if key in [curses.KEY_ENTER, ord('\n')]:
161 161
                 self.hide()
162 162
                 return self.menu_items[self.menu_position][1]()
163
-
164 163
             if key == curses.KEY_UP:
165 164
                 self.navigate(-1)
166 165
             elif key == curses.KEY_DOWN:
167 166
old mode 100644
168 167
new mode 100755
... ...
@@ -10,8 +10,9 @@ from action import Action
10 10
 
11 11
 class Window(Action):
12 12
 
13
-    def __init__(self, height, width, maxy, maxx, title, can_go_back, action_panel = None):
13
+    def __init__(self, height, width, maxy, maxx, title, can_go_back, action_panel = None, items = [], menu_helper = None, position = 0, tab_enabled = True, can_go_next = False, read_text=False):
14 14
         self.can_go_back = can_go_back
15
+        self.can_go_next = can_go_next
15 16
         self.height = height
16 17
         self.width = width;
17 18
         self.y = (maxy - height) / 2
... ...
@@ -22,9 +23,39 @@ class Window(Action):
22 22
         self.contentwin.bkgd(' ', curses.color_pair(2)) #Default Window color
23 23
         self.contentwin.erase()
24 24
         self.contentwin.box()
25
+        self.tab_enabled=tab_enabled
26
+        self.read_text=read_text
27
+
28
+        self.position = position
29
+        self.items = items
30
+        self.menu_helper = menu_helper
25 31
         self.contentwin.addstr(0, (width - 1 - len(title)) / 2 , title)#
32
+        newy = 5;
33
+
26 34
         if self.can_go_back:
27 35
             self.contentwin.addstr(height - 3, 5, '<Go Back>')
36
+        if self.can_go_next and self.can_go_back:
37
+            self.update_next_item()
38
+
39
+        self.dist = 0
40
+
41
+        if items:
42
+        #To select items, we need to identify up left right keys
43
+
44
+            self.dist=self.width-11
45
+            self.dist-=len('<Go Back>')
46
+            count=0
47
+            for item in self.items:
48
+                self.dist-=len(item[0])
49
+                count+=1
50
+            self.dist=self.dist/count
51
+            self.contentwin.keypad(1)
52
+            newy += len('<Go Back>')
53
+            newy += self.dist
54
+            for item in self.items:
55
+                self.contentwin.addstr(height - 3, newy, item[0])
56
+                newy += len(item[0])
57
+                newy += self.dist
28 58
 
29 59
         self.textwin = curses.newwin(height - 5, width - 5)
30 60
         self.textwin.bkgd(' ', curses.color_pair(2)) #Default Window color
... ...
@@ -37,51 +68,213 @@ class Window(Action):
37 37
         self.shadowpanel = curses.panel.new_panel(self.shadowwin)
38 38
 
39 39
         self.action_panel = action_panel
40
-
40
+        self.refresh(0, True)
41 41
         self.hide_window()
42 42
 
43
+    def update_next_item(self):
44
+        self.position=1
45
+        self.items.append(('<Next>', self.next_function, False))
46
+        self.tab_enabled=False
47
+
48
+
49
+    def next_function(self, params):
50
+        return ActionResult(True, None)
51
+
43 52
     def set_action_panel(self, action_panel):
44 53
         self.action_panel = action_panel
45 54
 
55
+    def update_menu(self,action_result):
56
+        if action_result.result and 'goNext' in action_result.result and action_result.result['goNext']:
57
+            return ActionResult(True, None)
58
+        if self.position == 0:
59
+            self.contentwin.addstr(self.height - 3, 5, '<Go Back>')
60
+            self.contentwin.refresh()
61
+            self.hide_window()
62
+            self.action_panel.hide()
63
+            return ActionResult(False, None)
64
+        else:
65
+            if (action_result.result != None and 'diskIndex' in action_result.result):
66
+                params = action_result.result['diskIndex']
67
+                if self.menu_helper:
68
+                    self.menu_helper(params)
69
+
70
+            result = self.items[self.position-1][1](None)
71
+            if result.success:
72
+                self.hide_window()
73
+                self.action_panel.hide()
74
+                return result
75
+            else:
76
+                if 'goBack' in result.result and result.result['goBack']:
77
+                    self.contentwin.refresh()
78
+                    self.hide_window()
79
+                    self.action_panel.hide()
80
+                    return ActionResult(False, None)
81
+
46 82
     def do_action(self):
47 83
         self.show_window()
84
+        if self.tab_enabled:
85
+            self.refresh(0, False)
86
+        else:
87
+            self.refresh(0, True)
48 88
         action_result = self.action_panel.do_action()
49 89
 
50 90
         if action_result.success:
91
+            if action_result.result and 'goNext' in action_result.result and action_result.result['goNext']:
92
+                return ActionResult(True, None)
93
+            if self.position!=0:    #saving the disk index
94
+                self.items[self.position-1][1](None)
95
+            if self.items:
96
+                return self.update_menu(action_result)
51 97
             self.hide_window()
52 98
             return action_result
53 99
         else:
100
+            if not self.tab_enabled and action_result.result !=None and 'direction' in action_result.result:
101
+                self.refresh(action_result.result['direction'], True)
54 102
             if (action_result.result != None and 'goBack' in action_result.result and action_result.result['goBack']):
55 103
                 self.hide_window()
56 104
                 self.action_panel.hide()
57 105
                 return action_result
58 106
             else:
59 107
                 #highlight the GoBack and keep going
60
-                self.contentwin.addstr(self.height - 3, 5, '<Go Back>', curses.color_pair(3))
61
-                self.contentwin.refresh()
108
+                self.refresh(0, True)
62 109
 
63 110
         while action_result.success == False:
64
-            key = self.contentwin.getch()
65
-            if key in [curses.KEY_ENTER, ord('\n')]:
66
-                #remove highlight from Go Back
67
-                self.contentwin.addstr(self.height - 3, 5, '<Go Back>')
68
-                self.contentwin.refresh()
69
-                self.hide_window()
70
-                self.action_panel.hide()
71
-                return ActionResult(False, None)
72
-            elif key in [ord('\t')]:
73
-                #remove highlight from Go Back
74
-                self.contentwin.addstr(self.height - 3, 5, '<Go Back>')
75
-                self.contentwin.refresh()
76
-                # go do the action inside the panel
77
-                action_result = self.action_panel.do_action()
111
+            if self.read_text:
112
+                is_go_back= self.position==0
113
+                action_result = self.action_panel.do_action(returned=True, go_back=is_go_back)
78 114
                 if action_result.success:
115
+                    if self.items:
116
+                        return self.update_menu(action_result)
79 117
                     self.hide_window()
80 118
                     return action_result
81 119
                 else:
82
-                    #highlight the GoBack and keep going
83
-                    self.contentwin.addstr(self.height - 3, 5, '<Go Back>', curses.color_pair(3))
84
-                    self.contentwin.refresh()
120
+                    if (action_result.result != None and 'goBack' in action_result.result and action_result.result['goBack']):
121
+                        self.hide_window()
122
+                        self.action_panel.hide()
123
+                        return action_result
124
+                    if action_result.result and 'direction' in action_result.result:
125
+                        self.refresh(action_result.result['direction'], True)
126
+            else:
127
+                key = self.contentwin.getch()
128
+                if key in [curses.KEY_ENTER, ord('\n')]:
129
+                    #remove highlight from Go Back
130
+                    if self.position == 0:
131
+                        self.contentwin.addstr(self.height - 3, 5, '<Go Back>')
132
+                        self.contentwin.refresh()
133
+                        self.hide_window()
134
+                        self.action_panel.hide()
135
+                        return ActionResult(False, None)
136
+                    else:
137
+                        if (action_result.result != None and 'diskIndex' in action_result.result):
138
+                            params = action_result.result['diskIndex']
139
+                            if self.menu_helper:
140
+                                self.menu_helper(params)
141
+                        result = self.items[self.position-1][1](None)
142
+                        if result.success:
143
+                            self.hide_window()
144
+                            self.action_panel.hide()
145
+                            return result
146
+                        else:
147
+                            if 'goBack' in result.result and result.result['goBack']:
148
+                                self.contentwin.refresh()
149
+                                self.hide_window()
150
+                                self.action_panel.hide()
151
+                                return ActionResult(False, None)
152
+                elif key in [ord('\t')]:
153
+                    if not self.tab_enabled:
154
+                        continue;
155
+                    #remove highlight from Go Back
156
+                    self.refresh(0, False)
157
+                    # go do the action inside the panel
158
+                    action_result = self.action_panel.do_action()
159
+                    if action_result.success:
160
+                        self.hide_window()
161
+                        return action_result
162
+                    else:
163
+                        #highlight the GoBack and keep going
164
+                        self.refresh(0, True)
165
+                elif key == curses.KEY_UP or key == curses.KEY_LEFT:
166
+                    if key == curses.KEY_UP and self.tab_enabled==False:
167
+                        self.action_panel.navigate(-1)
168
+                        action_result = self.action_panel.do_action()
169
+                        if action_result.success:
170
+                            if self.items:
171
+                                return self.update_menu(action_result)
172
+                            self.hide_window()
173
+                            return action_result
174
+                        else:
175
+                            if 'direction' in action_result.result:
176
+                            #highlight the GoBack and keep going
177
+                                self.refresh(action_result.result['direction'], True)
178
+                    else:
179
+                        self.refresh(-1, True)
180
+
181
+                elif key == curses.KEY_DOWN or key == curses.KEY_RIGHT:
182
+                    if key == curses.KEY_DOWN and self.tab_enabled==False:
183
+                        self.action_panel.navigate(1)
184
+                        action_result = self.action_panel.do_action()
185
+                        if action_result.success:
186
+                            if self.items:
187
+                                return self.update_menu(action_result)
188
+                            self.hide_window()
189
+                            return action_result
190
+                        else:
191
+                            if 'direction' in action_result.result:
192
+                            #highlight the GoBack and keep going
193
+                                self.refresh(action_result.result['direction'], True)
194
+                    else:
195
+                        self.refresh(1, True)
196
+
197
+
198
+    def refresh(self, n, select):
199
+        if not self.can_go_back:
200
+            return
201
+        self.position += n
202
+        if self.position < 0:
203
+            self.position = 0
204
+        elif self.items and self.position > len(self.items):  #add 1 for the <go back>
205
+            self.position = len(self.items)
206
+
207
+        if not self.items and not self.can_go_next:
208
+            self.position = 0
209
+        #add the highlight
210
+        newy = 5;
211
+        if self.position == 0:   #go back
212
+            if select:
213
+                self.contentwin.addstr(self.height - 3, 5, '<Go Back>', curses.color_pair(3))
214
+            elif self.items: #show user the last selected items
215
+                self.contentwin.addstr(self.height - 3, 5, '<Go Back>', curses.color_pair(1))
216
+            else: #if Go back is the only one shown, do not highlight at all
217
+                self.contentwin.addstr(self.height - 3, 5, '<Go Back>')
218
+
219
+            newy += len('<Go Back>')
220
+            newy += self.dist
221
+
222
+            if self.items:
223
+                for item in self.items:
224
+                    self.contentwin.addstr(self.height - 3, newy, item[0])
225
+                    newy += len(item[0])
226
+                    newy += self.dist
227
+
228
+        else:
229
+            self.contentwin.addstr(self.height - 3, 5, '<Go Back>')
230
+            newy += len('<Go Back>')
231
+            newy += self.dist
232
+            index = 1
233
+            for item in self.items:
234
+                if index == self.position:
235
+                    if select:
236
+                        self.contentwin.addstr(self.height - 3, newy, item[0], curses.color_pair(3))
237
+                    else:
238
+                        self.contentwin.addstr(self.height - 3, newy, item[0], curses.color_pair(1))
239
+
240
+                else:
241
+                    self.contentwin.addstr(self.height - 3, newy, item[0])
242
+                newy += len(item[0])
243
+                newy += self.dist
244
+                index += 1
245
+
246
+        self.contentwin.refresh()   
85 247
 
86 248
     def show_window(self):
87 249
         y = self.y
... ...
@@ -101,6 +294,9 @@ class Window(Action):
101 101
         curses.panel.update_panels()
102 102
         curses.doupdate()
103 103
 
104
+        if self.can_go_next:
105
+            self.position = 1
106
+
104 107
     def hide_window(self):
105 108
         self.shadowpanel.hide()
106 109
         self.contentpanel.hide()
107 110
old mode 100644
108 111
new mode 100755
... ...
@@ -9,7 +9,7 @@ from window import Window
9 9
 from readtext import ReadText
10 10
 
11 11
 class WindowStringReader(object):
12
-    def __init__(self, maxy, maxx, height, width, field, confirmation_err_msg, echo_char, accepted_chars, validation_fn, conversion_fn, title, display_string, inputy, install_config, default_string = None):
12
+    def __init__(self, maxy, maxx, height, width, field, confirmation_err_msg, echo_char, accepted_chars, validation_fn, conversion_fn, title, display_string, inputy, install_config, default_string = None, tab_enabled=False):
13 13
         self.title = title
14 14
         self.display_string = display_string
15 15
         self.install_config = install_config
... ...
@@ -22,9 +22,11 @@ class WindowStringReader(object):
22 22
 
23 23
         self.startx = (self.maxx - self.width) / 2
24 24
         self.starty = (self.maxy - self.height) / 2
25
+        self.tab_enabled=False
26
+        self.can_go_next=True
25 27
 
26
-        self.window = Window(self.height, self.width, self.maxy, self.maxx, self.title, True)
27
-        self.read_text = ReadText(maxy, maxx, self.window.content_window(), self.inputy, install_config, field, confirmation_err_msg, echo_char, accepted_chars, validation_fn, conversion_fn, default_string)
28
+        self.window = Window(self.height, self.width, self.maxy, self.maxx, self.title, True, items=[], tab_enabled=self.tab_enabled, position=1, can_go_next=self.can_go_next, read_text=self.can_go_next)
29
+        self.read_text = ReadText(maxy, maxx, self.window.content_window(), self.inputy, install_config, field, confirmation_err_msg, echo_char, accepted_chars, validation_fn, conversion_fn, default_string, tab_enabled=self.tab_enabled)
28 30
         self.window.set_action_panel(self.read_text)
29 31
         self.window.addstr(0, 0, self.display_string)
30 32