class Openall_time_applet::Gui::Win_main ROWS_BOLD = [:timestamp, :time, :descr, :ttime, :tkm, :ttype, :tdescr, :cost, :task] attr_reader :args, :gui def initialize(args) @args = args @oata = @args[:oata] @ob = @oata.ob @log = @oata.log @gui = Gtk::Builder.new.add("#{File.dirname(__FILE__)}/../glade/win_main.glade") @gui.translate @gui.connect_signals{|h| method(h)} #Shortcut-variables to very used widgets. @window = @gui["window"] @expander = @gui["expOverview"] @tv = @gui["tvTimelogs"] @tvpt = @gui["tvTimelogsPrepareTransfer"] #Set icon for window. @window.icon = "#{File.dirname(__FILE__)}/../gfx/icon_time_black.png" #Settings for various widgets. Gtk2_expander_settings.new(:expander => @expander, :name => "main_expander", :db => @oata.db) Gtk2_window_settings.new(:window => @window, :name => "main_window", :db => @oata.db) #Trigger max. height stuff. self.on_expOverview_activate(@expander) #Generate list-store containing tasks for the task-column. @task_ls = Gtk::ListStore.new(String, String) iter = @task_ls.append iter[0] = _("None") iter[1] = "0" tasks = [_("Choose:")] @ob.static(:Task, :tasks_to_show) do |task| iter = @task_ls.append iter[0] = task.name iter[1] = task.id.to_s tasks << task end self.task_ls_resort @gui["cbTask"].init(tasks) #Generate list-store containing time-types. @time_types = Openall_time_applet::Models::Timelog.time_types time_types_ls = Gtk::ListStore.new(String, String) @time_types.each do |key, val| iter = time_types_ls.append iter[0] = val iter[1] = key end #Set up completion for description entry. @descr_ec = Gtk::EntryCompletion.new @descr_ec.model = Gtk::ListStore.new(String) @descr_ec.text_column = 0 @gui["txtDescr"].completion = @descr_ec self.reload_descr_completion @descr_ec.signal_connect("match-selected", &self.method(:on_descr_entrycompletion_selected)) init_data = @tv.init( :type => :treestore, :reorderable => false, :sortable => false, :cols => [ _("ID"), { :title => _("Stamp"), :type => :string, :markup => true, :expand => false }, { :title => _("Time"), :type => :string, :markup => true, :expand => false }, { :title => _("Description"), :type => :string, :markup => true, :expand => true, :fixed_width => 160 }, { :title => _("T-time"), :type => :string, :markup => true, :expand => false }, { :title => _("T-km"), :type => :string, :markup => true }, { :title => _("T-Descr."), :type => :string, :markup => true, :expand => true, :fixed_width => 160 }, { :title => _("T-type"), :type => :combo, :model => time_types_ls, :has_entry => false, :fixed_width => 120, :markup => true }, { :title => _("Cost"), :type => :string, :markup => true }, { :title => _("Fixed"), :type => :toggle, :expand => false }, { :title => _("Internal")[0, 3] + ".", :type => :toggle, :expand => false }, { :title => _("Task"), :type => :combo, :model => @task_ls, :has_entry => false, :markup => true, :expand => true, :fixed_width => 160 }, { :title => _("Track"), :type => :toggle, :expand => false } ] ) @tv_settings = Gtk2_treeview_settings.new( :tv => @tv, :col_ids => { 0 => :id, 1 => :timestamp, 2 => :time, 3 => :descr, 4 => :ttime, 5 => :tkm, 6 => :tdescr, 7 => :ttype, 8 => :cost, 9 => :fixed, 10 => :int, 11 => :task, 12 => :track } ) Knj::Gtk2::Tv.editable_text_renderers_to_model( :ob => @oata.ob, :tv => @tv, :model_class => :Timelog, :renderers => init_data[:renderers], :change_before => proc{|d| if d[:col_no] == 2 and @oata.timelog_active and @oata.timelog_active.id == d[:model].id raise _("You cannot edit the time for the active timelog.") end @dont_reload = true }, :change_after => proc{ @dont_reload = false }, :on_edit => proc{|d| @tv_editting = true }, :on_edit_done => proc{|d| @tv_editting = nil }, :cols => { @tv_settings.col_orig_no_for_id(:timestamp) => { :col => :timestamp, :value_callback => self.method(:tv_editable_timestamp_callback), :value_set_callback => self.method(:tv_editable_timestamp_set_callback) }, @tv_settings.col_orig_no_for_id(:time) => { :col => :time, :value_callback => proc{ |data| Knj::Strings.human_time_str_to_secs(data[:value]) }, :value_set_callback => proc{ |data| Knj::Strings.secs_to_human_time_str(data[:value], :secs => false) } }, @tv_settings.col_orig_no_for_id(:descr) => :descr, @tv_settings.col_orig_no_for_id(:ttime) => { :col => :time_transport, :value_callback => proc{ |data| Knj::Strings.human_time_str_to_secs(data[:value]) }, :value_set_callback => proc{ |data| Knj::Strings.secs_to_human_time_str(data[:value], :secs => false) } }, @tv_settings.col_orig_no_for_id(:tkm) => {:col => :transportlength, :type => :int}, @tv_settings.col_orig_no_for_id(:tdescr) => {:col => :transportdescription}, @tv_settings.col_orig_no_for_id(:ttype) => { :col => :timetype, :value_callback => lambda{|data| @time_types.key(data[:value]) }, :value_set_callback => proc{|data| Knj::Web.html(@time_types.fetch(data[:value])) } }, @tv_settings.col_orig_no_for_id(:cost) => {:col => :transportcosts, :type => :human_number, :decimals => 2}, @tv_settings.col_orig_no_for_id(:fixed) => {:col => :travelfixed}, @tv_settings.col_orig_no_for_id(:int) => {:col => :workinternal}, @tv_settings.col_orig_no_for_id(:task) => { :col => :task_id, :value_callback => lambda{|data| #Return ID of the found task or 0 if none was found. @ob.list(:Task) do |task| return task.id if task.name == data[:value] end return 0 }, :value_set_callback => proc{|data| #Return the task-name for the current rows timelog. data[:model].task_name } }, @tv_settings.col_orig_no_for_id(:track) => { :value_callback => lambda{|data| if !data[:model] Knj::Gtk2.msgbox(_("You cannot track a date. Please track a timelog instead.")) return false else if data[:value] @oata.timelog_active = data[:model] elsif data[:model] == @oata.timelog_active @oata.timelog_stop_tracking end return data[:model] == @oata.timelog_active end } } } ) #The ID column should not be visible (it is only used to identify which timelog the row represents). @tv.columns[0].visible = false #Move the columns around to the right order (the way Jacob wanted them). @tv.move_column_after(@tv.columns[10], @tv.columns[3]) @tv.move_column_after(@tv.columns[11], @tv.columns[3]) @tv.move_column_after(@tv.columns[11], @tv.columns[5]) @tv.move_column_after(@tv.columns[9], @tv.columns[6]) @tv.move_column_after(@tv.columns[11], @tv.columns[8]) #When a new row is selected, is should be evaluated if the minus-button should be active or not. @tv.selection.signal_connect("changed", &self.method(:validate_minus_active)) self.validate_minus_active #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. init_data[:renderers][2].signal_connect_after("editing-started", :time, &self.method(:on_cell_editingStarted)) #Fills the timelogs-treeview with data. self.reload_timelogs #Reload the treeview if something happened to a timelog. @reload_id = @ob.connect("object" => :Timelog, "signals" => ["add", "update"], &self.method(:reload_timelogs)) @reload_id_delete = @ob.connect("object" => :Timelog, "signals" => ["delete"], &self.method(:on_timelog_delete)) @reload_id_update = @ob.connect("object" => :Timelog, "signals" => ["update"], &self.method(:on_timelog_update)) @reload_tasks_id = @ob.connect("object" => :Task, "signals" => ["add"], &self.method(:on_task_added)) #Update switch-button. self.update_switch_button #Update switch-button when active timelog is changed. @event_timelog_active_changed = @oata.events.connect(:timelog_active_changed, &self.method(:on_timelog_active_changed)) #This timeout controls the updating of the timelog-info-frame and the time-counter for the active timelog in the treeview. @timeout_id = Gtk.timeout_add(1000, &self.method(:timeout_update_sec)) self.timeout_update_sec if @oata.timelog_active #Initializes sync-box. lab_transfer = _("Transfer") lab_transfer = "#{lab_transfer[0, 1]}_#{lab_transfer[1, 99]}" #For 'r'-shortcut. @gui["btnSyncPrepareTransfer"].label = lab_transfer #Generate list-store containing tasks for the task-column. @task_ls = Gtk::ListStore.new(String, String) iter = @task_ls.append iter[0] = _("None") iter[1] = 0.to_s tasks = [_("Choose:")] @ob.static(:Task, :tasks_to_show) do |task| iter = @task_ls.append iter[0] = task.name iter[1] = task.id.to_s tasks << task end #Initialize timelog treeview. init_data = Knj::Gtk2::Tv.init(@tvpt, [ _("ID"), { :title => _("Description"), :type => :string, :expand => true }, _("Stamp"), _("Time"), _("T-time"), _("T-km"), { :title => _("T-Descr."), :type => :string, :expand => true }, _("Cost"), { :title => _("Fixed"), :type => :toggle }, { :title => _("Internal")[0, 3] + ".", :type => :toggle }, { :title => _("Skip"), :type => :toggle }, { :title => _("Task"), :type => :combo, :model => @task_ls, :has_entry => false, :expand => true, :markup => true }, _("Sync time") ]) @tv_settings_pt = Gtk2_treeview_settings.new( :id => "win_main_tvTimelogsPrepareTransfer", :tv => @tvpt, :col_ids => { 0 => :id, 1 => :descr, 2 => :timestamp, 3 => :time, 4 => :ttime, 5 => :tkm, 6 => :tdescr, 7 => :cost, 8 => :fixed, 9 => :internal, 10 => :skip, 11 => :task, 12 => :sync_time } ) @tvpt.move_column_after(@tvpt.columns[1], @tvpt.columns[3]) @tvpt.move_column_after(@tvpt.columns[11], @tvpt.columns[3]) @tvpt.move_column_after(@tvpt.columns[9], @tvpt.columns[4]) @tvpt.move_column_after(@tvpt.columns[10], @tvpt.columns[5]) @tvpt.move_column_after(@tvpt.columns[9], @tvpt.columns[6]) #Make columns editable. Knj::Gtk2::Tv.editable_text_renderers_to_model( :ob => @oata.ob, :tv => @tvpt, :model_class => :Timelog, :renderers => init_data[:renderers], :change_before => proc{ @dont_reload_sync = true }, :change_after => proc{ @dont_reload_sync = false; self.update_sync_totals }, :cols => { 10 => {:col => :sync_need, :type => :toggle_rev}, 12 => { :col => :time_sync, :value_callback => proc{ |data| Knj::Strings.human_time_str_to_secs(data[:value]) }, :value_set_callback => proc{ |data| Knj::Strings.secs_to_human_time_str(data[:value], :secs => false) } } } ) @tvpt.columns[0].visible = false @gui["vboxPrepareTransfer"].hide @reload_preparetransfer_id = @ob.connect("object" => :Timelog, "signals" => ["add", "update"], &self.method(:reload_timelogs_preparetransfer)) #Show the window. @window.show self.timelog_info_trigger width = @window.size[0] @window.resize(width, 1) end def window_resize_height_disable hints = Gdk::Geometry.new hints.min_width = 1 hints.max_width = 9999 hints.min_height = 1 hints.max_height = 1 @window.set_geometry_hints(@expander, hints, Gdk::Window::HINT_MAX_SIZE) end def window_resize_height_enable hints = Gdk::Geometry.new hints.min_width = 1 hints.max_width = 9999 hints.min_height = 1 hints.max_height = 9999 @window.set_geometry_hints(@expander, hints, Gdk::Window::HINT_MAX_SIZE) end def task_ls_resort @task_ls.set_sort_column_id(0) @task_ls.set_sort_func(0, &lambda{|iter1, iter2| task_id_1 = iter1[1].to_i task_id_2 = iter2[1].to_i task_name_1 = iter1[0].to_s.downcase task_name_2 = iter2[0].to_s.downcase if task_id_1 == 0 return -1 elsif task_id_2 == 0 return 1 else return task_name_1 <=> task_name_2 end }) end #Called after a new task has been added. def on_task_added(task) puts "New task added: #{task.to_hash}" #Add the new task to the treeview rows. iter = @task_ls.append iter[0] = task.name iter[1] = task.id.to_s self.task_ls_resort renderer = @tv_settings.cellrenderer_for_id(:task) renderer.model = @task_ls #Add the new task to the combobox in the top. @gui["cbTask"].append_model(:model => task) @gui["cbTask"].resort end #This is called when an item from the description-entry-completion-menu is selected. This method sets the selected text in the description-entry. def on_descr_entrycompletion_selected(me, model, iter) text = model.get_value(iter, 0) me.entry.text = text return true end #This method is called when the active timelog is changed. It calls various events to update the switch-button, update information in treeview and more instantly (instead of waiting for the 1-sec timeout which will seem like a delay). def on_timelog_active_changed(*args) self.update_switch_button self.check_rows self.timelog_info_trigger end #This method is called every second in order to update various information when tracking timelogs (stop-button-time, treeview-time and more). def timeout_update_sec #Update various information in the main treeview (time-counter). self.check_rows self.timelog_info_trigger #Returns true in order to continue calling this method every second. return true end #This is called when updating a timelogs timestamp through the treeview. def tv_editable_timestamp_callback(data) begin return data[:model].timestamp.update_from_str(data[:value]) rescue => e Knj::Gtk2.msgbox(e.message) return data[:model].timestamp end end #This is called when a timelogs timestamp should be shown. def tv_editable_timestamp_set_callback(data) return Datet.in(data[:value]).strftime("%H:%M") end #Reloads the suggestions for the description-entry-completion. def reload_descr_completion added = {} @descr_ec.model.clear @ob.list(:Worktime, "orderby" => [["timestamp", "desc"]]) do |worktime| next if added.key?(worktime[:comment]) added[worktime[:comment]] = true @descr_ec.model.append[0] = worktime[:comment] end @ob.list(:Timelog, "orderby" => [["timestamp", "desc"]]) do |timelog| next if added.key?(timelog[:descr]) added[timelog[:descr]] = true @descr_ec.model.append[0] = timelog[:descr] end end def reload_timelogs_preparetransfer return nil if @dont_reload_sync or @tvpt.destroyed? @dont_reload_sync = true begin @tvpt.model.clear @timelogs_sync_count = 0 tnow_str = Time.now.strftime("%Y %m %d") @ob.list(:Timelog, "task_id_not" => ["", 0], "orderby" => [["timestamp", "desc"]]) do |timelog| #Read time and transport from timelog. time = timelog[:time].to_i transport = timelog[:time_transport].to_i #If transport is logged, then the work if offsite. It should be rounded up by 30 min. Else 15 min. round up. if transport > 0 roundup = 1800 else roundup = 900 end #Do the actual counting. count_rounded_time = 0 loop do break if count_rounded_time >= time count_rounded_time += roundup end #Set sync-time on timelog. timelog[:time_sync] = count_rounded_time tstamp = timelog.timestamp if tstamp.strftime("%Y %m %d") == tnow_str tstamp_str = tstamp.strftime("%H:%M") else tstamp_str = tstamp.strftime("%d/%m") end @tv_settings_pt.append( :id => timelog.id, :descr => timelog[:descr], :timestamp => tstamp_str, :time => timelog.time_as_human, :ttime => timelog.time_transport_as_human, :tkm => Knj::Locales.number_out(timelog[:transportlength], 0), :tdescr => timelog.transport_descr_short, :cost => Knj::Locales.number_out(timelog[:transportcosts], 2), :fixed => Knj::Strings.yn_str(timelog[:travelfixed], true, false), :internal => Knj::Strings.yn_str(timelog[:workinternal], true, false), :skip => Knj::Strings.yn_str(timelog[:sync_need], false, true), :task => timelog.task_name, :sync_time => Knj::Strings.secs_to_human_time_str(count_rounded_time, :secs => false) ) @timelogs_sync_count += 1 end ensure @dont_reload_sync = nil end end def on_btnSync_clicked self.reload_timelogs_preparetransfer if @timelogs_sync_count <= 0 #Show error-message and destroy the window, if no timelogs was to be synced. Knj::Gtk2.msgbox("msg" => _("There is nothing to sync at this time."), "run" => false) else #...else show the window. @expander.hide self.update_sync_totals @gui["vboxPrepareTransfer"].show end end def update_sync_totals total_secs = 0 @tvpt.model.each do |model, path, iter| time_val = @tvpt.model.get_value(iter, 12) col_no_skip = @tv_settings_pt.col_orig_no_for_id(:skip) skip_val = @tvpt.model.get_value(iter, col_no_skip) if skip_val != 1 begin total_secs += Knj::Strings.human_time_str_to_secs(time_val) rescue #ignore - user is properly entering stuff. end end end @gui["labTotal"].markup = "#{_("Total hours:")} #{Knj::Strings.secs_to_human_time_str(total_secs, :secs => false)}" end def on_btnCancelPrepareTransfer_clicked @expander.show @gui["vboxPrepareTransfer"].hide end #This method is called, when editting starts in a description-, time- or task-cell. If it is the active timelog, then editting is canceled. def on_cell_editingStarted(renderer, editable, path, col_title) iter = @tv.model.get_iter(path) timelog_id = @tv.model.get_value(iter, 0).to_i if tlog = @oata.timelog_active and tlog.id.to_i == timelog_id renderer.stop_editing(true) Knj::Gtk2.msgbox(_("You cannot edit this on the active timelog.")) end end #This method is used to do stuff without having the treeview reloading. It executes the given block and then makes the treeview reloadable again. def dont_reload @dont_reload = true begin yield ensure @dont_reload = false end end #Removes all timelogs from the treeview and adds them again. Does nothing if the 'dont_reload'-variable is set. def reload_timelogs self.reload_descr_completion return nil if @dont_reload or @tv.destroyed? @log.debug("Reloading main treeview.") tnow_str = Time.now.strftime("%Y %m %d") @tv.model.clear #Create date-parent elements that the timelogs will be appended under. dates = {} now_year = Time.now.year @ob.list(:Timelog, "orderby" => [["timestamp", "desc"]]) do |tlog| tstamp = Datet.in(tlog[:timestamp]) next if !tstamp str = tstamp.out(:time => false) if !dates.key?(str) tstamp_year = tstamp.year if tstamp_year != now_year date_str = str else date_str = tstamp.out(:time => false, :year => false) end iter = @tv_settings.tv.model.append(nil) iter[1] = "#{Knj::Web.html(date_str)}" dates[str] = { :iter => iter, :tlogs => [] } end end #Append the timelogs to the parent dates. @ob.list(:Timelog, "orderby" => ["task_id", "descr", "timestamp"]) do |timelog| begin tstamp_str = timelog.timestamp_str rescue => e tstamp_str = "[#{_("error")}: #{e.message}" end if tstamp = timelog.timestamp tstamp_str = tstamp.strftime("%H:%M") parent = dates[tstamp.out(:time => false)][:iter] else parent = nil end @tv_settings.append_adv( :parent => parent, :data => { :id => timelog.id, :timestamp => tstamp_str, :time => timelog.time_as_human, :descr => Knj::Web.html(timelog[:descr]), :ttime => timelog.time_transport_as_human, :tkm => Knj::Locales.number_out(timelog[:transportlength], 0), :ttype => @time_types[timelog[:timetype]], :tdescr => Knj::Web.html(timelog[:transportdescription]), :cost => Knj::Locales.number_out(timelog[:transportcosts], 2), :fixed => Knj::Strings.yn_str(timelog[:travelfixed], true, false), :int => Knj::Strings.yn_str(timelog[:workinternal], true, false), :task => timelog.task_name } ) end #Make all dates expand their content (timelogs). @tv.expand_all #Reset cache of which rows are set to bold. @bold_rows = {} end def timelog_tv_data_by_timelog(timelog) @tv.model.each do |model, path, iter| timelog_i_id = iter[0].to_i if timelog.id.to_i == timelog_i_id return { :timelog => timelog, :iter => iter, :path => path } end end raise Errno::ENOENT, sprintf(_("Could not find timelog in treeview: '%s'."), timelog.id) end def date_tv_data_by_datet(datet, args = nil) datet_str = datet.out(:time => false) add_after = nil @tv.model.each do |model, path, iter| #Skip the iter's that are timelogs (we look for a parent-date). timelog_id = iter[0].to_i next if timelog_id != 0 date_i_str = Php4r.strip_tags(iter[1]) if date_i_str == datet_str return { :iter => iter, :path => path } end datet_i = Datet.in(date_i_str) if datet_i < datet add_after = iter end end if args and args[:add] iter = @tv.insert_after(nil, add_after) iter[1] = "#{Knj::Web.html(datet.out(:time => false))}" return { :iter => iter } end raise sprintf(_("Could not find iter from that date: '%s'."), datet) end #This method is called every time a timelog is updated (changed). This is needed to move timelogs around under the right dates, when the date is changed for a timelog. def on_timelog_update(timelog) #Get the treeview-data for the selected timelog. tlog_data = self.timelog_tv_data_by_timelog(timelog) #Get the date from the parent treeview-iter. parent_iter = tlog_data[:iter].parent parent_date = Php4r.strip_tags(parent_iter[1]) #Get the date from the selected timelog. tlog_date_str = timelog.timestamp.out(:time => false, :year => false) #The date of the timelog has been updated, and the timelog has to be moved elsewhere in the treeview. if parent_date != tlog_date_str #Wait 5 miliseconds so the 'dont_reload' variable wont be 'true' any more. Gtk.timeout_add(5) do @log.debug("Timestamps wasnt the same - reload treeview (parent: #{parent_date} vs tlog: #{tlog_date_str}).") #Reload timelogs to make the changed timelog appear under the right date. self.reload_timelogs #Re-select the timelog in the treeview. begin if !timelog.deleted? tlog_data = self.timelog_tv_data_by_timelog(timelog) @tv.selection.select_iter(tlog_data[:iter]) end rescue Errno::ENOENT #Ignore - it has been deleted. end #Return false for the timeout, so it wont be called again. false end end end #This method handels events for when a timelog is deleted. It removes the timelog from various treeviews, so it isnt necessary to do a complete reload of them, which takes a lot longer. def on_timelog_delete(timelog) del_id = timelog.id.to_i @tv.model.each do |model, path, iter| timelog_id = model.get_value(iter, 0).to_i next if timelog_id <= 0 if timelog_id == del_id model.remove(iter) break end end @tvpt.model.each do |model, path, iter| timelog_id = model.get_value(iter, 0).to_i next if timelog_id <= 0 if timelog_id == del_id model.remove(iter) break end end end #Called when the quit-menu-item is activated. def on_imiQuit_activate #Check if a timelog needs to be synced. If so the user needs to confirm he really wants to quit. timelog_found = nil do_destroy = true @ob.list(:Timelog, "task_id_not" => ["", 0]) do |timelog| if timelog.time_total > 0 or timelog.time_total(:transport => true) > 0 or timelog[:sync_need].to_i == 1 timelog_found = timelog break end end if timelog_found if Knj::Gtk2.msgbox(sprintf(_("The timelog '%s' has not been synced. Are you sure you want to quit?"), timelog_found[:descr]), "yesno") != "yes" do_destroy = false end end @oata.destroy if do_destroy end def on_imiPreferences_activate @oata.show_preferences end def on_imiWeekview_activate @oata.show_worktime_overview end def on_window_destroy #Unconnect reload-event. Else it will crash on call to destroyed object. Also frees up various ressources. @ob.unconnect("object" => :Timelog, "conn_ids" => [@reload_id, @reload_id_update, @reload_id_delete, @reload_preparetransfer_id]) @ob.unconnect("object" => :Task, "conn_ids" => [@reload_tasks_id]) @oata.events.disconnect(:timelog_active_changed, @event_timelog_active_changed) if @event_timelog_active_changed @event_timelog_active_changed = nil Gtk.timeout_remove(@timeout_id) end def on_expOverview_activate(expander) if expander.expanded? self.window_resize_height_enable @log.debug("Current window-size: #{@window.size}") size = @window.size @window.resize(size[0], 480) if size[1] < 480 self.timelog_info_trigger else self.window_resize_height_disable self.timelog_info_trigger end end #This method handles the "Timelog info"-frame. Hides, shows and updates the info in it. def timelog_info_trigger if tlog = @oata.timelog_active time_tracked = @oata.timelog_active_time_tracked + tlog.time_total @gui["btnSwitch"].label = "#{_("Stop")} (#{Knj::Strings.secs_to_human_short_time(time_tracked)})" #Update icon every second while showing main-window, so it looks like stop-button and tray-icon-time is in sync (else tray will only update every 30 sec. which will make it look out of sync, even though it wont be). @oata.ti.update_icon end end def on_btnSwitch_clicked if @oata.timelog_active @oata.timelog_stop_tracking @gui["txtDescr"].grab_focus else descr = @gui["txtDescr"].text.to_s.strip task = @gui["cbTask"].sel if !task.is_a?(Knj::Datarow) task_id = 0 else task_id = task.id end timelog = @ob.get_by(:Timelog, { "task_id" => task_id, "descr_lower" => descr, "timestamp_day" => Time.now }) if !timelog timelog = @ob.add(:Timelog, { :task_id => task_id, :descr => descr }) end @oata.timelog_active = timelog @gui["txtDescr"].text = "" @gui["cbTask"].sel = _("Choose:") end self.update_switch_button end #This method updates the switch button to start or stop, based on the if a timelog is tracked or not. def update_switch_button but = @gui["btnSwitch"] tlog_act = @oata.timelog_active if tlog_act but.image = Gtk::Image.new(Gtk::Stock::MEDIA_STOP, Gtk::IconSize::BUTTON) @gui["txtDescr"].text = tlog_act[:descr] if task = tlog_act.task @gui["cbTask"].sel = task.name else @gui["cbTask"].sel = _("Choose:") end @gui["txtDescr"].sensitive = false @gui["cbTask"].sensitive = false else but.image = Gtk::Image.new(Gtk::Stock::MEDIA_RECORD, Gtk::IconSize::BUTTON) but.label = _("Start") @gui["txtDescr"].text = "" @gui["cbTask"].sel = _("Choose:") @gui["txtDescr"].sensitive = true @gui["cbTask"].sensitive = true end end #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. def check_rows return nil if @tv_editting act_timelog = @oata.timelog_active if act_timelog act_timelog_id = act_timelog.id else act_timelog_id = nil end col_no_track = @tv_settings.col_orig_no_for_id(:track) @tv.model.each do |model, path, iter| timelog_id = model.get_value(iter, 0).to_i bold = false #Update time tracked. if timelog_id == act_timelog_id secs = act_timelog.time_total + @oata.timelog_active_time_tracked col_no = @tv_settings.col_orig_no_for_id(:time) iter[col_no] = "#{Knj::Strings.secs_to_human_time_str(secs, :secs => false)}" bold = true @log.debug("Setting track-check for timelog '#{timelog_id}'.") iter[col_no_track] = 1 else if iter[col_no_track] == 1 #Track-check-value is set for a timelog that isnt being tracked - remove it. @log.debug("Remove track-check for timelog '#{timelog_id}'.") iter[col_no_track] = 0 end if @bold_rows[timelog_id] == true #Remove bold text if timelog is not being tracked. ROWS_BOLD.each do |row_no| col_no = @tv_settings.col_orig_no_for_id(row_no) cur_val = model.get_value(iter, col_no) iter[col_no] = cur_val.gsub(/^/i, "").gsub(/<\/b>$/i, "") end @bold_rows.delete(timelog_id) end end #Set all columns to bold if not already set. if bold and !@bold_rows.key?(timelog_id) ROWS_BOLD.each do |row_no| col_no = @tv_settings.col_orig_no_for_id(row_no) iter[col_no] = "#{model.get_value(iter, col_no)}" end @bold_rows[timelog_id] = true end end end def on_miSyncStatic_activate @oata.sync_static("transient_for" => @window) end def on_btnMinus_clicked sel = @tv.sel tlog = @ob.get(:Timelog, sel[0]) if sel if !sel or !tlog Knj::Gtk2.msgbox(_("Please choose a timelog to delete."), "warning") return nil end return nil if Knj::Gtk2.msgbox(_("Do you want to remove this timelog?"), "yesno") != "yes" begin @log.debug("Deleting timelog from pressing minus and confirming: '#{tlog.id}'.") @ob.delete(tlog) rescue => e Knj::Gtk2.msgbox(sprintf(_("Could not delete the timelog: %s"), e.message)) end end def on_btnPlus_clicked #Add new timelog to database. timelog = @ob.add(:Timelog) @log.debug("Timelog added from pressing plus-button: '#{timelog.to_hash}'.") #Focus new timelog in treeview and open the in-line-editting for the description. added_id = timelog.id.to_i @tv.model.each do |model, path, iter| col_no = @tv_settings.col_no_for_id(:id) timelog_id = model.get_value(iter, col_no).to_i if timelog_id == added_id @log.debug("Setting focus to added timelog.") col = @tv.columns[1] @tv.set_cursor(path, col, true) break end end end #Redirects 'enter'-events to 'switch'-click-event. def on_txtDescr_activate(*args) self.on_btnSwitch_clicked end def on_btnSyncPrepareTransfer_clicked begin #Start syncing for real. @oata.sync_real #Hide the 'Prepare transfer'-box and show the expander once again. @gui["vboxPrepareTransfer"].hide @expander.show rescue => e Knj::Gtk2.msgbox(Knj::Errors.error_str(e)) end end #Enables or disables the minus-button based on what is selected in the treeview. def validate_minus_active(*args) sel = @tv.sel if sel and sel[0].to_i > 0 @gui["btnMinus"].sensitive = true else @gui["btnMinus"].sensitive = false end end end