tmis 0.1.3

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 (94) hide show
  1. checksums.yaml +7 -0
  2. data/COPYING +674 -0
  3. data/README.md +44 -0
  4. data/Rakefile +64 -0
  5. data/bin/tmis +4 -0
  6. data/lib/tmis/engine/database.rb +58 -0
  7. data/lib/tmis/engine/export/timetable_exporter.rb +366 -0
  8. data/lib/tmis/engine/import/abstract_spreadsheet.rb +53 -0
  9. data/lib/tmis/engine/import/spreadsheet_roo.rb +136 -0
  10. data/lib/tmis/engine/import/timetable_manager.rb +110 -0
  11. data/lib/tmis/engine/import/timetable_reader.rb +79 -0
  12. data/lib/tmis/engine/mailer/mailer.rb +51 -0
  13. data/lib/tmis/engine/migrations/10_create_speciality_subjects.rb +17 -0
  14. data/lib/tmis/engine/migrations/11_create_emails.rb +10 -0
  15. data/lib/tmis/engine/migrations/12_add_indexes.rb +32 -0
  16. data/lib/tmis/engine/migrations/1_create_groups.rb +11 -0
  17. data/lib/tmis/engine/migrations/2_create_subgroups.rb +10 -0
  18. data/lib/tmis/engine/migrations/3_create_subjects.rb +11 -0
  19. data/lib/tmis/engine/migrations/4_create_cabinets.rb +12 -0
  20. data/lib/tmis/engine/migrations/5_create_lecturers.rb +14 -0
  21. data/lib/tmis/engine/migrations/6_create_studies.rb +15 -0
  22. data/lib/tmis/engine/migrations/7_create_courses.rb +9 -0
  23. data/lib/tmis/engine/migrations/8_create_specialities.rb +9 -0
  24. data/lib/tmis/engine/migrations/9_create_semesters.rb +10 -0
  25. data/lib/tmis/engine/models/cabinet.rb +18 -0
  26. data/lib/tmis/engine/models/course.rb +11 -0
  27. data/lib/tmis/engine/models/email.rb +19 -0
  28. data/lib/tmis/engine/models/group.rb +31 -0
  29. data/lib/tmis/engine/models/lecturer.rb +45 -0
  30. data/lib/tmis/engine/models/semester.rb +4 -0
  31. data/lib/tmis/engine/models/speciality.rb +3 -0
  32. data/lib/tmis/engine/models/speciality_subject.rb +6 -0
  33. data/lib/tmis/engine/models/study.rb +56 -0
  34. data/lib/tmis/engine/models/subgroup.rb +21 -0
  35. data/lib/tmis/engine/models/subject.rb +19 -0
  36. data/lib/tmis/engine/verificator.rb +96 -0
  37. data/lib/tmis/interface/forms/about.rb +24 -0
  38. data/lib/tmis/interface/forms/console.rb +28 -0
  39. data/lib/tmis/interface/forms/debug_console.rb +32 -0
  40. data/lib/tmis/interface/forms/edit_study.rb +110 -0
  41. data/lib/tmis/interface/forms/expand_changes.rb +128 -0
  42. data/lib/tmis/interface/forms/export_general_timetable.rb +68 -0
  43. data/lib/tmis/interface/forms/export_group_timetable.rb +158 -0
  44. data/lib/tmis/interface/forms/export_lecturer_timetable.rb +171 -0
  45. data/lib/tmis/interface/forms/find.rb +71 -0
  46. data/lib/tmis/interface/forms/import.rb +36 -0
  47. data/lib/tmis/interface/forms/settings.rb +125 -0
  48. data/lib/tmis/interface/forms/ui_about.rb +88 -0
  49. data/lib/tmis/interface/forms/ui_console.rb +68 -0
  50. data/lib/tmis/interface/forms/ui_debug_console.rb +82 -0
  51. data/lib/tmis/interface/forms/ui_edit_study.rb +202 -0
  52. data/lib/tmis/interface/forms/ui_expand_changes.rb +134 -0
  53. data/lib/tmis/interface/forms/ui_export_general_timetable.rb +142 -0
  54. data/lib/tmis/interface/forms/ui_export_group_timetable.rb +160 -0
  55. data/lib/tmis/interface/forms/ui_export_lecturer_timetable.rb +160 -0
  56. data/lib/tmis/interface/forms/ui_find.rb +77 -0
  57. data/lib/tmis/interface/forms/ui_import.rb +134 -0
  58. data/lib/tmis/interface/forms/ui_settings.rb +417 -0
  59. data/lib/tmis/interface/mainwindow.rb +933 -0
  60. data/lib/tmis/interface/models/cabinet_table_model.rb +133 -0
  61. data/lib/tmis/interface/models/course_table_model.rb +87 -0
  62. data/lib/tmis/interface/models/group_table_model.rb +190 -0
  63. data/lib/tmis/interface/models/lecturer_table_model.rb +111 -0
  64. data/lib/tmis/interface/models/semester_table_model.rb +137 -0
  65. data/lib/tmis/interface/models/speciality_subject_table_model.rb +288 -0
  66. data/lib/tmis/interface/models/speciality_table_model.rb +87 -0
  67. data/lib/tmis/interface/models/study_table_model.rb +323 -0
  68. data/lib/tmis/interface/models/subgroup_table_model.rb +136 -0
  69. data/lib/tmis/interface/models/subject_table_model.rb +90 -0
  70. data/lib/tmis/interface/ui_mainwindow.rb +928 -0
  71. data/lib/tmis.rb +45 -0
  72. data/spec/config.rb +49 -0
  73. data/spec/database_spec.rb +18 -0
  74. data/spec/export/timetable_exporter_mocks.rb +20 -0
  75. data/spec/export/timetable_exporter_spec.rb +34 -0
  76. data/spec/factories/factories.rb +65 -0
  77. data/spec/import/test_data/raspisanie_2013.csv +104 -0
  78. data/spec/import/timetable_importer_mocks.rb +6 -0
  79. data/spec/import/timetable_manager_spec.rb +16 -0
  80. data/spec/import/timetable_reader_spec.rb +111 -0
  81. data/spec/import/timetable_roo_spec.rb +48 -0
  82. data/spec/mailer/mailer_spec.rb +37 -0
  83. data/spec/mainwindow_spec.rb +18 -0
  84. data/spec/models/cabinet_spec.rb +33 -0
  85. data/spec/models/course_spec.rb +26 -0
  86. data/spec/models/group_spec.rb +33 -0
  87. data/spec/models/lecturer_spec.rb +38 -0
  88. data/spec/models/semester_spec.rb +26 -0
  89. data/spec/models/speciality_spec.rb +26 -0
  90. data/spec/models/speciality_subject_spec.rb +9 -0
  91. data/spec/models/study_spec.rb +9 -0
  92. data/spec/models/subgroup_spec.rb +26 -0
  93. data/spec/models/subject_spec.rb +39 -0
  94. metadata +290 -0
