Go nuts with Qt
authorSam Moore <[email protected]>
Mon, 18 Aug 2014 14:47:00 +0000 (22:47 +0800)
committerSam Moore <[email protected]>
Mon, 18 Aug 2014 13:47:21 +0000 (21:47 +0800)
We can do all the things I promised!*
And we can do it without having to implement a vim style stdio based interface!
Or adding 16 extra mouse buttons!

Qt can parse XML or even SVG all by itself though...
I'm going to ignore that and just keep treating it as a menu system.

* Commit --amend

Well, the set of things we can do is neither a subset nor a superset of the things I promised.

15 files changed:
src/DejaVuSansMono.ttf [deleted file]
src/bezier.cpp
src/controlpanel.cpp
src/controlpanel.h
src/document.cpp
src/document.h
src/fonts/BleedingCowboys.ttf [new file with mode: 0644]
src/fonts/ComicSans.ttf [new file with mode: 0644]
src/fonts/DejaVuSansMono.ttf [new file with mode: 0644]
src/main.cpp
src/main.h
src/screen.cpp
src/screen.h
src/view.cpp
src/view.h

diff --git a/src/DejaVuSansMono.ttf b/src/DejaVuSansMono.ttf
deleted file mode 100644 (file)
index 1376228..0000000
Binary files a/src/DejaVuSansMono.ttf and /dev/null differ
index da65336..b3129e4 100644 (file)
@@ -71,10 +71,10 @@ static pair<Real, Real> BezierTurningPoints(const Real & p0, const Real & p1, co
                if (t < 0) t = 0;
                return pair<Real, Real>(t, t);
        }
-       Debug("a, b, c are %f, %f, %f", Float(a), Float(b), Float(c));
+       //Debug("a, b, c are %f, %f, %f", Float(a), Float(b), Float(c));
        if (b*b - 4*a*c < 0)
        {
-               Debug("No real roots");
+               //Debug("No real roots");
                return pair<Real, Real>(0,1);
        }
        pair<Real, Real> tsols = SolveQuadratic(a, b, c);
@@ -102,7 +102,7 @@ Rect Bezier::SolveBounds() const
        Evaluate(tp0, o, tsols.first);
        Evaluate(tp1, o, tsols.second);
        
-       Debug("x: tp0 is %f tp1 is %f", Float(tp0), Float(tp1));
+       //Debug("x: tp0 is %f tp1 is %f", Float(tp0), Float(tp1));
        
        vector<const Real*> v(4);
        v[0] = &x0;
@@ -122,7 +122,7 @@ Rect Bezier::SolveBounds() const
        Evaluate(o, tp1, tsols.second);
        
        
-       Debug("y: tp0 is %f tp1 is %f", Float(tp0), Float(tp1));
+       //Debug("y: tp0 is %f tp1 is %f", Float(tp0), Float(tp1));
        
        v[0] = &y0;
        v[1] = &y3;
@@ -133,7 +133,7 @@ Rect Bezier::SolveBounds() const
        result.y = *(v[0]);
        result.h = *(v[3]) - result.y;
        
-       Debug("Solved Bezier %s bounds as %s", Str().c_str(), result.Str().c_str());
+       //Debug("Solved Bezier %s bounds as %s", Str().c_str(), result.Str().c_str());
        return result;
 }
 
index 984a459..41013d0 100644 (file)
@@ -3,25 +3,28 @@
  */
 
 #include "controlpanel.h"
+
+#ifndef CONTROLPANEL_DISABLED
+
 #include "view.h"
 #include "screen.h"
 #include "document.h"
+#include <string>
+#include <algorithm>
 
