[Tex/LaTex] How to define a new environment with a fancy box (tikz) around sample source-code (listings)

beamerboxesenvironmentslistingstikz-pgf

This is what I want to do:

\documentclass{beamer}

\begin{document}

\begin{frame}[fragile]{Frame 1}

    Some \structure{Python} code here:

    % I want to define a new "pythoncode" environment, so I can use it like this:
    \begin{pythoncode}[hello.py]
    #!/usr/bin/env python
    def main():
        print "Hello, World!"
    if __name__ == '__main__':
        main()
    \end{pythoncode}

\end{frame}

\end{document}

But I want to customize the source-code box beyond what listings package allow me. Jan Hlavacek suggested me to use tikz package for that, and pointed me to a few examples.

Okay, that's great! Tikz gives me enough power and freedom for customization! All I need now is creating a new environment, so that I can use this environment just like in the code above.

However, I'm having trouble mixing tikz with listings inside a new environment. I've tried the following code, but it doesn't work (I get Missing \endcsname inserted.):

\usepackage{listings}
\usepackage{tikz}
\usepackage{environ}

\tikzstyle{sourcecodebox} = [
    draw=blue, very thick,
    rectangle, rounded corners,
    inner sep=10pt
]
\tikzstyle{sourcecodetitle} = [
    fill=black, text=white,
    rectangle, rounded corners
]
\NewEnviron{pythoncode}[1]{
    \begin{tikzpicture}
    \node[sourcecodebox](box){
        \begin{lstlisting}[
            language=Python,
            basicstyle=\ttfamily\footnotesize,
            escapeinside={(*@}{@*)},
            %numbers=left,
            breaklines=true,
            breakatwhitespace=true,
            showspaces=false,
            showstringspaces=false,
            frame=shadowbox,
            frameround=rrrt,
            rulecolor=\color{black},
            rulesepcolor=\color{gray}
        ]
        \BODY
        \end{lstlisting}
    };
    \node[sourcecodetitle] at (box.north west) {#1};
    \end{tikzpicture}
}

Related questions:

Best Answer

Note that \NewEnviron does not support verbatim. The documentation should point that out. As already commented the \lstnewenvironment must be used for this. Alternatively place the verbatim files into external files which are then read using \listinputlisting.

Find below my solution which stores the resulting listings first into a box which is then used inside the TikZ node. IMHO you should not mix the listings frame argument with additional frames from TikZ. You should produce all framing using TikZ (not to be confused with beamers \frame macro/environment). Beamer provides some nice framed boxes as well!

I had to add the linewidth argument and a \makebox, otherwise the listings frame is to wide. You might want to adjust that.

Best Regards, Martin

\documentclass{beamer}

\usepackage{listings}
\usepackage{tikz}

\tikzstyle{sourcecodebox} = [
    draw=blue, very thick,
    rectangle, rounded corners,
    inner sep=10pt
]
\tikzstyle{sourcecodetitle} = [
    fill=black, text=white,
    rectangle, rounded corners
]

\makeatletter
\lstnewenvironment{pythoncode}[1][]{%
    \def\pythoncodetitle{#1}%
    \lstset{%
        language=Python,
        basicstyle=\ttfamily\footnotesize,
        escapeinside={(*@}{@*)},
        %numbers=left,
        breaklines=true,
        breakatwhitespace=true,
        showspaces=false,
        showstringspaces=false,
        frame=shadowbox,
        frameround=rrrt,
        linewidth=.75\linewidth,
        rulecolor=\color{black},
        rulesepcolor=\color{gray}
    }%
    \setbox\@tempboxa=\hbox\bgroup\color@setgroup
}%
{%
    \color@endgroup\egroup
    \begin{tikzpicture}
        \node[sourcecodebox] (box)
            % Makebox is needed to take the frame added by listings into account
            {\makebox[.75\linewidth][l]{\box\@tempboxa}};
        \node[sourcecodetitle] at (box.north west) {\pythoncodetitle};
    \end{tikzpicture}
}

\begin{document}

\begin{frame}[fragile]{Frame 1}

    Some \structure{Python} code here:
    \begin{pythoncode}[hello.py]
    #!/usr/bin/env python
    def main():
        print "Hello, World!"
    if __name__ == '__main__':
        main()
    \end{pythoncode}

\end{frame}

\end{document}