keepyourhead 0.2.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 (47) hide show
  1. data/COPYING.txt +674 -0
  2. data/LICENSE.rdoc +20 -0
  3. data/README.rdoc +3 -0
  4. data/bin/keepyourhead +6 -0
  5. data/data/glade/DialogEditLatex.glade +180 -0
  6. data/data/glade/DialogEditText.glade +66 -0
  7. data/data/glade/DialogTrainStart.glade +101 -0
  8. data/data/glade/WindowEdit.glade +1419 -0
  9. data/data/glade/WindowTrain.glade +271 -0
  10. data/data/images/error_back.png +0 -0
  11. data/data/images/error_front.png +0 -0
  12. data/data/images/loading_back.png +0 -0
  13. data/data/images/loading_front.png +0 -0
  14. data/lib/Keepyourhead.rb +62 -0
  15. data/lib/Keepyourhead/Application.rb +94 -0
  16. data/lib/Keepyourhead/Cache.rb +233 -0
  17. data/lib/Keepyourhead/Compilation.rb +38 -0
  18. data/lib/Keepyourhead/Images.rb +62 -0
  19. data/lib/Keepyourhead/Preferences.rb +52 -0
  20. data/lib/Keepyourhead/Requirements.rb +72 -0
  21. data/lib/Keepyourhead/Resources.rb +45 -0
  22. data/lib/Keepyourhead/Training.rb +132 -0
  23. data/lib/Keepyourhead/Utils.rb +97 -0
  24. data/lib/Keepyourhead/Version.rb +43 -0
  25. data/lib/Keepyourhead/database/Base.rb +77 -0
  26. data/lib/Keepyourhead/database/BaseStatistic.rb +63 -0
  27. data/lib/Keepyourhead/database/BaseTopicFlashcardContainer.rb +43 -0
  28. data/lib/Keepyourhead/database/Collection.rb +43 -0
  29. data/lib/Keepyourhead/database/Database.rb +139 -0
  30. data/lib/Keepyourhead/database/File.rb +224 -0
  31. data/lib/Keepyourhead/database/FileRoot.rb +44 -0
  32. data/lib/Keepyourhead/database/Flashcard.rb +116 -0
  33. data/lib/Keepyourhead/database/Topic.rb +40 -0
  34. data/lib/Keepyourhead/database/Visitor.rb +33 -0
  35. data/lib/Keepyourhead/database/XmlAccessor.rb +315 -0
  36. data/lib/Keepyourhead/gui/Action.rb +91 -0
  37. data/lib/Keepyourhead/gui/DialogEditLatex.rb +132 -0
  38. data/lib/Keepyourhead/gui/DialogEditText.rb +104 -0
  39. data/lib/Keepyourhead/gui/DialogTrainStart.rb +63 -0
  40. data/lib/Keepyourhead/gui/FlashcardViewController.rb +112 -0
  41. data/lib/Keepyourhead/gui/WindowEdit.rb +469 -0
  42. data/lib/Keepyourhead/gui/WindowEditActions.rb +440 -0
  43. data/lib/Keepyourhead/gui/WindowEditAdapters.rb +329 -0
  44. data/lib/Keepyourhead/gui/WindowTrain.rb +167 -0
  45. data/lib/Keepyourhead/style/Style.rb +48 -0
  46. data/lib/Keepyourhead/style/StyleLatex.rb +235 -0
  47. metadata +100 -0