-#ifndef CONTROLPANEL_DISABLED
+using namespace std;
 
 namespace IPDF
 {
        
        
 ControlPanel::ControlPanel(RunArgs & args, QWidget * p) : QMainWindow(p), 
-       m_view(args.view), m_doc(args.doc), m_screen(args.screen)
+       m_view(args.view), m_doc(args.doc), m_screen(args.screen), m_width(300), m_height(300),
+       m_state(ControlPanel::ABOUT), m_on_ok(NULL)
 {
        // Size
-       resize(300,300);
-       // Title
-       setWindowTitle("IPDF Control Panel");
-       // Tooltip
-       setToolTip("This is the IPDF Control Panel.\nDo you feel in control?");
+       resize(m_width,m_height);
+
        
        // Main menues
        CreateMainMenu();
@@ -29,14 +32,30 @@ ControlPanel::ControlPanel(RunArgs & args, QWidget * p) : QMainWindow(p),
        CreateDocumentMenu();
        CreateScreenMenu();
        
+       CreateLayout();
+       
        UpdateAll();
+}
 
+void ControlPanel::CreateLayout()
+{
+       m_text_edit = new QTextEdit(this);
+       m_text_edit->setGeometry(10,35,m_width-20,m_height-100);
+       
+       m_ok_button = new QPushButton("OK", this);
+       m_ok_button->setGeometry(10,35+m_height-90, m_width-20, 50);
+       connect(m_ok_button, SIGNAL(clicked()), this, SLOT(PressOK()));
 }
 
 QMenu * ControlPanel::CreateMainMenu()
 {
        QMenu * main = menuBar()->addMenu("&Main");
        
+       QAction * about = new QAction("&About", this);
+       main->addAction(about);
+       connect(about, SIGNAL(triggered()), this, SLOT(StateAbout()));
+       
+       
        // Quit entry
        QAction * quit = new QAction("&Quit", this);
        main->addAction(quit);
@@ -47,6 +66,24 @@ QMenu * ControlPanel::CreateMainMenu()
 QMenu * ControlPanel::CreateDocumentMenu()
 {
        QMenu * document = menuBar()->addMenu("&Document");
+       
+       m_document_set_font = new QAction("&Set Insertion Font", this);
+       document->addAction(m_document_set_font);
+       connect(m_document_set_font, SIGNAL(triggered()), this, SLOT(SetDocumentFont()));
+       
+       m_document_insert_text = new QAction("&Insert Text", this);
+       document->addAction(m_document_insert_text);
+       connect(m_document_insert_text, SIGNAL(triggered()), this, SLOT(StateInsertText()));
+       
+       m_document_load_svg = new QAction("&Load SVG From File", this);
+       document->addAction(m_document_load_svg);
+       connect(m_document_load_svg, SIGNAL(triggered()), this, SLOT(LoadSVGIntoDocument()));
+       
+       m_document_parse_svg = new QAction("&Input SVG Manually", this);
+       document->addAction(m_document_parse_svg);
+       connect(m_document_parse_svg, SIGNAL(triggered()), this, SLOT(StateParseSVG()));
+       
+       
        return document;
 }
 
@@ -54,7 +91,9 @@ QMenu * ControlPanel::CreateViewMenu()
 {
        QMenu * view = menuBar()->addMenu("&View");
        
-
+       m_view_set_bounds = new QAction("&Set bounds", this);
+       view->addAction(m_view_set_bounds);
+       connect(m_view_set_bounds, SIGNAL(triggered()), this, SLOT(SetViewBounds()));
        
        return view;
 }
@@ -67,9 +106,11 @@ QMenu * ControlPanel::CreateScreenMenu()
        
        m_screen_gpu_rendering = new QAction("&GPU Rendering", this);
        m_screen_gpu_rendering->setCheckable(true);
+       m_screen_gpu_rendering->setToolTip("Uses the GPU for Rendering");
        
        m_screen_cpu_rendering = new QAction("&CPU Rendering", this);
        m_screen_cpu_rendering->setCheckable(true);
+       m_screen_gpu_rendering->setToolTip("Uses the CPU for Rendering");
                
        screen->addAction(m_screen_gpu_rendering);
        screen->addAction(m_screen_cpu_rendering);
@@ -77,14 +118,73 @@ QMenu * ControlPanel::CreateScreenMenu()
        connect(m_screen_gpu_rendering, SIGNAL(triggered()), this, SLOT(SetGPURendering()));
        connect(m_screen_cpu_rendering, SIGNAL(triggered()), this, SLOT(SetCPURendering()));
        
+       m_screen_show_debug = new QAction("&Print Debug Info", this);
+       m_screen_show_debug->setCheckable(true);
+       
+       screen->addAction(m_screen_show_debug);
+       connect(m_screen_show_debug, SIGNAL(triggered()), this, SLOT(ToggleScreenDebugFont()));
+       
        return screen;
 }
 
+void ControlPanel::paintEvent(QPaintEvent * e)
+{
+//     Debug("Called");
+       
+}
+
+void ControlPanel::ChangeState(State next_state)
+{
+       m_state = next_state;
+       UpdateAll();
+}
+
+
 void ControlPanel::UpdateAll()
 {
        bool using_gpu_rendering = m_view.UsingGPURendering();
        m_screen_gpu_rendering->setChecked(using_gpu_rendering);
        m_screen_cpu_rendering->setChecked(!using_gpu_rendering);       
+       m_screen_show_debug->setChecked(m_screen.DebugFontShown());
+       
+       // update things based on state
+       const char * title;
+       const char * tooltip;
+       switch (m_state)
+       {
+               case INSERT_TEXT:
+                       title = "Insert Text";
+                       tooltip = "Type text to insert, press OK, simple.";
+                       m_text_edit->show();
+                       m_ok_button->show();
+                       m_on_ok = &ControlPanel::InsertTextIntoDocument;
+                       if (m_text_edit->toPlainText() == "")
+                               m_text_edit->setText("The quick brown\nfox jumps over\nthe lazy dog.");
+                       break;
+               case PARSE_SVG:
+                       title = "Parse SVG";
+                       tooltip = "Enter valid SVG and press OK to insert.";
+                       m_text_edit->show();
+                       m_ok_button->show();
+                       m_on_ok = &ControlPanel::InsertSVGIntoDocument;
+                       if (m_text_edit->toPlainText() == "")
+                               m_text_edit->setText("<svg width=\"104\" height=\"186\">\n<path d = \"m 57,185\n\t c 0,0 57,-13 32,-43\n\t -25,-30 -53,2 -25, -30\n\t 28,-32 52,17 28,-32\n\t -24,-50 -16,44 -35,12\n\t-19,-32 13,-64 13,-64\n\t 0,0 40,-50 -0,-14\n\t -40,36 -94,68 -59,109\n\t 35,41 45,62 45,62 z\"/>\n</svg>");
+                       
+                       break;
+               case ABOUT:
+               default:
+                       title = "IPDF Control Panel";
+                       tooltip = "This is the IPDF Control Panel\nDo you feel in control?";
+                       m_text_edit->hide();
+                       m_ok_button->hide();
+                       m_on_ok = NULL;
+                       break;
+       }
+       
+       // Title
+       setWindowTitle(title);
+       // Tooltip
+       setToolTip(tooltip);
 }
 
 void ControlPanel::SetGPURendering()
@@ -99,6 +199,79 @@ void ControlPanel::SetCPURendering()
        UpdateAll();
 }
 
