diff options
author | Will Thompson <will@willthompson.co.uk> | 2015-06-04 07:40:12 +0100 |
---|---|---|
committer | Will Thompson <will@willthompson.co.uk> | 2015-06-04 07:40:12 +0100 |
commit | 66f708d9348be386bd039ea063d5abc5b0729fdf (patch) | |
tree | 68ddfb06a26a799c31dd24f57247c322f88f648c | |
parent | e46d9fdb524de2844e85699b42e70a69491f1d65 (diff) | |
parent | 288b1ce03e58a40d7962685365c84d1f96b46baa (diff) |
Merge branch 'gtk3'
-rw-r--r-- | .gitignore | 2 | ||||
-rw-r--r-- | .travis.yml | 4 | ||||
-rw-r--r-- | Bustle/Gtk.hs | 54 | ||||
-rw-r--r-- | Bustle/UI.hs | 138 | ||||
-rw-r--r-- | Bustle/UI/AboutDialog.hs | 8 | ||||
-rw-r--r-- | Bustle/UI/Canvas.hs | 35 | ||||
-rw-r--r-- | Bustle/UI/FilterDialog.hs | 27 | ||||
-rw-r--r-- | Bustle/UI/OpenTwoDialog.hs | 17 | ||||
-rw-r--r-- | Bustle/UI/Recorder.hs | 26 | ||||
-rw-r--r-- | Bustle/UI/Util.hs | 2 | ||||
-rw-r--r-- | bustle.cabal | 9 | ||||
-rw-r--r-- | data/OpenTwoDialog.ui | 133 | ||||
-rw-r--r-- | data/bustle.ui | 437 |
13 files changed, 504 insertions, 388 deletions
@@ -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> |