clevic 0.13.0.b9 → 0.13.0.b10
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +3 -0
- data/lib/clevic/action_builder.rb +16 -16
- data/lib/clevic/ar_methods.rb +22 -22
- data/lib/clevic/attribute_list.rb +5 -5
- data/lib/clevic/cache_table.rb +18 -18
- data/lib/clevic/dataset_roller.rb +3 -3
- data/lib/clevic/default_view.rb +4 -4
- data/lib/clevic/delegate.rb +8 -8
- data/lib/clevic/delegates/combo_delegate.rb +22 -22
- data/lib/clevic/delegates/distinct_delegate.rb +5 -5
- data/lib/clevic/delegates/relational_delegate.rb +6 -6
- data/lib/clevic/delegates/set_delegate.rb +3 -3
- data/lib/clevic/dirty.rb +1 -1
- data/lib/clevic/emitter.rb +3 -3
- data/lib/clevic/extensions.rb +4 -4
- data/lib/clevic/field.rb +68 -68
- data/lib/clevic/field_valuer.rb +26 -26
- data/lib/clevic/filter_command.rb +6 -6
- data/lib/clevic/framework.rb +3 -3
- data/lib/clevic/generic_format.rb +2 -2
- data/lib/clevic/many_field.rb +3 -3
- data/lib/clevic/model_builder.rb +88 -84
- data/lib/clevic/model_column.rb +13 -13
- data/lib/clevic/ordered_dataset.rb +7 -7
- data/lib/clevic/qt/action_builder.rb +3 -3
- data/lib/clevic/qt/browser.rb +28 -28
- data/lib/clevic/qt/clipboard.rb +5 -5
- data/lib/clevic/qt/combo_delegate.rb +12 -12
- data/lib/clevic/qt/distinct_delegate.rb +1 -1
- data/lib/clevic/qt/extensions.rb +4 -4
- data/lib/clevic/qt/qt_combo_box.rb +7 -7
- data/lib/clevic/qt/relational_delegate.rb +5 -5
- data/lib/clevic/qt/search_dialog.rb +15 -15
- data/lib/clevic/qt/simplest_delegate.rb +2 -2
- data/lib/clevic/qt/table_model.rb +46 -46
- data/lib/clevic/qt/table_view.rb +48 -48
- data/lib/clevic/qt/text_delegate.rb +9 -9
- data/lib/clevic/rails_models_loaders.rb +3 -3
- data/lib/clevic/record.rb +8 -8
- data/lib/clevic/sampler.rb +6 -6
- data/lib/clevic/sequel_ar_adapter.rb +22 -22
- data/lib/clevic/sequel_clevic.rb +10 -10
- data/lib/clevic/sequel_meta.rb +5 -5
- data/lib/clevic/sequel_naked.rb +4 -4
- data/lib/clevic/swing/action.rb +20 -20
- data/lib/clevic/swing/action_builder.rb +2 -2
- data/lib/clevic/swing/boolean_delegate.rb +3 -3
- data/lib/clevic/swing/browser.rb +37 -37
- data/lib/clevic/swing/cell_editor.rb +13 -13
- data/lib/clevic/swing/cell_renderer.rb +7 -7
- data/lib/clevic/swing/clipboard.rb +19 -19
- data/lib/clevic/swing/combo_delegate.rb +26 -26
- data/lib/clevic/swing/confirm_dialog.rb +7 -7
- data/lib/clevic/swing/delegate.rb +4 -4
- data/lib/clevic/swing/extensions.rb +24 -24
- data/lib/clevic/swing/field.rb +1 -1
- data/lib/clevic/swing/relational_delegate.rb +2 -2
- data/lib/clevic/swing/row_header.rb +32 -32
- data/lib/clevic/swing/search_dialog.rb +31 -31
- data/lib/clevic/swing/selection_model.rb +12 -12
- data/lib/clevic/swing/swing_table_index.rb +6 -6
- data/lib/clevic/swing/table_model.rb +30 -30
- data/lib/clevic/swing/table_view.rb +54 -54
- data/lib/clevic/swing/table_view_focus.rb +4 -4
- data/lib/clevic/swing/tag_delegate.rb +14 -14
- data/lib/clevic/swing/tag_editor.rb +4 -4
- data/lib/clevic/swing/text_area_delegate.rb +6 -6
- data/lib/clevic/swing/text_delegate.rb +4 -4
- data/lib/clevic/table_index.rb +14 -14
- data/lib/clevic/table_model.rb +30 -30
- data/lib/clevic/table_searcher.rb +19 -19
- data/lib/clevic/table_view.rb +92 -92
- data/lib/clevic/table_view_paste.rb +19 -19
- data/lib/clevic/version.rb +1 -1
- data/lib/clevic/view.rb +22 -22
- data/models/accounts_models.rb +10 -10
- data/models/examples.rb +2 -2
- data/models/times_models.rb +32 -32
- data/models/values_models.rb +2 -2
- data/test/test_cache_table.rb +15 -15
- data/test/test_helper.rb +7 -7
- data/test/test_model_index_extensions.rb +6 -6
- data/test/test_table_model.rb +6 -6
- data/test/test_table_searcher.rb +25 -25
- metadata +33 -35
@@ -12,7 +12,7 @@ class TableView
|
|
12
12
|
def paste
|
13
13
|
busy_cursor do
|
14
14
|
sanity_check_read_only
|
15
|
-
|
15
|
+
|
16
16
|
# Try text/html then text/plain as tsv or csv
|
17
17
|
# LATER maybe use the java-native-application at some point for
|
18
18
|
# cut'n'paste internally?
|
@@ -28,14 +28,14 @@ class TableView
|
|
28
28
|
rescue PasteError => e
|
29
29
|
show_error e.message
|
30
30
|
end
|
31
|
-
|
31
|
+
|
32
32
|
# Paste suitable html to the selection
|
33
33
|
# Check for presence of tr tags, and make sure there are no colspan or rowspan attributes
|
34
34
|
# on td tags.
|
35
35
|
def paste_html
|
36
36
|
emit_status_text "Fetching data."
|
37
37
|
html = clipboard.html
|
38
|
-
|
38
|
+
|
39
39
|
# This should really be factored out somewhere and tested thoroughly
|
40
40
|
emit_status_text "Analysing data."
|
41
41
|
doc =
|
@@ -44,7 +44,7 @@ class TableView
|
|
44
44
|
else
|
45
45
|
Hpricot.parse( html )
|
46
46
|
end
|
47
|
-
|
47
|
+
|
48
48
|
# call the plain text paste if we don't have tabular data
|
49
49
|
if doc.search( "//tr" ).size == 0
|
50
50
|
paste_text
|
@@ -62,13 +62,13 @@ class TableView
|
|
62
62
|
#{cell_list}
|
63
63
|
EOF
|
64
64
|
end
|
65
|
-
|
65
|
+
|
66
66
|
# run through the tabular data and convert to simple array
|
67
67
|
emit_status_text "Pasting data."
|
68
68
|
ary = ( doc / :tr ).map do |row|
|
69
69
|
( row / :td ).map do |cell|
|
70
70
|
# trim leading and trailing \r\n\t
|
71
|
-
|
71
|
+
|
72
72
|
# check for br
|
73
73
|
unless cell.search( '//br' ).empty?
|
74
74
|
# treat br as separate lines
|
@@ -79,11 +79,11 @@ class TableView
|
|
79
79
|
end.gsub( /^[\r\n\t]*/, '').gsub( /[\r\n\t]*$/, '')
|
80
80
|
end
|
81
81
|
end
|
82
|
-
|
82
|
+
|
83
83
|
paste_array ary
|
84
84
|
end
|
85
85
|
end
|
86
|
-
|
86
|
+
|
87
87
|
# LATER probably need a PasteParser or something, to figure
|
88
88
|
# out if a file is tsv or csv
|
89
89
|
# Try tsv first, because number formats often have embedded ','.
|
@@ -92,7 +92,7 @@ class TableView
|
|
92
92
|
# TODO could also heuristically check paste selection area
|
93
93
|
def paste_text
|
94
94
|
text = clipboard.text
|
95
|
-
|
95
|
+
|
96
96
|
case text
|
97
97
|
when /\t/
|
98
98
|
paste_array( CSV.parse( text, :col_sep => "\t" ) )
|
@@ -103,7 +103,7 @@ class TableView
|
|
103
103
|
paste_value_to_selection( text )
|
104
104
|
end
|
105
105
|
end
|
106
|
-
|
106
|
+
|
107
107
|
# Paste array to either a single selection or a matching multiple selection
|
108
108
|
# TODO Check for rectangularness, ie csv_arr.map{|row| row.size}.uniq.size == 1
|
109
109
|
def paste_array( arr )
|
@@ -124,21 +124,21 @@ class TableView
|
|
124
124
|
if selection_model.ranges.size != 1
|
125
125
|
raise PasteError, "Can't paste tabular data to multiple selection."
|
126
126
|
end
|
127
|
-
|
127
|
+
|
128
128
|
if selection_model.ranges.first.height != arr.size
|
129
129
|
raise PasteError, "Height of paste area (#{selection_model.ranges.first.height}) doesn't match height of data (#{arr.size})."
|
130
130
|
end
|
131
|
-
|
131
|
+
|
132
132
|
if selection_model.ranges.first.width != arr.first.size
|
133
133
|
raise PasteError, "Width of paste area (#{selection_model.ranges.first.width}) doesn't match width of data (#{arr.first.size})."
|
134
134
|
end
|
135
|
-
|
135
|
+
|
136
136
|
# size is the same, so do the paste
|
137
137
|
paste_to_index( selected_index, arr )
|
138
138
|
end
|
139
139
|
end
|
140
140
|
end
|
141
|
-
|
141
|
+
|
142
142
|
# set all indexes in the selection to the value
|
143
143
|
def paste_value_to_selection( value )
|
144
144
|
selection_model.selected_indexes.each do |index|
|
@@ -146,7 +146,7 @@ class TableView
|
|
146
146
|
# save records to db via view, so we get error messages
|
147
147
|
save_row( index )
|
148
148
|
end
|
149
|
-
|
149
|
+
|
150
150
|
# notify of changed data
|
151
151
|
model.data_changed do |change|
|
152
152
|
sorted = selection_model.selected_indexes.sort
|
@@ -154,7 +154,7 @@ class TableView
|
|
154
154
|
change.bottom_right = sorted.last
|
155
155
|
end
|
156
156
|
end
|
157
|
-
|
157
|
+
|
158
158
|
# Paste an array to the index, replacing whatever is at that index
|
159
159
|
# and whatever is at other indices matching the size of the pasted
|
160
160
|
# csv array. Create new rows if there aren't enough.
|
@@ -163,7 +163,7 @@ class TableView
|
|
163
163
|
csv_arr.each_with_index do |row,row_index|
|
164
164
|
# append row if we need one
|
165
165
|
model.add_new_item if top_left_index.row + row_index >= model.row_count
|
166
|
-
|
166
|
+
|
167
167
|
row.each_with_index do |field, field_index|
|
168
168
|
unless top_left_index.column + field_index >= model.column_count
|
169
169
|
# do paste
|
@@ -183,7 +183,7 @@ class TableView
|
|
183
183
|
# save records to db via view, so we get error messages
|
184
184
|
save_row( top_left_index.choppy {|i| i.row += row_index; i.column = 0 } )
|
185
185
|
end
|
186
|
-
|
186
|
+
|
187
187
|
# make the gui refresh
|
188
188
|
model.data_changed do |change|
|
189
189
|
change.top_left = top_left_index
|
@@ -193,7 +193,7 @@ class TableView
|
|
193
193
|
end
|
194
194
|
end
|
195
195
|
end
|
196
|
-
|
196
|
+
|
197
197
|
end
|
198
198
|
|
199
199
|
end
|
data/lib/clevic/version.rb
CHANGED
data/lib/clevic/view.rb
CHANGED
@@ -5,28 +5,28 @@ module Clevic
|
|
5
5
|
# This contains the definition of a particular view of an entity.
|
6
6
|
# See Clevic::ModelBuilder.
|
7
7
|
class View
|
8
|
-
|
8
|
+
|
9
9
|
class << self
|
10
10
|
def define_ui_block( &block )
|
11
11
|
@define_ui_block ||= block
|
12
12
|
end
|
13
|
-
|
13
|
+
|
14
14
|
def order
|
15
15
|
@order ||= []
|
16
16
|
end
|
17
|
-
|
17
|
+
|
18
18
|
# sometimes order has duplicates. So this is all unique
|
19
19
|
# defined views in order of definition, or as specified.
|
20
20
|
def views
|
21
21
|
order.uniq
|
22
22
|
end
|
23
|
-
|
23
|
+
|
24
24
|
def []( view_name )
|
25
25
|
order.find do |view|
|
26
26
|
view.name =~ /#{view_name.to_s}/i
|
27
27
|
end
|
28
28
|
end
|
29
|
-
|
29
|
+
|
30
30
|
# Handle situations where the array passed to
|
31
31
|
# Clevic::View.order has entity_class
|
32
32
|
# objects in it. In other words, if there is one, pass back it's
|
@@ -40,7 +40,7 @@ module Clevic
|
|
40
40
|
end
|
41
41
|
end
|
42
42
|
end
|
43
|
-
|
43
|
+
|
44
44
|
def entity_class( *args )
|
45
45
|
if args.size == 0
|
46
46
|
@entity_class || raise( "entity_class not specified for #{name}" )
|
@@ -48,11 +48,11 @@ module Clevic
|
|
48
48
|
self.entity_class = args.first
|
49
49
|
end
|
50
50
|
end
|
51
|
-
|
51
|
+
|
52
52
|
def entity_class=( some_class )
|
53
53
|
@entity_class = some_class
|
54
54
|
end
|
55
|
-
|
55
|
+
|
56
56
|
def widget_name( *args )
|
57
57
|
if args.size == 0
|
58
58
|
# the class name by default
|
@@ -62,7 +62,7 @@ module Clevic
|
|
62
62
|
end
|
63
63
|
end
|
64
64
|
end
|
65
|
-
|
65
|
+
|
66
66
|
# args can be anything that has a writer method. Often this
|
67
67
|
# will be entity_class
|
68
68
|
# block contains the ModelBuilder DSL
|
@@ -74,33 +74,33 @@ module Clevic
|
|
74
74
|
end
|
75
75
|
end
|
76
76
|
end
|
77
|
-
|
77
|
+
|
78
78
|
# use block from constructor, or class ui block from eg Clevic::Record
|
79
79
|
def define_ui_block
|
80
80
|
@define_ui_block || self.class.define_ui_block
|
81
81
|
end
|
82
|
-
|
82
|
+
|
83
83
|
# For descendants to override easily
|
84
84
|
def entity_class
|
85
85
|
@entity_class || self.class.entity_class
|
86
86
|
end
|
87
87
|
attr_writer :entity_class
|
88
|
-
|
88
|
+
|
89
89
|
# The title to display, eg in a tab
|
90
90
|
def title
|
91
91
|
@title || self.class.name
|
92
92
|
end
|
93
93
|
attr_writer :title
|
94
|
-
|
94
|
+
|
95
95
|
def fields
|
96
96
|
@fields ||= define_ui.fields
|
97
97
|
end
|
98
|
-
|
98
|
+
|
99
99
|
# used by the framework-specific code to name widgets
|
100
100
|
def widget_name
|
101
101
|
@widget_name || self.class.widget_name
|
102
102
|
end
|
103
|
-
|
103
|
+
|
104
104
|
def model_builder( value = nil, &block )
|
105
105
|
if value.nil?
|
106
106
|
@model_builder ||= ModelBuilder.new( self )
|
@@ -110,7 +110,7 @@ module Clevic
|
|
110
110
|
end
|
111
111
|
end
|
112
112
|
attr_writer :model_builder
|
113
|
-
|
113
|
+
|
114
114
|
# return a default UI constructed from model metadata
|
115
115
|
def define_ui
|
116
116
|
if define_ui_block.nil?
|
@@ -123,23 +123,23 @@ module Clevic
|
|
123
123
|
model_builder( &define_ui_block )
|
124
124
|
end
|
125
125
|
end
|
126
|
-
|
126
|
+
|
127
127
|
# callback for view/model specific actions
|
128
128
|
def define_actions( table_view, action_builder )
|
129
129
|
end
|
130
|
-
|
130
|
+
|
131
131
|
# callback for notify
|
132
132
|
def notify_field( table_view, model_index )
|
133
133
|
ndc = model_index.field.notify_data_changed
|
134
134
|
case ndc
|
135
135
|
when Proc
|
136
136
|
ndc.call( self, table_view, model_index )
|
137
|
-
|
137
|
+
|
138
138
|
when Symbol
|
139
139
|
send( ndc, table_view, model_index )
|
140
140
|
end
|
141
141
|
end
|
142
|
-
|
142
|
+
|
143
143
|
# Define data changed events. Default is to call notify_data_changed
|
144
144
|
# for each field in the rectangular area defined by top_left and bottom_right
|
145
145
|
# (which are include Clevic::TableIndex)
|
@@ -157,10 +157,10 @@ module Clevic
|
|
157
157
|
end
|
158
158
|
end
|
159
159
|
end
|
160
|
-
|
160
|
+
|
161
161
|
# callback for key presses
|
162
162
|
def notify_key_press( table_view, key_press_event, current_model_index )
|
163
163
|
end
|
164
|
-
|
164
|
+
|
165
165
|
end
|
166
166
|
end
|
data/models/accounts_models.rb
CHANGED
@@ -16,9 +16,9 @@ db.test_connection
|
|
16
16
|
class Entry < Sequel::Model
|
17
17
|
many_to_one :debit, :class_name => 'Account', :key => :debit_id
|
18
18
|
many_to_one :credit, :class_name => 'Account', :key => :credit_id
|
19
|
-
|
19
|
+
|
20
20
|
include Clevic::Record
|
21
|
-
|
21
|
+
|
22
22
|
define_ui do
|
23
23
|
plain :date, :sample => '88-WWW-99'
|
24
24
|
distinct :supplier do |f|
|
@@ -28,7 +28,7 @@ class Entry < Sequel::Model
|
|
28
28
|
f.notify_data_changed = lambda do |entity_view, table_view, model_index|
|
29
29
|
if model_index.entity.credit.nil? && model_index.entity.debit.nil?
|
30
30
|
entity_view.update_from_description( model_index )
|
31
|
-
|
31
|
+
|
32
32
|
# move edit cursor to amount field
|
33
33
|
table_view.selection_model.clear
|
34
34
|
table_view.override_next_index( model_index.choppy( :column => :amount ) )
|
@@ -43,10 +43,10 @@ class Entry < Sequel::Model
|
|
43
43
|
plain :cheque_number
|
44
44
|
plain :active, :sample => 'WW'
|
45
45
|
plain :vat, :label => 'VAT', :sample => 'WW', :tooltip => 'Does this include VAT?'
|
46
|
-
|
46
|
+
|
47
47
|
dataset.order( :date, :id )
|
48
48
|
end
|
49
|
-
|
49
|
+
|
50
50
|
# Copy the values for the credit and debit fields
|
51
51
|
# from the previous similar entry with a similar description
|
52
52
|
def self.update_from_description( current_index )
|
@@ -56,13 +56,13 @@ class Entry < Sequel::Model
|
|
56
56
|
filter( current_index.attribute.to_sym => current_index.attribute_value ). \
|
57
57
|
order( :date.desc ). \
|
58
58
|
first
|
59
|
-
|
59
|
+
|
60
60
|
if similar != nil
|
61
61
|
# set the values
|
62
62
|
current_index.entity.debit = similar.debit
|
63
63
|
current_index.entity.credit = similar.credit
|
64
64
|
current_index.entity.category = similar.category
|
65
|
-
|
65
|
+
|
66
66
|
# emit signal to that whole row has changed
|
67
67
|
current_index.model.data_changed do |change|
|
68
68
|
change.top_left = current_index.choppy( :column => 0 )
|
@@ -75,9 +75,9 @@ end
|
|
75
75
|
class Account < Sequel::Model
|
76
76
|
one_to_many :debits, :class_name => 'Entry', :key => :debit_id, :order => :date
|
77
77
|
one_to_many :credits, :class_name => 'Entry', :key => :credit_id, :order => :date
|
78
|
-
|
78
|
+
|
79
79
|
include Clevic::Record
|
80
|
-
|
80
|
+
|
81
81
|
# define how fields are displayed
|
82
82
|
define_ui do
|
83
83
|
plain :name
|
@@ -86,7 +86,7 @@ class Account < Sequel::Model
|
|
86
86
|
plain :pastel_number, :alignment => :right, :label => 'Pastel'
|
87
87
|
plain :fringe, :format => "%.1f"
|
88
88
|
plain :active
|
89
|
-
|
89
|
+
|
90
90
|
dataset.order( :name, :account_type )
|
91
91
|
end
|
92
92
|
end
|
data/models/examples.rb
CHANGED
@@ -31,10 +31,10 @@ See also Clevic::ModelBuilder and Clevic::Field
|
|
31
31
|
|
32
32
|
== Work hours database using Sqlite
|
33
33
|
:include:models/times_sqlite_models.rb
|
34
|
-
|
34
|
+
|
35
35
|
== Work hours database using Postgres
|
36
36
|
:include:models/times_psql_models.rb
|
37
|
-
|
37
|
+
|
38
38
|
== Work hours database using ActiveRecord style models
|
39
39
|
:include:models/times_ar_style_models.rb
|
40
40
|
|
data/models/times_models.rb
CHANGED
@@ -11,7 +11,7 @@ class Entry < Sequel::Model
|
|
11
11
|
many_to_one :invoice
|
12
12
|
many_to_one :activity
|
13
13
|
many_to_one :project
|
14
|
-
|
14
|
+
|
15
15
|
# spans of time more than 8 ours are coloured violet
|
16
16
|
# because they're often the result of typos.
|
17
17
|
def time_color
|
@@ -19,21 +19,21 @@ class Entry < Sequel::Model
|
|
19
19
|
# 8 hours
|
20
20
|
'darkviolet' if self.end - start > 8.hours
|
21
21
|
end
|
22
|
-
|
22
|
+
|
23
23
|
# tooltip for spans of time > 8 hours
|
24
24
|
def time_tooltip
|
25
25
|
return if self.end.nil? || start.nil?
|
26
26
|
'Time interval greater than 8 hours' if self.end - start > 8.hours
|
27
27
|
end
|
28
|
-
|
28
|
+
|
29
29
|
define_ui do
|
30
30
|
plain :date, :sample => '28-WWW-08'
|
31
|
-
|
31
|
+
|
32
32
|
# The project field
|
33
33
|
relational :project do |field|
|
34
34
|
field.display = :project
|
35
35
|
field.dataset.filter( :active => true ).order{ lower(project) }
|
36
|
-
|
36
|
+
|
37
37
|
# handle data changed events. In this case,
|
38
38
|
# auto-fill-in the invoice field.
|
39
39
|
field.notify_data_changed do |entity_view, table_view, model_index|
|
@@ -45,31 +45,31 @@ class Entry < Sequel::Model
|
|
45
45
|
end
|
46
46
|
end
|
47
47
|
end
|
48
|
-
|
48
|
+
|
49
49
|
relational :invoice do |f|
|
50
50
|
f.display 'invoice_number'
|
51
51
|
f.dataset.filter( :status => 'not sent' ).order( :invoice_number )
|
52
52
|
end
|
53
|
-
|
53
|
+
|
54
54
|
# call time_color method for foreground color value
|
55
55
|
plain :start, :foreground => :time_color, :tooltip => :time_tooltip
|
56
|
-
|
56
|
+
|
57
57
|
# another way to call time_color method for foreground color value
|
58
58
|
plain :end, :foreground => lambda{|x| x.time_color}, :tooltip => :time_tooltip
|
59
|
-
|
59
|
+
|
60
60
|
# multiline text
|
61
61
|
text :description, :sample => 'This is a long string designed to hold lots of data and description'
|
62
|
-
|
62
|
+
|
63
63
|
relational :activity do |f|
|
64
64
|
f.display 'activity'
|
65
65
|
f.sample 'Troubleshooting'
|
66
66
|
f.dataset.filter( :active => true ).order{ lower(activity) }
|
67
67
|
end
|
68
|
-
|
68
|
+
|
69
69
|
distinct :module, :tooltip => 'Module or sub-project'
|
70
70
|
plain :charge, :tooltip => 'Is this time billable?'
|
71
71
|
distinct :person, :default => 'John', :tooltip => 'The person who did the work'
|
72
|
-
|
72
|
+
|
73
73
|
dataset.order( :date, :start, :id )
|
74
74
|
end
|
75
75
|
|
@@ -77,11 +77,11 @@ class Entry < Sequel::Model
|
|
77
77
|
action_builder.action :smart_copy, 'Smart Copy', :shortcut => 'Ctrl+"' do
|
78
78
|
smart_copy( view )
|
79
79
|
end
|
80
|
-
|
80
|
+
|
81
81
|
action_builder.action :invoice_from_project, 'Invoice from Project', :shortcut => 'Ctrl+Shift+I' do
|
82
82
|
invoice_from_project( view, view.current_index ) do
|
83
83
|
# execute the block if the invoice is changed
|
84
|
-
|
84
|
+
|
85
85
|
# save this before selection model is cleared
|
86
86
|
current_index = view.current_index
|
87
87
|
view.selection_model.clear
|
@@ -89,35 +89,35 @@ class Entry < Sequel::Model
|
|
89
89
|
end
|
90
90
|
end
|
91
91
|
end
|
92
|
-
|
92
|
+
|
93
93
|
# do a smart copy from the previous line
|
94
94
|
def self.smart_copy( view )
|
95
95
|
view.sanity_check_read_only
|
96
96
|
view.sanity_check_ditto
|
97
|
-
|
97
|
+
|
98
98
|
# need a reference to current_index here, because selection_model.clear will
|
99
99
|
# invalidate view.current_index. And anyway, its shorter and easier to read.
|
100
100
|
current_index = view.current_index
|
101
101
|
if current_index.row >= 1
|
102
102
|
# fetch previous item
|
103
103
|
previous_item = view.model.collection[current_index.row - 1]
|
104
|
-
|
104
|
+
|
105
105
|
# copy the relevant fields
|
106
106
|
current_index.entity.date = previous_item.date if current_index.entity.date.nil?
|
107
107
|
# depends on previous line
|
108
108
|
current_index.entity.start = previous_item.end if current_index.entity.date == previous_item.date
|
109
|
-
|
109
|
+
|
110
110
|
# copy rest of fields
|
111
111
|
[:project, :invoice, :activity, :module, :charge, :person].each do |attr|
|
112
112
|
current_index.entity.send( "#{attr.to_s}=", previous_item.send( attr ) )
|
113
113
|
end
|
114
|
-
|
114
|
+
|
115
115
|
# tell view to update
|
116
116
|
view.model.data_changed do |change|
|
117
117
|
change.top_left = current_index.choppy( :column => 0 )
|
118
118
|
change.bottom_right = current_index.choppy( :column => view.model.fields.size - 1 )
|
119
119
|
end
|
120
|
-
|
120
|
+
|
121
121
|
# move to the first empty time field
|
122
122
|
next_field =
|
123
123
|
if current_index.entity.start.nil?
|
@@ -125,7 +125,7 @@ class Entry < Sequel::Model
|
|
125
125
|
else
|
126
126
|
:end
|
127
127
|
end
|
128
|
-
|
128
|
+
|
129
129
|
# next cursor location
|
130
130
|
view.selection_model.clear
|
131
131
|
view.current_index = current_index.choppy( :column => next_field )
|
@@ -142,10 +142,10 @@ class Entry < Sequel::Model
|
|
142
142
|
unless invoice.nil?
|
143
143
|
# make a reference to the invoice
|
144
144
|
current_index.entity.invoice = invoice
|
145
|
-
|
145
|
+
|
146
146
|
# update view from top_left to bottom_right
|
147
147
|
table_view.model.data_changed( current_index.choppy( :column => :invoice ) )
|
148
|
-
|
148
|
+
|
149
149
|
unless block.nil?
|
150
150
|
if block.arity == 1
|
151
151
|
block.call( invoice )
|
@@ -156,14 +156,14 @@ class Entry < Sequel::Model
|
|
156
156
|
end
|
157
157
|
end
|
158
158
|
end
|
159
|
-
|
159
|
+
|
160
160
|
end
|
161
161
|
|
162
162
|
class Invoice < Sequel::Model
|
163
163
|
include Clevic::Record
|
164
|
-
|
164
|
+
|
165
165
|
one_to_many :entries
|
166
|
-
|
166
|
+
|
167
167
|
define_ui do
|
168
168
|
plain :date
|
169
169
|
distinct :client
|
@@ -173,7 +173,7 @@ class Invoice < Sequel::Model
|
|
173
173
|
plain :quote_date, :format => '%d-%b-%y', :edit_format => '%d-%b-%Y', :tooltip => 'the date and time when the quote was supplied', :default => lambda{|x| DateTime.now}
|
174
174
|
plain :quote_amount
|
175
175
|
plain :description
|
176
|
-
|
176
|
+
|
177
177
|
dataset.order( :invoice_number )
|
178
178
|
end
|
179
179
|
end
|
@@ -182,17 +182,17 @@ class Project < Sequel::Model
|
|
182
182
|
one_to_many :entries
|
183
183
|
|
184
184
|
include Clevic::Record
|
185
|
-
|
185
|
+
|
186
186
|
define_ui do
|
187
187
|
plain :project
|
188
188
|
plain :description
|
189
189
|
distinct :client
|
190
190
|
plain :rate
|
191
191
|
plain :active
|
192
|
-
|
192
|
+
|
193
193
|
dataset.order( :project )
|
194
194
|
end
|
195
|
-
|
195
|
+
|
196
196
|
# Return the latest invoice for this project
|
197
197
|
# Not part of the UI.
|
198
198
|
def latest_invoice
|
@@ -207,12 +207,12 @@ class Activity < Sequel::Model
|
|
207
207
|
one_to_many :entries
|
208
208
|
|
209
209
|
include Clevic::Record
|
210
|
-
|
210
|
+
|
211
211
|
# define how fields are displayed
|
212
212
|
define_ui do
|
213
213
|
plain :activity
|
214
214
|
plain :active
|
215
|
-
|
215
|
+
|
216
216
|
dataset.order( :activity )
|
217
217
|
end
|
218
218
|
end
|