+void ControlPanel::ToggleScreenDebugFont()
+{
+       bool state = m_screen.DebugFontShown();
+       m_screen.ShowDebugFont(!state);
+       UpdateAll();
+       
+}
+
+void ControlPanel::SetViewBounds()
+{
+       bool ok;
+       Real xx = QInputDialog::getDouble(this, "View X Coordinate", "Enter X coordinate:", 0, -2e-30, 2e30,30,&ok);
+       
+       Real yy = QInputDialog::getDouble(this, "View Y Coordinate", "Enter Y coordinate:", 0, -2e-30, 2e30,30,&ok);
+       
+       Real w = QInputDialog::getDouble(this, "View Width", "Enter Width:", 1, -2e-30, 2e30,30,&ok);
+       
+       Real h = QInputDialog::getDouble(this, "View Height", "Enter Height:", 1, -2e-30, 2e30,30,&ok);
+       m_view.SetBounds(Rect(xx,yy,w,h));
+       
+}
+
+void ControlPanel::InsertTextIntoDocument()
+{
+       const Rect & bounds = m_view.GetBounds();
+       Real xx = bounds.x + bounds.w/Real(2);
+       Real yy = bounds.y + bounds.h/Real(2);
+       
+       string msg = m_text_edit->toPlainText().toStdString();
+       Real scale = bounds.w / Real(2);
+       Debug("Insert \"%s\" at %f, %f, scale %f", msg.c_str(), Float(xx), Float(yy), Float(scale));
+       //m_doc.Add(RECT_OUTLINE, bounds, 0); // debugging; text needs to go in the boujnds
+       m_doc.AddText(msg, xx, yy, scale);
+       m_view.ForceRenderDirty();
+       m_view.ForceBufferDirty();
+       m_view.ForceBoundsDirty();
+}
+void ControlPanel::InsertSVGIntoDocument()
+{
+       Rect bounds(m_view.GetBounds());
+       bounds.w /= Real(m_screen.ViewportWidth());
+       bounds.h /= Real(m_screen.ViewportHeight());
+       
+       m_doc.ParseSVG(m_text_edit->toPlainText().toStdString(), bounds);
+       m_view.ForceRenderDirty();
+       m_view.ForceBufferDirty();
+       m_view.ForceBoundsDirty();
+}
+
+void ControlPanel::LoadSVGIntoDocument()
+{
+
+       QString filename = QFileDialog::getOpenFileName(this, "Open SVG", "svg-tests", "Image Files (*.svg)");
+       if (filename == "")
+               return;
+       
+       Rect bounds(m_view.GetBounds());
+       bounds.w /= Real(m_screen.ViewportWidth());
+       bounds.h /= Real(m_screen.ViewportHeight());
+       
+       m_doc.LoadSVG(filename.toStdString(), bounds);
+       m_view.ForceRenderDirty();
+       m_view.ForceBufferDirty();
+       m_view.ForceBoundsDirty();
+}
+
+void ControlPanel::SetDocumentFont()
+{
+       QString filename = QFileDialog::getOpenFileName(this, "Set Font", "fonts", "True Type Fonts (*.ttf)");
+       if (filename != "")
+               m_doc.SetFont(filename.toStdString());
+}
+
 ControlPanel * ControlPanel::g_panel = NULL;
 
 int ControlPanel::Run(void * args)