@@ -0,0 +1,469 @@
1
+ # Copyright 2008 Burghard Oliver
2
+ #
3
+ # This file is part of KeepYourHead.
4
+ #
5
+ # KeepYourHead is free software: you can redistribute it and/or modify
6
+ # it under the terms of the GNU General Public License as published by
7
+ # the Free Software Foundation, either version 3 of the License, or
8
+ # (at your option) any later version.
9
+ #
10
+ # KeepYourHead is distributed in the hope that it will be useful,
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ # GNU General Public License for more details.
14
+ #
15
+ # You should have received a copy of the GNU General Public License
16
+ # along with KeepYourHead. If not, see <http://www.gnu.org/licenses/>.
17
+
18
+
19
+ module KeepYourHead
20
+ class WindowEdit
21
+ attr_reader :database
22
+ attr_reader :selected
23
+ attr_reader :widget
24
+
25
+ # Interface for implementing Database::Visitor
26
+ ### class Database::Visitor
27
+ def onItemChange(item)
28
+ iter = @mapItemIter[item]
29
+ if iter then # can be called before item is added with onItemAdd
30
+ iter.set_value( 0, MyAdaptor.name(item) )
31
+ updateView
32
+ end
33
+ end
34
+ def onItemRemove(item)
35
+ @selectedRealization = MyAdaptor.realization( item )
36
+
37
+ @selected = nil if @selected == item
38
+
39
+ iter = @mapItemIter[item]
40
+ assert iter
41
+
42
+ @mapItemIter[item] = nil
43
+ @mapIterItem[iter] = nil
44
+
45
+ model = @treeview_flashcards.model
46
+ model.remove iter
47
+ end
48
+ def onItemInsert(item)
49
+ previous = MyAdaptor.previousSibling(item)
50
+ parent = MyAdaptor.parent(item)
51
+
52
+ previousIter = previous ? @mapItemIter[previous] : nil
53
+ parentIter = @mapItemIter[parent]
54
+
55
+ addTreeModelItemAfter( @treeview_flashcards.model, item, parentIter, previousIter)
56
+ iter = @mapItemIter[item]
57
+
58
+ if parentIter then
59
+ @treeview_flashcards.expand_row(parentIter.path, true)
60
+ else
61
+ @treeview_flashcards.expand_row(iter.path, true)
62
+ end
63
+
64
+ if @selectedRealization == MyAdaptor.realization( item ) then
65
+ @treeview_flashcards.selection.select_iter iter
66
+ end
67
+ end
68
+ def onReload
69
+ build_tree_model
70
+ end
71
+ ### end
72
+
73
+ def database=(database)
74
+ self.database.viewerRemove self if self.database
75
+
76
+ @database = database
77
+ onReload
78
+
79
+ self.database.viewerAdd self if self.database
80
+ end
81
+
82
+ # insert a whole subtree
83
+ def addTreeModelItemAfter( model, item, parentIter = nil, previousIter = nil )
84
+ iter = model.insert_after(parentIter,previousIter)
85
+
86
+ iter.set_value( 0, MyAdaptor.name(item) )
87
+ iter.set_value( 1, item )
88
+ iter.set_value( 2, @pixbufs[item.class] )
89
+
90
+ @mapIterItem[iter] = item
91
+ @mapItemIter[item] = iter
92
+
93
+ lastChild = nil
94
+ MyAdaptor.children(item).each{ |childItem|
95
+ lastChild = addTreeModelItemAfter(model, childItem, iter, lastChild)
96
+ }
97
+
98
+ iter
99
+ end
100
+ def build_tree_model
101
+ model = Gtk::TreeStore.new(String, Object, Gdk::Pixbuf)
102
+
103
+ @mapIterItem = {}
104
+ @mapItemIter = {}
105
+
106
+ lastChild = nil
107
+ MyAdaptor.children(self.database).each{ |childItem|
108
+ lastChild = addTreeModelItemAfter(model, childItem, nil, lastChild)
109
+ } if self.database
110
+
111
+ @treeview_flashcards.model = model
112
+ @treeview_flashcards.expand_all
113
+ end
114
+
115
+ Widgets = [
116
+ "treeview_flashcards", "image_front", "image_back",
117
+ "entryNameFlashcard", "entryTypeFlashcard",
118
+ "entryNameTopic",
119
+ "entryNameCollection",
120
+ "entryOriginAuthor", "entryOriginType", "entryOriginDate",
121
+ "textviewLatexHeader", "textviewLatexPrepend", "textviewLatexAppend",
122
+ "notebookContent",
123
+ "hboxFront", "hscaleFront",
124
+ "hboxBack", "hscaleBack",
125
+ "labelFileCount", "labelFileDistribution",
126
+ "labelCollectionCount", "labelCollectionDistribution",
127
+ ]
128
+
129
+ def initialize(database)
130
+ @disableUpdateView = nil
131
+
132
+
133
+ @glade = GladeXML.new(Resources::system("glade/WindowEdit.glade")) { |handler| method(handler) }
134
+
135
+ @pixbufs = {
136
+ Database::File => "gtk-file",
137
+ Database::Collection => "gnome-stock-book-green",
138
+ Database::Topic => "gtk-directory",
139
+ Database::Flashcard => "gnome-stock-attach",
140
+ }
141
+
142
+ @pixbufs.keys.each { |k|
143
+ iconFactory = Gtk::IconFactory.lookup_default @pixbufs[k]
144
+ @pixbufs[k] = iconFactory.render_icon Gtk::Style.new, Gtk::Widget::TEXT_DIR_NONE, Gtk::STATE_NORMAL, Gtk::IconSize::MENU, nil, nil
145
+ }
146
+ # a bit complicated because Gnome Stock items are not directly supported
147
+
148
+ @window = @glade.get_widget("window_edit")
149
+ @widget = @window
150
+
151
+ Widgets.each { |name|
152
+ widget = @glade.get_widget(name)
153
+ assert widget
154
+ eval("@#{name} = widget")
155
+ }
156
+
157
+ @lazyUpdate = LazyUpdate.new( self )
158
+
159
+
160
+ renderer = Gtk::CellRendererText.new
161
+ renderer.xalign = 0.0
162
+
163
+ # col_offset = @treeview_flashcards.insert_column(-1, "Name",
164
+ # renderer,
165
+ # 'text' => 0)
166
+ column = Gtk::TreeViewColumn.new( "Name" )
167
+ renderer = Gtk::CellRendererPixbuf.new
168
+ column.pack_start renderer, false
169
+ # column.add_attribute renderer, "stock-id", 2
170
+ column.add_attribute renderer, "pixbuf", 2
171
+ renderer = Gtk::CellRendererText.new
172
+ column.pack_start renderer, true
173
+ column.add_attribute renderer, "text", 0
174
+
175
+ @treeview_flashcards.insert_column( column, -1 )
176
+
177
+ @flashcardViewControllerFront = FlashcardViewController.new @image_front, @hscaleFront, @hboxFront
178
+ @flashcardViewControllerBack = FlashcardViewController.new @image_back, @hscaleBack, @hboxBack
179
+
180
+ initActions
181
+
182
+ self.database = database
183
+
184
+ @window.show
185
+
186
+ updateView
187
+ end
188
+
189
+ def on_window_edit_delete_event
190
+ callAction("Quit")
191
+ @window.destroy if self.database != nil
192
+ end
193
+
194
+ def on_window_edit_destroy
195
+ self.database = nil
196
+ @treeview_flashcards, @image_front, @image_back = nil, nil, nil
197
+
198
+ @actions.values.each { |a|
199
+ a.widgets.each { |w|
200
+ a.removeActivator w
201
+ }
202
+ }
203
+
204
+ $application.quit
205
+ end
206
+
207
+ def helperRead( obj, baseMethod )
208
+ [ obj.method( baseMethod ), nil ]
209
+ end
210
+ def helperWrite( obj, baseMethod )
211
+ [ nil, obj.method( (baseMethod.to_s+"=").to_sym) ]
212
+ end
213
+ def helper( obj, baseMethod )
214
+ [ obj.method( baseMethod ), obj.method( (baseMethod.to_s+"=").to_sym) ]
215
+ end
216
+
217
+ def visibleFields
218
+ fields = []
219
+
220
+ if @selected then
221
+ fields +=
222
+ case @selected
223
+ when Database::File
224
+ [
225
+ [ helperWrite(@labelFileCount,:text), proc{ "#{@selected.count} ( #{@selected.countActive} / #{@selected.countPassive} )" }, nil ].flatten,
226
+ [ helperWrite(@labelFileDistribution,:text), proc{
227
+ "#{d = @selected.distribution; d[d.length+3] = 0; d = d.map{ |v| v || 0 }; d.join(" | ")}" }, nil ].flatten,
228
+ ]
229
+ when Database::Collection
230
+ [
231
+ [ helperWrite(@labelCollectionCount,:text), proc{ "#{@selected.count} ( #{@selected.countActive} / #{@selected.countPassive} )" }, nil ].flatten,
232
+ [ helperWrite(@labelCollectionDistribution,:text), proc{
233
+ "#{d = @selected.distribution; d[d.length+3] = 0; d = d.map{ |v| v || 0 }; d.join(" | ")}" }, nil ].flatten,
234
+
235
+ [ helper(@entryNameCollection,:text), helper(@selected,:name) ].flatten,
236
+ [ helper(@entryOriginAuthor,:text), helper(@selected,:originAuthor) ].flatten,
237
+ [ helper(@entryOriginType,:text), helper(@selected,:originType) ].flatten,
238
+ [ helper(@entryOriginDate,:text), helper(@selected,:originDate) ].flatten,
239
+ [ helper(@textviewLatexHeader.buffer,:text), helper(@selected,:latexHeader) ].flatten,
240
+ [ helper(@textviewLatexPrepend.buffer,:text), helper(@selected,:latexPrepend) ].flatten,
241
+ [ helper(@textviewLatexAppend.buffer,:text), helper(@selected,:latexAppend) ].flatten,
242
+ ]
243
+ when Database::Topic
244
+ [
245
+ [ helper(@entryNameTopic,:text), helper(@selected,:name) ].flatten,
246
+ ]
247
+ when Database::Flashcard
248
+ [
249
+ [ helper(@entryNameFlashcard,:text), helper(@selected,:name) ].flatten,
250
+ [ helper(@entryTypeFlashcard,:text), helper(@selected,:type) ].flatten,
251
+ ]
252
+ else
253
+ throw ExceptionNotImplemented.new
254
+ end
255
+ end
256
+
257
+ fields
258
+ end
259
+
260
+ def updateImages( )
261
+ @lastFilenames ||= {}
262
+
263
+ [Database::FRONT, Database::BACK].each{ |type|
264
+ $cache.hasCacheItem( self.selected, type ) { |cacheItem|
265
+ filenames = Images.fromCacheItem( type, cacheItem )
266
+
267
+ case type
268
+ when Database::FRONT
269
+ @flashcardViewControllerFront.filenames = filenames
270
+ when Database::BACK
271
+ @flashcardViewControllerBack.filenames = filenames
272
+ end
273
+ }
274
+ }
275
+ end
276
+
277
+ class LazyUpdate
278
+ attr_accessor :running, :state, :selected, :view, :when
279
+
280
+ Delay = 0.5
281
+ Timeout = 1000 * Delay / 3.0 * 1.1
282
+
283
+ def initialize( view )
284
+ self.running = false
285
+ self.view = view
286
+ end
287
+
288
+ def restart
289
+ if not self.selected == view.selected then
290
+ if view.selected.kind_of? Database::Flashcard then
291
+ self.selected = view.selected
292
+ else
293
+ self.selected = nil # no more updates
294
+ end
295
+ end
296
+
297
+ view.updateImages if view.selected == self.selected
298
+
299
+ self.state = :waiting
300
+ self.when = Time.now + Delay
301
+
302
+ if not self.running then
303
+ self.running = true
304
+ Gtk.timeout_add( Timeout ) {
305
+ if self.state == :waiting and self.when < Time.now then
306
+ self.state = :running
307
+
308
+ Thread.new {
309
+ begin
310
+ $cache.getCacheItem( self.selected, Database::FRONT ) if self.selected
311
+ $cache.getCacheItem( self.selected, Database::BACK ) if self.selected
312
+ rescue
313
+ puts $!.backtrace, $!
314
+ end
315
+ }
316
+ end
317
+
318
+ if self.state == :running then
319
+ ret = true
320
+ $cache.hasCacheItem( self.selected, Database::FRONT ) {|cacheItem| ret &&= cacheItem } if self.selected
321
+ $cache.hasCacheItem( self.selected, Database::BACK ) { |cacheItem| ret &&= cacheItem } if self.selected
322
+ if ret then
323
+ self.state = :done
324
+ end
325
+
326
+ view.updateImages if view.selected == self.selected
327
+ end
328
+ self.running = (self.state != :done)
329
+
330
+ self.running
331
+ }
332
+ end
333
+ end
334
+ end
335
+
336
+ def updateView
337
+ return unless @treeview_flashcards
338
+
339
+ return if @disableUpdateView
340
+ @disableUpdateView = true
341
+ @disableOnContentChange = true # unterdrücke change events während view update
342
+
343
+ @lastSelected = selected
344
+
345
+ iter = @treeview_flashcards && @treeview_flashcards.selection && @treeview_flashcards.selection.selected
346
+ @selected = iter && iter[1]
347
+
348
+ visibleFields.each{ |readView, writeView, readData, writeData|
349
+ if writeView and readData then
350
+ if not readView or readView.call != readData.call then
351
+ writeView.call readData.call
352
+ end
353
+ end
354
+ }
355
+
356
+ if @selected then
357
+ case @selected
358
+ when Database::File
359
+ @notebookContent.page = 1
360
+ when Database::Collection
361
+ @notebookContent.page = 2
362
+ when Database::Topic
363
+ @notebookContent.page = 3
364
+ when Database::Flashcard
365
+ @notebookContent.page = 4
366
+
367
+ updateImages
368
+ @lazyUpdate.restart
369
+ else
370
+ throw ExceptionNotSupported.new
371
+ end
372
+ else
373
+ @notebookContent.page = 0
374
+ end
375
+
376
+ @actions.values.each { |a| a.updateEnable }
377
+
378
+ @disableOnContentChange = false
379
+ @disableUpdateView = false
380
+ end
381
+
382
+ def onContentChange
383
+ return if @disableOnContentChange
384
+ @disableOnContentChange = true
385
+
386
+ visibleFields.each{ |readView, writeView, readData, writeData|
387
+ if writeData and readView then
388
+ if not readData or readView.call != readData.call then
389
+ writeData.call readView.call
390
+ end
391
+ end
392
+ }
393
+
394
+ updateView
395
+ @disableOnContentChange = false
396
+ end
397
+
398
+ def on_treeview_flashcards_cursor_changed
399
+ # onContentChange
400
+ updateView
401
+ end
402
+
403
+ def onEditLatexHeader
404
+ if @selected && @selected.class == Database::Collection then
405
+ dialog = DialogEditText.new( "Header" )
406
+ dialog.text = @selected.latexHeader
407
+ if dialog.run then
408
+ @selected.latexHeader = dialog.text
409
+ end
410
+ dialog.destroy
411
+ end
412
+ end
413
+ def onEditLatexPrepend
414
+ if @selected && @selected.class == Database::Collection then
415
+ dialog = DialogEditText.new( "Vorspann" )
416
+ dialog.text = @selected.latexPrepend
417
+ if dialog.run then
418
+ @selected.latexPrepend = dialog.text
419
+ end
420
+ dialog.destroy
421
+ end
422
+ end
423
+ def onEditLatexAppend
424
+ if @selected && @selected.class == Database::Collection then
425
+ dialog = DialogEditText.new( "Anhang" )
426
+ dialog.text = @selected.latexAppend
427
+ if dialog.run then
428
+ @selected.latexAppend = dialog.text
429
+ end
430
+ dialog.destroy
431
+ end
432
+ end
433
+
434
+ def onEditFront
435
+ if @selected && @selected.class == Database::Flashcard then
436
+ dialog = DialogEditFlashcardLatex.new @selected, Database::FRONT
437
+ dialog.text = @selected.front
438
+
439
+ if dialog.run then
440
+ @selected.front = dialog.text
441
+ end
442
+
443
+ dialog.destroy
444
+ end
445
+ end
446
+ def onEditBack
447
+ if @selected && @selected.class == Database::Flashcard then
448
+ dialog = DialogEditFlashcardLatex.new @selected, Database::BACK
449
+ dialog.text = @selected.back
450
+
451
+ if dialog.run then
452
+ @selected.back = dialog.text
453
+ end
454
+
455
+ dialog.destroy
456
+ end
457
+ end
458
+
459
+
460
+
461
+
462
+
463
+
464
+ end
465
+
466
+
467
+
468
+ end
469
+