openall_time_applet 0.0.21 → 0.0.22
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +2 -0
- data/Gemfile.lock +5 -0
- data/VERSION +1 -1
- data/classes/connection.rb +5 -5
- data/glade/win_main.glade +125 -42
- data/gui/trayicon.rb +1 -1
- data/gui/win_main.rb +204 -29
- data/gui/win_worktime_overview.rb +2 -2
- data/lib/openall_time_applet.rb +12 -6
- data/models/timelog.rb +61 -3
- data/models/worktime.rb +1 -1
- data/openall_time_applet.gemspec +8 -4
- data/spec/openall_time_applet_spec.rb +6 -1
- metadata +35 -15
- data/glade/win_sync_overview.glade +0 -121
- data/gui/win_sync_overview.rb +0 -213
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -5,6 +5,7 @@ GEM
|
|
5
5
|
glib2 (>= 1.1.3)
|
6
6
|
cairo (1.12.2)
|
7
7
|
pkg-config
|
8
|
+
datet (0.0.4)
|
8
9
|
diff-lcs (1.1.3)
|
9
10
|
gdk_pixbuf2 (1.1.3)
|
10
11
|
glib2 (>= 1.1.3)
|
@@ -17,6 +18,8 @@ GEM
|
|
17
18
|
atk (>= 1.1.3)
|
18
19
|
gdk_pixbuf2 (>= 1.1.3)
|
19
20
|
pango (>= 1.1.3)
|
21
|
+
http2 (0.0.0)
|
22
|
+
knjrbfw
|
20
23
|
jeweler (1.8.4)
|
21
24
|
bundler (~> 1.0)
|
22
25
|
git (>= 1.2.5)
|
@@ -53,8 +56,10 @@ PLATFORMS
|
|
53
56
|
|
54
57
|
DEPENDENCIES
|
55
58
|
bundler (>= 1.0.0)
|
59
|
+
datet
|
56
60
|
gettext
|
57
61
|
gtk2
|
62
|
+
http2
|
58
63
|
jeweler (~> 1.8.3)
|
59
64
|
json
|
60
65
|
knjrbfw
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.0.
|
1
|
+
0.0.22
|
data/classes/connection.rb
CHANGED
@@ -4,7 +4,7 @@ require "json"
|
|
4
4
|
class Openall_time_applet::Connection
|
5
5
|
def initialize(args)
|
6
6
|
@args = args
|
7
|
-
@http =
|
7
|
+
@http = Http2.new(
|
8
8
|
:host => @args[:host],
|
9
9
|
:port => @args[:port],
|
10
10
|
:follow_redirects => false,
|
@@ -17,10 +17,10 @@ class Openall_time_applet::Connection
|
|
17
17
|
|
18
18
|
def login
|
19
19
|
#For some weird reason OpenAll seems to only accept multipart-post-requests??
|
20
|
-
@http.post_multipart("index.php?c=Auth&m=validateLogin", {"username" => @args[:username], "password" => @args[:password]})
|
20
|
+
@http.post_multipart(:url => "index.php?c=Auth&m=validateLogin", :post => {"username" => @args[:username], "password" => @args[:password]})
|
21
21
|
|
22
22
|
#Verify login by reading dashboard HTML.
|
23
|
-
res = @http.get("index.php?c=Dashboard")
|
23
|
+
res = @http.get(:url => "index.php?c=Dashboard")
|
24
24
|
|
25
25
|
if !res.body.match(/<ul\s*id="webticker"\s*>/) and !res.body.index("<a href=\"index.php?c=Dashboard&m=editDashboard\">") == nil
|
26
26
|
tmp_path = "#{Knj::Os.tmpdir}/openall_login_debug.txt"
|
@@ -44,9 +44,9 @@ class Openall_time_applet::Connection
|
|
44
44
|
|
45
45
|
#Send request to OpenAll via HTTP.
|
46
46
|
if args[:post]
|
47
|
-
res = @http.post_multipart(args[:url], args[:post])
|
47
|
+
res = @http.post_multipart(:url => args[:url], :post => args[:post])
|
48
48
|
else
|
49
|
-
res = @http.get(args[:url])
|
49
|
+
res = @http.get(:url => args[:url])
|
50
50
|
end
|
51
51
|
|
52
52
|
raise _("Empty body returned from OpenAll.") if res.body.to_s.strip.length <= 0
|
data/glade/win_main.glade
CHANGED
@@ -146,59 +146,29 @@
|
|
146
146
|
</packing>
|
147
147
|
</child>
|
148
148
|
<child>
|
149
|
-
<object class="
|
149
|
+
<object class="GtkHButtonBox" id="hbuttonbox3">
|
150
150
|
<property name="visible">True</property>
|
151
151
|
<property name="can_focus">False</property>
|
152
|
-
<property name="n_columns">3</property>
|
153
152
|
<child>
|
154
|
-
<object class="
|
153
|
+
<object class="GtkButton" id="btnSwitch">
|
154
|
+
<property name="label" translatable="yes">Switch</property>
|
155
155
|
<property name="visible">True</property>
|
156
|
-
<property name="can_focus">
|
157
|
-
<property name="
|
158
|
-
|
159
|
-
|
160
|
-
<child>
|
161
|
-
<object class="GtkLabel" id="labRunningTask">
|
162
|
-
<property name="visible">True</property>
|
163
|
-
<property name="can_focus">False</property>
|
164
|
-
<property name="xalign">0</property>
|
165
|
-
</object>
|
166
|
-
<packing>
|
167
|
-
<property name="left_attach">1</property>
|
168
|
-
<property name="right_attach">2</property>
|
169
|
-
</packing>
|
170
|
-
</child>
|
171
|
-
<child>
|
172
|
-
<object class="GtkLabel" id="labRunningTime">
|
173
|
-
<property name="visible">True</property>
|
174
|
-
<property name="can_focus">False</property>
|
175
|
-
<property name="xalign">0</property>
|
156
|
+
<property name="can_focus">True</property>
|
157
|
+
<property name="receives_default">True</property>
|
158
|
+
<property name="use_action_appearance">False</property>
|
159
|
+
<signal name="clicked" handler="on_btnSwitch_clicked" swapped="no"/>
|
176
160
|
</object>
|
177
161
|
<packing>
|
178
|
-
<property name="
|
179
|
-
<property name="
|
162
|
+
<property name="expand">False</property>
|
163
|
+
<property name="fill">True</property>
|
164
|
+
<property name="position">0</property>
|
180
165
|
</packing>
|
181
166
|
</child>
|
182
167
|
</object>
|
183
|
-
<packing>
|
184
|
-
<property name="expand">True</property>
|
185
|
-
<property name="fill">True</property>
|
186
|
-
<property name="position">2</property>
|
187
|
-
</packing>
|
188
|
-
</child>
|
189
|
-
<child>
|
190
|
-
<object class="GtkButton" id="btnSwitch">
|
191
|
-
<property name="label" translatable="yes">Switch</property>
|
192
|
-
<property name="visible">True</property>
|
193
|
-
<property name="can_focus">True</property>
|
194
|
-
<property name="receives_default">True</property>
|
195
|
-
<property name="use_action_appearance">False</property>
|
196
|
-
<signal name="clicked" handler="on_btnSwitch_clicked" swapped="no"/>
|
197
|
-
</object>
|
198
168
|
<packing>
|
199
169
|
<property name="expand">False</property>
|
200
170
|
<property name="fill">True</property>
|
201
|
-
<property name="position">
|
171
|
+
<property name="position">2</property>
|
202
172
|
</packing>
|
203
173
|
</child>
|
204
174
|
</object>
|
@@ -349,6 +319,119 @@
|
|
349
319
|
<property name="position">2</property>
|
350
320
|
</packing>
|
351
321
|
</child>
|
322
|
+
<child>
|
323
|
+
<object class="GtkVBox" id="vboxPrepareTransfer">
|
324
|
+
<property name="visible">True</property>
|
325
|
+
<property name="can_focus">False</property>
|
326
|
+
<child>
|
327
|
+
<object class="GtkFrame" id="frame2">
|
328
|
+
<property name="visible">True</property>
|
329
|
+
<property name="can_focus">False</property>
|
330
|
+
<property name="label_xalign">0</property>
|
331
|
+
<property name="shadow_type">none</property>
|
332
|
+
<child>
|
333
|
+
<object class="GtkAlignment" id="alignment3">
|
334
|
+
<property name="visible">True</property>
|
335
|
+
<property name="can_focus">False</property>
|
336
|
+
<property name="left_padding">12</property>
|
337
|
+
<child>
|
338
|
+
<object class="GtkVBox" id="vbox4">
|
339
|
+
<property name="visible">True</property>
|
340
|
+
<property name="can_focus">False</property>
|
341
|
+
<property name="spacing">3</property>
|
342
|
+
<child>
|
343
|
+
<object class="GtkViewport" id="viewport2">
|
344
|
+
<property name="visible">True</property>
|
345
|
+
<property name="can_focus">False</property>
|
346
|
+
<child>
|
347
|
+
<object class="GtkScrolledWindow" id="scrolledwindow2">
|
348
|
+
<property name="visible">True</property>
|
349
|
+
<property name="can_focus">True</property>
|
350
|
+
<property name="hscrollbar_policy">automatic</property>
|
351
|
+
<property name="vscrollbar_policy">automatic</property>
|
352
|
+
<child>
|
353
|
+
<object class="GtkTreeView" id="tvTimelogsPrepareTransfer">
|
354
|
+
<property name="visible">True</property>
|
355
|
+
<property name="can_focus">True</property>
|
356
|
+
</object>
|
357
|
+
</child>
|
358
|
+
</object>
|
359
|
+
</child>
|
360
|
+
</object>
|
361
|
+
<packing>
|
362
|
+
<property name="expand">True</property>
|
363
|
+
<property name="fill">True</property>
|
364
|
+
<property name="position">0</property>
|
365
|
+
</packing>
|
366
|
+
</child>
|
367
|
+
<child>
|
368
|
+
<object class="GtkHButtonBox" id="hbuttonbox4">
|
369
|
+
<property name="visible">True</property>
|
370
|
+
<property name="can_focus">False</property>
|
371
|
+
<property name="spacing">3</property>
|
372
|
+
<property name="layout_style">end</property>
|
373
|
+
<child>
|
374
|
+
<object class="GtkButton" id="btnSyncPrepareTransfer">
|
375
|
+
<property name="label">gtk-harddisk</property>
|
376
|
+
<property name="visible">True</property>
|
377
|
+
<property name="can_focus">True</property>
|
378
|
+
<property name="receives_default">True</property>
|
379
|
+
<property name="use_action_appearance">False</property>
|
380
|
+
<property name="use_stock">True</property>
|
381
|
+
<signal name="clicked" handler="on_btnSyncPrepareTransfer_clicked" swapped="no"/>
|
382
|
+
</object>
|
383
|
+
<packing>
|
384
|
+
<property name="expand">False</property>
|
385
|
+
<property name="fill">False</property>
|
386
|
+
<property name="position">0</property>
|
387
|
+
</packing>
|
388
|
+
</child>
|
389
|
+
</object>
|
390
|
+
<packing>
|
391
|
+
<property name="expand">False</property>
|
392
|
+
<property name="fill">True</property>
|
393
|
+
<property name="position">1</property>
|
394
|
+
</packing>
|
395
|
+
</child>
|
396
|
+
</object>
|
397
|
+
</child>
|
398
|
+
</object>
|
399
|
+
</child>
|
400
|
+
<child type="label">
|
401
|
+
<object class="GtkLabel" id="label3">
|
402
|
+
<property name="visible">True</property>
|
403
|
+
<property name="can_focus">False</property>
|
404
|
+
<property name="label" translatable="yes"><b>Prepare transfer</b></property>
|
405
|
+
<property name="use_markup">True</property>
|
406
|
+
</object>
|
407
|
+
</child>
|
408
|
+
</object>
|
409
|
+
<packing>
|
410
|
+
<property name="expand">True</property>
|
411
|
+
<property name="fill">True</property>
|
412
|
+
<property name="position">0</property>
|
413
|
+
</packing>
|
414
|
+
</child>
|
415
|
+
<child>
|
416
|
+
<object class="GtkLabel" id="labTotal">
|
417
|
+
<property name="visible">True</property>
|
418
|
+
<property name="can_focus">False</property>
|
419
|
+
<property name="xalign">0</property>
|
420
|
+
<property name="label" translatable="yes">[totals]</property>
|
421
|
+
</object>
|
422
|
+
<packing>
|
423
|
+
<property name="expand">False</property>
|
424
|
+
<property name="fill">True</property>
|
425
|
+
<property name="position">1</property>
|
426
|
+
</packing>
|
427
|
+
</child>
|
428
|
+
</object>
|
429
|
+
<packing>
|
430
|
+
<property name="expand">True</property>
|
431
|
+
<property name="fill">True</property>
|
432
|
+
<property name="position">3</property>
|
433
|
+
</packing>
|
434
|
+
</child>
|
352
435
|
<child>
|
353
436
|
<object class="GtkStatusbar" id="statusbar">
|
354
437
|
<property name="visible">True</property>
|
@@ -358,7 +441,7 @@
|
|
358
441
|
<packing>
|
359
442
|
<property name="expand">False</property>
|
360
443
|
<property name="fill">True</property>
|
361
|
-
<property name="position">
|
444
|
+
<property name="position">4</property>
|
362
445
|
</packing>
|
363
446
|
</child>
|
364
447
|
</object>
|
data/gui/trayicon.rb
CHANGED
@@ -39,7 +39,7 @@ class Openall_time_applet::Gui::Trayicon
|
|
39
39
|
|
40
40
|
#Calculate minutes tracked and generate variables.
|
41
41
|
secs = Time.now.to_i - @args[:oata].timelog_active_time.to_i + @args[:oata].timelog_active.time_total
|
42
|
-
mins = (secs.to_f / 60.0)
|
42
|
+
mins = (secs.to_f / 60.0).floor
|
43
43
|
|
44
44
|
if mins >= 60
|
45
45
|
hours = mins / 60
|
data/gui/win_main.rb
CHANGED
@@ -118,7 +118,7 @@ class Openall_time_applet::Gui::Win_main
|
|
118
118
|
:model_class => :Timelog,
|
119
119
|
:renderers => init_data[:renderers],
|
120
120
|
:change_before => proc{|d|
|
121
|
-
if
|
121
|
+
if d[:col_no] == 3 and @args[:oata].timelog_active and @args[:oata].timelog_active.id == d[:model].id
|
122
122
|
raise _("You cannot edit the time for the active timelog.")
|
123
123
|
end
|
124
124
|
|
@@ -127,6 +127,12 @@ class Openall_time_applet::Gui::Win_main
|
|
127
127
|
:change_after => proc{
|
128
128
|
@dont_reload = false
|
129
129
|
},
|
130
|
+
:on_edit => proc{|d|
|
131
|
+
@tv_editting = true
|
132
|
+
},
|
133
|
+
:on_edit_done => proc{|d|
|
134
|
+
@tv_editting = nil
|
135
|
+
},
|
130
136
|
:cols => {
|
131
137
|
1 => :descr,
|
132
138
|
2 => {:col => :timestamp, :type => :datetime},
|
@@ -160,10 +166,7 @@ class Openall_time_applet::Gui::Win_main
|
|
160
166
|
|
161
167
|
|
162
168
|
#Connect certain column renderers to the editingStarted-method, so editing can be canceled, if the user tries to edit forbidden data on the active timelog.
|
163
|
-
init_data[:renderers][1].signal_connect_after("editing-started", :descr, &self.method(:on_cell_editingStarted))
|
164
|
-
init_data[:renderers][2].signal_connect_after("editing-started", :timestamp, &self.method(:on_cell_editingStarted))
|
165
169
|
init_data[:renderers][3].signal_connect_after("editing-started", :time, &self.method(:on_cell_editingStarted))
|
166
|
-
init_data[:renderers][11].signal_connect_after("editing-started", :task, &self.method(:on_cell_editingStarted))
|
167
170
|
|
168
171
|
|
169
172
|
#Fills the timelogs-treeview with data.
|
@@ -194,13 +197,179 @@ class Openall_time_applet::Gui::Win_main
|
|
194
197
|
end
|
195
198
|
|
196
199
|
|
200
|
+
|
201
|
+
#Initializes sync-box.
|
202
|
+
@gui["btnSyncPrepareTransfer"].label = _("Transfer")
|
203
|
+
|
204
|
+
#Generate list-store containing tasks for the task-column.
|
205
|
+
task_ls = Gtk::ListStore.new(String, String)
|
206
|
+
iter = task_ls.append
|
207
|
+
iter[0] = _("None")
|
208
|
+
iter[1] = 0.to_s
|
209
|
+
|
210
|
+
tasks = [_("Choose:")]
|
211
|
+
@args[:oata].ob.list(:Task, {"orderby" => "title"}) do |task|
|
212
|
+
iter = task_ls.append
|
213
|
+
iter[0] = task[:title]
|
214
|
+
iter[1] = task.id.to_s
|
215
|
+
tasks << task
|
216
|
+
end
|
217
|
+
|
218
|
+
#Initialize timelog treeview.
|
219
|
+
init_data = Knj::Gtk2::Tv.init(@gui["tvTimelogsPrepareTransfer"], [
|
220
|
+
_("ID"),
|
221
|
+
_("Description"),
|
222
|
+
_("Timestamp"),
|
223
|
+
_("Time"),
|
224
|
+
_("Transport"),
|
225
|
+
_("Length"),
|
226
|
+
_("Transport descr."),
|
227
|
+
_("Transport costs"),
|
228
|
+
{
|
229
|
+
:title => _("Fixed travel"),
|
230
|
+
:type => :toggle
|
231
|
+
},
|
232
|
+
{
|
233
|
+
:title => _("Int. work"),
|
234
|
+
:type => :toggle
|
235
|
+
},
|
236
|
+
{
|
237
|
+
:title => _("Sync?"),
|
238
|
+
:type => :toggle
|
239
|
+
},
|
240
|
+
{
|
241
|
+
:title => _("Task"),
|
242
|
+
:type => :combo,
|
243
|
+
:model => task_ls,
|
244
|
+
:has_entry => false
|
245
|
+
},
|
246
|
+
_("Sync time")
|
247
|
+
])
|
248
|
+
|
249
|
+
#Make columns editable.
|
250
|
+
Knj::Gtk2::Tv.editable_text_renderers_to_model(
|
251
|
+
:ob => @args[:oata].ob,
|
252
|
+
:tv => @gui["tvTimelogsPrepareTransfer"],
|
253
|
+
:model_class => :Timelog,
|
254
|
+
:renderers => init_data[:renderers],
|
255
|
+
:change_before => proc{ @dont_reload_sync = true },
|
256
|
+
:change_after => proc{ @dont_reload_sync = false; self.update_sync_totals },
|
257
|
+
:cols => {
|
258
|
+
1 => :descr,
|
259
|
+
2 => {:col => :timestamp, :type => :datetime},
|
260
|
+
3 => {:col => :time, :type => :time_as_sec},
|
261
|
+
4 => {:col => :time_transport, :type => :time_as_sec},
|
262
|
+
5 => {:col => :transportlength, :type => :int},
|
263
|
+
6 => {:col => :transportdescription},
|
264
|
+
7 => {:col => :transportcosts, :type => :human_number, :decimals => 2},
|
265
|
+
8 => {:col => :travelfixed},
|
266
|
+
9 => {:col => :workinternal},
|
267
|
+
10 => {:col => :sync_need},
|
268
|
+
11 => {
|
269
|
+
:col => :task_id,
|
270
|
+
:value_callback => lambda{ |data|
|
271
|
+
task = @args[:oata].ob.get_by(:Task, {"title" => data[:value]})
|
272
|
+
|
273
|
+
if !task
|
274
|
+
return 0
|
275
|
+
else
|
276
|
+
return task.id
|
277
|
+
end
|
278
|
+
},
|
279
|
+
:value_set_callback => proc{ |data| data[:model].task_name }
|
280
|
+
},
|
281
|
+
12 => {:col => :time_sync, :type => :time_as_sec}
|
282
|
+
}
|
283
|
+
)
|
284
|
+
@gui["tvTimelogsPrepareTransfer"].columns[0].visible = false
|
285
|
+
@gui["vboxPrepareTransfer"].hide
|
286
|
+
@reload_preparetransfer_id = @args[:oata].ob.connect("object" => :Timelog, "signals" => ["add", "update", "delete"], &self.method(:reload_timelogs))
|
287
|
+
|
288
|
+
|
289
|
+
|
197
290
|
#Show the window.
|
198
|
-
@gui["window"].
|
291
|
+
@gui["window"].show
|
199
292
|
self.timelog_info_trigger
|
200
293
|
width = @gui["window"].size[0]
|
201
294
|
@gui["window"].resize(width, 1)
|
202
295
|
end
|
203
296
|
|
297
|
+
def reload_timelogs_preparetransfer
|
298
|
+
return nil if @dont_reload_sync or @gui["tvTimelogsPrepareTransfer"].destroyed?
|
299
|
+
@gui["tvTimelogsPrepareTransfer"].model.clear
|
300
|
+
@timelogs_sync_count = 0
|
301
|
+
|
302
|
+
@args[:oata].ob.list(:Timelog, {"sync_need" => 1, "task_id_not" => ["", 0], "orderby" => "timestamp"}) do |timelog|
|
303
|
+
#Read time and transport from timelog.
|
304
|
+
time = timelog[:time].to_i
|
305
|
+
transport = timelog[:time_transport].to_i
|
306
|
+
|
307
|
+
#If transport is logged, then the work if offsite. It should be rounded up by 30 min. Else 15 min. round up.
|
308
|
+
if transport > 0
|
309
|
+
roundup = 1800
|
310
|
+
else
|
311
|
+
roundup = 900
|
312
|
+
end
|
313
|
+
|
314
|
+
#Do the actual counting.
|
315
|
+
count_rounded_time = 0
|
316
|
+
loop do
|
317
|
+
break if count_rounded_time >= time
|
318
|
+
count_rounded_time += roundup
|
319
|
+
end
|
320
|
+
|
321
|
+
#Set sync-time on timelog.
|
322
|
+
timelog[:time_sync] = count_rounded_time
|
323
|
+
|
324
|
+
Knj::Gtk2::Tv.append(@gui["tvTimelogsPrepareTransfer"], [
|
325
|
+
timelog.id,
|
326
|
+
timelog[:descr],
|
327
|
+
timelog.timestamp_str,
|
328
|
+
timelog.time_as_human,
|
329
|
+
timelog.time_transport_as_human,
|
330
|
+
Knj::Locales.number_out(timelog[:transportlength], 0),
|
331
|
+
timelog.transport_descr_short,
|
332
|
+
Knj::Locales.number_out(timelog[:transportcosts], 2),
|
333
|
+
Knj::Strings.yn_str(timelog[:travelfixed], true, false),
|
334
|
+
Knj::Strings.yn_str(timelog[:workinternal], true, false),
|
335
|
+
Knj::Strings.yn_str(timelog[:sync_need], true, false),
|
336
|
+
timelog.task_name,
|
337
|
+
Knj::Strings.secs_to_human_time_str(count_rounded_time)
|
338
|
+
])
|
339
|
+
@timelogs_sync_count += 1
|
340
|
+
end
|
341
|
+
end
|
342
|
+
|
343
|
+
def on_btnSync_clicked
|
344
|
+
self.reload_timelogs_preparetransfer
|
345
|
+
|
346
|
+
if @timelogs_sync_count <= 0
|
347
|
+
#Show error-message and destroy the window, if no timelogs was to be synced.
|
348
|
+
Knj::Gtk2.msgbox("msg" => _("There is nothing to sync at this time."), "run" => false)
|
349
|
+
else
|
350
|
+
#...else show the window.
|
351
|
+
@gui["expOverview"].hide
|
352
|
+
self.update_sync_totals
|
353
|
+
@gui["vboxPrepareTransfer"].show
|
354
|
+
end
|
355
|
+
end
|
356
|
+
|
357
|
+
def update_sync_totals
|
358
|
+
total_secs = 0
|
359
|
+
|
360
|
+
@gui["tvTimelogsPrepareTransfer"].model.each do |model, path, iter|
|
361
|
+
time_val = @gui["tvTimelogsPrepareTransfer"].model.get_value(iter, 12)
|
362
|
+
|
363
|
+
begin
|
364
|
+
total_secs += Knj::Strings.human_time_str_to_secs(time_val)
|
365
|
+
rescue
|
366
|
+
#ignore - user is properly entering stuff.
|
367
|
+
end
|
368
|
+
end
|
369
|
+
|
370
|
+
@gui["labTotal"].markup = "<b>#{_("Total hours:")}</b> #{Knj::Strings.secs_to_human_time_str(total_secs)}"
|
371
|
+
end
|
372
|
+
|
204
373
|
#This method is called, when editting starts in a description-, time- or task-cell. If it is the active timelog, then editting is canceled.
|
205
374
|
def on_cell_editingStarted(renderer, editable, path, col_title)
|
206
375
|
iter = @gui["tvTimelogs"].model.get_iter(path)
|
@@ -285,6 +454,8 @@ class Openall_time_applet::Gui::Win_main
|
|
285
454
|
def on_window_destroy
|
286
455
|
#Unconnect reload-event. Else it will crash on call to destroyed object. Also frees up various ressources.
|
287
456
|
@args[:oata].ob.unconnect("object" => :Timelog, "conn_id" => @reload_id)
|
457
|
+
@args[:oata].ob.unconnect("object" => :Timelog, "conn_id" => @reload_preparetransfer_id)
|
458
|
+
|
288
459
|
@args[:oata].events.disconnect(:timelog_active_changed, @event_timelog_active_changed) if @event_timelog_active_changed
|
289
460
|
@event_timelog_active_changed = nil
|
290
461
|
Gtk.timeout_remove(@timeout_id)
|
@@ -306,27 +477,8 @@ class Openall_time_applet::Gui::Win_main
|
|
306
477
|
#This method handles the "Timelog info"-frame. Hides, shows and updates the info in it.
|
307
478
|
def timelog_info_trigger
|
308
479
|
if tlog = @args[:oata].timelog_active
|
309
|
-
task = tlog.task
|
310
|
-
if !task
|
311
|
-
task_text = "[#{_("no task sat on the timelog")}]"
|
312
|
-
else
|
313
|
-
task_text = task.name
|
314
|
-
end
|
315
|
-
|
316
|
-
@gui["labRunningTimelog"].label = tlog[:descr]
|
317
|
-
@gui["labRunningTask"].label = task_text
|
318
|
-
|
319
480
|
time_tracked = Knj::Strings.secs_to_human_time_str(@args[:oata].timelog_active_time_tracked + tlog.time_total)
|
320
|
-
|
321
|
-
@gui["labRunningTime"].label = time_tracked
|
322
|
-
|
323
|
-
@gui["txtDescr"].hide
|
324
|
-
@gui["cbTask"].hide
|
325
|
-
@gui["tableRunning"].show_all
|
326
|
-
else
|
327
|
-
@gui["txtDescr"].show
|
328
|
-
@gui["cbTask"].show
|
329
|
-
@gui["tableRunning"].hide
|
481
|
+
@gui["btnSwitch"].label = time_tracked[0..4]
|
330
482
|
end
|
331
483
|
end
|
332
484
|
|
@@ -362,14 +514,31 @@ class Openall_time_applet::Gui::Win_main
|
|
362
514
|
if tlog_act
|
363
515
|
but.image = Gtk::Image.new(Gtk::Stock::MEDIA_STOP, Gtk::IconSize::BUTTON)
|
364
516
|
but.label = _("Stop")
|
517
|
+
|
518
|
+
@gui["txtDescr"].text = tlog_act[:descr]
|
519
|
+
|
520
|
+
if task = tlog_act.task
|
521
|
+
@gui["cbTask"].sel = task.name
|
522
|
+
else
|
523
|
+
@gui["cbTask"].sel = _("Choose:")
|
524
|
+
end
|
525
|
+
|
526
|
+
@gui["txtDescr"].sensitive = false
|
527
|
+
@gui["cbTask"].sensitive = false
|
365
528
|
else
|
366
529
|
but.image = Gtk::Image.new(Gtk::Stock::MEDIA_RECORD, Gtk::IconSize::BUTTON)
|
367
530
|
but.label = _("Start")
|
531
|
+
|
532
|
+
@gui["txtDescr"].text = ""
|
533
|
+
@gui["cbTask"].sel = _("Choose:")
|
534
|
+
@gui["txtDescr"].sensitive = true
|
535
|
+
@gui["cbTask"].sensitive = true
|
368
536
|
end
|
369
537
|
end
|
370
538
|
|
371
539
|
#This method runs through all rows in the treeview and checks if a row should be marked with bold. It also increases the time in the time-column for the tracked timelog.
|
372
540
|
def check_rows
|
541
|
+
return nil if @tv_editting
|
373
542
|
act_timelog = @args[:oata].timelog_active
|
374
543
|
|
375
544
|
if act_timelog
|
@@ -403,10 +572,6 @@ class Openall_time_applet::Gui::Win_main
|
|
403
572
|
end
|
404
573
|
end
|
405
574
|
|
406
|
-
def on_btnSync_clicked
|
407
|
-
@args[:oata].show_prepare_sync
|
408
|
-
end
|
409
|
-
|
410
575
|
def on_miSyncStatic_activate
|
411
576
|
@args[:oata].sync_static("transient_for" => @gui["window"])
|
412
577
|
end
|
@@ -446,4 +611,14 @@ class Openall_time_applet::Gui::Win_main
|
|
446
611
|
def on_txtDescr_activate(*args)
|
447
612
|
self.on_btnSwitch_clicked
|
448
613
|
end
|
614
|
+
|
615
|
+
def on_btnSyncPrepareTransfer_clicked
|
616
|
+
begin
|
617
|
+
#Destroy this window and start syncing for real.
|
618
|
+
@gui["window"].destroy
|
619
|
+
@args[:oata].sync_real
|
620
|
+
rescue => e
|
621
|
+
Knj::Gtk2.msgbox(Knj::Errors.error_str(e))
|
622
|
+
end
|
623
|
+
end
|
449
624
|
end
|
@@ -9,7 +9,7 @@ class Openall_time_applet::Gui::Win_worktime_overview
|
|
9
9
|
@gui.translate
|
10
10
|
@gui.connect_signals{|h| method(h)}
|
11
11
|
|
12
|
-
@date =
|
12
|
+
@date = Datet.new
|
13
13
|
self.build_week
|
14
14
|
|
15
15
|
@gui["window"].show_all
|
@@ -66,7 +66,7 @@ class Openall_time_applet::Gui::Win_worktime_overview
|
|
66
66
|
|
67
67
|
#Draw all the days.
|
68
68
|
stats[:days_total].keys.sort.each do |day_no|
|
69
|
-
date =
|
69
|
+
date = Datet.in(Time.new(date.year, date.month, day_no))
|
70
70
|
|
71
71
|
day_title = Gtk::Label.new
|
72
72
|
day_title.markup = "<b>#{date.day_str(:short => true)} #{date.time.strftime("%d/%m")} - #{stats[:days_total][day_no][:first_time].time.strftime("%H:%M")}</b>"
|
data/lib/openall_time_applet.rb
CHANGED
@@ -2,13 +2,19 @@
|
|
2
2
|
require "rubygems"
|
3
3
|
|
4
4
|
#For secs-to-human-string (MySQL-format), model-framework, database-framework, options-framework, date-framework and more.
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
5
|
+
gems = ["wref", "datet", "http2", "knjrbfw"]
|
6
|
+
gems.each do |gem|
|
7
|
+
fpath = "#{File.dirname(__FILE__)}/../../#{gem}/lib/#{gem}.rb"
|
8
|
+
if File.exists?(fpath)
|
9
|
+
puts "Require custom Gem-path: '#{fpath}'."
|
10
|
+
require fpath
|
11
|
+
else
|
12
|
+
puts "Require Gem normally: '#{gem}'."
|
13
|
+
require gem
|
14
|
+
end
|
10
15
|
end
|
11
16
|
|
17
|
+
|
12
18
|
require "sqlite3"
|
13
19
|
require "gettext"
|
14
20
|
require "base64"
|
@@ -185,7 +191,7 @@ class Openall_time_applet
|
|
185
191
|
loop do
|
186
192
|
enabled = Knj::Strings.yn_str(Knj::Opts.get("reminder_enabled"), true, false)
|
187
193
|
if enabled and !@reminder_next
|
188
|
-
@reminder_next =
|
194
|
+
@reminder_next = Datet.new
|
189
195
|
@reminder_next.mins + Knj::Opts.get("reminder_every_minute").to_i
|
190
196
|
elsif enabled and @reminder_next and Time.now >= @reminder_next
|
191
197
|
self.reminding_exec
|
data/models/timelog.rb
CHANGED
@@ -14,7 +14,7 @@ class Openall_time_applet::Models::Timelog < Knj::Datarow
|
|
14
14
|
|
15
15
|
#Fix default time-type (SQLite3 doesnt support this).
|
16
16
|
self[:timetype] = "normal" if self[:timetype].to_s == ""
|
17
|
-
self[:timestamp] =
|
17
|
+
self[:timestamp] = Datet.new.dbstr if self[:timestamp].to_s == ""
|
18
18
|
end
|
19
19
|
|
20
20
|
#Treat data before inserting into database.
|
@@ -24,11 +24,43 @@ class Openall_time_applet::Models::Timelog < Knj::Datarow
|
|
24
24
|
d.data[:parent_timelog_id] = 0 if !d.data.key?(:parent_timelog_id)
|
25
25
|
end
|
26
26
|
|
27
|
+
#This method is called from objects-framework when it wants to delete this object. It checks for various cases where the timelog shouldnt be deleted.
|
28
|
+
def delete
|
29
|
+
#Ensure that child timelogs with logged time do not get deleted automatically!
|
30
|
+
self.child_timelogs do |child_timelog|
|
31
|
+
if child_timelog.time_total > 0
|
32
|
+
raise sprintf(_("Child timelog '%1$s' (%2$s) has logged time (%3$s) and this timelog cannot be deleted."), child_timelog[:descr], child_timelog.id, child_timelog.time_as_human)
|
33
|
+
end
|
34
|
+
|
35
|
+
if child_timelog.time_total(:transport => true) > 0
|
36
|
+
raise _("A child timelog has logged transport time and this timelog cannot be deleted.")
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
#This method proxies updates to child timelogs as well (not time though).
|
42
|
+
def update(hash)
|
43
|
+
#Update the data on this object.
|
44
|
+
super(hash)
|
45
|
+
|
46
|
+
#Certain data should be updated on child timelogs as well.
|
47
|
+
hash.each do |key, val|
|
48
|
+
key = key.to_sym
|
49
|
+
|
50
|
+
case key
|
51
|
+
when :descr, :task_id, :timetype, :transportdescription, :travelfixed, :workinternal, :sync_need
|
52
|
+
self.child_timelogs do |child_timelog|
|
53
|
+
child_timelog[key] = val
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
27
59
|
#Pushes timelogs and time to OpenAll.
|
28
60
|
def self.push_time_updates(d, args)
|
29
61
|
args[:oata].oa_conn do |conn|
|
30
62
|
#Go through timelogs that needs syncing and has a task set.
|
31
|
-
self.ob.list(:Timelog, {"sync_need" => 1, "task_id_not" => 0}) do |timelog|
|
63
|
+
self.ob.list(:Timelog, {"sync_need" => 1, "task_id_not" => ["", 0]}) do |timelog|
|
32
64
|
secs_sum = timelog[:time_sync].to_i + timelog[:time_transport].to_i
|
33
65
|
next if secs_sum <= 0 or !timelog.task
|
34
66
|
|
@@ -50,8 +82,23 @@ class Openall_time_applet::Models::Timelog < Knj::Datarow
|
|
50
82
|
}
|
51
83
|
)
|
52
84
|
|
85
|
+
parent_timelog = timelog.parent_timelog
|
86
|
+
timelog.delete_empty_children
|
87
|
+
|
53
88
|
#Delete timelog.
|
54
|
-
|
89
|
+
if timelog.child_timelogs("count" => true) <= 0
|
90
|
+
d.ob.delete(timelog)
|
91
|
+
else
|
92
|
+
timelog[:time] = 0
|
93
|
+
timelog[:time_transport] = 0
|
94
|
+
end
|
95
|
+
|
96
|
+
if parent_timelog
|
97
|
+
parent_timelog_child_timelogs = parent_timelog.child_timelogs("count" => true)
|
98
|
+
if parent_timelog_child_timelogs <= 0
|
99
|
+
d.ob.delete(parent_timelog)
|
100
|
+
end
|
101
|
+
end
|
55
102
|
end
|
56
103
|
end
|
57
104
|
end
|
@@ -97,4 +144,15 @@ class Openall_time_applet::Models::Timelog < Knj::Datarow
|
|
97
144
|
def time_transport_as_human
|
98
145
|
return Knj::Strings.secs_to_human_time_str(self.time_total(:transport => true))
|
99
146
|
end
|
147
|
+
|
148
|
+
def delete_empty_children
|
149
|
+
self.child_timelogs do |child_timelog|
|
150
|
+
time = child_timelog.time_total
|
151
|
+
time_transport = child_timelog.time_total(:transport => true)
|
152
|
+
|
153
|
+
if time <= 0 and time_transport <= 0
|
154
|
+
self.ob.delete(child_timelog)
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
100
158
|
end
|
data/models/worktime.rb
CHANGED
@@ -18,7 +18,7 @@ class Openall_time_applet::Models::Worktime < Knj::Datarow
|
|
18
18
|
save_hash = {
|
19
19
|
:openall_uid => wt_d["uid"],
|
20
20
|
:task_id => task.id,
|
21
|
-
:timestamp =>
|
21
|
+
:timestamp => Datet.in(wt_d["timestamp"]),
|
22
22
|
:worktime => Knj::Strings.human_time_str_to_secs(wt_d["worktime"]),
|
23
23
|
:transporttime => Knj::Strings.human_time_str_to_secs(wt_d["transporttime"]),
|
24
24
|
:comment => wt_d["comment"]
|
data/openall_time_applet.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{openall_time_applet}
|
8
|
-
s.version = "0.0.
|
8
|
+
s.version = "0.0.22"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Kasper Johansen"]
|
12
|
-
s.date = %q{2012-07-
|
12
|
+
s.date = %q{2012-07-16}
|
13
13
|
s.description = %q{Off-line time-tracking for OpenAll with syncing when online.}
|
14
14
|
s.email = %q{k@spernj.org}
|
15
15
|
s.executables = ["OpenAll Timelogging", "openall_time_applet.rb"]
|
@@ -39,12 +39,10 @@ Gem::Specification.new do |s|
|
|
39
39
|
"glade/win_main.glade",
|
40
40
|
"glade/win_overview.glade",
|
41
41
|
"glade/win_preferences.glade",
|
42
|
-
"glade/win_sync_overview.glade",
|
43
42
|
"glade/win_worktime_overview.glade",
|
44
43
|
"gui/trayicon.rb",
|
45
44
|
"gui/win_main.rb",
|
46
45
|
"gui/win_preferences.rb",
|
47
|
-
"gui/win_sync_overview.rb",
|
48
46
|
"gui/win_worktime_overview.rb",
|
49
47
|
"lib/openall_time_applet.rb",
|
50
48
|
"locales/da_DK/LC_MESSAGES/default.mo",
|
@@ -74,6 +72,8 @@ Gem::Specification.new do |s|
|
|
74
72
|
s.add_runtime_dependency(%q<gettext>, [">= 0"])
|
75
73
|
s.add_runtime_dependency(%q<json>, [">= 0"])
|
76
74
|
s.add_runtime_dependency(%q<rmagick>, [">= 0"])
|
75
|
+
s.add_runtime_dependency(%q<datet>, [">= 0"])
|
76
|
+
s.add_runtime_dependency(%q<http2>, [">= 0"])
|
77
77
|
s.add_development_dependency(%q<rspec>, ["~> 2.8.0"])
|
78
78
|
s.add_development_dependency(%q<rdoc>, ["~> 3.12"])
|
79
79
|
s.add_development_dependency(%q<bundler>, [">= 1.0.0"])
|
@@ -86,6 +86,8 @@ Gem::Specification.new do |s|
|
|
86
86
|
s.add_dependency(%q<gettext>, [">= 0"])
|
87
87
|
s.add_dependency(%q<json>, [">= 0"])
|
88
88
|
s.add_dependency(%q<rmagick>, [">= 0"])
|
89
|
+
s.add_dependency(%q<datet>, [">= 0"])
|
90
|
+
s.add_dependency(%q<http2>, [">= 0"])
|
89
91
|
s.add_dependency(%q<rspec>, ["~> 2.8.0"])
|
90
92
|
s.add_dependency(%q<rdoc>, ["~> 3.12"])
|
91
93
|
s.add_dependency(%q<bundler>, [">= 1.0.0"])
|
@@ -99,6 +101,8 @@ Gem::Specification.new do |s|
|
|
99
101
|
s.add_dependency(%q<gettext>, [">= 0"])
|
100
102
|
s.add_dependency(%q<json>, [">= 0"])
|
101
103
|
s.add_dependency(%q<rmagick>, [">= 0"])
|
104
|
+
s.add_dependency(%q<datet>, [">= 0"])
|
105
|
+
s.add_dependency(%q<http2>, [">= 0"])
|
102
106
|
s.add_dependency(%q<rspec>, ["~> 2.8.0"])
|
103
107
|
s.add_dependency(%q<rdoc>, ["~> 3.12"])
|
104
108
|
s.add_dependency(%q<bundler>, [">= 1.0.0"])
|
@@ -8,7 +8,7 @@ describe "OpenallTimeApplet" do
|
|
8
8
|
end
|
9
9
|
|
10
10
|
it "should be able to clone timelogs" do
|
11
|
-
date =
|
11
|
+
date = Datet.new
|
12
12
|
date.days - 1
|
13
13
|
|
14
14
|
timelog = $oata.ob.add(:Timelog, {
|
@@ -32,6 +32,11 @@ describe "OpenallTimeApplet" do
|
|
32
32
|
|
33
33
|
timelogs = $oata.ob.list(:Timelog, "orderby" => "timestamp").to_a
|
34
34
|
raise "Expected amount of timelogs to be 2 but it wasnt: #{timelogs.length}" if timelogs.length != 2
|
35
|
+
|
36
|
+
#Or else it wont be possible to delete main timelog.
|
37
|
+
timelog.child_timelogs do |child_timelog|
|
38
|
+
child_timelog[:time] = 0
|
39
|
+
end
|
35
40
|
end
|
36
41
|
|
37
42
|
it "should automatically delete sub-timelogs" do
|
metadata
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
name: openall_time_applet
|
3
3
|
version: !ruby/object:Gem::Version
|
4
4
|
prerelease:
|
5
|
-
version: 0.0.
|
5
|
+
version: 0.0.22
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
8
|
- Kasper Johansen
|
@@ -10,7 +10,7 @@ autorequire:
|
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
12
|
|
13
|
-
date: 2012-07-
|
13
|
+
date: 2012-07-16 00:00:00 +02:00
|
14
14
|
default_executable:
|
15
15
|
dependencies:
|
16
16
|
- !ruby/object:Gem::Dependency
|
@@ -80,8 +80,30 @@ dependencies:
|
|
80
80
|
prerelease: false
|
81
81
|
version_requirements: *id006
|
82
82
|
- !ruby/object:Gem::Dependency
|
83
|
-
name:
|
83
|
+
name: datet
|
84
84
|
requirement: &id007 !ruby/object:Gem::Requirement
|
85
|
+
none: false
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: "0"
|
90
|
+
type: :runtime
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: *id007
|
93
|
+
- !ruby/object:Gem::Dependency
|
94
|
+
name: http2
|
95
|
+
requirement: &id008 !ruby/object:Gem::Requirement
|
96
|
+
none: false
|
97
|
+
requirements:
|
98
|
+
- - ">="
|
99
|
+
- !ruby/object:Gem::Version
|
100
|
+
version: "0"
|
101
|
+
type: :runtime
|
102
|
+
prerelease: false
|
103
|
+
version_requirements: *id008
|
104
|
+
- !ruby/object:Gem::Dependency
|
105
|
+
name: rspec
|
106
|
+
requirement: &id009 !ruby/object:Gem::Requirement
|
85
107
|
none: false
|
86
108
|
requirements:
|
87
109
|
- - ~>
|
@@ -89,10 +111,10 @@ dependencies:
|
|
89
111
|
version: 2.8.0
|
90
112
|
type: :development
|
91
113
|
prerelease: false
|
92
|
-
version_requirements: *
|
114
|
+
version_requirements: *id009
|
93
115
|
- !ruby/object:Gem::Dependency
|
94
116
|
name: rdoc
|
95
|
-
requirement: &
|
117
|
+
requirement: &id010 !ruby/object:Gem::Requirement
|
96
118
|
none: false
|
97
119
|
requirements:
|
98
120
|
- - ~>
|
@@ -100,10 +122,10 @@ dependencies:
|
|
100
122
|
version: "3.12"
|
101
123
|
type: :development
|
102
124
|
prerelease: false
|
103
|
-
version_requirements: *
|
125
|
+
version_requirements: *id010
|
104
126
|
- !ruby/object:Gem::Dependency
|
105
127
|
name: bundler
|
106
|
-
requirement: &
|
128
|
+
requirement: &id011 !ruby/object:Gem::Requirement
|
107
129
|
none: false
|
108
130
|
requirements:
|
109
131
|
- - ">="
|
@@ -111,10 +133,10 @@ dependencies:
|
|
111
133
|
version: 1.0.0
|
112
134
|
type: :development
|
113
135
|
prerelease: false
|
114
|
-
version_requirements: *
|
136
|
+
version_requirements: *id011
|
115
137
|
- !ruby/object:Gem::Dependency
|
116
138
|
name: jeweler
|
117
|
-
requirement: &
|
139
|
+
requirement: &id012 !ruby/object:Gem::Requirement
|
118
140
|
none: false
|
119
141
|
requirements:
|
120
142
|
- - ~>
|
@@ -122,10 +144,10 @@ dependencies:
|
|
122
144
|
version: 1.8.3
|
123
145
|
type: :development
|
124
146
|
prerelease: false
|
125
|
-
version_requirements: *
|
147
|
+
version_requirements: *id012
|
126
148
|
- !ruby/object:Gem::Dependency
|
127
149
|
name: rcov
|
128
|
-
requirement: &
|
150
|
+
requirement: &id013 !ruby/object:Gem::Requirement
|
129
151
|
none: false
|
130
152
|
requirements:
|
131
153
|
- - ">="
|
@@ -133,7 +155,7 @@ dependencies:
|
|
133
155
|
version: "0"
|
134
156
|
type: :development
|
135
157
|
prerelease: false
|
136
|
-
version_requirements: *
|
158
|
+
version_requirements: *id013
|
137
159
|
description: Off-line time-tracking for OpenAll with syncing when online.
|
138
160
|
email: k@spernj.org
|
139
161
|
executables:
|
@@ -166,12 +188,10 @@ files:
|
|
166
188
|
- glade/win_main.glade
|
167
189
|
- glade/win_overview.glade
|
168
190
|
- glade/win_preferences.glade
|
169
|
-
- glade/win_sync_overview.glade
|
170
191
|
- glade/win_worktime_overview.glade
|
171
192
|
- gui/trayicon.rb
|
172
193
|
- gui/win_main.rb
|
173
194
|
- gui/win_preferences.rb
|
174
|
-
- gui/win_sync_overview.rb
|
175
195
|
- gui/win_worktime_overview.rb
|
176
196
|
- lib/openall_time_applet.rb
|
177
197
|
- locales/da_DK/LC_MESSAGES/default.mo
|
@@ -198,7 +218,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
198
218
|
requirements:
|
199
219
|
- - ">="
|
200
220
|
- !ruby/object:Gem::Version
|
201
|
-
hash:
|
221
|
+
hash: -3066886317205834200
|
202
222
|
segments:
|
203
223
|
- 0
|
204
224
|
version: "0"
|
@@ -1,121 +0,0 @@
|
|
1
|
-
<?xml version="1.0" encoding="UTF-8"?>
|
2
|
-
<interface>
|
3
|
-
<requires lib="gtk+" version="2.24"/>
|
4
|
-
<!-- interface-naming-policy project-wide -->
|
5
|
-
<object class="GtkWindow" id="window">
|
6
|
-
<property name="can_focus">False</property>
|
7
|
-
<property name="title" translatable="yes">Prepare transfer</property>
|
8
|
-
<property name="window_position">center</property>
|
9
|
-
<property name="default_width">850</property>
|
10
|
-
<property name="default_height">480</property>
|
11
|
-
<signal name="destroy" handler="on_window_destroy" swapped="no"/>
|
12
|
-
<child>
|
13
|
-
<object class="GtkVBox" id="vbox1">
|
14
|
-
<property name="visible">True</property>
|
15
|
-
<property name="can_focus">False</property>
|
16
|
-
<child>
|
17
|
-
<object class="GtkFrame" id="frame1">
|
18
|
-
<property name="visible">True</property>
|
19
|
-
<property name="can_focus">False</property>
|
20
|
-
<property name="label_xalign">0</property>
|
21
|
-
<property name="shadow_type">none</property>
|
22
|
-
<child>
|
23
|
-
<object class="GtkAlignment" id="alignment1">
|
24
|
-
<property name="visible">True</property>
|
25
|
-
<property name="can_focus">False</property>
|
26
|
-
<property name="left_padding">12</property>
|
27
|
-
<child>
|
28
|
-
<object class="GtkVBox" id="vbox2">
|
29
|
-
<property name="visible">True</property>
|
30
|
-
<property name="can_focus">False</property>
|
31
|
-
<property name="spacing">3</property>
|
32
|
-
<child>
|
33
|
-
<object class="GtkViewport" id="viewport1">
|
34
|
-
<property name="visible">True</property>
|
35
|
-
<property name="can_focus">False</property>
|
36
|
-
<child>
|
37
|
-
<object class="GtkScrolledWindow" id="scrolledwindow1">
|
38
|
-
<property name="visible">True</property>
|
39
|
-
<property name="can_focus">True</property>
|
40
|
-
<property name="hscrollbar_policy">automatic</property>
|
41
|
-
<property name="vscrollbar_policy">automatic</property>
|
42
|
-
<child>
|
43
|
-
<object class="GtkTreeView" id="tvTimelogs">
|
44
|
-
<property name="visible">True</property>
|
45
|
-
<property name="can_focus">True</property>
|
46
|
-
</object>
|
47
|
-
</child>
|
48
|
-
</object>
|
49
|
-
</child>
|
50
|
-
</object>
|
51
|
-
<packing>
|
52
|
-
<property name="expand">True</property>
|
53
|
-
<property name="fill">True</property>
|
54
|
-
<property name="position">0</property>
|
55
|
-
</packing>
|
56
|
-
</child>
|
57
|
-
<child>
|
58
|
-
<object class="GtkHButtonBox" id="hbuttonbox1">
|
59
|
-
<property name="visible">True</property>
|
60
|
-
<property name="can_focus">False</property>
|
61
|
-
<property name="spacing">3</property>
|
62
|
-
<property name="layout_style">end</property>
|
63
|
-
<child>
|
64
|
-
<object class="GtkButton" id="btnSync">
|
65
|
-
<property name="label">gtk-harddisk</property>
|
66
|
-
<property name="visible">True</property>
|
67
|
-
<property name="can_focus">True</property>
|
68
|
-
<property name="receives_default">True</property>
|
69
|
-
<property name="use_action_appearance">False</property>
|
70
|
-
<property name="use_stock">True</property>
|
71
|
-
<signal name="clicked" handler="on_btnSync_clicked" swapped="no"/>
|
72
|
-
</object>
|
73
|
-
<packing>
|
74
|
-
<property name="expand">False</property>
|
75
|
-
<property name="fill">False</property>
|
76
|
-
<property name="position">0</property>
|
77
|
-
</packing>
|
78
|
-
</child>
|
79
|
-
</object>
|
80
|
-
<packing>
|
81
|
-
<property name="expand">False</property>
|
82
|
-
<property name="fill">True</property>
|
83
|
-
<property name="position">1</property>
|
84
|
-
</packing>
|
85
|
-
</child>
|
86
|
-
</object>
|
87
|
-
</child>
|
88
|
-
</object>
|
89
|
-
</child>
|
90
|
-
<child type="label">
|
91
|
-
<object class="GtkLabel" id="label2">
|
92
|
-
<property name="visible">True</property>
|
93
|
-
<property name="can_focus">False</property>
|
94
|
-
<property name="label" translatable="yes"><b>Prepare transfer</b></property>
|
95
|
-
<property name="use_markup">True</property>
|
96
|
-
</object>
|
97
|
-
</child>
|
98
|
-
</object>
|
99
|
-
<packing>
|
100
|
-
<property name="expand">True</property>
|
101
|
-
<property name="fill">True</property>
|
102
|
-
<property name="position">0</property>
|
103
|
-
</packing>
|
104
|
-
</child>
|
105
|
-
<child>
|
106
|
-
<object class="GtkLabel" id="labTotal">
|
107
|
-
<property name="visible">True</property>
|
108
|
-
<property name="can_focus">False</property>
|
109
|
-
<property name="xalign">0</property>
|
110
|
-
<property name="label" translatable="yes">[totals]</property>
|
111
|
-
</object>
|
112
|
-
<packing>
|
113
|
-
<property name="expand">False</property>
|
114
|
-
<property name="fill">True</property>
|
115
|
-
<property name="position">1</property>
|
116
|
-
</packing>
|
117
|
-
</child>
|
118
|
-
</object>
|
119
|
-
</child>
|
120
|
-
</object>
|
121
|
-
</interface>
|
data/gui/win_sync_overview.rb
DELETED
@@ -1,213 +0,0 @@
|
|
1
|
-
#This class handels the window that will be shown before the actual sync takes place.
|
2
|
-
class Openall_time_applet::Gui::Win_sync_overview
|
3
|
-
def initialize(args)
|
4
|
-
@args = args
|
5
|
-
|
6
|
-
@gui = Gtk::Builder.new.add("../glade/win_sync_overview.glade")
|
7
|
-
@gui.translate
|
8
|
-
@gui.connect_signals{|h|method(h)}
|
9
|
-
@gui["btnSync"].label = _("Transfer")
|
10
|
-
|
11
|
-
#Generate list-store containing tasks for the task-column.
|
12
|
-
task_ls = Gtk::ListStore.new(String, String)
|
13
|
-
iter = task_ls.append
|
14
|
-
iter[0] = _("None")
|
15
|
-
iter[1] = 0.to_s
|
16
|
-
|
17
|
-
tasks = [_("Choose:")]
|
18
|
-
@args[:oata].ob.list(:Task, {"orderby" => "title"}) do |task|
|
19
|
-
iter = task_ls.append
|
20
|
-
iter[0] = task[:title]
|
21
|
-
iter[1] = task.id.to_s
|
22
|
-
tasks << task
|
23
|
-
end
|
24
|
-
|
25
|
-
#Initialize timelog treeview.
|
26
|
-
init_data = Knj::Gtk2::Tv.init(@gui["tvTimelogs"], [
|
27
|
-
_("ID"),
|
28
|
-
_("Description"),
|
29
|
-
_("Timestamp"),
|
30
|
-
_("Time"),
|
31
|
-
_("Transport"),
|
32
|
-
_("Length"),
|
33
|
-
_("Transport descr."),
|
34
|
-
_("Transport costs"),
|
35
|
-
{
|
36
|
-
:title => _("Fixed travel"),
|
37
|
-
:type => :toggle
|
38
|
-
},
|
39
|
-
{
|
40
|
-
:title => _("Int. work"),
|
41
|
-
:type => :toggle
|
42
|
-
},
|
43
|
-
{
|
44
|
-
:title => _("Sync?"),
|
45
|
-
:type => :toggle
|
46
|
-
},
|
47
|
-
{
|
48
|
-
:title => _("Task"),
|
49
|
-
:type => :combo,
|
50
|
-
:model => task_ls,
|
51
|
-
:has_entry => false
|
52
|
-
},
|
53
|
-
_("Sync time")
|
54
|
-
])
|
55
|
-
|
56
|
-
#Make columns editable.
|
57
|
-
Knj::Gtk2::Tv.editable_text_renderers_to_model(
|
58
|
-
:ob => @args[:oata].ob,
|
59
|
-
:tv => @gui["tvTimelogs"],
|
60
|
-
:model_class => :Timelog,
|
61
|
-
:renderers => init_data[:renderers],
|
62
|
-
:change_before => proc{ @dont_reload = true },
|
63
|
-
:change_after => proc{ @dont_reload = false; self.update_totals },
|
64
|
-
:cols => {
|
65
|
-
1 => :descr,
|
66
|
-
2 => {:col => :timestamp, :type => :datetime},
|
67
|
-
3 => {:col => :time, :type => :time_as_sec},
|
68
|
-
4 => {:col => :time_transport, :type => :time_as_sec},
|
69
|
-
5 => {:col => :transportlength, :type => :int},
|
70
|
-
6 => {:col => :transportdescription},
|
71
|
-
7 => {:col => :transportcosts, :type => :human_number, :decimals => 2},
|
72
|
-
8 => {:col => :travelfixed},
|
73
|
-
9 => {:col => :workinternal},
|
74
|
-
10 => {:col => :sync_need},
|
75
|
-
11 => {
|
76
|
-
:col => :task_id,
|
77
|
-
:value_callback => lambda{ |data|
|
78
|
-
task = @args[:oata].ob.get_by(:Task, {"title" => data[:value]})
|
79
|
-
|
80
|
-
if !task
|
81
|
-
return 0
|
82
|
-
else
|
83
|
-
return task.id
|
84
|
-
end
|
85
|
-
},
|
86
|
-
:value_set_callback => proc{ |data| data[:model].task_name }
|
87
|
-
},
|
88
|
-
12 => {:col => :time_sync, :type => :time_as_sec}
|
89
|
-
}
|
90
|
-
)
|
91
|
-
@gui["tvTimelogs"].columns[0].visible = false
|
92
|
-
|
93
|
-
|
94
|
-
=begin
|
95
|
-
rowc = 1
|
96
|
-
@args[:oata].ob.list(:Timelog, {"sync_need" => 1, "task_id_not" => 0}) do |timelog|
|
97
|
-
|
98
|
-
|
99
|
-
#Spawn widgets.
|
100
|
-
timelog_label = Gtk::Label.new(timelog[:descr])
|
101
|
-
timelog_label.xalign = 0
|
102
|
-
timelog_label.selectable = true
|
103
|
-
|
104
|
-
logged_time_label = Gtk::Label.new(Knj::Strings.secs_to_human_time_str(timelog[:time]))
|
105
|
-
logged_time_label.xalign = 0
|
106
|
-
logged_time_label.selectable = true
|
107
|
-
|
108
|
-
sync_time_text = Gtk::Entry.new
|
109
|
-
sync_time_text.text = Knj::Strings.secs_to_human_time_str(count_rounded_time)
|
110
|
-
sync_time_text.signal_connect(:changed, &self.method(:on_syncTimeText_changed))
|
111
|
-
@sync_time_text_widgets[timelog.id] = sync_time_text
|
112
|
-
|
113
|
-
#Attach widgets in table.
|
114
|
-
@gui["tableTimelogs"].attach(timelog_label, 0, 1, rowc, rowc + 1)
|
115
|
-
@gui["tableTimelogs"].attach(logged_time_label, 1, 2, rowc, rowc + 1)
|
116
|
-
@gui["tableTimelogs"].attach(sync_time_text, 2, 3, rowc, rowc + 1)
|
117
|
-
|
118
|
-
rowc += 1
|
119
|
-
end
|
120
|
-
=end
|
121
|
-
|
122
|
-
self.reload_timelogs
|
123
|
-
self.update_totals
|
124
|
-
|
125
|
-
@reload_id = @args[:oata].ob.connect("object" => :Timelog, "signals" => ["add", "update", "delete"], &self.method(:reload_timelogs))
|
126
|
-
|
127
|
-
if @timelogs_count <= 0
|
128
|
-
#Show error-message and destroy the window, if no timelogs was to be synced.
|
129
|
-
Knj::Gtk2.msgbox("msg" => _("There is nothing to sync at this time."), "run" => false)
|
130
|
-
@gui["window"].destroy
|
131
|
-
else
|
132
|
-
#...else show the window.
|
133
|
-
@gui["window"].show_all
|
134
|
-
end
|
135
|
-
end
|
136
|
-
|
137
|
-
def reload_timelogs
|
138
|
-
return nil if @dont_reload or @gui["tvTimelogs"].destroyed?
|
139
|
-
@gui["tvTimelogs"].model.clear
|
140
|
-
@timelogs_count = 0
|
141
|
-
|
142
|
-
@args[:oata].ob.list(:Timelog, {"sync_need" => 1, "task_id_not" => ["", 0], "orderby" => "timestamp"}) do |timelog|
|
143
|
-
#Read time and transport from timelog.
|
144
|
-
time = timelog[:time].to_i
|
145
|
-
transport = timelog[:time_transport].to_i
|
146
|
-
|
147
|
-
#If transport is logged, then the work if offsite. It should be rounded up by 30 min. Else 15 min. round up.
|
148
|
-
if transport > 0
|
149
|
-
roundup = 1800
|
150
|
-
else
|
151
|
-
roundup = 900
|
152
|
-
end
|
153
|
-
|
154
|
-
#Do the actual counting.
|
155
|
-
count_rounded_time = 0
|
156
|
-
loop do
|
157
|
-
break if count_rounded_time >= time
|
158
|
-
count_rounded_time += roundup
|
159
|
-
end
|
160
|
-
|
161
|
-
#Set sync-time on timelog.
|
162
|
-
timelog[:time_sync] = count_rounded_time
|
163
|
-
|
164
|
-
Knj::Gtk2::Tv.append(@gui["tvTimelogs"], [
|
165
|
-
timelog.id,
|
166
|
-
timelog[:descr],
|
167
|
-
timelog.timestamp_str,
|
168
|
-
timelog.time_as_human,
|
169
|
-
timelog.time_transport_as_human,
|
170
|
-
Knj::Locales.number_out(timelog[:transportlength], 0),
|
171
|
-
timelog.transport_descr_short,
|
172
|
-
Knj::Locales.number_out(timelog[:transportcosts], 2),
|
173
|
-
Knj::Strings.yn_str(timelog[:travelfixed], true, false),
|
174
|
-
Knj::Strings.yn_str(timelog[:workinternal], true, false),
|
175
|
-
Knj::Strings.yn_str(timelog[:sync_need], true, false),
|
176
|
-
timelog.task_name,
|
177
|
-
Knj::Strings.secs_to_human_time_str(count_rounded_time)
|
178
|
-
])
|
179
|
-
@timelogs_count += 1
|
180
|
-
end
|
181
|
-
end
|
182
|
-
|
183
|
-
def on_btnSync_clicked
|
184
|
-
begin
|
185
|
-
#Destroy this window and start syncing for real.
|
186
|
-
@gui["window"].destroy
|
187
|
-
@args[:oata].sync_real
|
188
|
-
rescue => e
|
189
|
-
Knj::Gtk2.msgbox(Knj::Errors.error_str(e))
|
190
|
-
end
|
191
|
-
end
|
192
|
-
|
193
|
-
def update_totals
|
194
|
-
total_secs = 0
|
195
|
-
|
196
|
-
@gui["tvTimelogs"].model.each do |model, path, iter|
|
197
|
-
time_val = @gui["tvTimelogs"].model.get_value(iter, 12)
|
198
|
-
|
199
|
-
begin
|
200
|
-
total_secs += Knj::Strings.human_time_str_to_secs(time_val)
|
201
|
-
rescue
|
202
|
-
#ignore - user is properly entering stuff.
|
203
|
-
end
|
204
|
-
end
|
205
|
-
|
206
|
-
@gui["labTotal"].markup = "<b>#{_("Total hours:")}</b> #{Knj::Strings.secs_to_human_time_str(total_secs)}"
|
207
|
-
end
|
208
|
-
|
209
|
-
def on_window_destroy
|
210
|
-
#Unconnect reload-event. Else it will crash on call to destroyed object. Also frees up various ressources.
|
211
|
-
@args[:oata].ob.unconnect("object" => :Timelog, "conn_id" => @reload_id)
|
212
|
-
end
|
213
|
-
end
|