shnaider_carproj 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +10 -0
  3. data/Gemfile.lock +71 -0
  4. data/diagrams/add_owner.jpg +0 -0
  5. data/diagrams/delete_owner.jpg +0 -0
  6. data/diagrams/er.png +0 -0
  7. data/diagrams/owner.jpg +0 -0
  8. data/diagrams/owner.sai2 +0 -0
  9. data/diagrams/start.jpg +0 -0
  10. data/lib/car/controllers/publisher_input_form_controller_create.rb +44 -0
  11. data/lib/car/controllers/publisher_input_form_controller_edit.rb +52 -0
  12. data/lib/car/controllers/publisher_list_controller.rb +99 -0
  13. data/lib/car/tenant_db_data_source.rb +63 -0
  14. data/lib/car/ui/tenant_input_form.rb +69 -0
  15. data/lib/car/ui/tenant_list_view.rb +168 -0
  16. data/lib/controllers/tab_students_controller.rb +43 -0
  17. data/lib/data_sources/car_db_data_source.rb +43 -0
  18. data/lib/data_sources/db_client.rb +34 -0
  19. data/lib/db_config/carshering_config.yaml +5 -0
  20. data/lib/db_config/config.yaml +5 -0
  21. data/lib/db_config/migrations/create_db.sql +3 -0
  22. data/lib/db_config/migrations/create_tables.sql +29 -0
  23. data/lib/db_config/mock_data/mock_data.sql +55 -0
  24. data/lib/models/car.rb +31 -0
  25. data/lib/models/owner.rb +32 -0
  26. data/lib/models/student.rb +102 -0
  27. data/lib/models/student_base.rb +100 -0
  28. data/lib/models/student_short.rb +50 -0
  29. data/lib/models/tenant.rb +40 -0
  30. data/lib/owner/controllers/owner_input_form_controller_create.rb +44 -0
  31. data/lib/owner/controllers/owner_input_form_controller_edit.rb +53 -0
  32. data/lib/owner/controllers/owner_list_controller.rb +107 -0
  33. data/lib/owner/owner_db_data_source.rb +70 -0
  34. data/lib/owner/ui/owner_input_form.rb +69 -0
  35. data/lib/owner/ui/owner_list_view.rb +169 -0
  36. data/lib/repositories/adapters/db_source_adapter.rb +54 -0
  37. data/lib/repositories/adapters/file_source_adapter.rb +37 -0
  38. data/lib/repositories/containers/data_list.rb +74 -0
  39. data/lib/repositories/containers/data_list_student_short.rb +18 -0
  40. data/lib/repositories/containers/data_table.rb +35 -0
  41. data/lib/repositories/data_sources/db_data_source.rb +32 -0
  42. data/lib/repositories/data_sources/file_data_source.rb +75 -0
  43. data/lib/repositories/data_sources/transformers/data_transformer_base.rb +15 -0
  44. data/lib/repositories/data_sources/transformers/data_transformer_json.rb +16 -0
  45. data/lib/repositories/data_sources/transformers/data_transformer_yaml.rb +16 -0
  46. data/lib/repositories/student_repository.rb +32 -0
  47. data/lib/state_holders/list_state_notifier.rb +62 -0
  48. data/lib/tenant/controllers/tenant_input_form_controller_create.rb +44 -0
  49. data/lib/tenant/controllers/tenant_input_form_controller_edit.rb +53 -0
  50. data/lib/tenant/controllers/tenant_list_controller.rb +107 -0
  51. data/lib/tenant/tenant_db_data_source.rb +90 -0
  52. data/lib/tenant/ui/tenant_input_form.rb +69 -0
  53. data/lib/tenant/ui/tenant_list_view.rb +169 -0
  54. data/lib/views/main_window.rb +25 -0
  55. data/lib/views/tab_students.rb +148 -0
  56. data/requirements.docx +0 -0
  57. data/shnaider_carproj.gemspec +15 -0
  58. data/test/car_test.rb +33 -0
  59. data/test/logger.rb +27 -0
  60. data/test/main.rb +5 -0
  61. data/test/owner_test.rb +48 -0
  62. data/test/state_notifier_test.rb +80 -0
  63. data/test/tenant_test.rb +48 -0
  64. metadata +118 -0
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ require './models/student'
4
+ require './models/student_short'
5
+ require './repositories/containers/data_list_student_short'
6
+
7
+ class FileDataSource
8
+ attr_writer :data_transformer
9
+
10
+ def initialize(data_transformer)
11
+ self.students = []
12
+ self.seq_id = 1
13
+ self.data_transformer = data_transformer
14
+ end
15
+
16
+ def load_from_file(file_path)
17
+ hash_list = data_transformer.str_to_hash_list(File.read(file_path))
18
+ self.students = hash_list.map { |h| Student.from_hash(h) }
19
+ update_seq_id
20
+ end
21
+
22
+ def save_to_file(file_path)
23
+ hash_list = students.map(&:to_hash)
24
+ File.write(file_path, data_transformer.hash_list_to_str(hash_list))
25
+ end
26
+
27
+ def student_by_id(student_id)
28
+ students.detect { |s| s.id == student_id }
29
+ end
30
+
31
+ # Получить page по счету count элементов (страница начинается с 1)
32
+ def paginated_short_students(page, count, existing_data_list = nil)
33
+ offset = (page - 1) * count
34
+ slice = students[offset, count].map { |s| StudentShort.from_student(s) }
35
+
36
+ return DataListStudentShort.new(slice) if existing_data_list.nil?
37
+
38
+ existing_data_list.replace_objects(slice)
39
+ existing_data_list
40
+ end
41
+
42
+ def sorted
43
+ students.sort_by(&:last_name_and_initials)
44
+ end
45
+
46
+ def add_student(student)
47
+ student.id = seq_id
48
+ students << student
49
+ self.seq_id += 1
50
+ student.id
51
+ end
52
+
53
+ def replace_student(student_id, student)
54
+ idx = students.find_index { |s| s.id == student_id }
55
+ students[idx] = student
56
+ end
57
+
58
+ def remove_student(student_id)
59
+ students.reject! { |s| s.id == student_id }
60
+ end
61
+
62
+ def student_count
63
+ students.count
64
+ end
65
+
66
+ private
67
+
68
+ # Метод для актуализации seq_id
69
+ def update_seq_id
70
+ self.seq_id = students.max_by(&:id).id + 1
71
+ end
72
+
73
+ attr_reader :data_transformer
74
+ attr_accessor :students, :seq_id
75
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ class DataTransformerBase
4
+ private_class_method :new
5
+
6
+ protected
7
+
8
+ def str_to_hash_list(str)
9
+ raise NotImplementedError('Should be implemented in child')
10
+ end
11
+
12
+ def hash_list_to_str(hash_list)
13
+ raise NotImplementedError('Should be implemented in child')
14
+ end
15
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'data_transformer_base'
4
+ require 'json'
5
+
6
+ class DataTransformerJSON < DataTransformerBase
7
+ public_class_method :new
8
+
9
+ def str_to_hash_list(str)
10
+ JSON.parse(str, { symbolize_names: true })
11
+ end
12
+
13
+ def hash_list_to_str(hash_list)
14
+ JSON.pretty_generate(hash_list)
15
+ end
16
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'data_transformer_base'
4
+ require 'yaml'
5
+
6
+ class DataTransformerYAML < DataTransformerBase
7
+ public_class_method :new
8
+
9
+ def str_to_hash_list(str)
10
+ YAML.safe_load(str).map { |h| h.transform_keys(&:to_sym) }
11
+ end
12
+
13
+ def hash_list_to_str(hash_list)
14
+ hash_list.map { |h| h.transform_keys(&:to_s) }.to_yaml
15
+ end
16
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ class StudentRepository
4
+ def initialize(data_source_adapter)
5
+ @data_source_adapter = data_source_adapter
6
+ end
7
+
8
+ def student_by_id(student_id)
9
+ @data_source_adapter.student_by_id(student_id)
10
+ end
11
+
12
+ # Получить page по счету count элементов (страница начинается с 1)
13
+ def paginated_short_students(page, count, existing_data_list = nil)
14
+ @data_source_adapter.paginated_short_students(page, count, existing_data_list)
15
+ end
16
+
17
+ def add_student(student)
18
+ @data_source_adapter.add_student(student)
19
+ end
20
+
21
+ def replace_student(student_id, student)
22
+ @data_source_adapter.replace_student(student_id, student)
23
+ end
24
+
25
+ def remove_student(student_id)
26
+ @data_source_adapter.remove_student(student_id)
27
+ end
28
+
29
+ def student_count
30
+ @data_source_adapter.student_count
31
+ end
32
+ end
@@ -0,0 +1,62 @@
1
+
2
+ require './logger'
3
+ class ListStateNotifier
4
+ attr_reader :items
5
+
6
+ def initialize
7
+ @items = []
8
+ @listeners = []
9
+ end
10
+
11
+ # устанавливает новое значение для items и уведомляет всех слушателей.
12
+ def set_all(objects)
13
+ LoggerHolder.instance.debug('ListStateNotifier: set_all')
14
+ @items = objects
15
+ notify_listeners
16
+ end
17
+
18
+ # добавляет объект в массив items и уведомляет всех слушателей.
19
+ def add(object)
20
+ LoggerHolder.instance.debug('ListStateNotifier: add')
21
+ @items << object
22
+ notify_listeners
23
+ end
24
+ # возвращает объект из массива items по индексу.
25
+ def get(number)
26
+ LoggerHolder.instance.debug('ListStateNotifier: get')
27
+ @items[number]
28
+ end
29
+
30
+ # удаляет объект из массива items и уведомляет всех слушателей.
31
+ def delete(object)
32
+ LoggerHolder.instance.debug('ListStateNotifier: delete')
33
+ @items.delete(object)
34
+ notify_listeners
35
+ end
36
+
37
+ # заменяет объект в массиве items на новый объект и уведомляет всех слушателей.
38
+ def replace(object, new_object)
39
+ LoggerHolder.instance.debug('ListStateNotifier: replace')
40
+ index = @items.index(object)
41
+ @items[index] = new_object
42
+ notify_listeners
43
+ end
44
+ # добавляет нового слушателя в массив listeners.
45
+ def add_listener(listener)
46
+ LoggerHolder.instance.debug('ListStateNotifier: add_listener')
47
+ @listeners << listener
48
+ end
49
+ # удаляет слушателя из массива listeners.
50
+ def delete_listener(listener)
51
+ LoggerHolder.instance.debug('ListStateNotifier: delete_listener')
52
+ @listeners.delete(listener)
53
+ end
54
+
55
+ # уведомляет всех слушателей о изменении массива items.
56
+ def notify_listeners
57
+ LoggerHolder.instance.debug('notify_listeners')
58
+ @listeners.each do |listener|
59
+ listener.update(@items)
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'win32api'
4
+
5
+ class TenantInputFormControllerCreate
6
+ def initialize(parent_controller)
7
+ @parent_controller = parent_controller
8
+ @tenant_rep = TenantDbDataSource.new
9
+ end
10
+
11
+ def set_view(view)
12
+ @view = view
13
+ end
14
+
15
+ def on_view_created
16
+ # begin
17
+ # @student_rep = StudentRepository.new(DBSourceAdapter.new)
18
+ # rescue Mysql2::Error::ConnectionError
19
+ # on_db_conn_error
20
+ # end
21
+ end
22
+
23
+ def process_fields(fields)
24
+ begin
25
+ puts fields
26
+ item = Tenant.new(-1, *fields.values)
27
+ puts item
28
+ item = @tenant_rep.add(item)
29
+ @parent_controller.state_notifier.add(item)
30
+ @view.close
31
+ rescue ArgumentError => e
32
+ api = Win32API.new('user32', 'MessageBox', ['L', 'P', 'P', 'L'], 'I')
33
+ api.call(0, e.message, 'Error', 0)
34
+ end
35
+ end
36
+
37
+ private
38
+
39
+ def on_db_conn_error
40
+ api = Win32API.new('user32', 'MessageBox', ['L', 'P', 'P', 'L'], 'I')
41
+ api.call(0, "No connection to DB", "Error", 0)
42
+ @view.close
43
+ end
44
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'win32api'
4
+
5
+ class TenantInputFormControllerEdit
6
+ def initialize(parent_controller, item)
7
+ @parent_controller = parent_controller
8
+ @item = item
9
+ @tenant_rep = TenantDbDataSource.new
10
+ end
11
+
12
+ def set_view(view)
13
+ @view = view
14
+ end
15
+
16
+ def on_view_created
17
+ # begin
18
+ # @student_rep = StudentRepository.new(DBSourceAdapter.new)
19
+ # rescue Mysql2::Error::ConnectionError
20
+ # on_db_conn_error
21
+ # end
22
+
23
+ # @item = @tenant_rep.get(@item_id)
24
+ # @view.make_readonly(:git, :telegram, :email, :phone)
25
+ populate_fields(@item)
26
+ end
27
+
28
+ def populate_fields(item)
29
+ @view.set_value(:first_name, item.first_name)
30
+ @view.set_value(:last_name, item.last_name)
31
+ @view.set_value(:phone, item.phone)
32
+ end
33
+
34
+ def process_fields(fields)
35
+ begin
36
+ item = Tenant.new(@item.tenant_id, *fields.values)
37
+ item = @tenant_rep.change(item)
38
+ @parent_controller.state_notifier.replace(@item, item)
39
+ @view.close
40
+ rescue ArgumentError => e
41
+ api = Win32API.new('user32', 'MessageBox', ['L', 'P', 'P', 'L'], 'I')
42
+ api.call(0, e.message, 'Error', 0)
43
+ end
44
+ end
45
+
46
+ private
47
+
48
+ def on_db_conn_error
49
+ api = Win32API.new('user32', 'MessageBox', ['L', 'P', 'P', 'L'], 'I')
50
+ api.call(0, "No connection to DB", "Error", 0)
51
+ @view.close
52
+ end
53
+ end
@@ -0,0 +1,107 @@
1
+ # frozen_string_literal: true
2
+
3
+ require './state_holders/list_state_notifier'
4
+ require_relative '../ui/tenant_input_form'
5
+ require_relative 'tenant_input_form_controller_create'
6
+ require_relative 'tenant_input_form_controller_edit'
7
+ require_relative '../tenant_db_data_source'
8
+ require 'win32api'
9
+
10
+ # Класс TenantListController отвечает за управление списком
11
+ # авторов, используя различные методы для обновления данных и
12
+ # взаимодействия с пользовательским интерфейсом.
13
+ class TenantListController
14
+
15
+ attr_reader :state_notifier
16
+
17
+ def initialize(view)
18
+ LoggerHolder.instance.debug('TenantListController: initialize')
19
+ @view = view
20
+ @state_notifier = ListStateNotifier.new
21
+ @state_notifier.add_listener(@view)
22
+ @tenant_rep = TenantDbDataSource.new
23
+
24
+ @sort_columns = %w[TenantID FirstName LastName Phone]
25
+ @sort_by = @sort_columns.first
26
+
27
+ @phone_filter_columns = [nil, true, false]
28
+ @phone_filter = @phone_filter_columns.first
29
+ end
30
+
31
+ def on_view_created
32
+ # begin
33
+ # @student_rep = StudentRepository.new(DBSourceAdapter.new)
34
+ # rescue Mysql2::Error::ConnectionError
35
+ # on_db_conn_error
36
+ # end
37
+ end
38
+
39
+ def show_view
40
+ @view.create.show
41
+ end
42
+
43
+ def show_modal_add
44
+ LoggerHolder.instance.debug('TenantListController: show_modal_add')
45
+ controller = TenantInputFormControllerCreate.new(self)
46
+ view = TenantInputForm.new(controller)
47
+ controller.set_view(view)
48
+ view.create.show
49
+ end
50
+
51
+ # метод, который создает контроллер TenantInputFormControllerEdit, представление OwnerInputForm, устанавливает связи между ними и показывает модальное окно.
52
+ def show_modal_edit(current_page, per_page, selected_row)
53
+ LoggerHolder.instance.debug('TenantListController: show_modal_edit')
54
+ # item_num = (current_page - 1) * per_page + selected_row
55
+
56
+ item = @state_notifier.get(selected_row)
57
+
58
+ controller = TenantInputFormControllerEdit.new(self, item)
59
+ view = TenantInputForm.new(controller)
60
+ controller.set_view(view)
61
+ view.create.show
62
+ end
63
+
64
+ # метод, который получает выбранный элемент из state_notifier, удаляет его из базы данных и из state_notifier
65
+ def delete_selected(current_page, per_page, selected_row)
66
+ LoggerHolder.instance.debug('TenantListController: delete_selected')
67
+ begin
68
+ item = @state_notifier.get(selected_row)
69
+ @tenant_rep.delete(item.tenant_id)
70
+ @state_notifier.delete(item)
71
+ rescue
72
+ api = Win32API.new('user32', 'MessageBox', ['L', 'P', 'P', 'L'], 'I')
73
+ api.call(0, "You cannot delete the tenant because he is associated with some car", "Error", 0)
74
+ end
75
+ end
76
+
77
+ # етод, который получает список авторов из базы данных, устанавливает их в state_notifier и обновляет пользовательский интерфейс
78
+ def refresh_data(page, per_page)
79
+ # begin
80
+ # @data_list = @student_rep.paginated_short_students(page, per_page, @data_list)
81
+ # @view.update_student_count(@student_rep.student_count)
82
+ # rescue
83
+ # on_db_conn_error
84
+ # end
85
+ items = @tenant_rep.get_list(per_page, page, @sort_by, 'ASC', @phone_filter)
86
+ @state_notifier.set_all(items)
87
+ @view.update_count(@tenant_rep.count)
88
+ end
89
+
90
+ def sort(page, per_page, sort_index)
91
+ @sort_by = @sort_columns[sort_index]
92
+ refresh_data(page, per_page)
93
+ end
94
+
95
+ def filter_phone(page, per_page, filter_index)
96
+ @phone_filter = @phone_filter_columns[filter_index]
97
+ refresh_data(page, per_page)
98
+ end
99
+
100
+ private
101
+
102
+ def on_db_conn_error
103
+ api = Win32API.new('user32', 'MessageBox', ['L', 'P', 'P', 'L'], 'I')
104
+ api.call(0, "No connection to DB", "Error", 0)
105
+ exit(false)
106
+ end
107
+ end
@@ -0,0 +1,90 @@
1
+ require 'mysql2'
2
+ require_relative '../lib/data_sources/lient'
3
+
4
+ class TenantDbDataSource
5
+ def initialize
6
+ @client = DBClient.instance
7
+ end
8
+
9
+ # def add(tenant)
10
+ # query = "INSERT INTO Owner (FirstName, LastName, FatherName) VALUES ('#{tenant.first_name}', '#{tenant.last_name}', #{tenant.father_name.nil? ? 'NULL' : "'#{tenant.father_name}'"})"
11
+ # @client.query(query)
12
+ # end
13
+
14
+
15
+
16
+ # добавляет нового автора в базу данных, возвращает созданную запись.
17
+ def add(tenant)
18
+ query = "INSERT INTO Tenant (FirstName, LastName, FatherName) VALUES ('#{tenant.first_name}', '#{tenant.last_name}', #{tenant.phone.nil? ? 'NULL' : "'#{tenant.phone}'"})"
19
+ @client.query(query)
20
+ tenant_id = @client.last_id
21
+ get(tenant_id)
22
+ end
23
+
24
+ # изменяет данные об авторе в базе данных, возвращает измененную запись.
25
+ def change(tenant)
26
+ query = "UPDATE Tenant SET FirstName='#{tenant.first_name}', LastName='#{tenant.last_name}', Phone=#{tenant.phone.nil? ? 'NULL' : "'#{tenant.phone}'"} WHERE tenantID=#{tenant.tenant_id}"
27
+ @client.query(query)
28
+ get(tenant.tenant_id)
29
+ end
30
+
31
+ # удаляет запись об авторе из базы данных.
32
+ def delete(id)
33
+ query = "DELETE FROM Tenant WHERE tenantID=#{id}"
34
+ @client.query(query)
35
+ end
36
+
37
+ # возвращает запись об авторе по заданному id.
38
+ def get(id)
39
+ query = "SELECT * FROM Tenant WHERE tenantID=#{id}"
40
+ result = @client.query(query).first
41
+ if result
42
+ Tenant.new(result[:'TenantID'], result[:'FirstName'], result[:'LastName'], result[:'Phone'])
43
+ else
44
+ nil
45
+ end
46
+ end
47
+
48
+ # def get_list(page_size, page_num, sort_field, sort_direction)
49
+ # offset = (page_num - 1) * page_size
50
+ # query = "SELECT * FROM Owner ORDER BY #{sort_field} #{sort_direction} LIMIT #{page_size} OFFSET #{offset}"
51
+ # results = @client.query(query)
52
+ #
53
+ # tenants = []
54
+ # results.each do |result|
55
+ # tenants << Owner.new(result[:'tenantID'], result[:'FirstName'], result[:'LastName'], result[:'FatherName'])
56
+ # end
57
+ #
58
+ # tenants
59
+ # end
60
+
61
+ # возвращает список авторов с учетом фильтра по наличию отчества и сортировки, позволяет задавать количество элементов на странице и номер страницы.
62
+ def get_list(page_size, page_num, sort_field, sort_direction, has_phone = nil)
63
+ offset = (page_num - 1) * page_size
64
+ query = "SELECT * FROM Tenant"
65
+
66
+ if has_phone == true
67
+ query += " WHERE Phone IS NOT NULL"
68
+ elsif has_phone == false
69
+ query += " WHERE Phone IS NULL"
70
+ end
71
+
72
+ query += " ORDER BY #{sort_field} #{sort_direction} LIMIT #{page_size} OFFSET #{offset}"
73
+ results = @client.query(query)
74
+
75
+ tenants = []
76
+ results.each do |result|
77
+ tenants << Tenant.new(result[:'TenantID'], result[:'FirstName'], result[:'LastName'], result[:'Phone'])
78
+ end
79
+
80
+ tenants
81
+ end
82
+
83
+ # возвращает количество записей об авторах в базе данных.
84
+ def count
85
+ query = "SELECT COUNT(*) FROM Tenant"
86
+ result = @client.query(query).first
87
+
88
+ result[:'COUNT(*)']
89
+ end
90
+ end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'glimmer-dsl-libui'
4
+ require_relative '../controllers/tenant_input_form_controller_create'
5
+ require './models/tenant'
6
+ require 'win32api'
7
+
8
+ class TenantInputForm
9
+ include Glimmer
10
+
11
+ def initialize(controller, existing_student = nil)
12
+ @item = existing_student.to_hash unless existing_student.nil?
13
+ @controller = controller
14
+ @entries = {}
15
+ end
16
+
17
+ def on_create
18
+ @controller.on_view_created
19
+ end
20
+
21
+ def create
22
+ @root_container = window('Арендатор', 300, 70) {
23
+ resizable false
24
+
25
+ vertical_box {
26
+ @student_form = form {
27
+ stretchy false
28
+
29
+ fields = [[:first_name, 'Имя арендатора'], [:last_name, 'Фамилия арендатора'], [:phone, 'Номер телефона']]
30
+
31
+ fields.each do |field|
32
+ @entries[field[0]] = entry {
33
+ label field[1]
34
+ }
35
+ end
36
+ }
37
+
38
+ button('Сохранить') {
39
+ stretchy false
40
+
41
+ on_clicked {
42
+ values = @entries.transform_values { |v| v.text.force_encoding("utf-8").strip }
43
+ values.transform_values! { |v| v.empty? ? nil : v}
44
+
45
+ @controller.process_fields(values)
46
+ }
47
+ }
48
+ }
49
+ }
50
+ on_create
51
+ @root_container
52
+ end
53
+
54
+ def set_value(field, value)
55
+ return unless @entries.include?(field)
56
+
57
+ @entries[field].text = value
58
+ end
59
+
60
+ def make_readonly(*fields)
61
+ fields.each do |field|
62
+ @entries[field].read_only = true
63
+ end
64
+ end
65
+
66
+ def close
67
+ @root_container.destroy
68
+ end
69
+ end