index f799375..90394f8 100644 (file)
@@ -16,7 +16,9 @@
 #include <QMenu>
 #include <QMenuBar>
 #include <QApplication>
-
+#include <QTextEdit>
+#include <QInputDialog>
+#include <QFileDialog>
 
 
 namespace IPDF
@@ -47,34 +49,69 @@ namespace IPDF
                        };
                        
                        static int Run(void * args);
-                       static void Update() {if (g_panel != NULL) g_panel->UpdateAll();};
-                       
-                       ControlPanel(RunArgs & a, QWidget * p = NULL);
-                       virtual ~ControlPanel() {}
+                       static void Update() {if (g_panel != NULL) g_panel->UpdateAll();}                       
+       
+               private:
+                       typedef enum {
+                               ABOUT,
+                               INSERT_TEXT,
+                               PARSE_SVG
+                       } State;
                        
                private slots:
                        void SetGPURendering();
                        void SetCPURendering();
-
+                       void ToggleScreenDebugFont();
+                       void SetViewBounds();
+                       void LoadSVGIntoDocument();
+                       void SetDocumentFont();
+                       void StateInsertText() {ChangeState(INSERT_TEXT);}
+                       void StateAbout() {ChangeState(ABOUT);}
+                       void StateParseSVG() {ChangeState(PARSE_SVG);}
+                       void PressOK() {if (m_on_ok != NULL) (this->*m_on_ok)();}
 
                private:
                        static ControlPanel * g_panel;
-
-                       
+                       void paintEvent(QPaintEvent * e);
+                       ControlPanel(RunArgs & a, QWidget * p = NULL);
+                       virtual ~ControlPanel() {}
                        void UpdateAll();
-                                       
+                       void ChangeState(State next_state);
                        View & m_view;
                        Document & m_doc;
                        Screen & m_screen;
                        
+                       int m_width;
+                       int m_height;
+                       
+                       
+                       State m_state;
+                       
                        QMenu * CreateMainMenu();
                        QMenu * CreateViewMenu();
                        QMenu * CreateDocumentMenu();
                        QMenu * CreateScreenMenu();
+                       void CreateLayout();
+                       
+                       void InsertTextIntoDocument();
+                       void InsertSVGIntoDocument();
                        
                        QAction * m_screen_gpu_rendering;
                        QAction * m_screen_cpu_rendering;
+                       QAction * m_screen_show_debug;
+                       
+                       QAction * m_document_set_font;
+                       QAction * m_document_insert_text;
+                       QAction * m_document_parse_svg;
+                       QAction * m_document_load_svg;
+                       QAction * m_view_set_bounds;
                        
+               
+                       QTextEdit * m_text_edit;
+                       QPushButton * m_ok_button;
+                       
+                       void (ControlPanel::* m_on_ok)();
+
 
        };
 