@@ -0,0 +1,933 @@
1
+ # coding: UTF-8
2
+ #~~~~~~~~~~~~~~~~~~~~~~~~~~
3
+ # Copyright (C) 2013 Vladislav Mileshkin
4
+ #
5
+ # This file is part of TMIS.
6
+ #
7
+ # TMIS is free software: you can redistribute it and/or modify
8
+ # it under the terms of the GNU General Public License as published by
9
+ # the Free Software Foundation, either version 3 of the License, or
10
+ # (at your option) any later version.
11
+ #
12
+ # TMIS is distributed in the hope that it will be useful,
13
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
+ # GNU General Public License for more details.
16
+ #
17
+ # You should have received a copy of the GNU General Public License
18
+ # along with TMIS. If not, see <http://www.gnu.org/licenses/>.
19
+ #~~~~~~~~~~~~~~~~~~~~~~~~~~
20
+ require 'Qt'
21
+ require 'mail'
22
+ require 'tmpdir'
23
+ require 'fileutils'
24
+ #require '#Contracts'
25
+ #~~~~~~~~~~~~~~~~~~~~~~~~~~
26
+ require_relative '../engine/database'
27
+ require_relative '../engine/verificator'
28
+ require_relative '../engine/import/timetable_manager'
29
+ require_relative '../engine/import/timetable_reader'
30
+ require_relative '../engine/import/spreadsheet_roo'
31
+ require_relative '../engine/export/timetable_exporter.rb'
32
+ require_relative '../engine/mailer/mailer'
33
+ require_relative '../engine/models/cabinet'
34
+ require_relative '../engine/models/course'
35
+ require_relative '../engine/models/group'
36
+ require_relative '../engine/models/lecturer'
37
+ require_relative '../engine/models/semester'
38
+ require_relative '../engine/models/speciality'
39
+ require_relative '../engine/models/speciality_subject'
40
+ require_relative '../engine/models/study'
41
+ require_relative '../engine/models/subject'
42
+ require_relative '../engine/models/subgroup'
43
+ require_relative 'ui_mainwindow'
44
+ require_relative 'forms/about'
45
+ require_relative 'forms/find'
46
+ require_relative 'forms/settings'
47
+ require_relative 'forms/import'
48
+ require_relative 'forms/console'
49
+ require_relative 'forms/debug_console'
50
+ require_relative 'forms/export_general_timetable'
51
+ require_relative 'forms/export_lecturer_timetable'
52
+ require_relative 'forms/export_group_timetable'
53
+ require_relative 'forms/expand_changes'
54
+ require_relative 'models/cabinet_table_model'
55
+ require_relative 'models/course_table_model'
56
+ require_relative 'models/group_table_model'
57
+ require_relative 'models/lecturer_table_model'
58
+ require_relative 'models/semester_table_model'
59
+ require_relative 'models/speciality_table_model'
60
+ require_relative 'models/speciality_subject_table_model'
61
+ require_relative 'models/study_table_model'
62
+ require_relative 'models/subject_table_model'
63
+ require_relative 'models/subgroup_table_model'
64
+ #~~~~~~~~~~~~~~~~~~~~~~~~~~
65
+ #class Object
66
+ # def to_v
67
+ # Qt::Variant.new object_id
68
+ # end
69
+ #end
70
+ #
71
+ #class Qt::Variant
72
+ # def to_o
73
+ # ObjectSpace._id2ref to_int
74
+ # end
75
+ #end
76
+
77
+ class Object
78
+ def to_v
79
+ Qt::Variant.new(self)
80
+ end
81
+ end
82
+
83
+ class MainWindow < Qt::MainWindow
84
+
85
+ # File menu
86
+ slots 'on_newAction_triggered()'
87
+ slots 'on_openAction_triggered()'
88
+ slots 'on_saveAction_triggered()'
89
+ slots 'on_saveAsAction_triggered()'
90
+ slots 'on_importAction_triggered()'
91
+ slots 'on_closeAction_triggered()'
92
+ slots 'on_quitAction_triggered()'
93
+ # Tools menu
94
+ slots 'on_verifyLecturersAction_triggered()'
95
+ slots 'on_verifyCabinetsAction_triggered()'
96
+ slots 'on_showLecturerStubsAction_triggered()'
97
+ slots 'on_showCabinetStubsAction_triggered()'
98
+ slots 'on_showSubjectsStubsAction_triggered()'
99
+ slots 'on_verifyComputerCabinetsAction_triggered()'
100
+ slots 'on_verifyPreferredDaysAction_triggered()'
101
+ # Main
102
+ slots 'on_dateDateEdit_dateChanged()'
103
+ # Self
104
+ slots 'open_file()'
105
+ slots 'clear_recent_files()'
106
+ slots 'refreshTableViewModel(QVariant)'
107
+ # help
108
+ slots 'on_showManualAction_triggered()'
109
+ # views buttons
110
+ slots 'on_addCabinetPushButton_clicked()'
111
+ slots 'on_removeCabinetPushButton_clicked()'
112
+ slots 'on_addCoursePushButton_clicked()'
113
+ slots 'on_removeCoursePushButton_clicked()'
114
+ slots 'on_addGroupPushButton_clicked()'
115
+ slots 'on_removeGroupPushButton_clicked()'
116
+ slots 'on_addLecturerPushButton_clicked()'
117
+ slots 'on_removeLecturerPushButton_clicked()'
118
+ slots 'on_addSemesterPushButton_clicked()'
119
+ slots 'on_removeSemesterPushButton_clicked()'
120
+ slots 'on_addSpecialityPushButton_clicked()'
121
+ slots 'on_removeSpecialityPushButton_clicked()'
122
+ slots 'on_addSubgroupPushButton_clicked()'
123
+ slots 'on_removeSubgroupPushButton_clicked()'
124
+ slots 'on_addSubjectPushButton_clicked()'
125
+ slots 'on_removeSubjectPushButton_clicked()'
126
+ slots 'on_addSpecialitySubjectPushButton_clicked()'
127
+ slots 'on_removeSpecialitySubjectPushButton_clicked()'
128
+ #connect(@ui.addSpecialitySubjectPushButton, SLOT('clicked()'), self, SLOT('on_addSpecialitySubjectPushButton_clicked()'))
129
+ #connect(@ui.removeSpecialitySubjectPushButton, SLOT('clicked()'), self, SLOT('on_removeSpecialitySubjectPushButton_clicked()'))
130
+
131
+ slots 'on_tabWidget_currentChanged(int)'
132
+ slots 'on_dataTabWidget_currentChanged(int)'
133
+
134
+ slots 'on_findByLecturerAction_triggered()'
135
+ slots 'on_findBySubjectAction_triggered()'
136
+ slots 'on_findByCabinetAction_triggered()'
137
+
138
+ slots 'on_allAction_triggered()'
139
+ slots 'on_allCoincidenceAction_triggered()'
140
+ slots 'on_allNotAssignedAction_triggered()'
141
+
142
+ slots 'on_tarificationCheckBox_toggled(bool)'
143
+
144
+ slots 'on_debugConsoleAction_triggered()'
145
+
146
+ attr_reader :ui
147
+ attr_reader :study_table_models
148
+
149
+ def initialize(parent = nil)
150
+ super(parent)
151
+ @ui = Ui::MainWindow.new
152
+ @ui.setup_ui self
153
+ @ui.exportMenu.enabled = false
154
+ @study_table_views = [@ui.studiesTableView, @ui.studiesTableView2, @ui.studiesTableView3,
155
+ @ui.studiesTableView4, @ui.studiesTableView5, @ui.studiesTableView6]
156
+ @table_views = [[Cabinet, CabinetTableModel, @ui.cabinetsTableView], [Course, CourseTableModel, @ui.coursesTableView],
157
+ [Group, GroupTableModel, @ui.groupsTableView], [Lecturer, LecturerTableModel, @ui.lecturersTableView],
158
+ [Semester, SemesterTableModel, @ui.semestersTableView], [Speciality, SpecialityTableModel, @ui.specialitiesTableView],
159
+ [SpecialitySubject, SpecialitySubjectTableModel, @ui.specialitySubjectsTableView],
160
+ [Subgroup, SubgroupTableModel, @ui.subgroupsTableView], [Subject, SubjectTableModel, @ui.subjectsTableView]]
161
+ # Следующие два атрибута используются для обхода бага связанного с работой GC
162
+ # http://stackoverflow.com/questions/9715548/cant-display-more-than-one-table-model-inheriting-from-the-same-class-on-differ
163
+ @table_models = @study_table_models = []
164
+ @tables_views_to_hide = @study_table_views + [@ui.cabinetsTableView, @ui.coursesTableView, @ui.groupsTableView,
165
+ @ui.lecturersTableView, @ui.semestersTableView, @ui.specialitySubjectsTableView,
166
+ @ui.specialitiesTableView, @ui.subgroupsTableView, @ui.subjectsTableView, @ui.dateDateEdit,
167
+ @ui.dayLabel, @ui.dayLabel2, @ui.dayLabel3, @ui.dayLabel4, @ui.dayLabel5, @ui.dayLabel6,
168
+ @ui.subjectsListView, @ui.lecturersListView, @ui.cabinetsListView, @ui.tarificationCheckBox,
169
+ @ui.addCabinetPushButton, @ui.addCoursePushButton, @ui.addGroupPushButton,
170
+ @ui.addSubgroupPushButton, @ui.addLecturerPushButton, @ui.addSemesterPushButton,
171
+ @ui.addSpecialityPushButton, @ui.addSpecialitySubjectPushButton, @ui.addSubjectPushButton,
172
+ @ui.removeCabinetPushButton, @ui.removeCoursePushButton, @ui.removeGroupPushButton,
173
+ @ui.removeSubgroupPushButton, @ui.removeLecturerPushButton, @ui.removeSemesterPushButton,
174
+ @ui.removeSpecialityPushButton, @ui.removeSpecialitySubjectPushButton, @ui.removeSubjectPushButton]
175
+ @widgets_to_disable = [@ui.findMenu, @ui.exportMenu, @ui.verifyMenu, @ui.saveAsAction, @ui.expandChangesAction]
176
+ @tables_views_to_hide.each(&:hide)
177
+ @widgets_to_disable.each{ |x| x.enabled = false }
178
+ modeActionGroup = Qt::ActionGroup.new(self)
179
+ modeActionGroup.setExclusive(true)
180
+ modeActionGroup.addAction(@ui.weeklyViewAction)
181
+ modeActionGroup.addAction(@ui.dailyViewAction)
182
+ @temp = ->(){ "#{Dir.mktmpdir('tmis')}/temp.sqlite" }
183
+ connect(@ui.aboutQtAction, SIGNAL('triggered()')){ Qt::Application.aboutQt }
184
+ connect(@ui.aboutProgramAction, SIGNAL('triggered()')){ AboutDialog.new.exec }
185
+ connect(@ui.exportGeneralAction, SIGNAL('triggered()')) do
186
+ ExportGeneralTimetableDialog.new(Date.parse(@ui.dateDateEdit.date.toString(Qt::ISODate))).exec
187
+ end
188
+ connect(@ui.exportForLecturersAction, SIGNAL('triggered()')) do
189
+ ExportLecturerTimetableDialog.new(Date.parse(@ui.dateDateEdit.date.toString(Qt::ISODate))).exec
190
+ end
191
+ connect(@ui.exportForGroupsAction, SIGNAL('triggered()')) do
192
+ ExportGroupTimetableDialog.new(Date.parse(@ui.dateDateEdit.date.toString(Qt::ISODate))).exec
193
+ end
194
+ connect(@ui.settingsAction, SIGNAL('triggered()')){ SettingsDialog.new.exec }
195
+ connect(@ui.expandChangesAction, SIGNAL('triggered()')){ ExpandChangesDialog.new(self).exec }
196
+ @clear_recent_action = Qt::Action.new('Очистить', self)
197
+ @clear_recent_action.setData Qt::Variant.new('clear')
198
+ connect(@clear_recent_action, SIGNAL('triggered()'), self, SLOT('clear_recent_files()'))
199
+ @ui.dateDateEdit.setDate(Qt::Date.fromString(Date.today.to_s, Qt::ISODate))
200
+ setup_dateEdit(Date.today)
201
+ @ui.recentMenu.clear
202
+ @ui.recentMenu.addActions([@clear_recent_action] + Settings[:recent, :files].split.map{ |path| create_recent_action(path) })
203
+ #Settings[:app, :first_run] = ''
204
+ Settings.set_defaults_if_first_run
205
+ @console = ConsoleDialog.new self
206
+ connect(@console, SIGNAL('dialogClosed()')){ @study_table_models.each(&:cancelColoring) }
207
+ $TARIFICATION_MODE = false
208
+ end
209
+
210
+ def on_newAction_triggered
211
+ Database.instance.connect_to(@temp.())
212
+ create_stubs
213
+ Group.create(title: 'New')
214
+ show_tables
215
+ end
216
+
217
+ def create_stubs
218
+ Lecturer.create(surname: Settings[:stubs, :lecturer], stub: true)
219
+ Cabinet.create(title: Settings[:stubs, :cabinet], stub: true)
220
+ Subject.create(title: Settings[:stubs, :subject], stub: true)
221
+ end
222
+
223
+ def on_openAction_triggered
224
+ if (filename = Qt::FileDialog::getOpenFileName(self, 'Open File', '', 'TMIS databases (SQLite3)(*.sqlite)'))
225
+ Database.instance.connect_to filename
226
+ update_recent filename
227
+ show_tables
228
+ end
229
+ end
230
+
231
+ def on_saveAction_triggered
232
+ end
233
+
234
+ def on_saveAsAction_triggered
235
+ if (filename = Qt::FileDialog::getSaveFileName(self, 'Save File', 'NewTimetable.sqlite', 'TMIS databases (SQLite3)(*.sqlite)'))
236
+ filename.force_encoding('UTF-8')
237
+ FileUtils.cp(Database.instance.path, filename) unless Database.instance.path == filename
238
+ Database.instance.connect_to filename
239
+ update_recent filename
240
+ show_tables
241
+ end
242
+ end
243
+
244
+ def on_importAction_triggered
245
+ please_wait do
246
+ if (filename = Qt::FileDialog::getOpenFileName(self, 'Open File', '', 'Spreadsheets(*.xls *.xlsx *.ods *.csv)'))
247
+ if Database.instance.connected?
248
+ (id = ImportDialog.new(Date.parse(@ui.dateDateEdit.date.toString(Qt::ISODate)))).exec
249
+ else
250
+ (id = ImportDialog.new(Date.today)).exec
251
+ end
252
+ unless id.params.empty?
253
+ begin
254
+ sheet = SpreadsheetCreater.create filename
255
+ reader = TimetableReader.new(sheet, id.params[:sheet])
256
+ monday = Date.parse(@ui.dateDateEdit.date.toString(Qt::ISODate)).monday
257
+ if Database.instance.connected?
258
+ Database.instance.transaction do Study.where(date: (monday..(monday + 6))).each(&:delete) end
259
+ else
260
+ Database.instance.connect_to(@temp.())
261
+ create_stubs
262
+ end
263
+ TimetableManager.new(reader, id.params[:date]).save_to_db
264
+ show_tables
265
+ rescue => e
266
+ show_message "При импорте произошли ошибки,\nтаблица не была импортирована.\nПроверьте структуру таблицы."
267
+ end
268
+ end
269
+ end
270
+ end
271
+ end
272
+
273
+ def show_message(text)
274
+ box = Qt::MessageBox.new
275
+ box.setText text
276
+ box.exec
277
+ end
278
+
279
+ def on_closeAction_triggered
280
+ @tables_views_to_hide.each(&:hide)
281
+ @widgets_to_disable.each{ |x| x.enabled = false }
282
+ Database.instance.disconnect unless $TESTING
283
+ end
284
+
285
+ def on_quitAction_triggered
286
+ on_closeAction_triggered
287
+ recent = @ui.recentMenu.actions
288
+ Settings[:recent, :files] = recent[1..recent.size-1].map{ |a| a.data.value.to_s }.join(' ')
289
+ puts 'Sayonara!'
290
+ Qt::Application.quit
291
+ end
292
+
293
+ def on_allAction_triggered
294
+ begin
295
+ @console.browser.clear
296
+ @console.show
297
+ @console.browser.append verifyLecturers
298
+ @console.browser.append verifyCabinets
299
+ @console.browser.append showComputerCabinets
300
+ @console.browser.append showLecturerStubs
301
+ @console.browser.append showCabinetStubs
302
+ @console.browser.append showSubjectsStubs
303
+ @console.browser.append showPreferredDays
304
+ rescue
305
+ show_message "При проверке произошли ошибки.\nПроверьте таблицы данных и расписание"
306
+ end
307
+ end
308
+
309
+ def on_allCoincidenceAction_triggered
310
+ begin
311
+ @console.browser.clear
312
+ @console.show
313
+ @console.browser.append verifyLecturers
314
+ @console.browser.append verifyCabinets
315
+ rescue
316
+ show_message "При проверке произошли ошибки.\nПроверьте таблицы данных и расписание"
317
+ end
318
+ end
319
+
320
+ def on_allNotAssignedAction_triggered
321
+ begin
322
+ @console.browser.clear
323
+ @console.show
324
+ @console.browser.append showLecturerStubs
325
+ @console.browser.append showCabinetStubs
326
+ @console.browser.append showSubjectsStubs
327
+ rescue
328
+ show_message "При проверке произошли ошибки.\nПроверьте таблицы данных и расписание"
329
+ end
330
+ end
331
+
332
+ def verifyLecturers
333
+ date = Date.parse(@ui.dateDateEdit.date.toString(Qt::ISODate))
334
+ dates = date.monday..date.monday + 6
335
+ v = Verificator.new(dates)
336
+ res = v.verify(:lecturer_studies).map do |k, v|
337
+ date = k[0]
338
+ lecturer = Lecturer.where(id: k[1]).first
339
+ number = k[2]
340
+ if lecturer.stub
341
+ nil
342
+ else
343
+ v.each do |study|
344
+ tst = @study_table_models[date.cwday - 1]
345
+ tst.setColor(study.id, Qt::red)
346
+ end
347
+ "#{date} | #{lecturer} ведёт несколько пар одновременно! Номер пары: #{number}"
348
+ end
349
+ end
350
+ res = res.compact.join("\n")
351
+ end
352
+
353
+ def on_verifyLecturersAction_triggered
354
+ begin
355
+ @console.browser.clear
356
+ @console.show
357
+ @console.browser.append verifyLecturers
358
+ rescue
359
+ show_message "При проверке произошли ошибки.\nПроверьте таблицы данных и расписание"
360
+ end
361
+ end
362
+
363
+ def verifyCabinets
364
+ date = Date.parse(@ui.dateDateEdit.date.toString(Qt::ISODate))
365
+ dates = date.monday..date.monday + 6
366
+ v = Verificator.new(dates)
367
+ res = v.verify(:cabinet_studies).map do |k, v|
368
+ date = k[0]
369
+ cabinet= Cabinet.where(id: k[1]).first
370
+ number = k[2]
371
+ if cabinet.stub
372
+ nil
373
+ else
374
+ v.each{ |study| @study_table_models[date.cwday - 1].setColorCabinet(study.id, Qt::blue) }
375
+ "#{date} | В #{cabinet.title} проходит несколько пар одновременно! Номер пары: #{number}"
376
+ end
377
+ end
378
+ res = res.compact.join("\n")
379
+ end
380
+
381
+ def on_verifyCabinetsAction_triggered
382
+ begin
383
+ @console.browser.clear
384
+ @console.show
385
+ @console.browser.append verifyCabinets
386
+ rescue
387
+ show_message "При проверке произошли ошибки.\nПроверьте таблицы данных и расписание"
388
+ end
389
+ end
390
+
391
+ def showLecturerStubs
392
+ date = Date.parse(@ui.dateDateEdit.date.toString(Qt::ISODate))
393
+ dates = date.monday..date.monday + 6
394
+ v = Verificator.new(dates)
395
+ res = v.verify(:lecturer_stubs).map do |date, studies|
396
+ studies.map do |study|
397
+ @study_table_models[date.cwday - 1].setColor(study.id, Qt::green)
398
+ "#{date} | Не назначен преподаватель! Группа: #{study.get_group.title} Номер пары: #{study.number}"
399
+ end.join("\n")
400
+ end
401
+ res = res.compact.join("\n")
402
+ end
403
+
404
+ def on_showLecturerStubsAction_triggered
405
+ begin
406
+ @console.browser.clear
407
+ @console.show
408
+ @console.browser.append showLecturerStubs
409
+ rescue
410
+ show_message "При проверке произошли ошибки.\nПроверьте таблицы данных и расписание"
411
+ end
412
+ end
413
+
414
+ def showCabinetStubs
415
+ date = Date.parse(@ui.dateDateEdit.date.toString(Qt::ISODate))
416
+ dates = date.monday..date.monday + 6
417
+ v = Verificator.new(dates)
418
+ res = v.verify(:cabinet_stubs).map do |date, studies|
419
+ studies.map do |study|
420
+ @study_table_models[date.cwday - 1].setColorCabinet(study.id, Qt::green)
421
+ "#{date} | Не назначен кабинет! Группа: #{study.get_group.title} Номер пары: #{study.number}"
422
+ end.join("\n")
423
+ end
424
+ res = res.compact.join("\n")
425
+ end
426
+
427
+ def on_showCabinetStubsAction_triggered
428
+ begin
429
+ @console.browser.clear
430
+ @console.show
431
+ @console.browser.append showCabinetStubs
432
+ rescue
433
+ show_message "При проверке произошли ошибки.\nПроверьте таблицы данных и расписание"
434
+ end
435
+ end
436
+
437
+ def showSubjectsStubs
438
+ date = Date.parse(@ui.dateDateEdit.date.toString(Qt::ISODate))
439
+ dates = date.monday..date.monday + 6
440
+ v = Verificator.new(dates)
441
+ res = v.verify(:subject_stubs).map do |date, studies|
442
+ studies.map do |study|
443
+ @study_table_models[date.cwday - 1].setColor(study.id, Qt::green)
444
+ "#{date} | Не назначен предмет! Группа: #{study.get_group.title} Номер пары: #{study.number}"
445
+ end.join("\n")
446
+ end
447
+ res = res.compact.join("\n")
448
+ end
449
+
450
+ def on_showSubjectsStubsAction_triggered
451
+ begin
452
+ @console.browser.clear
453
+ @console.show
454
+ @console.browser.append showSubjectsStubs
455
+ rescue
456
+ show_message "При проверке произошли ошибки.\nПроверьте таблицы данных и расписание"
457
+ end
458
+ end
459
+
460
+ def showComputerCabinets
461
+ date = Date.parse(@ui.dateDateEdit.date.toString(Qt::ISODate))
462
+ dates = date.monday..date.monday + 6
463
+ v = Verificator.new(dates)
464
+ res = v.verify(:computer_cabinets).map do |date, studies|
465
+ studies.map do |study|
466
+ @study_table_models[date.cwday - 1].setColorCabinet(study.id, Qt::yellow)
467
+ "#{date} | Занятие подгруппы проходит не в компьютерном кабинете! Группа: #{study.get_group.title} Номер пары: #{study.number}"
468
+ end.join("\n")
469
+ end
470
+ res = res.compact.join("\n")
471
+ end
472
+
473
+ def on_verifyComputerCabinetsAction_triggered
474
+ begin
475
+ @console.browser.clear
476
+ @console.show
477
+ @console.browser.append showComputerCabinets
478
+ rescue
479
+ show_message "При проверке произошли ошибки.\nПроверьте таблицы данных и расписание"
480
+ end
481
+ end
482
+
483
+ def showPreferredDays
484
+ date = Date.parse(@ui.dateDateEdit.date.toString(Qt::ISODate))
485
+ dates = date.monday..date.monday + 6
486
+ v = Verificator.new(dates)
487
+ res = v.verify(:preferred_days).map do |date, studies|
488
+ studies.map do |study|
489
+ @study_table_models[date.cwday - 1].setColorCabinet(study.id, Qt::yellow)
490
+ "#{date} | #{study.lecturer.to_s} предпочитает вести занятия в другой день! Группа: #{study.get_group.title} Номер пары: #{study.number}"
491
+ end.join("\n")
492
+ end
493
+ res = res.compact.join("\n")
494
+ end
495
+
496
+ def on_verifyPreferredDaysAction_triggered
497
+ begin
498
+ @console.browser.clear
499
+ @console.show
500
+ @console.browser.append showPreferredDays
501
+ rescue
502
+ show_message "При проверке произошли ошибки.\nПроверьте таблицы данных и расписание"
503
+ end
504
+ end
505
+
506
+ #def groupAndSubgroup
507
+ # date = Date.parse(@ui.dateDateEdit.date.toString(Qt::ISODate))
508
+ # dates = date.monday..date.monday + 6
509
+ # v = Verificator.new(dates)
510
+ # res = v.verify(:group_and_subgroup).map do |k, v|
511
+ # date = k[0]
512
+ # lecturer = Lecturer.where(id: k[1]).first
513
+ # number = k[2]
514
+ # if lecturer.stub
515
+ # nil
516
+ # else
517
+ # v.each{ |study| @study_table_models[date.cwday - 1].setColor(study.id Qt::red) }
518
+ # "#{date} | #{lecturer} ведёт несколько пар одновременно! Номер пары: #{number}"
519
+ # end
520
+ # end
521
+ #end
522
+ #
523
+ #def on_verifyGroupAndSubgroup
524
+ # @console.browser.clear
525
+ # @console.show
526
+ # @console.browser.append groupAndSubgroup
527
+ #end
528
+
529
+ class EntityItemModel < Qt::AbstractItemModel
530
+ def initialize(lambda, parent = nil)
531
+ super(parent)
532
+ @get_entities = lambda
533
+ @entities = @get_entities.()
534
+ @size = @entities.size
535
+ end
536
+
537
+ def refresh
538
+ @entities = @get_entities.()
539
+ @size = @entities.size
540
+ emit layoutChanged()
541
+ end
542
+
543
+ def index(row, column, parent)
544
+ createIndex(row, column)
545
+ end
546
+
547
+ def parent(index)
548
+ Qt::ModelIndex.new()
549
+ end
550
+
551
+ def columnCount(parent = self)
552
+ 1
553
+ end
554
+
555
+ def rowCount(parent = self)
556
+ @size
557
+ end
558
+
559
+ def data(index, role = Qt::DisplayRole, data = nil)
560
+ if role == Qt::DisplayRole && index.valid?
561
+ @entities[index.row].to_s.to_v
562
+ else
563
+ Qt::Variant.new
564
+ end
565
+ end
566
+
567
+ def flags(index)
568
+ if index.valid?
569
+ Qt::ItemIsDragEnabled | super(index)
570
+ else
571
+ super(index)
572
+ end
573
+ end
574
+
575
+ def mimeData(indexes)
576
+ entity = @entities[indexes.first.row]
577
+ ba = Qt::ByteArray.new entity.id.to_s # Marshal.dump entity?
578
+ mime_type = "application/#{entity.class.to_s.downcase}"
579
+ mime_data = super indexes # для обхода ошибки сегментации Qt::MimeData создаётся с помощью родительского метода
580
+ mime_data.setData(mime_type, ba)
581
+ mime_data
582
+ end
583
+ end
584
+
585
+ def show_tables
586
+ @table_models = @table_views.map do |entity, table_model, table_view|
587
+ model = table_model.new(entity.all, table_view)
588
+ proxy_model = model
589
+ setup_table_view(table_view, proxy_model, Qt::HeaderView::Stretch)
590
+ model
591
+ end
592
+ setup_study_table_views
593
+ @ui.dateDateEdit.show
594
+ @tables_views_to_hide.each(&:show)
595
+ @widgets_to_disable.each{ |x| x.enabled = true }
596
+ #@ui.studiesTableView.setSpan(0, 0, 1, 3)
597
+ model = EntityItemModel.new(->(){ Subject.all }, self)
598
+ @ui.subjectsListView.setModel model
599
+ @ui.subjectsListView.show
600
+ model = EntityItemModel.new(->(){ Lecturer.all }, self)
601
+ @ui.lecturersListView.setModel model
602
+ @ui.lecturersListView.show
603
+ model = EntityItemModel.new(->(){ Cabinet.all }, self)
604
+ @ui.cabinetsListView.setModel model
605
+ @ui.cabinetsListView.show
606
+ end
607
+
608
+ def on_tarificationCheckBox_toggled(checked)
609
+ if checked
610
+ $TARIFICATION_MODE = true
611
+ model = EntityItemModel.new(->(){ [] }, self)
612
+ @ui.subjectsListView.setModel model
613
+ @ui.subjectsListView.show
614
+ model = EntityItemModel.new(->(){ [] }, self)
615
+ @ui.lecturersListView.setModel model
616
+ @ui.lecturersListView.show
617
+ else
618
+ $TARIFICATION_MODE = false
619
+ model = EntityItemModel.new(->(){ Subject.all }, self)
620
+ @ui.subjectsListView.setModel model
621
+ @ui.subjectsListView.show
622
+ model = EntityItemModel.new(->(){ Lecturer.all }, self)
623
+ @ui.lecturersListView.setModel model
624
+ @ui.lecturersListView.show
625
+ end
626
+ end
627
+
628
+ def setupListViews(index)
629
+ return false unless $TARIFICATION_MODE
630
+ string = index.model.data(index, Qt::UserRole).toString
631
+ if string.nil? || string.empty?
632
+ model = EntityItemModel.new(->(){ [] }, self)
633
+ @ui.subjectsListView.setModel model
634
+ @ui.subjectsListView.show
635
+ model = EntityItemModel.new(->(){ [] }, self)
636
+ @ui.lecturersListView.setModel model
637
+ @ui.lecturersListView.show
638
+ else
639
+ entity = Marshal.load(Base64.decode64(string))
640
+ p entity
641
+ if entity.class == Study
642
+ group = entity.groupable.get_group
643
+ course = group.course
644
+ if course.nil?
645
+ model = EntityItemModel.new(->(){ [] }, self)
646
+ @ui.subjectsListView.setModel model
647
+ @ui.subjectsListView.show
648
+ model = EntityItemModel.new(->(){ [] }, self)
649
+ @ui.lecturersListView.setModel model
650
+ @ui.lecturersListView.show
651
+ else
652
+ semester = course.current_semester
653
+ get_subjects = ->() do
654
+ if entity.lecturer && entity.subject.stub
655
+ SpecialitySubject.where(lecturer_id: entity.lecturer, speciality_id: group.speciality, semester_id: semester).map(&:subject)
656
+ else
657
+ SpecialitySubject.where(speciality_id: group.speciality, semester_id: semester).map(&:subject)
658
+ end
659
+ end
660
+ get_lecturers = ->() do
661
+ if entity.subject && entity.lecturer.stub
662
+ SpecialitySubject.where(subject_id: entity.subject, speciality_id: group.speciality, semester_id: semester).map(&:lecturer)
663
+ else
664
+ SpecialitySubject.where(speciality_id: group.speciality, semester_id: semester).map(&:lecturer)
665
+ end
666
+ end
667
+ model = EntityItemModel.new(get_subjects, self)
668
+ @ui.subjectsListView.setModel model
669
+ @ui.subjectsListView.show
670
+ model = EntityItemModel.new(get_lecturers, self)
671
+ @ui.lecturersListView.setModel model
672
+ @ui.lecturersListView.show
673
+ end
674
+ elsif entity.class == Group
675
+ group = entity
676
+ course = group.course
677
+ if course.nil?
678
+ model = EntityItemModel.new(->(){ [] }, self)
679
+ @ui.subjectsListView.setModel model
680
+ @ui.subjectsListView.show
681
+ model = EntityItemModel.new(->(){ [] }, self)
682
+ @ui.lecturersListView.setModel model
683
+ @ui.lecturersListView.show
684
+ else
685
+ semester = course.current_semester
686
+ get_subjects = ->() do
687
+ SpecialitySubject.where(speciality_id: group.speciality, semester_id: semester).map(&:subject)
688
+ end
689
+ get_lecturers = ->() do
690
+ SpecialitySubject.where(speciality_id: group.speciality, semester_id: semester).map(&:lecturer)
691
+ end
692
+ model = EntityItemModel.new(get_subjects, self)
693
+ @ui.subjectsListView.setModel model
694
+ @ui.subjectsListView.show
695
+ model = EntityItemModel.new(get_lecturers, self)
696
+ @ui.lecturersListView.setModel model
697
+ @ui.lecturersListView.show
698
+ end
699
+ end
700
+ end
701
+ end
702
+
703
+ def filterListViews(study64)
704
+ if study.subject
705
+
706
+ end
707
+ if study.lecturer
708
+
709
+ end
710
+ end
711
+
712
+ def setup_study_table_views
713
+ @ui.deleteAction.disconnect(SIGNAL('triggered()'))
714
+ monday = Date.parse(@ui.dateDateEdit.date.toString(Qt::ISODate)).monday
715
+ @study_table_models = @study_table_views.each_with_index.map do |view, index|
716
+ model = setup_study_table_view(view, monday + index)
717
+ model.disconnect(SIGNAL('studySaved(QVariant)'))
718
+ view.disconnect(SIGNAL('doubleClicked(QModelIndex)'))
719
+ view.disconnect(SIGNAL('customContextMenuRequested(QPoint)'))
720
+ view.disconnect(SIGNAL('clicked(QModelIndex)'))
721
+ model.disconnect(SIGNAL('refreshTarification(QModelIndex)'))
722
+ connect(view, SIGNAL('customContextMenuRequested(QPoint)'), model, SLOT('displayMenu(QPoint)'))
723
+ connect(view, SIGNAL('clicked(QModelIndex)')){ |index| setupListViews(index) }
724
+ connect(model, SIGNAL('refreshTarification(QModelIndex)')){ |index| setupListViews(index) }
725
+ connect(view, SIGNAL('doubleClicked(QModelIndex)'), model, SLOT('editStudy(QModelIndex)'))
726
+ #connect(model, SIGNAL('studySaved(QString)')){|study64| filterListViews(study64) }
727
+ connect(model, SIGNAL('studySaved(QVariant)'), self, SLOT('refreshTableViewModel(QVariant)'))
728
+ connect(@ui.deleteAction, SIGNAL('triggered()'), model, SLOT('removeData()'))
729
+ connect(@ui.cancelVerifyingAction, SIGNAL('triggered()'), model, SLOT('cancelColoring()'))
730
+ view.setContextMenuPolicy(Qt::CustomContextMenu)
731
+ model
732
+ end
733
+ end
734
+
735
+ def refreshTableViewModel(date_variant)
736
+ @study_table_models[date_variant.value.dayOfWeek - 1].refresh
737
+ end
738
+
739
+ def setup_study_table_view(view, date)
740
+ model = StudyTableModel.new(date, view)
741
+ view = setup_table_view2(view, model, Qt::HeaderView::Interactive)
742
+ model.columnCount.times{ |i| i.odd? ? view.setColumnWidth(i, 50) : view.setColumnWidth(i, 150) }
743
+ model.rowCount.times{ |i| view.setRowHeight(i, 50) }
744
+ model
745
+ end
746
+
747
+ def setup_table_view2(table_view, table_model, resize_mode)
748
+ table_view.setModel(table_model)
749
+ table_view.horizontalHeader.setResizeMode(resize_mode)
750
+ table_view.verticalHeader.setResizeMode(resize_mode)
751
+ table_view.show
752
+ table_view
753
+ end
754
+
755
+ ##Contract IsA[Qt::TableView], IsA[Qt::AbstractTableModel], IsA[Qt::Enum] => IsA[Qt::TableView]
756
+ def setup_table_view(table_view, table_model, resize_mode)
757
+ table_view.setModel(table_model)
758
+ table_view.horizontalHeader.setResizeMode(resize_mode)
759
+ table_view.verticalHeader.setResizeMode(Qt::HeaderView::ResizeToContents)
760
+ table_view.show
761
+ table_view
762
+ end
763
+
764
+ def open_file
765
+ filename = sender.data.value.to_s
766
+ if File.exist? filename
767
+ Database.instance.connect_to filename
768
+ update_recent filename
769
+ show_tables
770
+ end
771
+ end
772
+
773
+ #Contract String => Qt::Action
774
+ def create_recent_action(path)
775
+ action = Qt::Action.new(path[path.size-10..path.size], self)
776
+ connect(action, SIGNAL('triggered()'), self, SLOT('open_file()'))
777
+ action.setData Qt::Variant.new(path); action
778
+ end
779
+
780
+ #Contract String => Any
781
+ def update_recent(filename)
782
+ actions = @ui.recentMenu.actions
783
+ if actions.size > 5
784
+ @ui.recentMenu.clear
785
+ @ui.recentMenu.addActions([@clear_recent_action] + actions[1..actions.size-1])
786
+ else
787
+ @ui.recentMenu.addAction create_recent_action(filename)
788
+ end
789
+ end
790
+
791
+ def clear_recent_files
792
+ @ui.recentMenu.clear
793
+ @ui.recentMenu.addAction @clear_recent_action
794
+ end
795
+
796
+ def on_dateDateEdit_dateChanged
797
+ setup_dateEdit(Date.parse(@ui.dateDateEdit.date.toString(Qt::ISODate)))
798
+ setup_study_table_views if Database.instance.connected?
799
+ end
800
+
801
+ def setup_dateEdit(date)
802
+ type = date.cweek.even? ? "Чётная" : "Нечётная"
803
+ @ui.dateDateEdit.displayFormat = "Неделя №#{date.cweek} (#{type}) dddd - d MMMM yy"
804
+ end
805
+
806
+ def please_wait(&block)
807
+ @ui.statusbar.showMessage 'Please, wait...'
808
+ yield block
809
+ @ui.statusbar.clearMessage
810
+ end
811
+
812
+ def on_showManualAction_triggered
813
+ # binding doesn't include QHelpEngine
814
+ #helpEngine Qt::HelpEngineCore('test.qhc')
815
+ #links = helpEngine.linksForIdentifier('MyDialog::ChangeButton')
816
+ #if links.count
817
+ # helpData = helpEngine.fileData links.constBegin.value
818
+ # if !helpData.isEmpty
819
+ # displayHelp helpData
820
+ # end
821
+ #end
822
+ Qt::DesktopServices::openUrl(Qt::Url.new('https://github.com/Noein/TMIS/wiki'))
823
+ end
824
+
825
+ def on_addCabinetPushButton_clicked
826
+ @ui.cabinetsTableView.model.insert_new
827
+ end
828
+
829
+ def on_removeCabinetPushButton_clicked
830
+ @ui.cabinetsTableView.model.remove_current
831
+ end
832
+
833
+ def on_addCoursePushButton_clicked
834
+ @ui.coursesTableView.model.insert_new
835
+ end
836
+
837
+ def on_removeCoursePushButton_clicked
838
+ @ui.coursesTableView.model.remove_current
839
+ end
840
+
841
+ def on_addGroupPushButton_clicked
842
+ @ui.groupsTableView.model.insert_new
843
+ end
844
+
845
+ def on_removeGroupPushButton_clicked
846
+ @ui.groupsTableView.model.remove_current
847
+ end
848
+
849
+ def on_addLecturerPushButton_clicked
850
+ @ui.lecturersTableView.model.insert_new
851
+ end
852
+
853
+ def on_removeLecturerPushButton_clicked
854
+ @ui.lecturersTableView.model.remove_current
855
+ end
856
+
857
+ def on_addSemesterPushButton_clicked
858
+ @ui.semestersTableView.model.insert_new
859
+ end
860
+
861
+ def on_removeSemesterPushButton_clicked
862
+ @ui.semestersTableView.model.remove_current
863
+ end
864
+
865
+ def on_addSpecialitySubjectPushButton_clicked
866
+ @ui.specialitySubjectsTableView.model.insert_new
867
+ end
868
+
869
+ def on_removeSpecialitySubjectPushButton_clicked
870
+ @ui.specialitySubjectsTableView.model.remove_current
871
+ end
872
+
873
+ def on_addSpecialityPushButton_clicked
874
+ @ui.specialitiesTableView.model.insert_new
875
+ end
876
+
877
+ def on_removeSpecialityPushButton_clicked
878
+ @ui.specialitiesTableView.model.remove_current
879
+ end
880
+
881
+ def on_addSubgroupPushButton_clicked
882
+ @ui.subgroupsTableView.model.insert_new
883
+ end
884
+
885
+ def on_removeSubgroupPushButton_clicked
886
+ @ui.subgroupsTableView.model.remove_current
887
+ end
888
+
889
+ def on_addSubjectPushButton_clicked
890
+ @ui.subjectsTableView.model.insert_new
891
+ end
892
+
893
+ def on_removeSubjectPushButton_clicked
894
+ @ui.subjectsTableView.model.remove_current
895
+ end
896
+
897
+ def on_dataTabWidget_currentChanged(index)
898
+ if Database.instance.connected?
899
+ @table_views.each do |c, m, view|
900
+ model = view.model
901
+ model.refresh
902
+ proxy_model = model
903
+ view.model = proxy_model
904
+ end
905
+ end
906
+ end
907
+
908
+ def on_tabWidget_currentChanged(index)
909
+ if Database.instance.connected?
910
+ if index == 0
911
+ @study_table_models.each(&:refresh)
912
+ [@ui.subjectsListView, @ui.lecturersListView, @ui.cabinetsListView].each{|view| view.model.refresh }
913
+ end
914
+ end
915
+ end
916
+
917
+ def on_findByLecturerAction_triggered
918
+ FindDialog.new(:lecturer, self).show
919
+ end
920
+
921
+ def on_findBySubjectAction_triggered
922
+ FindDialog.new(:subject, self).show
923
+ end
924
+
925
+ def on_findByCabinetAction_triggered
926
+ FindDialog.new(:cabinet, self).show
927
+ end
928
+
929
+ def on_debugConsoleAction_triggered
930
+ DebugConsoleDialog.new(self).show
931
+ end
932
+
933
+ end