summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorWill Thompson <will@willthompson.co.uk>2015-06-04 07:40:12 +0100
committerWill Thompson <will@willthompson.co.uk>2015-06-04 07:40:12 +0100
commit66f708d9348be386bd039ea063d5abc5b0729fdf (patch)
tree68ddfb06a26a799c31dd24f57247c322f88f648c
parente46d9fdb524de2844e85699b42e70a69491f1d65 (diff)
parent288b1ce03e58a40d7962685365c84d1f96b46baa (diff)
Merge branch 'gtk3'
-rw-r--r--.gitignore2
-rw-r--r--.travis.yml4
-rw-r--r--Bustle/Gtk.hs54
-rw-r--r--Bustle/UI.hs138
-rw-r--r--Bustle/UI/AboutDialog.hs8
-rw-r--r--Bustle/UI/Canvas.hs35
-rw-r--r--Bustle/UI/FilterDialog.hs27
-rw-r--r--Bustle/UI/OpenTwoDialog.hs17
-rw-r--r--Bustle/UI/Recorder.hs26
-rw-r--r--Bustle/UI/Util.hs2
-rw-r--r--bustle.cabal9
-rw-r--r--data/OpenTwoDialog.ui133
-rw-r--r--data/bustle.ui437
13 files changed, 504 insertions, 388 deletions
diff --git a/.gitignore b/.gitignore
index 2f59fad..47fee50 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,5 +7,7 @@ Setup
tags
bustle-pcap.1
cabal-dev/
+.cabal-sandbox
+cabal.sandbox.config
bustle.appdata.xml
bustle.desktop
diff --git a/.travis.yml b/.travis.yml
index 1576466..5fe7a6d 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -5,10 +5,10 @@ ghc:
- 7.4
before_install:
- sudo apt-get update -qq
- - sudo apt-get install -qq libpcap-dev libgtk2.0-dev libcairo2-dev
+ - sudo apt-get install -qq libpcap-dev libgtk2.0-dev libgtk-3-dev libcairo2-dev
- which cabal
# Look I know this seems ridiculous but cairo seems to fail to build unless Cabal-the-library >= 1.18 is installed, but not depend on it (?)
- - cabal install Cabal
+ - cabal install Cabal cabal-install
- which cabal
# https://github.com/gtk2hs/gtk2hs/issues/73
- cabal install alex happy
diff --git a/Bustle/Gtk.hs b/Bustle/Gtk.hs
new file mode 100644
index 0000000..f8fbd52
--- /dev/null
+++ b/Bustle/Gtk.hs
@@ -0,0 +1,54 @@
+{-
+Bustle.Gtk: Stuff missing from the Gtk binding
+Copyright © 2015 Will Thompson
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+-}
+{-# LANGUAGE ForeignFunctionInterface #-}
+module Bustle.Gtk
+ ( b_windowSetTitlebar
+ , b_headerBarSetSubtitle
+ )
+where
+
+import Foreign.Ptr
+import Foreign.ForeignPtr
+
+import qualified System.Glib.GObject as GO
+import System.Glib.Properties (objectSetPropertyMaybeString)
+import Graphics.UI.Gtk
+
+
+foreign import ccall "gtk_window_set_titlebar"
+ gtk_window_set_titlebar :: Ptr GObject
+ -> Ptr GObject
+ -> IO ()
+
+
+b_windowSetTitlebar :: (WindowClass window,
+ WidgetClass widget)
+ => window
+ -> widget
+ -> IO ()
+b_windowSetTitlebar window titlebar =
+ withForeignPtr (GO.unGObject (GO.toGObject window)) $ \pWindow ->
+ withForeignPtr (GO.unGObject (GO.toGObject titlebar)) $ \pTitlebar ->
+ gtk_window_set_titlebar pWindow pTitlebar
+
+b_headerBarSetSubtitle :: WidgetClass headerBar -- BZZT
+ => headerBar
+ -> Maybe String
+ -> IO ()
+b_headerBarSetSubtitle = objectSetPropertyMaybeString "subtitle"
diff --git a/Bustle/UI.hs b/Bustle/UI.hs
index 733dd08..f700877 100644
--- a/Bustle/UI.hs
+++ b/Bustle/UI.hs
@@ -38,6 +38,7 @@ import Bustle.Application.Monad
import Bustle.Renderer
import Bustle.Types
import Bustle.Diagram
+import Bustle.Gtk (b_windowSetTitlebar, b_headerBarSetSubtitle)
import Bustle.Marquee (toString)
import Bustle.Util
import Bustle.UI.AboutDialog
@@ -62,7 +63,7 @@ import System.FilePath ( splitFileName, takeFileName, takeDirectory
, dropExtension, dropTrailingPathSeparator
, (</>), (<.>)
)
-import System.Directory (renameFile)
+import System.GIO.File.File (fileFromParseName, fileMove, FileCopyFlags(..))
type B a = Bustle BConfig BState a
@@ -80,8 +81,9 @@ data Page =
data WindowInfo =
WindowInfo { wiWindow :: Window
- , wiSave :: ImageMenuItem
- , wiExport :: MenuItem
+ , wiHeaderBar :: Widget -- TODO
+ , wiSave :: Button
+ , wiExport :: Button
, wiViewStatistics :: CheckMenuItem
, wiFilterNames :: MenuItem
, wiNotebook :: Notebook
@@ -235,6 +237,13 @@ aChallengerAppears wi rr = do
canvasScrollToBottom (wiCanvas wi)
setPage wi CanvasPage
+onMenuItemActivate :: MenuItemClass menuItem
+ => menuItem
+ -> IO ()
+ -> IO (ConnectId menuItem)
+onMenuItemActivate mi act =
+ on mi menuItemActivate act
+
finishedRecording :: WindowInfo
-> FilePath
-> Bool
@@ -250,7 +259,7 @@ finishedRecording wi tempFilePath producedOutput = do
io $ do
widgetSetSensitivity saveItem True
- onActivateLeaf saveItem $ showSaveDialog wi (return ())
+ saveItem `on` buttonActivated $ showSaveDialog wi (return ())
return ()
else do
setPage wi InstructionsPage
@@ -266,7 +275,22 @@ showSaveDialog wi savedCb = do
tempFileName = takeFileName tempFilePath
recorderChooseFile tempFileName mwindow $ \newFilePath -> do
- renameFile tempFilePath newFilePath
+ let tempFile = fileFromParseName tempFilePath
+ let newFile = fileFromParseName newFilePath
+
+ C.catch (fileMove tempFile newFile [FileCopyOverwrite] Nothing Nothing) $ \(GError _ _ msg) -> do
+ d <- messageDialogNew mwindow [DialogModal] MessageError ButtonsOk (__ "Couldn't save log")
+ let secondary :: String
+ secondary = printf
+ (__ "Error: <i>%s</i>\n\n\
+ \You might want to manually recover the log from the temporary file at\n\
+ \<tt>%s</tt>") (toString msg) tempFilePath
+ messageDialogSetSecondaryMarkup d secondary
+ widgetShowAll d
+ d `after` response $ \_ -> do
+ widgetDestroy d
+ return ()
+
widgetSetSensitivity (wiSave wi) False
wiSetLogDetails wi (SingleLog newFilePath)
savedCb
@@ -295,7 +319,7 @@ promptToSave wi = io $ do
dialogAddButton prompt stockSave ResponseYes
widgetShowAll prompt
- prompt `afterResponse` \resp -> do
+ prompt `after` response $ \resp -> do
let closeUp = widgetDestroy (wiWindow wi)
case resp of
ResponseYes -> showSaveDialog wi closeUp
@@ -320,15 +344,17 @@ emptyWindow = do
let getW cast name = io $ builderGetObject builder cast name
window <- getW castToWindow "diagramWindow"
- [newItem, openItem, saveItem, closeItem, aboutItem] <-
- mapM (getW castToImageMenuItem)
- ["new", "open", "save", "close", "about"]
- [newButton, openButton] <- mapM (getW castToButton) ["newButton", "openButton"]
- exportItem <- getW castToMenuItem "export"
- openTwoItem <- getW castToMenuItem "openTwo"
+ header <- getW castToWidget "header"
+
+ io $ b_windowSetTitlebar window header
+ [openItem, openTwoItem] <- mapM (getW castToMenuItem) ["open", "openTwo"]
+ [headerNew, headerSave, headerExport] <- mapM (getW castToButton) ["headerNew", "headerSave", "headerExport"]
+
viewStatistics <- getW castToCheckMenuItem "statistics"
filterNames <- getW castToMenuItem "filter"
+ aboutItem <- getW castToMenuItem "about"
+ [newButton, openButton] <- mapM (getW castToButton) ["newButton", "openButton"]
[nb, statsBook] <- mapM (getW castToNotebook)
["diagramOrNot", "statsBook"]
@@ -336,26 +362,29 @@ emptyWindow = do
-- Open two logs dialog
openTwoDialog <- embedIO $ \r ->
- setupOpenTwoDialog builder window $ \f1 f2 ->
+ setupOpenTwoDialog window $ \f1 f2 ->
makeCallback (loadInInitialWindow (TwoLogs f1 f2)) r
-- Set up the window itself
- embedIO $ onDestroy window . makeCallback maybeQuit
+ embedIO $ (window `on` objectDestroy) . makeCallback maybeQuit
-- File menu and related buttons
embedIO $ \r -> do
let new = makeCallback startRecording r
- onActivateLeaf newItem new
- onClicked newButton new
+ forM [headerNew, newButton] $ \button ->
+ button `on` buttonActivated $ new
- let open = makeCallback (openDialogue window) r
- onActivateLeaf openItem open
- onClicked openButton open
+ let open = makeCallback openDialogue r
+ onMenuItemActivate openItem open
+ openButton `on` buttonActivated $ open
- onActivateLeaf openTwoItem $ widgetShowAll openTwoDialog
+ onMenuItemActivate openTwoItem $ widgetShowAll openTwoDialog
- -- Help menu
- io $ onActivateLeaf aboutItem $ showAboutDialog window
+ -- TODO: really this wants to live in the application menu, but that entails binding GApplication,
+ -- GtkApplication, GMenu, GActionMap, GActionEntry, ...
+ --
+ -- Similarly, the drop-down menus would look better as popovers. But here we are.
+ io $ onMenuItemActivate aboutItem $ showAboutDialog window
m <- asks methodIcon
s <- asks signalIcon
@@ -377,8 +406,9 @@ emptyWindow = do
logDetailsRef <- io $ newIORef Nothing
let windowInfo = WindowInfo { wiWindow = window
- , wiSave = saveItem
- , wiExport = exportItem
+ , wiHeaderBar = header
+ , wiSave = headerSave
+ , wiExport = headerExport
, wiViewStatistics = viewStatistics
, wiFilterNames = filterNames
, wiNotebook = nb
@@ -391,9 +421,6 @@ emptyWindow = do
}
io $ window `on` deleteEvent $ promptToSave windowInfo
- io $ closeItem `on` menuItemActivate $ do
- prompted <- promptToSave windowInfo
- when (not prompted) (widgetDestroy window)
incWindows
io $ widgetShow window
return windowInfo
@@ -422,30 +449,27 @@ updateDisplayedLog wi rr = io $ do
canvasSetShapes canvas shapes regions (rrCentreOffset rr) windowWidth
-prettyDirectory :: String
- -> String
-prettyDirectory s = "(" ++ dropTrailingPathSeparator s ++ ")"
+splitFileName_ :: String
+ -> (String, String)
+splitFileName_ s = (dropTrailingPathSeparator d, f)
+ where
+ (d, f) = splitFileName s
logWindowTitle :: LogDetails
- -> String
-logWindowTitle (RecordedLog filepath) = "(*) " ++ takeFileName filepath
-logWindowTitle (SingleLog filepath) =
- intercalate " " [name, prettyDirectory directory]
+ -> (String, Maybe String)
+logWindowTitle (RecordedLog filepath) = ("*" ++ takeFileName filepath, Nothing)
+logWindowTitle (SingleLog filepath) = (name, Just directory)
where
- (directory, name) = splitFileName filepath
+ (directory, name) = splitFileName_ filepath
logWindowTitle (TwoLogs sessionPath systemPath) =
- intercalate " " $ filter (not . null)
- [ sessionName, sessionDirectory'
- , "&"
- , systemName, prettyDirectory systemDirectory
- ]
+ -- TODO: this looks terrible, need a custom widget
+ (sessionName ++ " & " ++ systemName,
+ Just $ if sessionDirectory == systemDirectory
+ then sessionDirectory
+ else sessionDirectory ++ " & " ++ systemDirectory)
where
- (sessionDirectory, sessionName) = splitFileName sessionPath
- (systemDirectory, systemName ) = splitFileName systemPath
- sessionDirectory' =
- if sessionDirectory == systemDirectory
- then ""
- else prettyDirectory sessionDirectory
+ (sessionDirectory, sessionName) = splitFileName_ sessionPath
+ (systemDirectory, systemName ) = splitFileName_ systemPath
logTitle :: LogDetails
-> String
@@ -460,8 +484,9 @@ wiSetLogDetails :: WindowInfo
-> IO ()
wiSetLogDetails wi logDetails = do
writeIORef (wiLogDetails wi) (Just logDetails)
- windowSetTitle (wiWindow wi)
- (printf (__ "%s - Bustle") (logWindowTitle logDetails) :: String)
+ let (title, subtitle) = logWindowTitle logDetails
+ (wiWindow wi) `set` [ windowTitle := title ]
+ b_headerBarSetSubtitle (wiHeaderBar wi) subtitle
setPage :: MonadIO io
=> WindowInfo
@@ -495,7 +520,7 @@ displayLog wi@(WindowInfo { wiWindow = window
updateDisplayedLog wi rr
widgetSetSensitivity exportItem True
- onActivateLeaf exportItem $ do
+ exportItem `on` buttonActivated $ do
shapes <- canvasGetShapes canvas
saveToPDFDialogue wi shapes
@@ -513,7 +538,7 @@ displayLog wi@(WindowInfo { wiWindow = window
else widgetHide statsBook
widgetSetSensitivity filterNames True
- onActivateLeaf filterNames $ do
+ onMenuItemActivate filterNames $ do
hidden <- readIORef hiddenRef
hidden' <- runFilterDialog window (sessionParticipants $ rrApplications rr) hidden
writeIORef hiddenRef hidden'
@@ -529,17 +554,16 @@ loadPixbuf filename = do
C.catch (fmap Just (pixbufNewFromFile iconName))
(\(GError _ _ msg) -> warn (toString msg) >> return Nothing)
-openDialogue :: Window -> B ()
-openDialogue window = embedIO $ \r -> do
- chooser <- fileChooserDialogNew Nothing (Just window) FileChooserActionOpen
+openDialogue :: B ()
+openDialogue = embedIO $ \r -> do
+ chooser <- fileChooserDialogNew Nothing Nothing FileChooserActionOpen
[ ("gtk-cancel", ResponseCancel)
, ("gtk-open", ResponseAccept)
]
- chooser `set` [ windowModal := True
- , fileChooserLocalOnly := True
+ chooser `set` [ fileChooserLocalOnly := True
]
- chooser `afterResponse` \resp -> do
+ chooser `after` response $ \resp -> do
when (resp == ResponseAccept) $ do
Just fn <- fileChooserGetFilename chooser
makeCallback (loadInInitialWindow (SingleLog fn)) r
@@ -574,7 +598,7 @@ saveToPDFDialogue wi shapes = do
TwoLogs p _ -> Just $ takeDirectory p
maybeM mdirectory $ fileChooserSetCurrentFolder chooser
- chooser `afterResponse` \resp -> do
+ chooser `after` response $ \resp -> do
when (resp == ResponseAccept) $ do
Just fn <- io $ fileChooserGetFilename chooser
let (width, height) = diagramDimensions shapes
diff --git a/Bustle/UI/AboutDialog.hs b/Bustle/UI/AboutDialog.hs
index 7ef4ce5..97334ce 100644
--- a/Bustle/UI/AboutDialog.hs
+++ b/Bustle/UI/AboutDialog.hs
@@ -50,12 +50,12 @@ showAboutDialog window = do
, aboutDialogAuthors := authors
, aboutDialogCopyright := "© 2008–2014 Will Thompson, Collabora Ltd. and contributors"
, aboutDialogLicense := license
+ , aboutDialogLogoIconName := Just "bustle"
+ , windowModal := True
+ , windowTransientFor := window
]
- dialog `afterResponse` \resp ->
+ dialog `after` response $ \resp ->
when (resp == ResponseCancel) (widgetDestroy dialog)
- windowSetTransientFor dialog window
- windowSetModal dialog True
- aboutDialogSetLogoIconName dialog (Just "bustle")
widgetShowAll dialog
diff --git a/Bustle/UI/Canvas.hs b/Bustle/UI/Canvas.hs
index 13ec44e..2bfd87b 100644
--- a/Bustle/UI/Canvas.hs
+++ b/Bustle/UI/Canvas.hs
@@ -35,6 +35,7 @@ import Data.IORef
import Control.Monad (when)
import Graphics.UI.Gtk
+import Graphics.Rendering.Cairo (Render, translate)
import Bustle.Diagram
import Bustle.Regions
@@ -131,11 +132,7 @@ setupCanvas canvas = do
"End" -> updateWith regionSelectionLast
_ -> stopEvent
- -- Expose events
- -- I think we could speed things up by only showing the revealed area
- -- rather than everything that's visible.
- layout `on` exposeEvent $ tryEvent $ io $ canvasUpdate canvas
-
+ layout `on` draw $ canvasDraw canvas
return ()
canvasInvalidateArea :: Canvas a
@@ -257,30 +254,30 @@ canvasGetShapes :: Canvas a
canvasGetShapes = readIORef . canvasShapes
-- | Redraws the currently-visible area of the canvas
-canvasUpdate :: Canvas a
- -> IO ()
-canvasUpdate canvas = do
- current <- canvasGetSelection canvas
- shapes <- canvasGetShapes canvas
- width <- readIORef $ canvasWidth canvas
+canvasDraw :: Canvas a
+ -> Render ()
+canvasDraw canvas = do
+ current <- io $ canvasGetSelection canvas
+ shapes <- io $ canvasGetShapes canvas
+ width <- io $ readIORef $ canvasWidth canvas
let shapes' = case current of
Nothing -> shapes
Just (Stripe y1 y2, _) -> Highlight (0, y1, width, y2):shapes
let layout = canvasLayout canvas
- hadj <- layoutGetHAdjustment layout
- hpos <- adjustmentGetValue hadj
- hpage <- adjustmentGetPageSize hadj
+ hadj <- io $ layoutGetHAdjustment layout
+ hpos <- io $ adjustmentGetValue hadj
+ hpage <- io $ adjustmentGetPageSize hadj
- vadj <- layoutGetVAdjustment layout
- vpos <- adjustmentGetValue vadj
- vpage <- adjustmentGetPageSize vadj
+ vadj <- io $ layoutGetVAdjustment layout
+ vpos <- io $ adjustmentGetValue vadj
+ vpage <- io $ adjustmentGetPageSize vadj
let r = (hpos, vpos, hpos + hpage, vpos + vpage)
- win <- layoutGetDrawWindow layout
- renderWithDrawable win $ drawRegion r (canvasShowBounds canvas) shapes'
+ translate (-hpos) (-vpos)
+ drawRegion r (canvasShowBounds canvas) shapes'
canvasFocus :: Canvas a
-> IO ()
diff --git a/Bustle/UI/FilterDialog.hs b/Bustle/UI/FilterDialog.hs
index 152931e..248ce8d 100644
--- a/Bustle/UI/FilterDialog.hs
+++ b/Bustle/UI/FilterDialog.hs
@@ -22,20 +22,35 @@ module Bustle.UI.FilterDialog
)
where
-import Data.List (intercalate)
+import Data.List (intercalate, groupBy, findIndices)
import qualified Data.Set as Set
import Data.Set (Set)
+import qualified Data.Function as F
import Graphics.UI.Gtk
import Bustle.Translation (__)
import Bustle.Types
+namespace :: String
+ -> (String, String)
+namespace name = case reverse (findIndices (== '.') name) of
+ [] -> ("", name)
+ (i:_) -> splitAt (i + 1) name
+
formatNames :: (UniqueName, Set OtherName)
-> String
formatNames (u, os)
| Set.null os = unUniqueName u
- | otherwise = intercalate "\n" . map unOtherName $ Set.toAscList os
+ | otherwise = intercalate "\n" . map (formatGroup . groupGroup) $ groups
+ where
+ groups = groupBy ((==) `F.on` fst) . map (namespace . unOtherName) $ Set.toAscList os
+
+ groupGroup [] = error "unpossible empty group from groupBy"
+ groupGroup xs@((ns, _):_) = (ns, map snd xs)
+
+ formatGroup (ns, [y]) = ns ++ y
+ formatGroup (ns, ys) = ns ++ "{" ++ (intercalate "," ys) ++ "}"
type NameStore = ListStore (Bool, (UniqueName, Set OtherName))
@@ -91,9 +106,11 @@ runFilterDialog :: WindowClass parent
-> IO (Set UniqueName) -- ^ The set of names to *hide*
runFilterDialog parent names currentlyHidden = do
d <- dialogNew
- windowSetTransientFor d parent
+ (windowWidth, windowHeight) <- windowGetSize parent
+ windowSetDefaultSize d (windowWidth * 7 `div` 8) (windowHeight `div` 2)
+ d `set` [ windowTransientFor := parent ]
dialogAddButton d stockClose ResponseClose
- vbox <- dialogGetUpper d
+ vbox <- fmap castToBox $ dialogGetContentArea d
boxSetSpacing vbox 6
nameStore <- makeStore names currentlyHidden
@@ -109,7 +126,7 @@ runFilterDialog parent names currentlyHidden = do
labelSetLineWrap instructions True
boxPackStart vbox instructions PackNatural 0
- containerAdd vbox sw
+ boxPackStart vbox sw PackGrow 0
widgetShowAll vbox
_ <- dialogRun d
diff --git a/Bustle/UI/OpenTwoDialog.hs b/Bustle/UI/OpenTwoDialog.hs
index 2128a58..440abc6 100644
--- a/Bustle/UI/OpenTwoDialog.hs
+++ b/Bustle/UI/OpenTwoDialog.hs
@@ -28,6 +28,7 @@ import Control.Monad (when)
import Graphics.UI.Gtk
import Bustle.Util
+import Paths_bustle
-- Propagates changes to d1's currently-selected folder to d2, if and only if
-- d2 doesn't have a currently-selected file (otherwise, choosing a file
@@ -37,7 +38,7 @@ propagateCurrentFolder :: FileChooserClass chooser
=> chooser
-> chooser
-> IO (ConnectId chooser)
-propagateCurrentFolder d1 d2 = d1 `onCurrentFolderChanged` do
+propagateCurrentFolder d1 d2 = d1 `on` currentFolderChanged $ do
f1 <- fileChooserGetCurrentFolder d1
f2 <- fileChooserGetCurrentFolder d2
otherFile <- fileChooserGetFilename d2
@@ -48,25 +49,27 @@ propagateCurrentFolder d1 d2 = d1 `onCurrentFolderChanged` do
fileChooserSetCurrentFolder d2 (fromJust f1)
return ()
-setupOpenTwoDialog :: Builder
- -> Window
+setupOpenTwoDialog :: Window
-> (FilePath -> FilePath -> IO ())
-> IO Dialog
-setupOpenTwoDialog builder parent callback = do
+setupOpenTwoDialog parent callback = do
+ builder <- builderNew
+ builderAddFromFile builder =<< getDataFileName "data/OpenTwoDialog.ui"
+
dialog <- builderGetObject builder castToDialog "openTwoDialog"
[sessionBusChooser, systemBusChooser] <-
mapM (builderGetObject builder castToFileChooserButton)
["sessionBusChooser", "systemBusChooser"]
openTwoOpenButton <- builderGetObject builder castToButton "openTwoOpenButton"
- windowSetTransientFor dialog parent
+ dialog `set` [ windowTransientFor := parent ]
dialog `on` deleteEvent $ tryEvent $ io $ widgetHide dialog
propagateCurrentFolder sessionBusChooser systemBusChooser
propagateCurrentFolder systemBusChooser sessionBusChooser
let hideMyself = do
- widgetHideAll dialog
+ widgetHide dialog
fileChooserUnselectAll sessionBusChooser
fileChooserUnselectAll systemBusChooser
@@ -82,7 +85,7 @@ setupOpenTwoDialog builder parent callback = do
connectGeneric "file-set" False systemBusChooser updateOpenSensitivity
updateOpenSensitivity
- dialog `afterResponse` \resp -> do
+ dialog `after` response $ \resp -> do
when (resp == ResponseAccept) $ do
Just f1 <- fileChooserGetFilename sessionBusChooser
Just f2 <- fileChooserGetFilename systemBusChooser
diff --git a/Bustle/UI/Recorder.hs b/Bustle/UI/Recorder.hs
index d0546bd..a5a1014 100644
--- a/Bustle/UI/Recorder.hs
+++ b/Bustle/UI/Recorder.hs
@@ -27,6 +27,7 @@ import Control.Monad (when, liftM)
import Control.Concurrent.MVar
import qualified Data.Map as Map
import Data.Monoid
+import Data.Maybe (maybeToList)
import Control.Monad.State (runStateT)
import Text.Printf
@@ -95,8 +96,11 @@ recorderRun filename mwindow incoming finished = C.handle newFailed $ do
monitor <- monitorNew BusTypeSession filename
dialog <- dialogNew
- maybe (return ()) (windowSetTransientFor dialog) mwindow
- dialog `set` [ windowModal := True ]
+ dialog `set` (map (windowTransientFor :=) (maybeToList mwindow))
+ dialog `set` [ windowModal := True
+ , windowTitle := ""
+ ]
+
label <- labelNew (Nothing :: Maybe String)
labelSetMarkup label $
@@ -121,19 +125,21 @@ recorderRun filename mwindow incoming finished = C.handle newFailed $ do
processor <- processBatch pendingRef n label incoming
processorId <- timeoutAdd processor 200
- bar <- progressBarNew
- pulseId <- timeoutAdd (progressBarPulse bar >> return True) 100
+ spinner <- spinnerNew
+ spinnerStart spinner
- vbox <- dialogGetUpper dialog
- boxPackStart vbox label PackGrow 0
- boxPackStart vbox bar PackNatural 0
+ vbox <- fmap castToBox $ dialogGetContentArea dialog
+ hbox <- hBoxNew False 8
+ boxPackStart hbox spinner PackNatural 0
+ boxPackStart hbox label PackGrow 0
+ boxPackStart vbox hbox PackGrow 0
dialogAddButton dialog "gtk-media-stop" ResponseClose
- dialog `afterResponse` \_ -> do
+ dialog `after` response $ \_ -> do
monitorStop monitor
signalDisconnect handlerId
- timeoutRemove pulseId
+ spinnerStop spinner
timeoutRemove processorId
-- Flush out any last messages from the queue.
processor
@@ -161,7 +167,7 @@ recorderChooseFile name mwindow callback = do
, fileChooserDoOverwriteConfirmation := True
]
- chooser `afterResponse` \resp -> do
+ chooser `after` response $ \resp -> do
when (resp == ResponseAccept) $ do
Just fn <- fileChooserGetFilename chooser
callback fn
diff --git a/Bustle/UI/Util.hs b/Bustle/UI/Util.hs
index eceab0c..7c6cdcc 100644
--- a/Bustle/UI/Util.hs
+++ b/Bustle/UI/Util.hs
@@ -41,5 +41,5 @@ displayError mwindow title mbody = do
maybeM mbody $ messageDialogSetSecondaryText dialog
- dialog `afterResponse` \_ -> widgetDestroy dialog
+ dialog `after` response $ \_ -> widgetDestroy dialog
widgetShowAll dialog
diff --git a/bustle.cabal b/bustle.cabal
index 249bc99..cc95d54 100644
--- a/bustle.cabal
+++ b/bustle.cabal
@@ -11,6 +11,7 @@ Maintainer: Will Thompson <will@willthompson.co.uk>
Data-files: data/dfeet-method.png,
data/dfeet-signal.png,
data/bustle.ui,
+ data/OpenTwoDialog.ui,
LICENSE
Build-type: Custom
Extra-source-files:
@@ -69,6 +70,7 @@ Executable bustle
Main-is: Bustle.hs
Other-modules: Bustle.Application.Monad
, Bustle.Diagram
+ , Bustle.Gtk
, Bustle.Loader
, Bustle.Loader.OldSkool
, Bustle.Loader.Pcap
@@ -107,7 +109,8 @@ Executable bustle
, directory
, filepath
, glib
- , gtk >= 0.12.4
+ , gio
+ , gtk3
, hgettext >= 0.1.5
, mtl
, pango
@@ -137,7 +140,7 @@ Executable test-monitor
, dbus
, directory
, filepath
- , gtk > 0.12
+ , gtk3
, glib
, hgettext
, mtl
@@ -191,7 +194,7 @@ Test-suite test-renderer
, dbus >= 0.10
, directory
, filepath
- , gtk
+ , gtk3
, mtl
, text
, pango
diff --git a/data/OpenTwoDialog.ui b/data/OpenTwoDialog.ui
new file mode 100644
index 0000000..463d5e7
--- /dev/null
+++ b/data/OpenTwoDialog.ui
@@ -0,0 +1,133 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <!-- interface-requires gtk+ 3.0 -->
+ <object class="GtkDialog" id="openTwoDialog">
+ <property name="can_focus">False</property>
+ <property name="border_width">5</property>
+ <property name="title" translatable="yes">Open a Pair of Logs</property>
+ <property name="resizable">False</property>
+ <property name="modal">True</property>
+ <property name="type_hint">dialog</property>
+ <property name="icon-name">bustle</property>
+ <child internal-child="vbox">
+ <object class="GtkBox" id="dialog-vbox1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">2</property>
+ <child internal-child="action_area">
+ <object class="GtkButtonBox" id="dialog-action_area1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="layout_style">end</property>
+ <child>
+ <object class="GtkButton" id="openTwoCancelButton">
+ <property name="label">gtk-cancel</property>
+ <property name="use_action_appearance">False</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_action_appearance">False</property>
+ <property name="use_stock">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="openTwoOpenButton">
+ <property name="label">gtk-open</property>
+ <property name="use_action_appearance">False</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_action_appearance">False</property>
+ <property name="use_stock">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="pack_type">end</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkTable" id="table1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="n_rows">2</property>
+ <property name="n_columns">2</property>
+ <property name="column_spacing">6</property>
+ <property name="row_spacing">6</property>
+ <child>
+ <object class="GtkFileChooserButton" id="systemBusChooser">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="title" translatable="yes">Select system bus log</property>
+ <property name="width_chars">30</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label44">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">System bus log:</property>
+ </object>
+ <packing>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label55">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">Session bus log:</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkFileChooserButton" id="sessionBusChooser">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="title" translatable="yes">Select session bus log</property>
+ <property name="width_chars">30</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="y_options">GTK_EXPAND</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <action-widgets>
+ <action-widget response="-6">openTwoCancelButton</action-widget>
+ <action-widget response="-3">openTwoOpenButton</action-widget>
+ </action-widgets>
+ </object>
+</interface>
diff --git a/data/bustle.ui b/data/bustle.ui
index 626feb1..45514b1 100644
--- a/data/bustle.ui
+++ b/data/bustle.ui
@@ -12,168 +12,6 @@
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
- <object class="GtkMenuBar" id="menubar1">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <child>
- <object class="GtkMenuItem" id="menuitem1">
- <property name="use_action_appearance">False</property>
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="label" translatable="yes">_File</property>
- <property name="use_underline">True</property>
- <child type="submenu">
- <object class="GtkMenu" id="menu1">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <child>
- <object class="GtkImageMenuItem" id="new">
- <property name="label">gtk-new</property>
- <property name="use_action_appearance">False</property>
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="use_underline">True</property>
- <property name="use_stock">True</property>
- <accelerator key="n" signal="activate" modifiers="GDK_CONTROL_MASK"/>
- </object>
- </child>
- <child>
- <object class="GtkImageMenuItem" id="open">
- <property name="label">gtk-open</property>
- <property name="use_action_appearance">False</property>
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="use_underline">True</property>
- <property name="use_stock">True</property>
- <accelerator key="o" signal="activate" modifiers="GDK_CONTROL_MASK"/>
- </object>
- </child>
- <child>
- <object class="GtkMenuItem" id="openTwo">
- <property name="use_action_appearance">False</property>
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="tooltip_text" translatable="yes">Display two logs—one for the session bus, one for the system bus—side by side.</property>
- <property name="label" translatable="yes">O_pen a Pair of Logs…</property>
- <property name="use_underline">True</property>
- </object>
- </child>
- <child>
- <object class="GtkImageMenuItem" id="save">
- <property name="label">gtk-save-as</property>
- <property name="use_action_appearance">False</property>
- <property name="visible">True</property>
- <property name="sensitive">False</property>
- <property name="can_focus">False</property>
- <property name="use_underline">True</property>
- <property name="use_stock">True</property>
- <accelerator key="s" signal="activate" modifiers="GDK_CONTROL_MASK"/>
- </object>
- </child>
- <child>
- <object class="GtkMenuItem" id="export">
- <property name="use_action_appearance">False</property>
- <property name="visible">True</property>
- <property name="sensitive">False</property>
- <property name="can_focus">False</property>
- <property name="label" translatable="yes">_Export as PDF…</property>
- <property name="use_underline">True</property>
- <accelerator key="s" signal="activate" modifiers="GDK_SHIFT_MASK | GDK_CONTROL_MASK"/>
- </object>
- </child>
- <child>
- <object class="GtkSeparatorMenuItem" id="separatormenuitem1">
- <property name="use_action_appearance">False</property>
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- </object>
- </child>
- <child>
- <object class="GtkImageMenuItem" id="close">
- <property name="label">gtk-close</property>
- <property name="use_action_appearance">False</property>
- <property name="visible">True</property>
- <property name="sensitive">True</property>
- <property name="can_focus">False</property>
- <property name="use_underline">True</property>
- <property name="use_stock">True</property>
- <accelerator key="w" signal="activate" modifiers="GDK_CONTROL_MASK"/>
- </object>
- </child>
- </object>
- </child>
- </object>
- </child>
- <child>
- <object class="GtkMenuItem" id="menuitem3">
- <property name="use_action_appearance">False</property>
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="label" translatable="yes">_View</property>
- <property name="use_underline">True</property>
- <child type="submenu">
- <object class="GtkMenu" id="menu2">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <child>
- <object class="GtkMenuItem" id="filter">
- <property name="use_action_appearance">False</property>
- <property name="visible">True</property>
- <property name="sensitive">False</property>
- <property name="can_focus">False</property>
- <property name="label" translatable="yes">_Filter Visible Services…</property>
- <property name="use_underline">True</property>
- <accelerator key="f" signal="activate" modifiers="GDK_CONTROL_MASK"/>
- </object>
- </child>
- <child>
- <object class="GtkCheckMenuItem" id="statistics">
- <property name="use_action_appearance">False</property>
- <property name="visible">True</property>
- <property name="sensitive">False</property>
- <property name="can_focus">False</property>
- <property name="label" translatable="yes">_Statistics</property>
- <property name="use_underline">True</property>
- <accelerator key="F9" signal="activate"/>
- </object>
- </child>
- </object>
- </child>
- </object>
- </child>
- <child>
- <object class="GtkMenuItem" id="menuitem4">
- <property name="use_action_appearance">False</property>
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="label" translatable="yes">_Help</property>
- <property name="use_underline">True</property>
- <child type="submenu">
- <object class="GtkMenu" id="menu3">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <child>
- <object class="GtkImageMenuItem" id="about">
- <property name="label">gtk-about</property>
- <property name="use_action_appearance">False</property>
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="use_underline">True</property>
- <property name="use_stock">True</property>
- </object>
- </child>
- </object>
- </child>
- </object>
- </child>
- </object>
- <packing>
- <property name="expand">False</property>
- <property name="fill">True</property>
- <property name="position">0</property>
- </packing>
- </child>
- <child>
<object class="GtkNotebook" id="diagramOrNot">
<property name="visible">True</property>
<property name="can_focus">True</property>
@@ -491,133 +329,172 @@
</object>
</child>
</object>
- <object class="GtkDialog" id="openTwoDialog">
- <property name="can_focus">False</property>
- <property name="border_width">5</property>
- <property name="title" translatable="yes">Open a Pair of Logs</property>
- <property name="resizable">False</property>
- <property name="modal">True</property>
- <property name="type_hint">dialog</property>
- <property name="icon-name">bustle</property>
- <child internal-child="vbox">
- <object class="GtkBox" id="dialog-vbox1">
+ <object class="GtkHeaderBar" id="header">
+ <property name="visible">True</property>
+ <property name="show-close-button">True</property>
+
+ <child>
+ <object class="GtkMenuButton" id="headerOpen">
<property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="orientation">vertical</property>
- <property name="spacing">2</property>
- <child internal-child="action_area">
- <object class="GtkButtonBox" id="dialog-action_area1">
+ <property name="sensitive">True</property>
+ <property name="tooltip_text" translatable="yes">Open an existing log</property>
+ <property name="popup">openMenu</property>
+ <style>
+ <class name="image-button"/>
+ </style>
+ <child>
+ <object class="GtkImage">
<property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="layout_style">end</property>
- <child>
- <object class="GtkButton" id="openTwoCancelButton">
- <property name="label">gtk-cancel</property>
- <property name="use_action_appearance">False</property>
- <property name="visible">True</property>
- <property name="can_focus">True</property>
- <property name="receives_default">True</property>
- <property name="use_action_appearance">False</property>
- <property name="use_stock">True</property>
- </object>
- <packing>
- <property name="expand">False</property>
- <property name="fill">False</property>
- <property name="position">0</property>
- </packing>
- </child>
- <child>
- <object class="GtkButton" id="openTwoOpenButton">
- <property name="label">gtk-open</property>
- <property name="use_action_appearance">False</property>
- <property name="visible">True</property>
- <property name="can_focus">True</property>
- <property name="receives_default">True</property>
- <property name="use_action_appearance">False</property>
- <property name="use_stock">True</property>
- </object>
- <packing>
- <property name="expand">False</property>
- <property name="fill">False</property>
- <property name="position">1</property>
- </packing>
- </child>
+ <property name="icon-name">document-open-symbolic</property>
+ <property name="icon-size">1</property>
</object>
- <packing>
- <property name="expand">False</property>
- <property name="fill">True</property>
- <property name="pack_type">end</property>
- <property name="position">0</property>
- </packing>
</child>
+ </object>
+ <packing>
+ <property name="pack-type">GTK_PACK_START</property>
+ </packing>
+ </child>
+
+ <!-- TODO: media-record-symbolic -->
+ <child>
+ <object class="GtkButton" id="headerNew">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Record</property>
+ <property name="tooltip_text" translatable="yes">Record a new log</property>
+ </object>
+ </child>
+
+ <child>
+ <object class="GtkMenuButton">
+ <property name="visible">True</property>
+ <property name="popup">filterStatsEtc</property>
+ <style>
+ <class name="image-button"/>
+ </style>
<child>
- <object class="GtkTable" id="table1">
+ <object class="GtkImage">
<property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="n_rows">2</property>
- <property name="n_columns">2</property>
- <property name="column_spacing">6</property>
- <property name="row_spacing">6</property>
- <child>
- <object class="GtkFileChooserButton" id="systemBusChooser">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="title" translatable="yes">Select system bus log</property>
- <property name="width_chars">30</property>
- </object>
- <packing>
- <property name="left_attach">1</property>
- <property name="right_attach">2</property>
- <property name="top_attach">1</property>
- <property name="bottom_attach">2</property>
- <property name="y_options"></property>
- </packing>
- </child>
- <child>
- <object class="GtkLabel" id="label44">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="xalign">0</property>
- <property name="label" translatable="yes">System bus log:</property>
- </object>
- <packing>
- <property name="top_attach">1</property>
- <property name="bottom_attach">2</property>
- </packing>
- </child>
- <child>
- <object class="GtkLabel" id="label55">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="xalign">0</property>
- <property name="label" translatable="yes">Session bus log:</property>
- </object>
- </child>
- <child>
- <object class="GtkFileChooserButton" id="sessionBusChooser">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="title" translatable="yes">Select session bus log</property>
- <property name="width_chars">30</property>
- </object>
- <packing>
- <property name="left_attach">1</property>
- <property name="right_attach">2</property>
- <property name="y_options">GTK_EXPAND</property>
- </packing>
- </child>
+ <property name="icon-name">open-menu-symbolic</property>
+ <property name="icon-size">1</property>
</object>
- <packing>
- <property name="expand">False</property>
- <property name="fill">True</property>
- <property name="position">1</property>
- </packing>
</child>
</object>
+ <packing>
+ <property name="pack-type">end</property>
+ </packing>
+ </child>
+
+ <child>
+ <object class="GtkButton" id="headerExport">
+ <property name="visible">True</property>
+ <property name="sensitive">False</property>
+ <property name="tooltip_text" translatable="yes">Export as PDF</property>
+ <style>
+ <class name="image-button"/>
+ </style>
+ <child>
+ <object class="GtkImage">
+ <property name="visible">True</property>
+ <property name="icon-name">document-send-symbolic</property>
+ <property name="icon-size">1</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="pack-type">end</property>
+ </packing>
+ </child>
+
+ <child>
+ <object class="GtkButton" id="headerSave">
+ <property name="visible">True</property>
+ <property name="sensitive">False</property>
+ <property name="tooltip_text" translatable="yes">Save</property>
+ <style>
+ <class name="image-button"/>
+ </style>
+ <child>
+ <object class="GtkImage">
+ <property name="visible">True</property>
+ <property name="icon-name">document-save-symbolic</property>
+ <property name="icon-size">1</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="pack-type">end</property>
+ </packing>
</child>
- <action-widgets>
- <action-widget response="-6">openTwoCancelButton</action-widget>
- <action-widget response="-3">openTwoOpenButton</action-widget>
- </action-widgets>
</object>
+
+ <object class="GtkMenu" id="filterStatsEtc">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">end</property>
+ <child>
+ <object class="GtkMenuItem" id="filter">
+ <property name="use_action_appearance">False</property>
+ <property name="visible">True</property>
+ <property name="sensitive">False</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">_Filter Visible Services…</property>
+ <property name="use_underline">True</property>
+ <accelerator key="f" signal="activate" modifiers="GDK_CONTROL_MASK"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkCheckMenuItem" id="statistics">
+ <property name="use_action_appearance">False</property>
+ <property name="visible">True</property>
+ <property name="sensitive">False</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">_Statistics</property>
+ <property name="use_underline">True</property>
+ <accelerator key="F9" signal="activate"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkSeparatorMenuItem" id="separatormenuitem1">
+ <property name="use_action_appearance">False</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImageMenuItem" id="about">
+ <property name="label">gtk-about</property>
+ <property name="use_action_appearance">False</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="use_underline">True</property>
+ <property name="use_stock">True</property>
+ </object>
+ </child>
+ </object>
+
+ <object class="GtkMenu" id="openMenu">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkImageMenuItem" id="open">
+ <property name="label">gtk-open</property>
+ <property name="use_action_appearance">False</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="use_underline">True</property>
+ <property name="use_stock">True</property>
+ <accelerator key="o" signal="activate" modifiers="GDK_CONTROL_MASK"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkMenuItem" id="openTwo">
+ <property name="use_action_appearance">False</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="tooltip_text" translatable="yes">Display two logs—one for the session bus, one for the system bus—side by side.</property>
+ <property name="label" translatable="yes">O_pen a Pair of Logs…</property>
+ <property name="use_underline">True</property>
+ </object>
+ </child>
+ </object>
</interface>