diff --git a/src/InspectDR.jl b/src/InspectDR.jl index 075dcb2..cfe594b 100644 --- a/src/InspectDR.jl +++ b/src/InspectDR.jl @@ -82,6 +82,7 @@ include("gtk_base.jl") include("gtk_events.jl") include("gtk_zoom.jl") include("gtk_markers.jl") +include("gtk_traces.jl") include("gtk_input.jl") include("gtk_top.jl") end diff --git a/src/base.jl b/src/base.jl index 4780da1..1e99abd 100644 --- a/src/base.jl +++ b/src/base.jl @@ -69,6 +69,7 @@ mutable struct Waveform{T} glyph::GlyphAttributes ext::PExtents2D strip::UInt8 #Y-strip + visible::Bool end #Input waveform: @@ -355,12 +356,12 @@ _add(mp::Multiplot, ::Type{T}) where T<:Plot = _add(mp, T()) #Set dataf1=false to overwrite optimizations for functions of 1 argument. -function _add(plot::Plot2D, x::Vector, y::Vector; id::String="", dataf1=true, strip=1) +function _add(plot::Plot2D, x::Vector, y::Vector; id::String="", dataf1=true, strip=1, visible=true) if dataf1 dataf1 = isincreasing(x) #Can we use optimizations? end ext = PExtents2D() #Don't care at the moment - ds = IWaveform(id, IDataset{dataf1}(x, y), line(), glyph(), ext, strip) + ds = IWaveform(id, IDataset{dataf1}(x, y), line(), glyph(), ext, strip, visible) push!(plot.data, ds) return ds end @@ -579,7 +580,7 @@ function _reduce(input::IWaveform, xext::PExtents1D, pdm::PointDropMatrix, xres_ ds = droppoints(pdm, hasline(input), hasglyph(input)) ? _reduce(input.ds, xext, xres_max) : _reduce_nodrop(input.ds, xext, xres_max) - return DWaveform(input.id, ds, input.line, input.glyph, input.ext, input.strip) + return DWaveform(input.id, ds, input.line, input.glyph, input.ext, input.strip, input.visible) end _reduce(inputlist::Vector{IWaveform}, xext::PExtents1D, pdm::PointDropMatrix, xres_max::Integer) = @@ -593,23 +594,25 @@ map2axis(input::T, x::InputXfrm1DSpec, y::InputXfrm1DSpec) where T<:IDataset = function map2axis(input::IWaveform, x::InputXfrm1DSpec, y::InputXfrm1DSpec) ds = map2axis(input.ds, x, y) - return IWaveform(input.id, ds, input.line, input.glyph, getextents(ds), input.strip) + return IWaveform(input.id, ds, input.line, input.glyph, getextents(ds), input.strip, input.visible) end function map2axis(inputlist::Vector{IWaveform}, xflist::Vector{InputXfrm2D}) n = length(inputlist) #WANTCONST nstrips = length(xflist) #WANTCONST emptyds = IDataset{true}([], []) #WANTCONST - result = Vector{IWaveform}(undef, n) + result = Vector{IWaveform}() for i in 1:n input = inputlist[i] + if !input.visible; continue; end #Don't process non-visible waveforms strip = input.strip if strip > 0 && strip <= nstrips - result[i] = map2axis(input, xflist[strip].x, xflist[strip].y) + wfrm = map2axis(input, xflist[strip].x, xflist[strip].y) else #No scale for this strip... return empty waveform - result = IWaveform(input.id, emptyds, input.line, input.glyph, PExtents2D(), strip) + wfrm = IWaveform(input.id, emptyds, input.line, input.glyph, PExtents2D(), strip, input.visible) end + push!(result, wfrm) end return result end diff --git a/src/gtk_base.jl b/src/gtk_base.jl index 1ce506f..c0fa885 100644 --- a/src/gtk_base.jl +++ b/src/gtk_base.jl @@ -33,11 +33,11 @@ function window_close(window::Gtk.GtkWindow) return end -function focus(window::Gtk.GtkWindow,widget) +function set_focus(window::Gtk.GtkWindow,widget::Gtk.GtkWidget) ccall((:gtk_window_set_focus,Gtk.libgtk),Nothing,(Ptr{Gtk.GObject},Ptr{Gtk.GObject}),window,widget) return window end -function focus(widget) +function set_focus(widget::Gtk.GtkWidget) ccall((:gtk_widget_grab_focus,Gtk.libgtk),Nothing,(Ptr{Gtk.GObject},),widget) return widget end @@ -59,6 +59,11 @@ function gdk_window_set_cursor(wnd, cursor::Ptr{Nothing}) return end +function gtk_tree_path_new_from_string(pstr::Ptr{Cchar}) + tp = ccall((:gtk_tree_path_new_from_string, Gtk.libgtk), Ptr{_Gtk.TreePath}, (Ptr{Cchar},), pstr) + return Gtk.GtkTreePath(tp, true) +end + #==Constants ===============================================================================# @@ -155,6 +160,19 @@ mutable struct GtkPlot end +#==Helper functions +===============================================================================# +function refresh_title(gplot::GtkPlot) + if length(gplot.src.title)> 0 + title = "InspectDR - $(gplot.src.title)" + else + title = "InspectDR" + end + set_gtk_property!(gplot.wnd, :title, title) + return +end + + #==Accessors ===============================================================================# function activestrip(w::PlotWidget) @@ -166,23 +184,34 @@ function activestrip(w::PlotWidget) return istrip end +#Tree views that model "ListStore"s can access indicies in a simpler fashion: +function listindex(treepath::_Gtk.TreePath) + _indices = Gtk.GAccessor.indices(treepath) + #For arbitrary tres, see: Gtk.depth(treepath) + return convert(Int, unsafe_load(_indices, 1)) +end -#==Mutators -===============================================================================# -function settitle(wnd::_Gtk.Window, title::String) - if length(title)> 0 - title = "InspectDR - $(title)" - else - title = "InspectDR" +#Find active subplot by polling which has focus: +#NOTE: Polling is a bit hacky... might be better to signal when set_focus is used?? +function active_subplot(gplot::GtkPlot) + for i in 1:length(gplot.subplots) + isactive = Gtk.get_gtk_property(gplot.subplots[i].widget, "is-focus", Bool) + if isactive return i; end end - set_gtk_property!(wnd, :title, title) + + return 0 #No active subplot end + +#==Mutators +===============================================================================# function settitle(gplot::GtkPlot, title::String) gplot.src.title = title - settitle(gplot.wnd, gplot.src.title) + refresh_title(gplot) end +set_focus(pwidget::PlotWidget) = set_focus(pwidget.widget) + #==Main functions ===============================================================================# diff --git a/src/gtk_input.jl b/src/gtk_input.jl index b26e413..7b746c1 100644 --- a/src/gtk_input.jl +++ b/src/gtk_input.jl @@ -119,7 +119,7 @@ end function handleevent_mousepress(::ISNormal, pwidget::PlotWidget, event::Gtk.GdkEventButton) # @show event.state, event.button, event.event_type focus_strip(pwidget, event.x, event.y) - focus(pwidget.widget) #In case not in focus + set_focus(pwidget) #In case not in focus if 3==event.button boxzoom_setstart(pwidget, event.x, event.y) #Changes state diff --git a/src/gtk_top.jl b/src/gtk_top.jl index f4d8761..e1e48f2 100644 --- a/src/gtk_top.jl +++ b/src/gtk_top.jl @@ -75,6 +75,11 @@ end nothing #Known value end +@guarded function cb_mnudatatraces(w::Ptr{Gtk.GObject}, gplot::GtkPlot) + tracedialog_show(gplot) + nothing #Known value +end + #==Higher-level event handlers ===============================================================================# @@ -246,6 +251,8 @@ function GtkPlot(mp::Multiplot) mnuexport = Gtk_addmenuitem(mnufile, "_Export") push!(mnufile, _Gtk.SeparatorMenuItem()) mnuquit = Gtk_addmenuitem(mnufile, "_Quit") + mnudata = Gtk_addmenu(mb, "_Data") + mnutraces = Gtk_addmenuitem(mnudata, "_Traces") grd = Gtk.Grid() #Main grid with different subplots. set_gtk_property!(grd, :column_homogeneous, true) #set_gtk_property!(grd, :column_spacing, 15) #Gap between @@ -262,19 +269,20 @@ function GtkPlot(mp::Multiplot) push!(vbox, grd) #Subplots push!(vbox, sbar_frame) #status bar wnd = Gtk.Window(vbox, "", 640, 480, true) - settitle(wnd, mp.title) gplot = GtkPlot(false, wnd, grd, [], mp, status) + refresh_title(gplot) sync_subplots(gplot) if length(gplot.subplots) > 0 - focus(wnd, gplot.subplots[end].widget) + set_focus(wnd, gplot.subplots[end].widget) end Gtk.showall(wnd) signal_connect(cb_wnddestroyed, wnd, "destroy", Nothing, (), false, gplot) signal_connect(cb_mnufileexport, mnuexport, "activate", Nothing, (), false, gplot) signal_connect(cb_mnufileclose, mnuquit, "activate", Nothing, (), false, gplot) + signal_connect(cb_mnudatatraces, mnutraces, "activate", Nothing, (), false, gplot) return gplot end @@ -301,11 +309,13 @@ end function refresh(gplot::GtkPlot) if !gplot.destroyed - settitle(gplot.wnd, gplot.src.title) + refresh_title(gplot) + active = active_subplot(gplot) set_gtk_property!(gplot.grd, :visible, false) #Suppress gliching sync_subplots(gplot) map(refresh, gplot.subplots) #Is this necessary? set_gtk_property!(gplot.grd, :visible, true) + set_focus(gplot.subplots[active].widget) #Restore focus after modifying gplot.grd Gtk.showall(gplot.grd) #TODO: find a way to force GUI to updates here... Animations don't refresh... sleep(eps(0.0)) #Ugly Hack: No guarantee this works... There must be a better way. diff --git a/src/gtk_traces.jl b/src/gtk_traces.jl new file mode 100644 index 0000000..feb39b6 --- /dev/null +++ b/src/gtk_traces.jl @@ -0,0 +1,135 @@ +#InspectDR: Trace control for plot widgets +#------------------------------------------------------------------------------- + +#==LibraryHacks +===============================================================================# + +function Base.length(listStore::Gtk.GtkListStore) + _len = ccall((:gtk_tree_model_iter_n_children, Gtk.libgtk), Cint, (Ptr{Gtk.GObject}, Ptr{Gtk.GtkTreeIter}), listStore, C_NULL) + return convert(Int, _len) +end + + +#==Constants +===============================================================================# + + +#==Types +===============================================================================# +mutable struct TraceDialog + wnd::Gtk.Window + gplot::GtkPlot + subplotidx::Int + tracestore::_Gtk.ListStore +end + + +#==Helper functions +===============================================================================# +function refresh_title(dlg::TraceDialog) + title = "InspectDR - $(dlg.gplot.src.title) (Traces)" + set_gtk_property!(dlg.wnd, :title, title) +end + + +#==Callback functions +===============================================================================# +@guarded function cb_tracevisible_toggle(w::Ptr{Gtk.GObject}, pathstr, dlg::TraceDialog) + treepath = gtk_tree_path_new_from_string(pathstr) + idx = listindex(treepath)+1 + toggle_visibility(dlg, idx) + nothing #Known value +end + + +#==Constructor-like functions +===============================================================================# +function TraceDialog(gplot::GtkPlot) + tracestore = _Gtk.ListStore(Bool, String) + treemodel = _Gtk.TreeModel(tracestore) + tracelist = _Gtk.TreeView(treemodel) + #Construct renderers: + rTxt = _Gtk.CellRendererText() + rTog = _Gtk.CellRendererToggle() + c1 = _Gtk.TreeViewColumn("Visible", rTog, Dict("active" => 0), resizable=false)#, min_width=200) + c2 = _Gtk.TreeViewColumn("Waveform", rTxt, Dict("text" => 1)) + push!(tracelist, c1, c2) + + vbox = _Gtk.Box(true, 0) + push!(vbox, tracelist) + wnd = Gtk.Window(vbox, "", 300, 150, true) + Gtk.GAccessor.modal(wnd, true) + Gtk.GAccessor.keep_above(wnd, true) + + tracedialog = TraceDialog(wnd, gplot, 1, tracestore) + refresh(tracedialog) + + signal_connect(cb_tracevisible_toggle, rTog, "toggled", Nothing, (Ptr{Cchar},), false, tracedialog) + return tracedialog +end + + +#==Main Functions +===============================================================================# +function refresh(dlg::TraceDialog) + refresh_title(dlg) + dlg.subplotidx = active_subplot(dlg.gplot) + if dlg.subplotidx < 1 + empty!(dlg.tracestore) + return + end + + splot = dlg.gplot.subplots[dlg.subplotidx].src + ntraces = length(splot.data) + (nstore, ncols) = size(dlg.tracestore) + + nmin = min(ntraces, nstore) + for i in 1:nmin #Overwrite existing data: + d = splot.data[i] + dlg.tracestore[i,1] = d.visible + dlg.tracestore[i,2] = d.id + end + + nmissing = max(0, ntraces-nstore) + for i in nmin .+ (1:nmissing) #Add slots for new traces + d = splot.data[i] + push!(dlg.tracestore, (d.visible, d.id)) + end + + nextra = max(0, nstore-ntraces) + for i in 1:nextra #Remove excess slots in store + pop!(dlg.tracestore) + end + + return +end + +function toggle_visibility(dlg::TraceDialog, idx::Int) + active = active_subplot(dlg.gplot) + if idx > 0 && active > 0 && active == dlg.subplotidx + #Don't toggle value unless active subplot matches dialog state... + splot = dlg.gplot.subplots[dlg.subplotidx].src + if idx <= length(splot.data) + splot.data[idx].visible = !splot.data[idx].visible + + #Immediately update displayed waveforms: + invalidate_datalist(splot) + end + end + + refresh(dlg) + refresh(dlg.gplot) +# refresh(dlg.gplot.subplots[dlg.subplotidx]) #Could just update subplot + return +end + + +#==High-level functions +===============================================================================# +function tracedialog_show(gplot::GtkPlot) + dlg = TraceDialog(gplot) + Gtk.showall(dlg.wnd) + return +end + +#Last line