index 57b8925..e0dd70b 100644 (file)
@@ -545,6 +545,24 @@ void Document::ParseSVGNode(pugi::xml_node & root, SVGMatrix & parent_transform)
        }
 }
 
+/**
+ * Parse an SVG string into a rectangle
+ */
+void Document::ParseSVG(const string & input, const Rect & bounds)
+{
+       using namespace pugi;
+       
+       xml_document doc_xml;
+       xml_parse_result result = doc_xml.load(input.c_str());
+       
+       if (!result)
+               Error("Couldn't parse SVG input - %s", result.description());
+               
+       Debug("Loaded XML - %s", result.description());
+       SVGMatrix transform = {bounds.w, 0,bounds.x, 0,bounds.h,bounds.y};
+       ParseSVGNode(doc_xml, transform);
+}
+
 /**
  * Load an SVG into a rectangle
  */
@@ -557,7 +575,7 @@ void Document::LoadSVG(const string & filename, const Rect & bounds)
        xml_parse_result result = doc_xml.load(input);
        
        if (!result)
-               Fatal("Couldn't load \"%s\" - %s", filename.c_str(), result.description());
+               Error("Couldn't load \"%s\" - %s", filename.c_str(), result.description());
                
        Debug("Loaded XML - %s", result.description());
        
@@ -758,7 +776,7 @@ void Document::SetFont(const string & font_filename)
                free(m_font_data);
        }
        
-       FILE *font_file = fopen("DejaVuSansMono.ttf", "rb");
+       FILE *font_file = fopen(font_filename.c_str(), "rb");
        fseek(font_file, 0, SEEK_END);
        size_t font_file_size = ftell(font_file);
        fseek(font_file, 0, SEEK_SET);
@@ -863,6 +881,7 @@ void Document::AddFontGlyphAtPoint(stbtt_fontinfo *font, int character, Real sca
        {
                AddGroup(start_index, end_index);
        }
+       Debug("Added Glyph \"%c\" at %f %f, scale %f", (char)character, Float(x), Float(y), Float(scale));
 
        stbtt_FreeShape(font, instructions);
 }
