weft-qda 0.9.8 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/weft.rb +3 -1
- data/lib/weft/WEFT-VERSION-STRING.rb +1 -1
- data/lib/weft/backend/sqlite/schema.rb +12 -0
- data/lib/weft/exceptions.rb +5 -1
- data/lib/weft/filters.rb +5 -2
- data/lib/weft/query.rb +12 -7
- data/lib/weft/wxgui.rb +11 -11
- data/lib/weft/wxgui/controls/category_tree.rb +20 -5
- data/lib/weft/wxgui/dialogs.rb +1 -1
- data/lib/weft/wxgui/error_handler.rb +9 -7
- data/lib/weft/wxgui/inspectors.rb +8 -2
- data/lib/weft/wxgui/inspectors/query.rb +8 -2
- data/lib/weft/wxgui/lang/en.rb +2 -1
- data/test/003-code.rb +7 -0
- data/test/005-query_review.rb +18 -1
- data/test/007-output_filters.rb +18 -2
- data/test/009c_backend_sqlite_bench.rb +2 -2
- metadata +2 -2
data/lib/weft.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
|
+
# $Header: /var/cvs/weft-qda/weft-qda/lib/weft.rb,v 1.11 2006/01/08 20:27:19 brokentoy Exp $
|
2
|
+
|
1
3
|
# maybe if running under rubygems
|
2
|
-
if not defined?(
|
4
|
+
if not defined?(WEFT_SHAREDIR)
|
3
5
|
WEFT_SHAREDIR = File.join( File.dirname(__FILE__), '..', 'share')
|
4
6
|
end
|
5
7
|
|
@@ -1 +1 @@
|
|
1
|
-
WEFT_VERSION_STRING = '0.
|
1
|
+
WEFT_VERSION_STRING = '1.0.0'
|
@@ -1,4 +1,5 @@
|
|
1
1
|
module QDA::Backend::SQLite::Schema
|
2
|
+
# The tables
|
2
3
|
SCHEMA_TABLES = <<'SCHEMA_TABLES'
|
3
4
|
CREATE TABLE category (
|
4
5
|
catid INTEGER PRIMARY KEY,
|
@@ -36,6 +37,9 @@ CREATE TABLE app_preference (
|
|
36
37
|
value TEXT);
|
37
38
|
SCHEMA_TABLES
|
38
39
|
|
40
|
+
# Triggers- these currently just ensure that coding is cleaned up when a
|
41
|
+
# document or category is deleted. In the future they also trigger entries
|
42
|
+
# into the undo/redo table.
|
39
43
|
SCHEMA_TRIGGERS = <<'SCHEMA_TRIGGERS'
|
40
44
|
CREATE TRIGGER insert_category
|
41
45
|
INSERT ON category
|
@@ -62,6 +66,10 @@ END;
|
|
62
66
|
SCHEMA_TRIGGERS
|
63
67
|
|
64
68
|
# This is here because it's written, but it's not in use yet.
|
69
|
+
#
|
70
|
+
# This is an outline of adding undo/redo facility using SQLite triggers, writing
|
71
|
+
# stepped actions to a undo action table, and recording SQL to restore the database
|
72
|
+
# to its prior state.
|
65
73
|
SCHEMA_UNDO = <<'SCHEMA_UNDO'
|
66
74
|
CREATE TABLE undoable (
|
67
75
|
actionid INTEGER PRIMARY KEY,
|
@@ -117,6 +125,8 @@ BEGIN
|
|
117
125
|
END;
|
118
126
|
SCHEMA_UNDO
|
119
127
|
|
128
|
+
# Indexes - those on the coding table make a big difference to speed of
|
129
|
+
# retrieving marked text.
|
120
130
|
SCHEMA_INDEXES = <<'SCHEMA_INDEXES'
|
121
131
|
|
122
132
|
CREATE INDEX document_idx
|
@@ -130,6 +140,8 @@ ON docmeta(metaname, docid);
|
|
130
140
|
|
131
141
|
SCHEMA_INDEXES
|
132
142
|
|
143
|
+
# model query for doing fast text searches from a reverse index
|
144
|
+
# (stored as categories).
|
133
145
|
RINDEX_SEARCH_MODEL_QUERY = <<'RINDEX_SEARCH_MODEL_QUERY'
|
134
146
|
SELECT document.docid AS docid, document.doctitle AS doctitle,
|
135
147
|
MAX( 0, code.offset - ?)
|
data/lib/weft/exceptions.rb
CHANGED
data/lib/weft/filters.rb
CHANGED
@@ -21,8 +21,11 @@ module QDA
|
|
21
21
|
# +filename+, which should be a string.
|
22
22
|
def import_file(klass, filename, opts = {}, &block)
|
23
23
|
ext = filename[-3,3]
|
24
|
-
|
25
|
-
|
24
|
+
filter_class = Filters.find_import_filter(klass, ext)
|
25
|
+
if not filter_class
|
26
|
+
raise FilterError.new("Cannot import a file of type '#{ext}'")
|
27
|
+
end
|
28
|
+
import(filter_class.new(), filename, &block)
|
26
29
|
end
|
27
30
|
|
28
31
|
def import(filter, content)
|
data/lib/weft/query.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# A
|
1
|
+
# A class modelling an arbitrarily complexe boolean query expression
|
2
2
|
module QDA
|
3
3
|
class Query
|
4
4
|
attr_accessor :dbid, :root
|
@@ -76,11 +76,11 @@ class Query
|
|
76
76
|
super(app)
|
77
77
|
case identifier
|
78
78
|
when Category # a category specified directly
|
79
|
-
@identifier = identifier.
|
79
|
+
@identifier = identifier.dbid
|
80
80
|
when String # a path to a category
|
81
|
-
@identifier = identifier
|
81
|
+
@identifier = app.get_category(identifier).dbid
|
82
82
|
when Fixnum # a category id
|
83
|
-
@identifier =
|
83
|
+
@identifier = identifier
|
84
84
|
when NilClass
|
85
85
|
raise ArgumentError.new("Unspecified category for CODED BY expression")
|
86
86
|
else
|
@@ -89,14 +89,19 @@ class Query
|
|
89
89
|
end
|
90
90
|
|
91
91
|
def to_s()
|
92
|
-
|
92
|
+
path = @app.get_category(@identifier).path
|
93
|
+
"( CODED BY(#{path}) )"
|
93
94
|
end
|
94
95
|
|
95
96
|
def calculate()
|
96
|
-
cat = @app.get_category(@identifier, false)
|
97
|
+
cat = @app.get_category(@identifier, false) # Will raise NotFoundError
|
97
98
|
@app.get_text_at_category( cat )
|
99
|
+
rescue NotFoundError => err
|
100
|
+
raise CalculationError,
|
101
|
+
"Could not retrieve category identified by #{@identifier}"
|
98
102
|
end
|
99
103
|
end
|
104
|
+
|
100
105
|
|
101
106
|
class WordSearchFunction < Function
|
102
107
|
attr_reader :word
|
@@ -166,4 +171,4 @@ class Query
|
|
166
171
|
end
|
167
172
|
end
|
168
173
|
end
|
169
|
-
end
|
174
|
+
end
|
data/lib/weft/wxgui.rb
CHANGED
@@ -10,7 +10,7 @@ require 'weft/wxgui/dialogs'
|
|
10
10
|
require 'weft/wxgui/inspectors'
|
11
11
|
require 'weft/wxgui/sidebar'
|
12
12
|
require 'weft/wxgui/workarea'
|
13
|
-
require 'weft/wxgui/error_handler
|
13
|
+
require 'weft/wxgui/error_handler'
|
14
14
|
|
15
15
|
|
16
16
|
module QDA
|
@@ -285,22 +285,21 @@ module QDA
|
|
285
285
|
end
|
286
286
|
prog.step('Saving document')
|
287
287
|
@app.save_document(doc, true)
|
288
|
-
prog.
|
288
|
+
prog.title = "Importing document '#{doc.title}'"
|
289
|
+
prog.step("Indexing document '#{doc.title}'")
|
289
290
|
indexer = QDA::WordIndexer.new()
|
290
291
|
indexer.feed(doc)
|
291
|
-
prog.step(
|
292
|
+
prog.step("Saving word indexes for '#{doc.title}'")
|
292
293
|
wordcount = indexer.words.keys.length
|
293
294
|
if wordcount > 0
|
294
295
|
prog.retarget(wordcount)
|
295
296
|
@app.save_reverse_index(doc.dbid, indexer.words, prog)
|
296
297
|
end
|
297
298
|
prog.finish()
|
298
|
-
rescue UserAbortedException, IOError => err
|
299
|
+
rescue FilterError, UserAbortedException, IOError => err
|
299
300
|
prog.finish() if prog
|
300
301
|
app.delete_document(doc) if doc && doc.dbid
|
301
|
-
|
302
|
-
"Document not imported",
|
303
|
-
OK|ICON_ERROR).show_modal()
|
302
|
+
ErrorDialog.display( "Document not imported", err.to_s() )
|
304
303
|
@workarea.cursor = Wx::Cursor.new(Wx::CURSOR_ARROW)
|
305
304
|
end
|
306
305
|
end
|
@@ -455,8 +454,9 @@ module QDA
|
|
455
454
|
# returns true if a new project was started, false if not
|
456
455
|
def on_open_project(e)
|
457
456
|
return false unless on_close_project(e)
|
458
|
-
file_dialog = FileDialog.new(@workarea, "Open Project",
|
459
|
-
|
457
|
+
file_dialog = FileDialog.new( @workarea, "Open Project",
|
458
|
+
"", "",
|
459
|
+
"Weft QDA Project Files (*.qdp)|*.qdp" )
|
460
460
|
return false unless file_dialog.show_modal() == Wx::ID_OK
|
461
461
|
|
462
462
|
open_project( file_dialog.get_path() )
|
@@ -528,7 +528,7 @@ module QDA
|
|
528
528
|
end
|
529
529
|
|
530
530
|
def on_saveas_project(e)
|
531
|
-
wildcard = "QDA Project Files (*.qdp)|*.qdp"
|
531
|
+
wildcard = "Weft QDA Project Files (*.qdp)|*.qdp"
|
532
532
|
file_dialog = Wx::FileDialog.new(@workarea, "Save Project As",
|
533
533
|
"", "", wildcard, Wx::SAVE)
|
534
534
|
|
@@ -565,7 +565,7 @@ module QDA
|
|
565
565
|
end
|
566
566
|
|
567
567
|
def on_import_n6(e)
|
568
|
-
wildcard = "Project Files (*.stp)|*.stp"
|
568
|
+
wildcard = "N6 Project Files (*.stp)|*.stp"
|
569
569
|
file_dialog = Wx::FileDialog.new(@workarea, "Import N6 Project", "",
|
570
570
|
"", wildcard )
|
571
571
|
if file_dialog.show_modal() == Wx::ID_OK
|
@@ -54,7 +54,7 @@ module QDA::GUI
|
|
54
54
|
def delete(id)
|
55
55
|
super(id)
|
56
56
|
del_cat = data.delete(id)
|
57
|
-
if del_cat.parent
|
57
|
+
if del_cat.parent && old_parent_id = value_to_ident(del_cat.parent)
|
58
58
|
set_item_data( old_parent_id,
|
59
59
|
@client.app.get_category(del_cat.parent.dbid) )
|
60
60
|
end
|
@@ -194,9 +194,11 @@ module QDA::GUI
|
|
194
194
|
|
195
195
|
movee = get_item_data( from )
|
196
196
|
destination = get_item_data( to )
|
197
|
+
# if for some reason these tree ids didn't correspond to categories...
|
198
|
+
return dont_move(from) unless movee and destination
|
197
199
|
# don't move root nodes
|
198
200
|
return dont_move(from) if not movee.parent
|
199
|
-
# ignore if no move
|
201
|
+
# ignore if no move - target is same as current parent
|
200
202
|
return dont_move(from) if movee.parent == destination
|
201
203
|
# don't attach to descendants
|
202
204
|
return dont_move(from) if destination.is_descendant_of?(movee)
|
@@ -218,14 +220,27 @@ module QDA::GUI
|
|
218
220
|
# of the node.
|
219
221
|
def move_item(from, to)
|
220
222
|
# don't alter if unchanged
|
221
|
-
|
222
|
-
|
223
|
+
from_parent = get_item_parent(from)
|
224
|
+
return from if from_parent == to
|
225
|
+
|
226
|
+
# remember expanded-or-collapsed state
|
227
|
+
reexpand = expanded?(from_parent)
|
228
|
+
|
229
|
+
# create a new tree item under the new parent
|
230
|
+
new_child = append_item(to, get_item_text(from),
|
231
|
+
get_item_data(from))
|
232
|
+
|
233
|
+
# recursively move all children underneath to the new node
|
223
234
|
child = get_first_child(from)[0]
|
224
235
|
while child != 0
|
225
236
|
move_item(child, new_child)
|
226
237
|
child = get_first_child(from)[0]
|
227
238
|
end
|
239
|
+
|
240
|
+
# restore my visibility settings
|
241
|
+
expand(to) if reexpand
|
228
242
|
delete( from )
|
243
|
+
|
229
244
|
new_child
|
230
245
|
end
|
231
246
|
|
@@ -311,4 +326,4 @@ module QDA::GUI
|
|
311
326
|
select_item(id)
|
312
327
|
end
|
313
328
|
end
|
314
|
-
end
|
329
|
+
end
|
data/lib/weft/wxgui/dialogs.rb
CHANGED
@@ -172,7 +172,7 @@ module QDA::GUI
|
|
172
172
|
filt::EXTENSIONS,
|
173
173
|
filt::EXTENSIONS ] }.join('|')
|
174
174
|
super(parent, Lang::IMPORT_DOC_DIALOGUE_TITLE,
|
175
|
-
"", "", wildcard, Wx::MULTIPLE)
|
175
|
+
"", "", wildcard, Wx::MULTIPLE|Wx::FILE_MUST_EXIST)
|
176
176
|
end
|
177
177
|
|
178
178
|
# return each selected file in turn into a block, passing in +path+ and
|
@@ -40,19 +40,21 @@ class CrashReportDialog < Wx::Dialog
|
|
40
40
|
end
|
41
41
|
|
42
42
|
def crash_details()
|
43
|
-
@crash ||= { 'when'
|
44
|
-
'os'
|
45
|
-
'config'
|
43
|
+
@crash ||= { 'when' => Time.now.to_s(),
|
44
|
+
'os' => Config::CONFIG['target_os'],
|
45
|
+
'config' => Config::CONFIG['build'],
|
46
|
+
'weft_version' => WEFT_VERSION_STRING,
|
46
47
|
'ruby_version' => [ Config::CONFIG['MAJOR'],
|
47
48
|
Config::CONFIG['MINOR'],
|
48
49
|
Config::CONFIG['TEENY'] ].join('.'),
|
49
|
-
'rs2exe'
|
50
|
-
'backtrace'
|
51
|
-
|
50
|
+
'rs2exe' => rs2exe,
|
51
|
+
'backtrace' => ( [ err.inspect ] +
|
52
|
+
err.backtrace ).join("\n") }
|
52
53
|
end
|
53
54
|
|
54
55
|
def crash_details_string
|
55
|
-
%w[when os config
|
56
|
+
%w[when weft_version os config
|
57
|
+
ruby_version rs2exe backtrace].inject('') do | str, key |
|
56
58
|
str << "#{key}: #{crash_details[key]}\n"
|
57
59
|
end
|
58
60
|
end
|
@@ -177,9 +177,13 @@ module QDA::GUI
|
|
177
177
|
|
178
178
|
# implements keyboard shortcuts for this window - these are activated by
|
179
179
|
# interaction with the text-box.
|
180
|
-
# currently,
|
180
|
+
# currently,
|
181
|
+
# CTRL-m is mapped to "Mark"
|
182
|
+
# CTRL-, is mapped to "Unmark"
|
183
|
+
# CTRL-f is mapped to "Start find"
|
184
|
+
|
181
185
|
def on_key_down(evt)
|
182
|
-
return unless evt.control_down()
|
186
|
+
return evt.skip() unless evt.control_down()
|
183
187
|
case evt.key_code()
|
184
188
|
when 70 # "f"
|
185
189
|
@text_box.start_find(self) if @text_box.respond_to?(:start_find)
|
@@ -188,6 +192,8 @@ module QDA::GUI
|
|
188
192
|
when 44 # ","
|
189
193
|
on_uncode(evt)
|
190
194
|
end
|
195
|
+
# required so default shortcuts like CTRL-C, CTRL-V work
|
196
|
+
evt.skip()
|
191
197
|
end
|
192
198
|
|
193
199
|
# the current category active in this window
|
@@ -80,8 +80,12 @@ module QDA::GUI
|
|
80
80
|
@last_query = query
|
81
81
|
set_cursor( Wx::BUSY_CURSOR )
|
82
82
|
@last_results = @last_query.calculate()
|
83
|
-
set_cursor( Wx::NORMAL_CURSOR )
|
84
83
|
return @last_results
|
84
|
+
rescue QDA::CalculationError => err
|
85
|
+
ErrorDialog.display("Error computing query", err.to_s)
|
86
|
+
return @last_results = QDA::FragmentTable.new()
|
87
|
+
ensure
|
88
|
+
set_cursor( Wx::NORMAL_CURSOR )
|
85
89
|
end
|
86
90
|
|
87
91
|
def get_target_argument(offset, as_string = false)
|
@@ -143,7 +147,9 @@ module QDA::GUI
|
|
143
147
|
def on_save_results(e)
|
144
148
|
# check for saved results
|
145
149
|
Wx::BusyCursor.busy do
|
146
|
-
|
150
|
+
query = get_query()
|
151
|
+
return unless query
|
152
|
+
results = get_results( query )
|
147
153
|
return unless results
|
148
154
|
@text_box.populate(results)
|
149
155
|
end
|
data/lib/weft/wxgui/lang/en.rb
CHANGED
@@ -52,7 +52,8 @@ Are you sure you want to proceed?"
|
|
52
52
|
DELETE_CATEGORY_TITLE= "Confirm category deletion"
|
53
53
|
DELETE_CATEGORY_WARNING =
|
54
54
|
"The category '%s' will permanently deleted, along with its memo and
|
55
|
-
any coding associated with it.
|
55
|
+
any coding associated with it. Any categories attached underneath this
|
56
|
+
category in the tree will also be deleted.
|
56
57
|
|
57
58
|
Are you sure you want to proceed?"
|
58
59
|
|
data/test/003-code.rb
CHANGED
@@ -336,6 +336,13 @@ class TestCode < Test::Unit::TestCase
|
|
336
336
|
|
337
337
|
sets = tbl.sets
|
338
338
|
assert_equal(1, sets.length)
|
339
|
+
|
340
|
+
tbl_2 = CodingTable.new()
|
341
|
+
result = tbl_2.merge(tbl)
|
342
|
+
assert( tbl_2.key?(3) )
|
343
|
+
assert_equal(1, tbl_2.num_of_docs)
|
344
|
+
assert_equal(6, tbl_2.num_of_chars)
|
345
|
+
assert_equal(1, tbl_2.num_of_codes)
|
339
346
|
end
|
340
347
|
|
341
348
|
def test_fragment_table
|
data/test/005-query_review.rb
CHANGED
@@ -101,16 +101,26 @@ class TestQuery < Test::Unit::TestCase
|
|
101
101
|
|
102
102
|
|
103
103
|
|
104
|
-
def
|
104
|
+
def test_and
|
105
105
|
cs_1 = CodingTable.new()
|
106
106
|
cs_2 = CodingTable.new()
|
107
|
+
|
107
108
|
cs_1.add( Code.new(3, 0, 4) )
|
109
|
+
expr = Query::AND.new(cs_1, cs_2)
|
110
|
+
result = expr.calculate()
|
111
|
+
|
112
|
+
assert_equal(0, result.num_of_docs)
|
113
|
+
assert_equal(0, result.num_of_chars)
|
114
|
+
assert_equal(0, result.num_of_passages)
|
115
|
+
|
108
116
|
cs_2.add( Code.new(3, 3, 5) )
|
109
117
|
expr = Query::AND.new(cs_1, cs_2)
|
110
118
|
result = expr.calculate()
|
111
119
|
assert_equal(1, result.num_of_docs)
|
112
120
|
assert_equal(1, result.num_of_chars)
|
113
121
|
assert_equal(1, result.num_of_passages)
|
122
|
+
|
123
|
+
# empty OR
|
114
124
|
end
|
115
125
|
|
116
126
|
def test_and_not
|
@@ -129,6 +139,13 @@ class TestQuery < Test::Unit::TestCase
|
|
129
139
|
cs_1 = CodingTable.new()
|
130
140
|
cs_2 = CodingTable.new()
|
131
141
|
cs_1.add( Code.new(3, 0, 4) )
|
142
|
+
|
143
|
+
expr = Query::OR.new(cs_1, cs_2)
|
144
|
+
result = expr.calculate()
|
145
|
+
assert_equal(1, result.num_of_docs)
|
146
|
+
assert_equal(4, result.num_of_chars)
|
147
|
+
assert_equal(1, result.num_of_passages)
|
148
|
+
|
132
149
|
cs_2.add( Code.new(3, 3, 5) )
|
133
150
|
expr = Query::OR.new(cs_1, cs_2)
|
134
151
|
result = expr.calculate()
|
data/test/007-output_filters.rb
CHANGED
@@ -37,7 +37,9 @@ class TestFilter < Test::Unit::TestCase
|
|
37
37
|
|
38
38
|
def test_export_document()
|
39
39
|
doc = Document.new('foo')
|
40
|
-
doc.
|
40
|
+
doc.memo = 'The memo'
|
41
|
+
doc << "First paragraph blah blah\n\n"
|
42
|
+
doc << "Second paragraph bar baz qux\n\n"
|
41
43
|
filter = Filters::DocumentTextOutput.new()
|
42
44
|
|
43
45
|
tmp = Tempfile.new('doc_text')
|
@@ -45,7 +47,21 @@ class TestFilter < Test::Unit::TestCase
|
|
45
47
|
tmp.rewind()
|
46
48
|
contents = tmp.read()
|
47
49
|
# Proper template not written yet
|
48
|
-
|
50
|
+
assert_match(/foo/m, contents, 'Looks a bit like the document with title')
|
51
|
+
assert_match(/blah/m, contents, 'Looks a bit like the document')
|
52
|
+
assert_match(/qux/m, contents, 'Looks a bit like the document')
|
53
|
+
|
54
|
+
filter = Filters::DocumentHTMLOutput.new()
|
55
|
+
|
56
|
+
tmp = Tempfile.new('doc_text')
|
57
|
+
filter.write(doc, tmp)
|
58
|
+
tmp.rewind()
|
59
|
+
contents = tmp.read()
|
60
|
+
puts contents
|
61
|
+
# Proper template not written yet
|
62
|
+
assert_match(/<h1>foo<\/h1>/m, contents, 'Looks a bit like the document with title')
|
63
|
+
assert_match(/blah/m, contents, 'Looks a bit like the document')
|
64
|
+
assert_match(/qux/m, contents, 'Looks a bit like the document')
|
49
65
|
end
|
50
66
|
|
51
67
|
def test_export_category()
|
@@ -43,7 +43,7 @@ class TestSQLiteBenchmark < Test::Unit::TestCase
|
|
43
43
|
doc.append("This is various text that'll be used for testing")
|
44
44
|
doc.append("Some more text with different letters?")
|
45
45
|
|
46
|
-
measure('Save Document',
|
46
|
+
measure('Save Document', 100) do
|
47
47
|
doc.dbid = nil
|
48
48
|
@app.save_document(doc, true)
|
49
49
|
end
|
@@ -67,7 +67,7 @@ class TestSQLiteBenchmark < Test::Unit::TestCase
|
|
67
67
|
@app.install_clean()
|
68
68
|
|
69
69
|
catparent = nil
|
70
|
-
measure('Save Category',
|
70
|
+
measure('Save Category', 100) do
|
71
71
|
cat = QDA::Category.new("About 'Something'", catparent, 'the "memo"')
|
72
72
|
@app.save_category(cat)
|
73
73
|
catparent = cat
|
metadata
CHANGED