index 6592f4b..3ff607b 100644 (file)
@@ -27,7 +27,7 @@ namespace IPDF
        class Document
        {
                public:
-                       Document(const std::string & filename = "", const std::string & font_filename = "DejaVuSansMono.ttf") : m_objects(), m_count(0), m_font_data(NULL), m_font()
+                       Document(const std::string & filename = "", const std::string & font_filename = "fonts/DejaVuSansMono.ttf") : m_objects(), m_count(0), m_font_data(NULL), m_font()
                        {
                                Load(filename);
                                if (font_filename != "")
@@ -65,6 +65,7 @@ namespace IPDF
                        
                        /** Load an SVG text file and add to the document **/
                        void LoadSVG(const std::string & filename, const Rect & bounds = Rect(0,0,1,1));
+                       void ParseSVG(const std::string & svg, const Rect & bounds = Rect(0,0,1,1));
                        
                        /** Parse an SVG node or SVG-group node, adding children to the document **/
                        void ParseSVGNode(pugi::xml_node & root, SVGMatrix & transform);
diff --git a/src/fonts/BleedingCowboys.ttf b/src/fonts/BleedingCowboys.ttf
new file mode 100644 (file)
index 0000000..9aeda00
Binary files /dev/null and b/src/fonts/BleedingCowboys.ttf differ
diff --git a/src/fonts/ComicSans.ttf b/src/fonts/ComicSans.ttf
new file mode 100644 (file)
index 0000000..d17e1be
Binary files /dev/null and b/src/fonts/ComicSans.ttf differ
diff --git a/src/fonts/DejaVuSansMono.ttf b/src/fonts/DejaVuSansMono.ttf
new file mode 100644 (file)
index 0000000..1376228
Binary files /dev/null and b/src/fonts/DejaVuSansMono.ttf differ
index 2612afc..848debc 100644 (file)
@@ -14,7 +14,7 @@ int main(int argc, char ** argv)
 
        Debug("Compiled with REAL = %d => \"%s\" sizeof(Real) == %d bytes", REAL, g_real_name[REAL], sizeof(Real));
 
-       Document doc;
+       Document doc("","fonts/ComicSans.ttf");
        srand(time(NULL));
 
        enum {OUTPUT_TO_BMP, LOOP} mode = LOOP;
@@ -86,9 +86,7 @@ int main(int argc, char ** argv)
        }
        else 
        {
-               //doc.AddBezier(Bezier(0,0, 1,0.5, 0.5,1, 1,1));
-               doc.AddText("The quick brown\nfox jumps over\nthe lazy dog",0.1,0,0.5);
-               //doc.AddBezier(Bezier(0,0,0,0.1,0,0.1,0,0.1));
+               doc.Add(RECT_OUTLINE, Rect(0,0,0,0),0); // hack to stop segfault if document is empty (:S)
        }
        Debug("Start!");
        Rect bounds(b[0],b[1],b[2],b[3]);
index 9e48967..5ca71ef 100644 (file)
@@ -82,7 +82,7 @@ inline void MainLoop(Document & doc, Screen & scr, View & view)
 {
        // order is important... segfaults occur when screen (which inits GL) is not constructed first -_-
 
-       scr.DebugFontInit("DejaVuSansMono.ttf");
+       scr.DebugFontInit("fonts/DejaVuSansMono.ttf");
        scr.SetMouseHandler(RatCatcher);
 
        double total_cpu_time = 0;
index 223f4a3..a32b1e2 100644 (file)
@@ -93,9 +93,12 @@ Screen::Screen()
 
        m_debug_font_atlas = 0;
        m_no_quit_requested = true;
+       m_show_debug_font = true;
        m_view = NULL;
        ResizeViewport(800, 600);
        
+       
+       
        Clear();
        Present();
        
@@ -454,7 +457,7 @@ struct fontvertex
 
 void Screen::DebugFontPrint(const char* str)
 {
-       if (!m_debug_font_atlas) return;
+       if (!m_debug_font_atlas || !m_show_debug_font) return;
 
        glPushDebugGroup(GL_DEBUG_SOURCE_APPLICATION, 41, -1, "Screen::DebugFontPrint()");
 
index 6b52a09..aed042d 100644 (file)
@@ -72,6 +72,9 @@ namespace IPDF
                
                void RequestQuit() {m_no_quit_requested = false;}
                bool QuitRequested() const {return !m_no_quit_requested;}
+               
+               void ShowDebugFont(bool show = true) {m_show_debug_font = show;}
+               bool DebugFontShown() const {return m_show_debug_font;}
        private:
                void ResizeViewport(int width, int height);
                void DebugFontFlush();
@@ -104,6 +107,7 @@ namespace IPDF
                int m_debug_font_index_head;
                View * m_view;
                bool m_no_quit_requested;
+               bool m_show_debug_font;
        };
 
 }
index a953579..d7d1e34 100644 (file)
@@ -77,6 +77,21 @@ void View::Translate(Real x, Real y)
        m_bounds_dirty = true;
 }
 
+/**
+ * Set View bounds
+ * @param bounds - New bounds
+ */
+void View::SetBounds(const Rect & bounds)
+{
+       m_bounds.x = bounds.x;
+       m_bounds.y = bounds.y;
+       m_bounds.w = bounds.w;
+       m_bounds.h = bounds.h;
+       if (!m_use_gpu_transform)
+               m_buffer_dirty = true;
+       m_bounds_dirty = true;
+}
+
 /**
  * Scale the View at a point
  * @param x, y - Coordinates to scale at (eg: Mouse cursor position)
index ff34415..fe93e6c 100644 (file)
@@ -27,11 +27,13 @@ namespace IPDF
                        
                        void Translate(Real x, Real y);
                        void ScaleAroundPoint(Real x, Real y, Real scale_amount);
+                       void SetBounds(const Rect & new_bounds);
                        
                        Rect TransformToViewCoords(const Rect& inp) const;
                        
                        const Rect& GetBounds() const { return m_bounds; }
                        
+                       
                        const bool UsingGPUTransform() const { return m_use_gpu_transform; } // whether view transform calculated on CPU or GPU
                        const bool UsingGPURendering() const { return m_use_gpu_rendering; } // whether GPU shaders are used or CPU rendering
                        void ToggleGPUTransform() { m_use_gpu_transform = (!m_use_gpu_transform); m_bounds_dirty = true; m_buffer_dirty = true; }

UCC git Repository :: git.ucc.asn.au