<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Blog | Surya Teja K]]></title><description><![CDATA[This blog contains the dev journey of Surya Teja K. I talk about Angular, Python, NodeJS, Rust, developing desktop applications and many more!]]></description><link>https://blog.suryatejak.in</link><generator>RSS for Node</generator><lastBuildDate>Thu, 21 May 2026 01:56:53 GMT</lastBuildDate><atom:link href="https://blog.suryatejak.in/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[GitRaven: April 2026 updates]]></title><description><![CDATA[Hey folks!
It's been a while since I've written about GitRaven.
This post will cover the new features I have introduced and talk about bug fixes.
AI Usage
I used ChatGPT or Google Gemini to help me fo]]></description><link>https://blog.suryatejak.in/gitraven-april-2026-updates</link><guid isPermaLink="true">https://blog.suryatejak.in/gitraven-april-2026-updates</guid><dc:creator><![CDATA[Surya Teja Karra]]></dc:creator><pubDate>Mon, 06 Apr 2026 18:15:10 GMT</pubDate><content:encoded><![CDATA[<p>Hey folks!</p>
<p>It's been a while since I've written about GitRaven.</p>
<p>This post will cover the new features I have introduced and talk about bug fixes.</p>
<h2>AI Usage</h2>
<p>I used ChatGPT or Google Gemini to help me for the updates shared below. They were very helpful in helping me see what I was doing wrong when working on stuff with GitRaven. For example, I ran into quite a few use-after-free bugs in the app because my understanding of freeing memory is different when compared to C++.</p>
<p>The most useful aspect of current AI is it's NLP tech that can understand the problem at hand and offer solutions for the code snippets and error logs I've shared.</p>
<p>I like to learn stuff by building apps and AI tech has made this approach <em>less</em> painful than it usually is.</p>
<h3>Context menu actions in Tree:</h3>
<p>GitRaven now supports context menu actions in the tree. This will allow users to quickly interact with a file/folder to stage/unstage, view in file manager or delete it entirely.</p>
<p>There are more items I'd like to bring onto context-menus but right now, my attention is drawn towards adding more Git related features (push/pull, fetch), etc.</p>
<img src="https://cdn.hashnode.com/uploads/covers/5f369166b6059d3aec17c9f4/f67efd13-3b5e-4e49-97ad-2dfeeb17967c.png" alt="GitRaven context-menu actions" style="display:block;margin:0 auto" />

<h3>Use Initializer Lists for initializing the app</h3>
<p>This is a good milestone in my opinion. The research that went to helping me realize the problem with memory bugs in the app was mind-numbing.  </p>
<p><strong>Bug:</strong><br />The app would randomly crash when performing <code>git status</code> equivalent. <code>GitManager</code> is an important class, used to perform all Git related actions on the repository.</p>
<p><strong>Cause:</strong></p>
<p>The crash happens at a function call when referenced via a pointer to <code>GitManager</code> class instance.</p>
<pre><code class="language-cpp">// inside header
// GitManager *m_gitManager;

// cpp code below
class Foo(GitManager *gitManager, QWidget *parent = nullptr)
    : QWidget{parent}
{
    m_gitManager = gitManager; // yikes!! but I didn't know this was bad then :(
}

void Foo::something()
{
  
  m-&gt;gitManager-&gt;doSomething(); // &lt;- app crashed here
}
</code></pre>
<p>This class is initialized in <code>main.cpp</code> along with <code>QApplication</code> and <code>MainWindow</code>. It was my first "use-after-free" bug. I read about these in CVEs but to experience it first-hand in my app was a definite first.</p>
<p>Anyway, after back-and-forth with ChatGPT, I updated the code to init member variables at the initializer list so that they are available for constructor() to do stuff. It also ensures other widgets that depend on this widget can also safely access stuff as it's not corrupted memory anymore.</p>
<p>I have posted about this on my <a href="https://social.linux.pizza/@shanmukhateja/115888727342467574">Mastodon</a> at the time.</p>
<h3>Checkout to different branches and tags</h3>
<p>I am so happy to report <code>git checkout</code> is working on GitRaven. This feature has been on my mind since the project's inception and I am all here for it!!</p>
<p>There is a known issue at the time of writing where checking to remote branches or tags do not show their names rather they show the git commit hash that points to it.</p>
<p>I added a FIXME in the code so I might eventually get around to it.</p>
<img src="https://cdn.hashnode.com/uploads/covers/5f369166b6059d3aec17c9f4/3a031855-d054-42c9-8b5c-606e673c034f.png" alt="" style="display:block;margin:0 auto" />

<blockquote>
<p>Fun Fact: Testing this feature led to discovering more memory leaks within the app which I will in the next item :)</p>
</blockquote>
<h3>Memory leaks galore!!</h3>
<p>The meat of this post is here! I ran into so many segmentation faults while testing the checkout feature with larger number of new/modified files. The app's would be stuck for few seconds while it renders the tree and updates the status bar.</p>
<p>It turns out, the leading cause for this was either <strong>missing destructors for QObject/QWidget classes</strong> and/or <strong>deleting variables or data incorrectly</strong>.</p>
<p><code>RavenTree</code> class was notorious for crashing the app at unexpected times. The issue turned out to be incorrectly deleting QList&lt;&gt; foo; items.<br />The app kept allocating memory on every <code>git status</code> call without properly freeing the memory which was previously allocated to the <code>QTreeView</code>.</p>
<p>My JS experience is to be blamed here. JS has a garbage collector which in simple terms, is a background thread that runs as long as the app is running to check and free memory when it's no longer in use.<br />In the case of C++ with Qt, that job has fallen on to me. Although C++ offers smart pointers to address this issue, Gemini suggested not to use it with Qt because it messes with Qt's parent-child relationship. I didn't quite fully understand what it meant because on paper, smart pointers are the solution to my problem. They would manage the memory for me while I focus on building my app.</p>
<p>Also, I am familiar with the smart pointers concept because of Rust. I built a <a href="https://blog.suryatejak.in/series/mystudio-ide">text editor</a> with GTK + Rust few years back for fun.</p>
<p><strong>Link to the fixed code:</strong><br /><a href="https://github.com/shanmukhateja/gitraven/blob/9d5151009a777b0d88a1a18687b5580e1fdb8777/raventree.cpp#L117">https://github.com/shanmukhateja/gitraven/blob/9d5151009a777b0d88a1a18687b5580e1fdb8777/raventree.cpp#L117</a></p>
<p>The fix was two steps:</p>
<ol>
<li><p>Add destructors to all classes that extend <code>QObject</code> or <code>QWidget</code>.</p>
</li>
<li><p>In order to clear a QList which holds list of pointers, we need to call <code>qDeleteAll()</code> followed by manually calling <code>QList::clear</code> on the member variables in <code>RavenTree</code></p>
</li>
</ol>
<blockquote>
<p>Note: This isn't over yet. I have found another set of memory related bugs with my libgit2 usage code and I plan on fixing them someday.</p>
</blockquote>
<p>I mentioned this briefly on <a href="https://social.linux.pizza/@shanmukhateja/116310654140242230">Mastodon</a> recently after struggling to understand why this was happening.</p>
<h3>Async Git status checks</h3>
<p>We now use a separate thread to handle <code>GitManager::status</code> for <code>git status</code> functionality via <code>libgit2</code>. Previously, the GUI thread would freeze up when <code>GitManager::status</code> was running on repositories with 100+ files.</p>
<p>The benefit is, the GUI thread isn't frozen although it stays a blank page - no indication that something is happening to the user.</p>
<blockquote>
<p>Note to self - I need to add a progress bar when running long running tasks.</p>
</blockquote>
<h3>CLion migration</h3>
<p>This is a personal change, not completely related to the project but I thought I'd share it here anyway. I migrated from Qt Creator to CLion last month to their free, non-commercial usage license tier.</p>
<p>Qt Creator is an excellent IDE however I am familiar with Jetbrains IDEs for work so I gave it a shot and I liked it.</p>
<h3>Minor cosmetic things and bug fixes</h3>
<ol>
<li><p>The app now shows the currently open folder's full path in title bar like "GitRaven - '/home/user/app'"</p>
</li>
<li><p>Rewrite <code>RavenTree</code>'s absolute path detection logic for individual node in the tree to fix bugs with parent-child relationship shown in the tree.</p>
</li>
<li><p>Add CLion specific folders/files to <code>.gitignore</code></p>
</li>
</ol>
<h2>Thank you</h2>
<p>Thank you for sticking to the end.</p>
<p>I hope to make time for this project and improve it further while I learn C++ and Qt.</p>
<p>You can follow me on <a href="https://social.linux.pizza/@shanmukhateja/">Mastodon</a>.</p>
<p>Bye for now :)</p>
]]></content:encoded></item><item><title><![CDATA[sleep-stopper: A simple GTK4 app to stop Linux from sleeping.]]></title><description><![CDATA[Hello,
I came up with a simple GUI for stopping Linux from sleeping when idle using Python for fun. This project came to be because of my comment:
https://social.linux.pizza/@shanmukhateja/115843701361619857
You can find GitHub link for this project ...]]></description><link>https://blog.suryatejak.in/sleep-stopper-a-simple-gtk4-app-to-stop-linux-from-sleeping</link><guid isPermaLink="true">https://blog.suryatejak.in/sleep-stopper-a-simple-gtk4-app-to-stop-linux-from-sleeping</guid><category><![CDATA[Python]]></category><category><![CDATA[Linux]]></category><category><![CDATA[Gtk 4]]></category><category><![CDATA[dbus]]></category><dc:creator><![CDATA[Surya Teja Karra]]></dc:creator><pubDate>Wed, 07 Jan 2026 17:07:52 GMT</pubDate><content:encoded><![CDATA[<p>Hello,</p>
<p>I came up with a simple GUI for stopping Linux from sleeping when idle using Python for fun. This project came to be because of my comment:</p>
<p><a target="_blank" href="https://social.linux.pizza/@shanmukhateja/115843701361619857">https://social.linux.pizza/@shanmukhateja/115843701361619857</a></p>
<p>You can find GitHub link for this project below:</p>
<p><a target="_blank" href="https://github.com/shanmukhateja/sleep-stopper/">https://github.com/shanmukhateja/sleep-stopper/</a></p>
<h1 id="heading-theory">Theory</h1>
<p>We use DBus interface <code>org.freedesktop.ScreenSaver</code> on Session Bus to tell Linux to disallow going to sleep on idle.</p>
<p>This feature is very useful when waiting on long-running tasks like system updates, media playback, etc. It is already part of all major applications like web browsers, system tools, media players, etc. and now, this project is also part of that list :)</p>
<p>I wrote this in Python as I’m very comfortable with it but it should be doable in any programming language.</p>
<p>The idea is to have 2 buttons - <strong>block sleep</strong> and <strong>unblock sleep</strong> shown to user by checking whether we have a valid cookie. This is an <code>int</code> value provided to us by <code>Inhibit</code> method from DBus interface when our app has successfully requested sleep inhibition.</p>
<p>In order to request inhibiting sleep, we need to provide 2 arguments:</p>
<ol>
<li><p><strong>App Title</strong><br /> Shows the app calling for sleep inhibition to user</p>
</li>
<li><p><strong>Reason</strong><br /> Shows a label for reason behind sleep inhibition for user’s understanding.</p>
</li>
</ol>
<p>We will need the following Python packages for this project:</p>
<ol>
<li><p>GTK4 (for GUI)</p>
</li>
<li><p>dbus-fast (for communicating with DBus)</p>
</li>
</ol>
<h1 id="heading-implementation">Implementation</h1>
<blockquote>
<p><strong>Note:</strong> This code is hacked together in about 3 hours and it’s only goal is to work as expected. It is not meant to deliver a polished experience.</p>
</blockquote>
<p>Let’s look at <code>app.py</code> which contains the GUI and the main logic:</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> dbus <span class="hljs-keyword">import</span> SleepStopperDBusClient

<span class="hljs-keyword">import</span> gi

gi.require_version(<span class="hljs-string">"Gtk"</span>, <span class="hljs-string">"4.0"</span>)
<span class="hljs-keyword">from</span> gi.repository <span class="hljs-keyword">import</span> Gtk


<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">SleepStopperApplication</span>(<span class="hljs-params">Gtk.ApplicationWindow</span>):</span>
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__init__</span>(<span class="hljs-params">self, **kwargs</span>):</span>
        super().__init__(**kwargs, title=<span class="hljs-string">"Sleep Stopper"</span>)
        self.set_size_request(<span class="hljs-number">300</span>, <span class="hljs-number">150</span>)

        self.vbox = Gtk.Box()
        self.set_child(self.vbox)

        self.dbus = SleepStopperDBusClient()

        self.block_button = Gtk.Button.new_with_label(<span class="hljs-string">"Block sleep"</span>)
        self.unblock_button = Gtk.Button.new_with_label(<span class="hljs-string">"Unblock Sleep"</span>)

        self.block_button.set_hexpand(<span class="hljs-literal">True</span>)
        self.unblock_button.set_hexpand(<span class="hljs-literal">True</span>)

        self.vbox.append(self.block_button)
        self.vbox.append(self.unblock_button)

        self.block_button.connect(<span class="hljs-string">"clicked"</span>, self.on_block_clicked)
        self.unblock_button.connect(<span class="hljs-string">"clicked"</span>, self.on_unblock_clicked)

        self.update_ui_state()

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">on_block_clicked</span>(<span class="hljs-params">self, _btn</span>):</span>
        result = self.dbus.inhibit()
        <span class="hljs-keyword">if</span> result:
            <span class="hljs-comment"># show toast </span>
            <span class="hljs-keyword">pass</span>
        <span class="hljs-keyword">else</span>:
            <span class="hljs-comment"># show error toast</span>
            <span class="hljs-keyword">pass</span>
        self.update_ui_state()

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">on_unblock_clicked</span>(<span class="hljs-params">self, _btn</span>):</span>
        result = self.dbus.uninhibit()
        <span class="hljs-keyword">if</span> result:
            <span class="hljs-comment"># show toast </span>
            <span class="hljs-keyword">pass</span>
        <span class="hljs-keyword">else</span>:
            <span class="hljs-comment"># show error toast</span>
            <span class="hljs-keyword">pass</span>
        self.update_ui_state()

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">update_ui_state</span>(<span class="hljs-params">self</span>):</span>
        is_active = self.dbus.is_active()

        <span class="hljs-keyword">if</span> is_active:
            <span class="hljs-comment"># Inhibit not on?</span>
            self.block_button.set_visible(<span class="hljs-literal">True</span>)
            self.unblock_button.set_visible(<span class="hljs-literal">False</span>)
        <span class="hljs-keyword">else</span>:
            <span class="hljs-comment"># Inhibit IS SET!!</span>
            self.block_button.set_visible(<span class="hljs-literal">False</span>)
            self.unblock_button.set_visible(<span class="hljs-literal">True</span>)

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">on_activate</span>(<span class="hljs-params">app</span>):</span>
    win = SleepStopperApplication(application=app)
    win.present()

app = Gtk.Application(application_id=<span class="hljs-string">"in.suryatejak.sleepstopper"</span>)
app.connect(<span class="hljs-string">"activate"</span>, on_activate)
app.run(<span class="hljs-literal">None</span>)
</code></pre>
<p>This is a pretty straight-forward Python code. We initialize the app, create a GtkWindow and show 2 buttons as discussed above.</p>
<p>The GUI logic is driven by a handy function <code>update_ui_state</code> which talks to our DBus client <code>SleepStopperDBusClient</code> (see <code>dbus.py</code> for its code).</p>
<p>I initially planned to show a Toast message of sorts but I realized that means more work which is not necessary for a simple tool.</p>
<p>Now, let’s look at <code>dbus.py</code> which sets up communication with DBus interface and provides handy functions for GUI to use.</p>
<p><strong>dbus.py</strong></p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> asyncio
<span class="hljs-keyword">from</span> dbus_fast.aio <span class="hljs-keyword">import</span> MessageBus

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">SleepStopperDBusClient</span>(<span class="hljs-params">object</span>):</span>

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__init__</span>(<span class="hljs-params">self</span>):</span>
        self.cookie = <span class="hljs-literal">None</span>
        self.runner = asyncio.get_event_loop()
        self.runner.run_until_complete(self.init())

    <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">init</span>(<span class="hljs-params">self</span>):</span>
        self.bus = <span class="hljs-keyword">await</span> MessageBus().connect()

        introspection = <span class="hljs-keyword">await</span> self.bus.introspect(<span class="hljs-string">'org.freedesktop.ScreenSaver'</span>, <span class="hljs-string">'/org/freedesktop/ScreenSaver'</span>)

        self.obj = self.bus.get_proxy_object(<span class="hljs-string">'org.freedesktop.ScreenSaver'</span>, <span class="hljs-string">'/org/freedesktop/ScreenSaver'</span>, introspection)

        self.screensaver = self.obj.get_interface(<span class="hljs-string">'org.freedesktop.ScreenSaver'</span>)

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">is_active</span>(<span class="hljs-params">self</span>):</span>
       <span class="hljs-keyword">return</span> self.cookie <span class="hljs-keyword">is</span> <span class="hljs-literal">None</span>

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">inhibit</span>(<span class="hljs-params">self</span>):</span>
        self.runner.run_until_complete(self._set_inhibit())
        <span class="hljs-keyword">return</span> <span class="hljs-literal">True</span>

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">uninhibit</span>(<span class="hljs-params">self</span>):</span>
        <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> self.cookie:
            <span class="hljs-keyword">return</span> <span class="hljs-literal">False</span>
        self.runner.run_until_complete(self._set_uninhibit())

    <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">_set_inhibit</span>(<span class="hljs-params">self</span>):</span>
        self.cookie = <span class="hljs-keyword">await</span> self.screensaver.call_inhibit(<span class="hljs-string">"Sleep Stopper"</span>, <span class="hljs-string">"User requested inhibit!"</span>)
        print(self.cookie)

    <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">_set_uninhibit</span>(<span class="hljs-params">self</span>):</span>
        <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> self.cookie:
            print(<span class="hljs-string">"self.cookie not set, ignoring request.."</span>)
            <span class="hljs-keyword">return</span> <span class="hljs-literal">False</span>
        <span class="hljs-keyword">await</span> self.screensaver.call_un_inhibit(self.cookie)
        self.cookie = <span class="hljs-literal">None</span>
        <span class="hljs-keyword">return</span> <span class="hljs-literal">True</span>
</code></pre>
<p>Here, we grab a reference to <code>org.freedesktop.ScreenSaver</code> from DBus so that we can call <code>Inhibit</code> and <code>UnInhibit</code> methods.</p>
<p>In order to make GTK work with async-await, I grabbed instance of asyncio’s Event loop in <code>self.runner</code> and use <code>run_until_complete</code> function to call the DBus methods.</p>
<blockquote>
<p>You may have noticed that I have added logic to ignore UnInhibit requests if <code>self.cookie</code> doesn’t exist and I feel this is probably unnecessary.</p>
<p>If an app requests to inhibit sleep BUT after sometime, it is either killed or the process exits without un-inhibiting sleep, the sleep inhibition request is gone.</p>
<p><em>Pretty cool, isn’t it?</em></p>
</blockquote>
<h1 id="heading-screenshots">Screenshots</h1>
<p><strong>Launch screen</strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1767804604108/4db10694-8e5d-4036-a491-d38bbbe5a247.png" alt="sleep-stopper startup UI" class="image--center mx-auto" /></p>
<p><strong>Sleep inhibit is active</strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1767804615638/755ffc07-b90d-4b66-aaba-88e69579d784.png" alt="sleep-stopper &quot;Unblock Sleep&quot; button is shown as sleep inhibit is active." class="image--center mx-auto" /></p>
<p><strong>KDE Plasma shows inhibit request along with the app title and reason.</strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1767804669136/50ab19b5-667a-4ff6-8f7d-8859075e31fc.png" alt="KDE Plasma power management UI showing sleep inhibit request by sleep-stopper app" class="image--center mx-auto" /></p>
<h1 id="heading-conclusion">Conclusion</h1>
<p>I hope this was informative :)</p>
<p>I built this tool for fun - to simply stretch my wings by building simple stuff. I wrote all this in around 3 hours (~2.5hrs) and I was fun project.</p>
<p>The challenge in here, was to code that dealt with separating synchronous code with <code>async-await</code> stuff. My current workaround works because I do not do anything complex with the return result.</p>
<p>Anyway, I hope you liked this post. Please give it a Like to show your appreciation. If you feel there’s something I missed or can do better, let me know in the comments.</p>
<p>Follow me on <a target="_blank" href="https://social.linux.pizza/@shanmukhateja/">Mastodon</a>.</p>
<p>Bye for now :)</p>
]]></content:encoded></item><item><title><![CDATA[GitRaven: How to setup Monaco Editor for QWebEngine]]></title><description><![CDATA[Hello,
This blog post will talk about integrating Monaco Editor into a Qt + C++ app using QWebEngine.
Background
GitRaven is being built to serve as a near-identical replacement to VSCode’s source control management. It aims to offer a similar, yet o...]]></description><link>https://blog.suryatejak.in/gitraven-how-to-setup-monaco-editor-for-qwebengine</link><guid isPermaLink="true">https://blog.suryatejak.in/gitraven-how-to-setup-monaco-editor-for-qwebengine</guid><category><![CDATA[Git]]></category><category><![CDATA[#MonacoEditor]]></category><category><![CDATA[editors]]></category><category><![CDATA[Qt]]></category><category><![CDATA[Open Source]]></category><category><![CDATA[C++]]></category><category><![CDATA[JavaScript]]></category><dc:creator><![CDATA[Surya Teja Karra]]></dc:creator><pubDate>Mon, 03 Nov 2025 17:47:16 GMT</pubDate><content:encoded><![CDATA[<p>Hello,</p>
<p>This blog post will talk about integrating <a target="_blank" href="https://microsoft.github.io/monaco-editor/">Monaco Editor</a> into a Qt + C++ app using <a target="_blank" href="https://doc.qt.io/qt-6/qtwebengine-index.html">QWebEngine</a>.</p>
<h1 id="heading-background">Background</h1>
<p>GitRaven is being built to serve as a near-identical replacement to VSCode’s source control management. It aims to offer a similar, yet <em>opinionated</em>, user experience.</p>
<p>I personally like side-by-side diff when dealing with file changes. It feels natural to me and I guess, I am <em>pampered</em> by VSCode for a couple of years.</p>
<p>In my first GitRaven post, you might have seen a diff viewer in the screenshots. It was built using a custom side-by-side implementation using <a target="_blank" href="https://invent.kde.org/frameworks/ktexteditor">KTextEditor</a>. I gave up on that approach as it was a difficult problem to solve with my current level.</p>
<blockquote>
<p>My future goal (distant) is to replace Monaco based diff viewer with a C++ native side-by-side diff viewer as seen in <a target="_blank" href="https://apps.kde.org/kompare/">Kompare</a> or Kate’s diff viewers.</p>
</blockquote>
<h1 id="heading-ai-usage">AI Usage</h1>
<p>This project uses AI to help me overcome challenges faced in the project. Some code written here has been derived from code snippets shared by LLMs.</p>
<h1 id="heading-theory">Theory</h1>
<p>The idea is to instantiate <a target="_blank" href="https://doc.qt.io/qt-6/qwebengineview.html">QWebEngineView</a> into the app which loads <code>index.html</code> file which contains essential code to initialize Monaco editor as well as setup communication with C++ side as needed.</p>
<ol>
<li><p>Create a new class <code>RavenMonaco</code> which extends from <code>QWebEngineView</code> and set it up in main window class.</p>
</li>
<li><p>Create a new class which extends <a target="_blank" href="https://doc.qt.io/qt-6/qwebenginepage.html">QWebEnginePage</a> to send commands to Monaco editor. This is primarily used to communicate with Monaco, to init the widget or update diff text when a different file is chosen. We also use it to log messages to <code>stdout</code> printed on JS’s <code>console</code> for debugging purposes.</p>
</li>
<li><p>Create the “bridge” class which maintains communication between JS ←—→ C++ side. It is currently used to save text from Monaco’s modified buffer to C++.</p>
</li>
<li><p>A custom class is needed which handles rendering of Monaco editor on-demand like when a user clicks on diff item from LHS tree. This class is also responsible to save text buffer contents sent in (3) to disk.</p>
</li>
<li><p>Lastly, we need a class that can serve Monaco code via a HTTP server for enabling <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers">Web Workers</a>.</p>
<blockquote>
<p>HTTP server is needed for improving performance of Monaco editor. You can find more info in <a target="_blank" href="https://github.com/microsoft/monaco-editor#faq">FAQ section</a> of the project.</p>
</blockquote>
</li>
</ol>
<h1 id="heading-implementation">Implementation</h1>
<p>Let’s start with custom web engine view class.</p>
<p><strong>ravenmonaco.h</strong></p>
<pre><code class="lang-cpp"><span class="hljs-meta">#<span class="hljs-meta-keyword">ifndef</span> RAVENMONACO_H</span>
<span class="hljs-meta">#<span class="hljs-meta-keyword">define</span> RAVENMONACO_H</span>

<span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">"ravenmonacopage.h"</span></span>
<span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">"ravenmonacohttpserver.h"</span></span>
<span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">"ravenmonacobridge.h"</span></span>

<span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">&lt;QWebEngineView&gt;</span></span>
<span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">&lt;QWidget&gt;</span></span>
<span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">&lt;qevent.h&gt;</span></span>

<span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">&lt;QJsonObject&gt;</span></span>

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">RavenMonaco</span> :</span> <span class="hljs-keyword">public</span> QWebEngineView
{
    Q_OBJECT
<span class="hljs-keyword">public</span>:
    <span class="hljs-function"><span class="hljs-keyword">explicit</span> <span class="hljs-title">RavenMonaco</span><span class="hljs-params">(QWidget *parent = <span class="hljs-literal">nullptr</span>)</span></span>;

    <span class="hljs-function">RavenMonacoPage *<span class="hljs-title">page</span><span class="hljs-params">()</span> <span class="hljs-keyword">const</span></span>;

<span class="hljs-keyword">protected</span>:
    <span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">resizeEvent</span><span class="hljs-params">(QResizeEvent *event)</span> <span class="hljs-keyword">override</span>
    </span>{
        <span class="hljs-comment">// Ensure that the web view resizes dynamically when the parent widget is resized</span>
        resize(event-&gt;size()); <span class="hljs-comment">// Resize webView to match the parent widget</span>
        QWidget::resizeEvent(event);
    }
    <span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">setDefaultUrl</span><span class="hljs-params">()</span></span>;

<span class="hljs-keyword">public</span> slots:
    <span class="hljs-comment">// Nice to have :)</span>
    <span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">setTheme</span><span class="hljs-params">(Qt::ColorScheme colorScheme)</span></span>;
<span class="hljs-keyword">private</span>:
    RavenMonacoPage *m_page;
    RavenMonacoHTTPServer *m_server;
    RavenMonacoBridge *m_bridge;
    QWebChannel *m_channel;
};

<span class="hljs-meta">#<span class="hljs-meta-keyword">endif</span> <span class="hljs-comment">// RAVENMONACO_H</span></span>
</code></pre>
<p>This is pretty straightforward. I store references to page, http server, bridge class and channel for future use.</p>
<p><strong>ravenmonaco.cpp</strong></p>
<pre><code class="lang-cpp"><span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">"ravenmonaco.h"</span></span>

<span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">&lt;QStyleHints&gt;</span></span>
<span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">&lt;QWebChannel&gt;</span></span>
<span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">&lt;qmessagebox.h&gt;</span></span>

<span class="hljs-keyword">namespace</span> fs = <span class="hljs-built_in">std</span>::filesystem;

RavenMonaco::RavenMonaco(QWidget *parent)
    : QWebEngineView{parent}
{
    <span class="hljs-comment">// Init page</span>
    m_page = <span class="hljs-keyword">new</span> RavenMonacoPage(<span class="hljs-keyword">this</span>);
    setPage(m_page);

    <span class="hljs-comment">// Light/dark theme switcher</span>
    QStyleHints *hint = QGuiApplication::styleHints();

    <span class="hljs-comment">// Init monaco when the page load is finished.</span>
    connect(page(), &amp;QWebEnginePage::loadFinished, <span class="hljs-keyword">this</span>, [<span class="hljs-keyword">this</span>](<span class="hljs-keyword">bool</span> ok) {
        <span class="hljs-keyword">if</span> (!ok) {
            qCritical() &lt;&lt; <span class="hljs-string">"Failed to load Monaco editor, check Monaco HTTP server."</span>;
            QMessageBox errorMsg(QMessageBox::Critical, <span class="hljs-string">"GitRaven"</span> , <span class="hljs-string">"Failed to load Diff Viewer components."</span>, QMessageBox::Ok);
            errorMsg.exec();
            <span class="hljs-built_in">std</span>::<span class="hljs-built_in">exit</span>(<span class="hljs-number">-1</span>);
        }

        <span class="hljs-comment">// Initialize Monaco internally (doesn't show up in UI yet)</span>
        page()-&gt;runJavaScript(<span class="hljs-string">"init()"</span>, <span class="hljs-number">0</span>, [<span class="hljs-keyword">this</span>](<span class="hljs-keyword">const</span> QVariant &amp;) {
            <span class="hljs-comment">// Update theme</span>
            setTheme(QGuiApplication::styleHints()-&gt;colorScheme());
        });
    });

    <span class="hljs-comment">// Init HTTP server for monaco-editor</span>
    m_server = <span class="hljs-keyword">new</span> RavenMonacoHTTPServer(<span class="hljs-keyword">this</span>);
    m_server-&gt;init();

    setDefaultUrl();

    <span class="hljs-comment">// Init bridge</span>
    m_bridge = <span class="hljs-keyword">new</span> RavenMonacoBridge(<span class="hljs-keyword">this</span>, (RavenEditor*)parent);
    m_channel = <span class="hljs-keyword">new</span> QWebChannel(<span class="hljs-keyword">this</span>);
    m_page-&gt;setWebChannel(m_channel);
    <span class="hljs-comment">// Inform JS side of a JS object available in </span>
    <span class="hljs-comment">// `window` that can communicate with C++ world.</span>
    m_channel-&gt;registerObject(<span class="hljs-string">"cppBridge"</span>, m_bridge);

    <span class="hljs-comment">// Light/dark theme switcher</span>
    connect(hint, &amp;QStyleHints::colorSchemeChanged, <span class="hljs-keyword">this</span>, &amp;RavenMonaco::setTheme);
}

<span class="hljs-function">RavenMonacoPage *<span class="hljs-title">RavenMonaco::page</span><span class="hljs-params">()</span> <span class="hljs-keyword">const</span>
</span>{
    <span class="hljs-keyword">return</span> m_page;
}

<span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">RavenMonaco::setDefaultUrl</span><span class="hljs-params">()</span>
</span>{
    setUrl(QUrl(<span class="hljs-string">"http://localhost:9191/index.html"</span>));
    load(url());
}

<span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">RavenMonaco::setTheme</span><span class="hljs-params">(Qt::ColorScheme colorScheme)</span>
</span>{
    QJsonObject obj;
    obj[<span class="hljs-string">"theme"</span>] = colorScheme == Qt::ColorScheme::Light ? <span class="hljs-string">"light"</span> : <span class="hljs-string">"dark"</span>;
    <span class="hljs-function">QJsonDocument <span class="hljs-title">jd</span><span class="hljs-params">(obj)</span></span>;
    m_page-&gt;runJavaScript(QString(<span class="hljs-string">"setTheme({opt})"</span>).replace(<span class="hljs-string">"{opt}"</span>, jd.toJson()));
}
</code></pre>
<p>This class does a couple of things:</p>
<ol>
<li><p>Calls <code>init()</code> in the JS side to initialize Monaco editor. I want to make the app “ready for rendering diffs” ASAP and so I came up with this logic.</p>
</li>
<li><p>Then, we setup color scheme detection based on OS’s preference (light/dark) and listen to this event. I personally like this feature.</p>
</li>
</ol>
<blockquote>
<p>It’s a (great) gesture from app developers and I appreciate every time an app does it. Most Linux apps follow this by default (at least ones I use) and naturally, I wanted to participate :)</p>
</blockquote>
<ol start="3">
<li>We initialize bridge class as well as <a target="_blank" href="https://doc.qt.io/qt-6/qwebchannel.html">QWebChannel</a> which is the tech magic that enables us to communicate with/from C++ &amp; JS side.</li>
</ol>
<p><strong>ravenmonacopage.h</strong></p>
<pre><code class="lang-cpp"><span class="hljs-meta">#<span class="hljs-meta-keyword">ifndef</span> RAVENMONACOPAGE_H</span>
<span class="hljs-meta">#<span class="hljs-meta-keyword">define</span> RAVENMONACOPAGE_H</span>

<span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">&lt;QWebEnginePage&gt;</span></span>

<span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">"gitmanager.h"</span></span>

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">RavenMonacoPage</span> :</span> <span class="hljs-keyword">public</span> QWebEnginePage
{
<span class="hljs-keyword">public</span>:
    <span class="hljs-function"><span class="hljs-keyword">explicit</span> <span class="hljs-title">RavenMonacoPage</span><span class="hljs-params">(QObject *parent = <span class="hljs-literal">nullptr</span>)</span></span>;
    <span class="hljs-comment">// This is needed to disable editing Monaco's modified buffer when dealing with</span>
    <span class="hljs-comment">// staged files.</span>
    <span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">setReadonly</span><span class="hljs-params">(<span class="hljs-keyword">bool</span> readonly)</span></span>;
    <span class="hljs-comment">// Used to update old/new text content inside Monaco on each file item click.</span>
    <span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">updateText</span><span class="hljs-params">(GitManager::GitDiffItem diffItem)</span></span>;
<span class="hljs-keyword">private</span>:
    <span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">javaScriptConsoleMessage</span><span class="hljs-params">(JavaScriptConsoleMessageLevel level,
                                          <span class="hljs-keyword">const</span> QString &amp;message, <span class="hljs-keyword">int</span> lineNumber,
                                          <span class="hljs-keyword">const</span> QString &amp;sourceID)</span> <span class="hljs-keyword">override</span></span>;

};

<span class="hljs-meta">#<span class="hljs-meta-keyword">endif</span> <span class="hljs-comment">// RAVENMONACOPAGE_H</span></span>
</code></pre>
<p><strong>ravenmonacopage.cpp</strong></p>
<pre><code class="lang-cpp"><span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">"ravenmonacopage.h"</span></span>

<span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">&lt;QJsonObject&gt;</span></span>


RavenMonacoPage::RavenMonacoPage(QObject *parent)
    : QWebEnginePage(parent)
{}

<span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">RavenMonacoPage::setReadonly</span><span class="hljs-params">(<span class="hljs-keyword">bool</span> readonly)</span>
</span>{
    runJavaScript(QString(<span class="hljs-string">"setReadonly({opt})"</span>)
    .replace(<span class="hljs-string">"{opt}"</span>, QVariant(readonly).toString()));
}

<span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">RavenMonacoPage::updateText</span><span class="hljs-params">(GitManager::GitDiffItem diffItem)</span>
</span>{
    <span class="hljs-comment">// Build JSON payload</span>
    <span class="hljs-comment">// Note: Is there a better way?</span>
    QJsonObject payloadJ;
    payloadJ[<span class="hljs-string">"oldText"</span>] = diffItem.oldFileContent;
    payloadJ[<span class="hljs-string">"oldPath"</span>] = diffItem.oldFilePath;
    payloadJ[<span class="hljs-string">"newText"</span>] = diffItem.newFileContent;
    payloadJ[<span class="hljs-string">"newPath"</span>] = diffItem.newFilePath;

    <span class="hljs-function">QJsonDocument <span class="hljs-title">payloadJD</span><span class="hljs-params">(payloadJ)</span></span>;

    QString payloadJDStr = QString(payloadJD.toJson());

    <span class="hljs-comment">// Send request</span>
    runJavaScript(QString(<span class="hljs-string">"update({opt})"</span>).replace(<span class="hljs-string">"{opt}"</span>, payloadJDStr));
}

<span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">RavenMonacoPage::javaScriptConsoleMessage</span><span class="hljs-params">(
    JavaScriptConsoleMessageLevel level,
    <span class="hljs-keyword">const</span> QString &amp;message,
    <span class="hljs-keyword">int</span> lineNumber, <span class="hljs-keyword">const</span>
    QString &amp;sourceID
)</span>
</span>{
    qDebug() &lt;&lt; <span class="hljs-string">"RavenMonacoPage::javaScriptConsoleMessage"</span>;
    qDebug() &lt;&lt; level &lt;&lt; message &lt;&lt; lineNumber &lt;&lt; sourceID;
}
</code></pre>
<p><strong>ravenmonacobridge.h</strong></p>
<pre><code class="lang-cpp"><span class="hljs-meta">#<span class="hljs-meta-keyword">ifndef</span> RAVENMONACOBRIDGE_H</span>
<span class="hljs-meta">#<span class="hljs-meta-keyword">define</span> RAVENMONACOBRIDGE_H</span>

<span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">&lt;QObject&gt;</span></span>
<span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">&lt;QDebug&gt;</span></span>

<span class="hljs-comment">// Forward declarations</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">RavenEditor</span>;</span>

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">RavenMonacoBridge</span> :</span> <span class="hljs-keyword">public</span> QObject
{
    Q_OBJECT
<span class="hljs-keyword">public</span>:
    <span class="hljs-function"><span class="hljs-keyword">explicit</span> <span class="hljs-title">RavenMonacoBridge</span><span class="hljs-params">(QObject *parent = <span class="hljs-literal">nullptr</span>, RavenEditor *editor = <span class="hljs-literal">nullptr</span>)</span></span>;
    <span class="hljs-function">Q_INVOKABLE <span class="hljs-keyword">void</span> <span class="hljs-title">saveModifiedChanges</span><span class="hljs-params">(QString modified)</span></span>;

<span class="hljs-keyword">private</span>:
    RavenEditor *m_ravenEditor;
};

<span class="hljs-meta">#<span class="hljs-meta-keyword">endif</span> <span class="hljs-comment">// RAVENMONACOBRIDGE_H</span></span>
</code></pre>
<p><strong>ravenmonacobridge.cpp</strong></p>
<pre><code class="lang-cpp"><span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">"ravenmonacobridge.h"</span></span>

<span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">"raveneditor.h"</span></span>

RavenMonacoBridge::RavenMonacoBridge(QObject *parent, RavenEditor *editor)
    : QObject{parent}
{
    m_ravenEditor = editor;
}

<span class="hljs-comment">/**
 * @brief This function is called by Monaco when user has modified file contents.
 * @param modifiedText - The modified text contents from Monaco side.
 */</span>
<span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">RavenMonacoBridge::saveModifiedChanges</span><span class="hljs-params">(QString modifiedText)</span>
</span>{
    qDebug() &lt;&lt; <span class="hljs-string">"RavenMonacoBridge::saveModifiedChanges called"</span>;

    emit m_ravenEditor-&gt;signalSaveModifiedChanges(modifiedText);
}
</code></pre>
<p>This class exists to trigger <code>saveModifiedChanges</code> from JS side and forward it to <code>RavenEditor</code> class which actually holds the business logic for processing the request.</p>
<p>Also, here’s the code that allows us to render either a placeholder or Monaco editor as required.</p>
<p><strong>ravenrhsview.h</strong></p>
<pre><code class="lang-cpp"><span class="hljs-meta">#<span class="hljs-meta-keyword">ifndef</span> RAVENRHSVIEW_H</span>
<span class="hljs-meta">#<span class="hljs-meta-keyword">define</span> RAVENRHSVIEW_H</span>

<span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">"gitmanager.h"</span></span>
<span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">"mainwindow.h"</span></span>

<span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">&lt;QVBoxLayout&gt;</span></span>
<span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">&lt;QWidget&gt;</span></span>

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">RavenRHSView</span> :</span> <span class="hljs-keyword">public</span> QWidget
{
    Q_OBJECT
<span class="hljs-keyword">public</span>:
    <span class="hljs-function"><span class="hljs-keyword">explicit</span> <span class="hljs-title">RavenRHSView</span><span class="hljs-params">(QWidget *parent)</span></span>;
    ~RavenRHSView() <span class="hljs-keyword">override</span>;

    <span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">initLandingInfo</span><span class="hljs-params">()</span></span>;

<span class="hljs-keyword">public</span> slots:
    <span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">updateUI</span><span class="hljs-params">(<span class="hljs-built_in">std</span>::optional&lt;GitManager::GitDiffItem&gt; item)</span></span>;

<span class="hljs-keyword">private</span>:
    <span class="hljs-keyword">bool</span> m_showLandingInfo = <span class="hljs-literal">true</span>;

    MainWindow *m_mainWindow;
    RavenTree   *m_ravenTree;
    RavenEditor *m_ravenEditor;

    QWidget *m_landingInfoWidget;
};

<span class="hljs-meta">#<span class="hljs-meta-keyword">endif</span> <span class="hljs-comment">// RAVENRHSVIEW_H</span></span>
</code></pre>
<p><strong>ravenrhsview.cpp</strong></p>
<pre><code class="lang-cpp"><span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">"ravenrhsview.h"</span></span>

<span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">"raveneditor.h"</span></span>

<span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">&lt;QLabel&gt;</span></span>

RavenRHSView::RavenRHSView(QWidget *parent)
    : QWidget{parent},
    m_mainWindow(<span class="hljs-keyword">static_cast</span>&lt;MainWindow*&gt;(topLevelWidget()-&gt;window())),
    m_ravenEditor(<span class="hljs-keyword">new</span> RavenEditor(<span class="hljs-keyword">this</span>)),  <span class="hljs-comment">// Editor widget</span>
    m_landingInfoWidget(<span class="hljs-keyword">new</span> QWidget(<span class="hljs-keyword">this</span>)) <span class="hljs-comment">// placeholder widget</span>
{
    <span class="hljs-comment">// Widget config</span>
    setLayout(<span class="hljs-keyword">new</span> QVBoxLayout(<span class="hljs-keyword">this</span>));
    layout()-&gt;addWidget(m_landingInfoWidget);

    updateUI(<span class="hljs-built_in">std</span>::nullopt);

    m_ravenTree = m_mainWindow-&gt;getRavenLHSView()-&gt;getRavenTree();
    connect(m_ravenTree, &amp;RavenTree::renderDiffItem, <span class="hljs-keyword">this</span>, &amp;RavenRHSView::updateUI);
}

RavenRHSView::~RavenRHSView()
{
    <span class="hljs-comment">// cleanup</span>
    disconnect(m_ravenTree, &amp;RavenTree::renderDiffItem, <span class="hljs-keyword">this</span>, &amp;RavenRHSView::updateUI);
}

<span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">RavenRHSView::updateUI</span><span class="hljs-params">(<span class="hljs-built_in">std</span>::optional&lt;GitManager::GitDiffItem&gt; item)</span>
</span>{
    qDebug() &lt;&lt; <span class="hljs-string">"RavenRHSView::updateUI called"</span>;

    m_showLandingInfo = !item.has_value();

    <span class="hljs-comment">// Determine whether we show Diff widget or the placeholder</span>
    <span class="hljs-keyword">if</span> (!m_showLandingInfo) {
        m_landingInfoWidget-&gt;hide();
        layout()-&gt;addWidget(m_ravenEditor);
        m_ravenEditor-&gt;openDiffItem(<span class="hljs-built_in">std</span>::move(item.value()));
    } <span class="hljs-keyword">else</span> {
        initLandingInfo();
    }
}

<span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">RavenRHSView::initLandingInfo</span><span class="hljs-params">()</span>
</span>{
    <span class="hljs-keyword">auto</span> widget = m_landingInfoWidget;

    <span class="hljs-keyword">auto</span> layout = <span class="hljs-keyword">new</span> QGridLayout(widget);
    layout-&gt;setAlignment(Qt::AlignCenter);
    widget-&gt;setLayout(layout);
    widget-&gt;setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);

    <span class="hljs-keyword">auto</span> *label = <span class="hljs-keyword">new</span> QLabel(widget);
    label-&gt;setText(<span class="hljs-string">"GitRaven"</span>);
    <span class="hljs-keyword">auto</span> icon = QIcon::fromTheme(<span class="hljs-string">"git"</span>);
    <span class="hljs-keyword">auto</span> iconLabel = <span class="hljs-keyword">new</span> QLabel(widget);
    iconLabel-&gt;setPixmap(icon.pixmap(<span class="hljs-number">64</span>, <span class="hljs-number">64</span>));

    layout-&gt;addWidget(iconLabel);
    layout-&gt;addWidget(label);
}
</code></pre>
<p><strong>ravenmonacohttpserver.h</strong></p>
<pre><code class="lang-cpp"><span class="hljs-meta">#<span class="hljs-meta-keyword">ifndef</span> RAVENMONACOHTTPSERVER_H</span>
<span class="hljs-meta">#<span class="hljs-meta-keyword">define</span> RAVENMONACOHTTPSERVER_H</span>

<span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">&lt;QObject&gt;</span></span>
<span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">&lt;QHttpServer&gt;</span></span>
<span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">&lt;QTcpServer&gt;</span></span>

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">RavenMonacoHTTPServer</span> :</span> QObject
{
    Q_OBJECT
<span class="hljs-keyword">public</span>:
    RavenMonacoHTTPServer(QObject *parent);
    ~RavenMonacoHTTPServer();

    <span class="hljs-function"><span class="hljs-keyword">int</span> <span class="hljs-title">init</span><span class="hljs-params">()</span></span>;

<span class="hljs-keyword">private</span>:
    QUrl *m_url;
    <span class="hljs-keyword">int</span> PORT = <span class="hljs-number">9191</span>;

    QHttpServer *m_server = <span class="hljs-keyword">new</span> QHttpServer(<span class="hljs-keyword">this</span>);
    QTcpServer *m_tcpserver = <span class="hljs-keyword">new</span> QTcpServer(<span class="hljs-keyword">this</span>);
};

<span class="hljs-meta">#<span class="hljs-meta-keyword">endif</span> <span class="hljs-comment">// RAVENMONACOHTTPSERVER_H</span></span>
</code></pre>
<p><strong>ravenmonacohttpserver.cpp</strong></p>
<pre><code class="lang-cpp"><span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">"ravenmonacohttpserver.h"</span></span>

<span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">&lt;filesystem&gt;</span></span>

<span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">"ravenutils.h"</span></span>

<span class="hljs-keyword">using</span> <span class="hljs-built_in">std</span>::filesystem::absolute;
<span class="hljs-keyword">using</span> <span class="hljs-built_in">std</span>::filesystem::path;

RavenMonacoHTTPServer::RavenMonacoHTTPServer(QObject *parent)
    : QObject(parent) {}

RavenMonacoHTTPServer::~RavenMonacoHTTPServer()
{
    m_tcpserver-&gt;close();
}

<span class="hljs-function"><span class="hljs-keyword">int</span> <span class="hljs-title">RavenMonacoHTTPServer::init</span><span class="hljs-params">()</span>
</span>{
    qDebug() &lt;&lt; <span class="hljs-string">"RavenMonacoHTTPServer::init() called"</span>;

    <span class="hljs-keyword">if</span> (!m_tcpserver-&gt;listen(QHostAddress::LocalHost, PORT) || !m_server-&gt;bind(m_tcpserver)) {
        qDebug() &lt;&lt; <span class="hljs-string">"RavenMonacoHTTPServer::init() Failed to bind port for HTTP server."</span>;
        <span class="hljs-keyword">return</span> <span class="hljs-number">-1</span>;
    }

    <span class="hljs-comment">// Listen to / path and return the requested file in URL.</span>
    <span class="hljs-comment">// Note: Can this be a security risk? For ex: "/index.html/../../../../etc/passwd"</span>
    m_server-&gt;route(<span class="hljs-string">"/&lt;arg&gt;"</span>, [] (<span class="hljs-keyword">const</span> QUrl &amp;url) {
        QString urlPath = url.path();

        <span class="hljs-keyword">if</span> (urlPath.length() &lt; <span class="hljs-number">5</span>) {
            <span class="hljs-keyword">return</span> QHttpServerResponse(<span class="hljs-string">""</span>);
        }

        <span class="hljs-comment">// Locate editor directory</span>
        path editorDirStdPath = path(RavenUtils::getEditorDirPath());
        path absolutePath = absolute(editorDirStdPath / path(urlPath.toStdString()));

        <span class="hljs-keyword">return</span> QHttpServerResponse::fromFile(QString::fromStdString(absolutePath));
    });

    <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;
}
</code></pre>
<h1 id="heading-results">Results</h1>
<p><strong>Default state (placeholder)</strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1762190732083/4d08f815-9056-4ecc-aad1-5cc343122690.png" alt="GitRaven default state with placeholder on right-hand side" class="image--center mx-auto" /></p>
<p><strong>Render a diff with Monaco (respecting OS color scheme)</strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1762190741044/1e660ee2-8bbb-4dd6-a3f0-0fb2ab403bfe.png" alt="GitRaven with Monaco rendering a side-by-side diff on right-hand side" class="image--center mx-auto" /></p>
<h1 id="heading-conclusion">Conclusion</h1>
<p>Thanks for your time!</p>
<p>I reluctantly introduced this feature into the project because I couldn’t find a viable alternative to it and building something similar is way too advanced for me at the moment.</p>
<p>Anyway, I hope you liked this post. Please give it a Like to show your appreciation. If you feel there’s something I missed or can do better, let me know in the comments.</p>
<p>Bye for now :-)</p>
]]></content:encoded></item><item><title><![CDATA[GitRaven: How to draw custom widgets on QTreeView rows]]></title><description><![CDATA[Hello,
In this blog post, we will be looking at customizing QTreeView to render custom widgets on each relevant rows.
We will be adding the ability to stage/unstage an file/folder from tree and also show a label for the file’s git status - Modified, ...]]></description><link>https://blog.suryatejak.in/gitraven-how-to-draw-custom-widgets-on-qtreeview-rows</link><guid isPermaLink="true">https://blog.suryatejak.in/gitraven-how-to-draw-custom-widgets-on-qtreeview-rows</guid><category><![CDATA[C++]]></category><category><![CDATA[Git]]></category><category><![CDATA[GUI]]></category><category><![CDATA[Qt]]></category><dc:creator><![CDATA[Surya Teja Karra]]></dc:creator><pubDate>Sun, 12 Oct 2025 16:57:18 GMT</pubDate><content:encoded><![CDATA[<p>Hello,</p>
<p>In this blog post, we will be looking at customizing <code>QTreeView</code> to render custom widgets on each relevant rows.</p>
<p>We will be adding the ability to stage/unstage an file/folder from tree and also show a label for the file’s git status - <strong>M</strong>odified, <strong>D</strong>eleted or <strong>U</strong>ncommitted.</p>
<blockquote>
<p>Check the Results section for the final result.</p>
</blockquote>
<h1 id="heading-theory">Theory</h1>
<p>The idea is to use a custom class which extends <code>QStyledItemDelegate</code>. Check the <a target="_blank" href="https://doc.qt.io/qt-6/model-view-programming.html#the-model-view-architecture">model view architecture</a> section from Qt documentation for more info.</p>
<p><a target="_blank" href="https://doc.qt.io/qt-6/model-view-programming.html#the-model-view-architecture"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1755011582701/5be637cb-ebbd-404f-8b58-5feda3c0e992.png" alt class="image--center mx-auto" /></a></p>
<p><em>Image credits: Qt documentation</em></p>
<p>So, when the view (in our case <code>QTreeView</code>) wants to render something like text or button, it will ask delegate class about the details. The Delegate class will in-turn ask the model class for data to be shown at a given row, column.</p>
<p>You can find more details on the model class used for this project in <a target="_blank" href="https://hashnode.com/post/cme4b8d43000702jm4bub148r">my previous post</a>. I have explained how we show a custom data struct to build the tree structure.</p>
<p>In my case, I will override <code>paint</code> and <code>sizeHint</code> functions.</p>
<p><strong>You can learn more about these functions on Qt docs:</strong></p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Class name</td><td>Link</td></tr>
</thead>
<tbody>
<tr>
<td>QStyledItemDelegate</td><td><a target="_blank" href="https://doc.qt.io/qt-6/qstyleditemdelegate.html">https://doc.qt.io/qt-6/qstyleditemdelegate.html</a></td></tr>
<tr>
<td><code>paint</code></td><td><a target="_blank" href="https://doc.qt.io/qt-6/qstyleditemdelegate.html#paint">https://doc.qt.io/qt-6/qstyleditemdelegate.html#paint</a></td></tr>
<tr>
<td><code>sizeHint</code></td><td><a target="_blank" href="https://doc.qt.io/qt-6/qstyleditemdelegate.html#sizeHint">https://doc.qt.io/qt-6/qstyleditemdelegate.html#sizeHint</a></td></tr>
</tbody>
</table>
</div><h1 id="heading-ai-usage">AI Usage</h1>
<p>I have checked with ChatGPT to fix issues on my code. For example, it helped me identity an issue where <code>QTreeView</code> wouldn’t display items properly when I use the custom delegate class.</p>
<p><strong>Solution:</strong> I need to call the base class functions inside `paint` <strong>before</strong> our custom widgets are painted to the screen like <code>QStyledItemDelegate::paint(painter, option, index);</code>.</p>
<h1 id="heading-implementation">Implementation</h1>
<p>So, my custom <code>sizeHint</code> function is pretty straightforward for now. It has zero custom functionality and will always return Qt’s default <code>sizeHint</code> result.</p>
<p>I’ll keep an eye on it as I go, but for now, I’ve got other things to worry about.</p>
<p>Now, the first task is to create our custom delegate class <code>RavenTreeDelegate</code>.</p>
<p><strong>RavenTreeDelegate.h:</strong></p>
<pre><code class="lang-cpp"><span class="hljs-meta">#<span class="hljs-meta-keyword">ifndef</span> RAVENTREEDELEGATE_H</span>
<span class="hljs-meta">#<span class="hljs-meta-keyword">define</span> RAVENTREEDELEGATE_H</span>

<span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">&lt;QStyledItemDelegate&gt;</span></span>


<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">RavenTreeDelegate</span> :</span> <span class="hljs-keyword">public</span> QStyledItemDelegate
{
    Q_OBJECT
<span class="hljs-keyword">public</span>:
    RavenTreeDelegate();

    <span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">paint</span><span class="hljs-params">(QPainter *painter,
               <span class="hljs-keyword">const</span> QStyleOptionViewItem &amp;option, <span class="hljs-keyword">const</span> QModelIndex &amp;index)</span> <span class="hljs-keyword">const</span> <span class="hljs-keyword">override</span></span>;
    <span class="hljs-function">QSize <span class="hljs-title">sizeHint</span><span class="hljs-params">(<span class="hljs-keyword">const</span> QStyleOptionViewItem &amp;option,
                   <span class="hljs-keyword">const</span> QModelIndex &amp;index)</span> <span class="hljs-keyword">const</span> <span class="hljs-keyword">override</span></span>;
};

<span class="hljs-meta">#<span class="hljs-meta-keyword">endif</span> <span class="hljs-comment">// RAVENTREEDELEGATE_H</span></span>
</code></pre>
<p><a target="_blank" href="https://doc.qt.io/qt-6/qstyleditemdelegate.html#sizeHint"><strong>RavenTreeDelegate.cpp</strong></a><strong>:</strong></p>
<p>Here lies the custom class in it’s entirety. Let me break it down and explain what’s happening.</p>
<pre><code class="lang-cpp"><span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">"raventreedelegate.h"</span></span>
<span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">"raventreeitem.h"</span></span>

<span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">&lt;QApplication&gt;</span></span>
<span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">&lt;QMouseEvent&gt;</span></span>
<span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">&lt;QPainter&gt;</span></span>

RavenTreeDelegate::RavenTreeDelegate() {}

<span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">RavenTreeDelegate::paint</span><span class="hljs-params">(QPainter *painter, <span class="hljs-keyword">const</span> QStyleOptionViewItem &amp;option, <span class="hljs-keyword">const</span> QModelIndex &amp;index)</span> <span class="hljs-keyword">const</span>
</span>{
    <span class="hljs-comment">// Save default painter settings</span>
    painter-&gt;save();
    <span class="hljs-comment">// Call base class' paint method to render default items</span>
    QStyledItemDelegate::paint(painter, option, index);

    <span class="hljs-comment">// Apply our changes on top of the default UI</span>
    <span class="hljs-keyword">if</span> (index.isValid())
    {
        <span class="hljs-keyword">auto</span> *treeItem = <span class="hljs-keyword">static_cast</span>&lt;RavenTreeItem*&gt;(index.internalPointer());
        <span class="hljs-keyword">auto</span> rect = option.rect;
        <span class="hljs-keyword">auto</span> isSelected = (option.state &amp; QStyle::State_Selected) == QStyle::State_Selected;
        <span class="hljs-comment">// Increase yOffset for rect to show custom UI items.</span>
        <span class="hljs-comment">// <span class="hljs-doctag">NOTE:</span> I don't why but adding this line FIXED alignment issues of my custom widgets.</span>
        <span class="hljs-comment">//       If you know why, please let me know. I am really curious!!!!</span>
        rect.setY(rect.y() + <span class="hljs-number">18</span>);

        <span class="hljs-comment">// Status text (shows D/M/U text at end of row)</span>
        <span class="hljs-keyword">auto</span> statusPoint = rect.topRight();
        statusPoint.setX(statusPoint.x() - <span class="hljs-number">20</span>);
        <span class="hljs-keyword">auto</span> font = painter-&gt;font();
        font.setWeight(QFont::Weight::Bold);

        <span class="hljs-comment">// Applicable to non-heading items only</span>
        <span class="hljs-keyword">if</span> (!treeItem-&gt;heading)
        {
            <span class="hljs-keyword">if</span> (treeItem-&gt;deleted)
            {
                <span class="hljs-comment">// Show "Deleted" status</span>
                painter-&gt;setFont(font);
                painter-&gt;setPen(isSelected ? option.palette.text().color() : Qt::red);
                painter-&gt;drawText(statusPoint, <span class="hljs-string">"D"</span>);
            }
            <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (treeItem-&gt;modified())
            {
                <span class="hljs-comment">// Show "Uncommitted" status</span>
                painter-&gt;setFont(font);
                painter-&gt;setPen(isSelected ? option.palette.text().color() : Qt::magenta);
                painter-&gt;drawText(statusPoint, <span class="hljs-string">"M"</span>);
            }
            <span class="hljs-keyword">else</span>
            {
                <span class="hljs-comment">// Show "Uncommitted" status</span>
                painter-&gt;setFont(font);
                painter-&gt;setPen(isSelected ? option.palette.text().color() : Qt::green);
                painter-&gt;drawText(statusPoint, <span class="hljs-string">"U"</span>);
            }

            <span class="hljs-comment">// Show button to + or - from Changes&lt;-&gt;Staging Area</span>

            QRect buttonRect = option.rect;
            buttonRect.setX(option.rect.topRight().x() - <span class="hljs-number">60</span>);
            <span class="hljs-comment">//buttonRect.setY(option.rect.y() - 80);</span>
            buttonRect.setWidth(<span class="hljs-number">24</span>);
            buttonRect.setHeight(<span class="hljs-number">24</span>);

            <span class="hljs-comment">// Create button widget here</span>
            QStyleOptionButton buttonOption;
            buttonOption.rect = buttonRect;
            buttonOption.state = option.state;
            buttonOption.features = QStyleOptionButton::Flat;
            buttonOption.palette = option.palette;

            QStyle *style = QApplication::style();

            <span class="hljs-comment">// WORKAROUND: Hide the `border-bottom` for `buttonOption` when treeview row is selected</span>
            QBrush buttonOptionBrush;
            buttonOptionBrush.setColor(Qt::GlobalColor::transparent);
            buttonOption.palette.setBrush(QPalette::ColorRole::HighlightedText, buttonOptionBrush);

            <span class="hljs-keyword">if</span> (treeItem-&gt;initiator == RavenTreeItem::STAGING)
            {
                <span class="hljs-comment">// Show - icon</span>
                buttonOption.text=<span class="hljs-string">"-"</span>;
                style-&gt;drawControl(QStyle::CE_PushButton, &amp;buttonOption, painter);
            }
            <span class="hljs-keyword">else</span>
            {
                <span class="hljs-comment">// Show + icon</span>
                buttonOption.text=<span class="hljs-string">"+"</span>;
                style-&gt;drawControl(QStyle::CE_PushButton, &amp;buttonOption, painter);
            }
        }
    }
    <span class="hljs-comment">// Restore painter default settings</span>
    painter-&gt;restore();
}

<span class="hljs-function">QSize <span class="hljs-title">RavenTreeDelegate::sizeHint</span><span class="hljs-params">(<span class="hljs-keyword">const</span> QStyleOptionViewItem &amp;option, <span class="hljs-keyword">const</span> QModelIndex &amp;index)</span> <span class="hljs-keyword">const</span>
</span>{
    <span class="hljs-keyword">return</span> QStyledItemDelegate::sizeHint(option, index);
}
</code></pre>
<blockquote>
<p><code>painter→save()</code> and <code>painter→restore()</code> are used to render the custom widgets with a different style than other UI elements. Here, <code>save()</code> stores the default settings inherited from base class and at the end of the <code>paint</code> function, we restore the defaults using <code>painter-&gt;restore()</code>.</p>
</blockquote>
<h2 id="heading-custom-text-git-status-label">Custom Text (Git status label)</h2>
<p>We use <code>rect</code> variable of type <code>QRect</code> to determine the <code>x</code>, <code>y</code>, <code>w</code> and <code>h</code> properties - x-axis, y-axis, width and height properties of the given widget (<em>is it a widget here?</em>) being rendered.</p>
<p>So the first custom widget which is a single char - <code>U</code>, <code>D</code> or <code>M</code> being rendered according to Git status of a given file.</p>
<pre><code class="lang-cpp"><span class="hljs-comment">// Status text (shows D/M/U text at end of row)</span>
<span class="hljs-keyword">auto</span> statusPoint = rect.topRight();
statusPoint.setX(statusPoint.x() - <span class="hljs-number">20</span>);
<span class="hljs-keyword">auto</span> font = painter-&gt;font();
font.setWeight(QFont::Weight::Bold);

<span class="hljs-comment">// Applicable to non-heading items only</span>
<span class="hljs-keyword">if</span> (!treeItem-&gt;heading) {
  <span class="hljs-keyword">if</span> (treeItem-&gt;deleted) {
    <span class="hljs-comment">// Show "Deleted" status</span>
    painter-&gt;setFont(font);
    painter-&gt;setPen(isSelected ? option.palette.text().color() : Qt::red);
    painter-&gt;drawText(statusPoint, <span class="hljs-string">"D"</span>);
  } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (treeItem-&gt;modified()) {
    <span class="hljs-comment">// Show "Uncommitted" status</span>
    painter-&gt;setFont(font);
    painter-&gt;setPen(isSelected ? option.palette.text().color() : Qt::magenta);
    painter-&gt;drawText(statusPoint, <span class="hljs-string">"M"</span>);
  } <span class="hljs-keyword">else</span> {
    <span class="hljs-comment">// Show "Uncommitted" status</span>
    painter-&gt;setFont(font);
    painter-&gt;setPen(isSelected ? option.palette.text().color() : Qt::green);
    painter-&gt;drawText(statusPoint, <span class="hljs-string">"U"</span>);
  }
</code></pre>
<p>We first grab the <code>QPoint</code> of the given row inside the tree. We are picking <code>topRight</code> here because the position is <em>almost</em> correct. We expect the text to be shown to the right-side of the row and vertically centered in the final output. So <code>topRight</code> has the “right” direction set but we introduce offsets to our liking.</p>
<p>This is where the file or folder’s name and icon are shown along with the tree’s expand/collapse button and the guidelines to determine which parent it belongs to.</p>
<p>Now, using the <code>statusPoint</code>, we can modify the positioning of our custom text widget. For instance, I will move it slightly back to the left so that it won’t be stuck at the far-end in each row.</p>
<p>Next, we subtract <code>20</code> from current <code>x</code> position on the UI. This number has been chosen after testing different values (starting from 5), nothing special. It felt right to my eyes.</p>
<p>Next, I added <strong>bold style</strong> when drawing labels. This is designed to give emphasis to the label.</p>
<p>Lastly, we paint it to the view however, we change the font color for the label according to file’s status. These colors were chosen to be closer to VSCode for my convenience.</p>
<pre><code class="lang-cpp">painter-&gt;setFont(font);
painter-&gt;setPen(isSelected ? option.palette.text().color() : Qt::red);
painter-&gt;drawText(statusPoint, <span class="hljs-string">"D"</span>);
</code></pre>
<p>Here, I set the font to be used for rendering our custom text. Note that, till now, we have just dictated the properties to be set for our text but this is where the text properties are picked.</p>
<p>So, by default, we set <code>Qt::red</code>, <code>Qt::magenta</code> or <code>Qt::green</code> as the default colors for <strong>deleted, modified and uncommitted</strong> (new files) states. There is an <code>if</code> check here to show Qt default text color when a tree node is selected to improve readability.</p>
<p>Lastly, we use the <code>drawText</code> function of <code>QPainter</code> class to draw the text. <em>It’s pretty simple</em>.</p>
<h2 id="heading-custom-button-stageunstage-items">Custom Button (Stage/Unstage items)</h2>
<p>So this was a little tricky. Unlike text, we don’t have a <code>drawButton</code> or <code>drawWidget</code> class. Instead, we will need to tackle this problem differently.</p>
<pre><code class="lang-cpp">
            <span class="hljs-comment">// Show button to + or - from Changes&lt;-&gt;Staging Area</span>

            QRect buttonRect = option.rect;
            buttonRect.setX(option.rect.topRight().x() - <span class="hljs-number">60</span>);
            buttonRect.setWidth(<span class="hljs-number">24</span>);
            buttonRect.setHeight(<span class="hljs-number">24</span>);

            <span class="hljs-comment">// Create button widget here</span>
            QStyleOptionButton buttonOption;
            buttonOption.rect = buttonRect;
            buttonOption.state = option.state;
            buttonOption.features = QStyleOptionButton::Flat;
            buttonOption.palette = option.palette;

            QStyle *style = QApplication::style();

            <span class="hljs-comment">// WORKAROUND: Hide the `border-bottom` for `buttonOption` </span>
            <span class="hljs-comment">//             when treeview row is selected.</span>
            QBrush buttonOptionBrush;
            buttonOptionBrush.setColor(Qt::GlobalColor::transparent);
            buttonOption.palette.setBrush(QPalette::ColorRole::HighlightedText, buttonOptionBrush);

            <span class="hljs-keyword">if</span> (treeItem-&gt;initiator == RavenTreeItem::STAGING)
            {
                <span class="hljs-comment">// Show - icon</span>
                buttonOption.text=<span class="hljs-string">"-"</span>;
                style-&gt;drawControl(QStyle::CE_PushButton, &amp;buttonOption, painter);
            }
            <span class="hljs-keyword">else</span>
            {
                <span class="hljs-comment">// Show + icon</span>
                buttonOption.text=<span class="hljs-string">"+"</span>;
                style-&gt;drawControl(QStyle::CE_PushButton, &amp;buttonOption, painter);
            }
</code></pre>
<p>So, just like before, we use <code>buttonRect</code> to adjust the <code>x</code> offset as well as the <code>width</code> &amp; <code>height</code> of the button. In this case, my offset value is <code>60</code> which will place this button a little bit before the status text.</p>
<p>In order to draw a button, it is very efficient (in my case) to use <code>QStyleOptionButton</code>. This is a very handy class that abstracts the button state management for me but offers us a way to draw custom button-like widgets.</p>
<pre><code class="lang-cpp">QStyleOptionButton buttonOption;
buttonOption.rect = buttonRect;
buttonOption.state = option.state;
buttonOption.features = QStyleOptionButton::Flat;
buttonOption.palette = option.palette;
</code></pre>
<p>I chose the Flat type button for my needs. You can read more about this in the <a target="_blank" href="https://doc.qt.io/qt-6/qstyleoptionbutton.html#ButtonFeature-enum">docs</a>. I also set the palette of the newly created button to inherit things from the tree’s row widget (again, is it right to call this a widget?).</p>
<p>Next, I set <code>+</code> or <code>-</code> char to imply the file is about to be staged or unstaged by checking the state.</p>
<p>Lastly, we draw this button to the UI using <code>QStyle::drawControl</code> function which we can derive from <code>QApplication</code>. This is a foreign concept to me coming from GTK. Here’s the <a target="_blank" href="https://doc.qt.io/qt-6/qstyle.html#drawControl">Qt docs link</a> for the function if you’re curious.</p>
<h1 id="heading-results">Results</h1>
<p><strong>Default state:</strong></p>
<p>You can see the tree now renders both the Git status label as well as the custom button like widget for staging and unstaging items.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1755015035981/8643a9e3-24bd-494e-b08b-9e5e16eea875.png" alt="GitRaven default UI" class="image--center mx-auto" /></p>
<p><strong>Selected item:</strong></p>
<p>Look at the color of the Git status label - it is not <code>Qt::magenta</code> anymore. This color is picked by Qt internally since the item is now in selected state.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1755015042911/2d4114a7-892d-4cc0-b3f4-b35a9871f60c.png" alt="GitRaven tree item selected UI" class="image--center mx-auto" /></p>
<h1 id="heading-conclusion">Conclusion</h1>
<p>This was a very fun feature to build. I didn’t realize how little things (taken for granted as it’s universally available in most tools) gets built. I would highly encourage people to build tools and see how do you fare against the giants.</p>
<p>I hope you have learned something here. This feature took me many days to implement, test and fix. I am so happy to make steady progress on this project.</p>
<p>Next up, the “diff viewer” shown above has been replaced with Monaco Editor. I plan on making a blog post on it soon.</p>
<p>You can follow me on <a target="_blank" href="https://social.linux.pizza/@shanmukhateja/">Mastodon</a>.</p>
<p>Bye for now :)</p>
]]></content:encoded></item><item><title><![CDATA[GitRaven: How to use QTreeView with a custom model class]]></title><description><![CDATA[Hi,
This blog post will cover how to create a custom model class based on QAbstractItemModel to render custom data with QTreeView in C++.
Theory
General idea:

Create a new class RavenTreeModel based on QAbstractTreeModel.

We need to override few me...]]></description><link>https://blog.suryatejak.in/gitraven-how-to-use-qtreeview-with-a-custom-model-class</link><guid isPermaLink="true">https://blog.suryatejak.in/gitraven-how-to-use-qtreeview-with-a-custom-model-class</guid><category><![CDATA[Qt]]></category><category><![CDATA[Git]]></category><category><![CDATA[C++]]></category><category><![CDATA[TreeView]]></category><category><![CDATA[Open Source]]></category><dc:creator><![CDATA[Surya Teja Karra]]></dc:creator><pubDate>Sat, 09 Aug 2025 13:48:13 GMT</pubDate><content:encoded><![CDATA[<p>Hi,</p>
<p>This blog post will cover how to create a custom model class based on <a target="_blank" href="https://doc.qt.io/qt-6/qabstractitemmodel.html">QAbstractItemModel</a> to render custom data with <code>QTreeView</code> in C++.</p>
<h1 id="heading-theory">Theory</h1>
<h2 id="heading-general-idea">General idea:</h2>
<ol>
<li><p>Create a new class <code>RavenTreeModel</code> based on <code>QAbstractTreeModel</code>.</p>
</li>
<li><p>We need to override few methods from parent class to succeed - <code>index</code>, <code>parent</code>, <code>rowCount</code>, <code>columnCount</code> and <code>data</code>.</p>
</li>
</ol>
<ul>
<li><p><code>index</code> - It is used to return a model index for a given row, column and an optional parent model index.</p>
</li>
<li><p><code>data</code> - It is used to return the data we would like to show for the given row and column. This could be a tree item’s text, icon, tooltip text when hovered, etc.</p>
</li>
<li><p><code>parent</code> - This function returns the parent for a given model.<br />  So let's say you have: <code>A -&gt; B -&gt; C</code><br />  Here, <code>A</code> is the parent of <code>B</code> and <code>B</code> is the parent of C. So when we traverse the custom model while rendering, we determine and return the parent of each node or we return an invalid index (which Qt accepts as a valid model index).</p>
</li>
<li><p><code>rowCount</code> - We compute and return the number of children for a given model index. If the given index is invalid, we return child count of the root node.</p>
</li>
</ul>
<h2 id="heading-root-node-and-qtreeview">“Root Node” and <code>QTreeView</code>:</h2>
<p>Consider the following tree structure which we need to represent:</p>
<pre><code class="lang-plaintext">A -&gt; B -&gt; C
// A is the root node in this scenario where B and C are child nodes of A
// B is the child node of A but is also a parent of C
// C is the child node of A and is also the leaf node in this tree.
</code></pre>
<p>In order to render this structure in <code>QTreeView</code>, we need to introduce a “container” node to host all the “root” nodes from the data description above. We will track this root node in code with a pointer inside our model as a member variable.</p>
<p><strong>Why?</strong></p>
<blockquote>
<p>The <code>QTreeView</code> expects a single root item for the model, from which all other tree items are derived. This root item acts as the starting point of the entire tree structure.</p>
<p>The <code>QAbstractItemModel</code> needs a root node to represent the topmost parent of your data. Without it, there wouldn't be a clear point from which the model’s hierarchy starts, which can cause issues in handling the tree’s structure and rendering.<br />—<br />ChatGPT</p>
</blockquote>
<p>Here’s the new flow:</p>
<ol>
<li><p><code>Root -&gt; A -&gt; B -&gt; C</code></p>
</li>
<li><p><code>Root -&gt; D -&gt; E -&gt; F</code><br /> Here, <code>A</code> and <code>D</code> are the actual user-visible "root" nodes of the tree however both these nodes are child nodes of <code>Root</code>.</p>
</li>
</ol>
<h1 id="heading-ai-usage">AI Usage</h1>
<p>As explained in my <a target="_blank" href="https://blog.suryatejak.in/gitraven-my-next-toy-project#heading-ai-usage">previous post</a>, this section will explain the AI usage for this part of the project.</p>
<p>I struggled to get the TreeView to work. It took me almost 8 hours of time at night to come up with the code shown below.</p>
<p>Finally, after admitting defeat, I resorted to ChatGPT and it helped me fix the issues. The bugs were in both <code>index</code> and <code>parent</code> functions.</p>
<p><em>Here’s my early attempt based on Star Delegate example code and some custom changes.</em></p>
<pre><code class="lang-c">
<span class="hljs-comment">// NOT WORKING!!!</span>
<span class="hljs-function">QModelIndex <span class="hljs-title">RavenTreeModel::index</span><span class="hljs-params">(<span class="hljs-keyword">int</span> row, <span class="hljs-keyword">int</span> column, <span class="hljs-keyword">const</span> QModelIndex &amp;parent)</span> <span class="hljs-keyword">const</span>
</span>{
    qDebug() &lt;&lt; <span class="hljs-string">"RavenTreeModel::index called"</span>;

    <span class="hljs-keyword">if</span> (!hasIndex(row, column, parent))
        <span class="hljs-keyword">return</span> {};

    RavenTreeItem *parentItem = parent.isValid()
                               ? <span class="hljs-keyword">static_cast</span>&lt;RavenTreeItem*&gt;(parent.internalPointer())
                               : rootItem.get();

    <span class="hljs-keyword">if</span> (<span class="hljs-keyword">auto</span> *childItem = parentItem-&gt;child(row))
        <span class="hljs-keyword">return</span> createIndex(row, column, childItem);
    <span class="hljs-keyword">return</span> {};

    <span class="hljs-keyword">return</span> QModelIndex();
}

<span class="hljs-function">QModelIndex <span class="hljs-title">RavenTreeModel::parent</span><span class="hljs-params">(<span class="hljs-keyword">const</span> QModelIndex &amp;child)</span> <span class="hljs-keyword">const</span>
</span>{
    qDebug() &lt;&lt; <span class="hljs-string">"RavenTreeModel::parent called"</span>;
    <span class="hljs-comment">// auto parent = child.data(12);</span>

    <span class="hljs-keyword">const</span> QModelIndex *val = <span class="hljs-literal">nullptr</span>;
    <span class="hljs-keyword">for</span> (<span class="hljs-keyword">auto</span> item: m_items)
    {
        <span class="hljs-keyword">if</span> (&amp;item.name() == child.internalPointer())
        {
            qDebug() &lt;&lt; item. &lt;&lt; child;
            val = &amp;child;
        }
    }
    <span class="hljs-keyword">return</span> *val;
}
</code></pre>
<h1 id="heading-implementation">Implementation</h1>
<p>Here's the model's header file - I use a custom data struct <code>RavenTreeItem</code> to hold the data of each node. <code>QModelIndex</code> is the class that tracks model index in the tree.</p>
<p><strong>RavenTreeModel.h</strong></p>
<pre><code class="lang-h"><span class="hljs-meta">#<span class="hljs-meta-keyword">ifndef</span> RAVENTREEMODEL_H</span>
<span class="hljs-meta">#<span class="hljs-meta-keyword">define</span> RAVENTREEMODEL_H</span>

<span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">&lt;QAbstractItemModel&gt;</span></span>
<span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">&lt;QObject&gt;</span></span>

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">RavenTreeModel</span>:</span> <span class="hljs-keyword">public</span> QAbstractItemModel

{
    Q_OBJECT

<span class="hljs-keyword">public</span>:
    <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">RavenTreeItem</span> {</span>
        QString name;
        QString fullPath;
        QString absolutePath;
        <span class="hljs-keyword">bool</span> binary;
        QList&lt;RavenTreeItem*&gt; children;
        <span class="hljs-keyword">int</span> *flag;
    };

    <span class="hljs-function"><span class="hljs-keyword">explicit</span> <span class="hljs-title">RavenTreeModel</span><span class="hljs-params">(QObject *parent = <span class="hljs-literal">nullptr</span>)</span></span>;
    ~RavenTreeModel() <span class="hljs-keyword">override</span>;

    <span class="hljs-function">QModelIndex <span class="hljs-title">index</span><span class="hljs-params">(<span class="hljs-keyword">int</span> row, <span class="hljs-keyword">int</span> column,
                      <span class="hljs-keyword">const</span> QModelIndex &amp;parent = QModelIndex())</span> <span class="hljs-keyword">const</span> <span class="hljs-keyword">override</span></span>;
    <span class="hljs-function">QModelIndex <span class="hljs-title">parent</span><span class="hljs-params">(<span class="hljs-keyword">const</span> QModelIndex &amp;child)</span> <span class="hljs-keyword">const</span> <span class="hljs-keyword">override</span></span>;
    <span class="hljs-function"><span class="hljs-keyword">int</span> <span class="hljs-title">rowCount</span><span class="hljs-params">(<span class="hljs-keyword">const</span> QModelIndex &amp;parent = QModelIndex())</span> <span class="hljs-keyword">const</span> <span class="hljs-keyword">override</span></span>;
    <span class="hljs-function"><span class="hljs-keyword">int</span> <span class="hljs-title">columnCount</span><span class="hljs-params">(<span class="hljs-keyword">const</span> QModelIndex &amp;parent = QModelIndex())</span> <span class="hljs-keyword">const</span> <span class="hljs-keyword">override</span></span>;
    <span class="hljs-function">QVariant <span class="hljs-title">data</span><span class="hljs-params">(<span class="hljs-keyword">const</span> QModelIndex &amp;index, <span class="hljs-keyword">int</span> role = Qt::DisplayRole)</span> <span class="hljs-keyword">const</span> <span class="hljs-keyword">override</span></span>;
    <span class="hljs-function">QVariant <span class="hljs-title">headerData</span><span class="hljs-params">(<span class="hljs-keyword">int</span> section, Qt::Orientation orientation,
                        <span class="hljs-keyword">int</span> role = Qt::DisplayRole)</span> <span class="hljs-keyword">const</span> <span class="hljs-keyword">override</span></span>;

    <span class="hljs-comment">// Helper functions</span>
    <span class="hljs-function">RavenTreeItem* <span class="hljs-title">getRootNode</span><span class="hljs-params">()</span></span>;
    <span class="hljs-function"><span class="hljs-keyword">static</span> RavenTreeItem* <span class="hljs-title">createNode</span><span class="hljs-params">(<span class="hljs-keyword">const</span> QString &amp;name, <span class="hljs-keyword">const</span> QString &amp;fullPath, <span class="hljs-keyword">const</span> QString &amp;absPath, <span class="hljs-keyword">int</span> flag, <span class="hljs-keyword">bool</span> binary)</span></span>;

<span class="hljs-keyword">private</span>:
    RavenTreeItem *rootNode;  <span class="hljs-comment">// &lt;-- "Root" node that acts like container of all the tree nodes.</span>

};

<span class="hljs-meta">#<span class="hljs-meta-keyword">endif</span> <span class="hljs-comment">// RAVENTREEMODEL_H</span></span>
</code></pre>
<blockquote>
<p>I have replaced the data type for <code>flag</code> property in my code for keeping things simple for this example. Hence, this property, in it's current state, might not make sense.</p>
</blockquote>
<p>Let's see the model class' actual implementation. <em>RavenTreeModel.cpp</em></p>
<pre><code class="lang-cpp"><span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">"raventreemodel.h"</span></span>

<span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">&lt;QIcon&gt;</span></span>

RavenTreeModel::RavenTreeModel(QObject *parent)
    : QAbstractItemModel(parent),
    rootNode(createNode(<span class="hljs-string">"Root"</span>, <span class="hljs-string">""</span>, <span class="hljs-string">""</span>, <span class="hljs-number">0</span>, <span class="hljs-literal">false</span>))
{}

RavenTreeModel::~RavenTreeModel() {
    qDeleteAll(rootNode-&gt;children);
    <span class="hljs-keyword">delete</span> rootNode;
};

<span class="hljs-function">QModelIndex <span class="hljs-title">RavenTreeModel::index</span><span class="hljs-params">(<span class="hljs-keyword">int</span> row, <span class="hljs-keyword">int</span> column, <span class="hljs-keyword">const</span> QModelIndex &amp;parent)</span> <span class="hljs-keyword">const</span>
</span>{
    <span class="hljs-keyword">if</span> (!hasIndex(row, column, parent)) <span class="hljs-keyword">return</span> QModelIndex();

    RavenTreeItem *parentNode = parent.isValid() ? <span class="hljs-keyword">static_cast</span>&lt;RavenTreeItem*&gt;(parent.internalPointer()) : rootNode;
    RavenTreeItem *childNode = parentNode-&gt;children.value(row);

    <span class="hljs-comment">// Ensure that the node is valid before creating the index</span>
    <span class="hljs-keyword">if</span> (!childNode) QModelIndex();

    <span class="hljs-keyword">return</span> createIndex(row, column, childNode);
}

<span class="hljs-function">QModelIndex <span class="hljs-title">RavenTreeModel::parent</span><span class="hljs-params">(<span class="hljs-keyword">const</span> QModelIndex &amp;child)</span> <span class="hljs-keyword">const</span>
</span>{
    <span class="hljs-keyword">if</span> (!child.isValid()) <span class="hljs-keyword">return</span> QModelIndex();

    RavenTreeItem *childNode = <span class="hljs-keyword">static_cast</span>&lt;RavenTreeItem*&gt;(child.internalPointer());
    RavenTreeItem *parentNode = <span class="hljs-literal">nullptr</span>;

    <span class="hljs-comment">// If it's rootNode, return invalid QModelIndex</span>
    <span class="hljs-keyword">if</span> (childNode == rootNode) <span class="hljs-keyword">return</span> QModelIndex();

    <span class="hljs-comment">// Find the parent node from `rootNode` children</span>
    <span class="hljs-keyword">for</span> (RavenTreeItem* node : <span class="hljs-keyword">static_cast</span>&lt;<span class="hljs-keyword">const</span> QList&lt;RavenTreeItem*&gt;&gt;(rootNode-&gt;children))
    {
        <span class="hljs-keyword">if</span> (node-&gt;children.contains(childNode)) {
            parentNode = node;
            <span class="hljs-keyword">break</span>;
        }
    }

    <span class="hljs-comment">// Handle invalid case</span>
    <span class="hljs-keyword">if</span> (!parentNode) <span class="hljs-keyword">return</span> QModelIndex();

    <span class="hljs-comment">// Found parentNode, compute row and return expected output</span>
    <span class="hljs-keyword">int</span> row = parentNode-&gt;children.indexOf(childNode);
    <span class="hljs-keyword">return</span> createIndex(row, <span class="hljs-number">0</span>, parentNode);
}

<span class="hljs-function"><span class="hljs-keyword">int</span> <span class="hljs-title">RavenTreeModel::rowCount</span><span class="hljs-params">(<span class="hljs-keyword">const</span> QModelIndex &amp;parent)</span> <span class="hljs-keyword">const</span>
</span>{
    <span class="hljs-comment">// Find row for given QModelIndex (if valid) or fallback to `rootNode`</span>
    <span class="hljs-keyword">auto</span> node = parent.isValid() ? <span class="hljs-keyword">static_cast</span>&lt;RavenTreeItem*&gt;(parent.internalPointer()) : rootNode;
    <span class="hljs-keyword">return</span> node-&gt;children.count();
}

<span class="hljs-function"><span class="hljs-keyword">int</span> <span class="hljs-title">RavenTreeModel::columnCount</span><span class="hljs-params">(<span class="hljs-keyword">const</span> QModelIndex &amp;parent)</span> <span class="hljs-keyword">const</span>
</span>{
    <span class="hljs-keyword">return</span> <span class="hljs-number">1</span>;
}

<span class="hljs-function">QVariant <span class="hljs-title">RavenTreeModel::data</span><span class="hljs-params">(<span class="hljs-keyword">const</span> QModelIndex &amp;index, <span class="hljs-keyword">int</span> role)</span> <span class="hljs-keyword">const</span>
</span>{
    <span class="hljs-keyword">if</span> (!index.isValid()) <span class="hljs-keyword">return</span> QVariant();

    RavenTreeItem *node = <span class="hljs-keyword">static_cast</span>&lt;RavenTreeItem*&gt;(index.internalPointer());

    <span class="hljs-keyword">if</span> (role == Qt::DisplayRole)
    {
        <span class="hljs-keyword">return</span> node-&gt;name;
    }

    <span class="hljs-keyword">if</span> (role == Qt::DecorationRole)
    {
        <span class="hljs-keyword">auto</span> iconName = node-&gt;children.size() &gt; <span class="hljs-number">0</span> ? <span class="hljs-string">"folder-symbolic"</span> : <span class="hljs-string">"filename-title-amarok"</span>;
        <span class="hljs-keyword">auto</span> iconProvider = QIcon::fromTheme(iconName);
        <span class="hljs-keyword">return</span> iconProvider;
    }

    <span class="hljs-keyword">if</span> (role == Qt::ToolTipRole)
    {
        <span class="hljs-keyword">return</span> node-&gt;fullPath;
    }

    <span class="hljs-keyword">return</span> QVariant();
}

<span class="hljs-function">RavenTreeModel::RavenTreeItem *<span class="hljs-title">RavenTreeModel::getRootNode</span><span class="hljs-params">()</span>
</span>{
    <span class="hljs-keyword">return</span> rootNode;
}

<span class="hljs-function">RavenTreeModel::RavenTreeItem *<span class="hljs-title">RavenTreeModel::createNode</span><span class="hljs-params">(<span class="hljs-keyword">const</span> QString &amp;name, <span class="hljs-keyword">const</span> QString &amp;fullPath, <span class="hljs-keyword">const</span> QString &amp;absPath, <span class="hljs-keyword">int</span> flag, <span class="hljs-keyword">bool</span> binary)</span>
</span>{
    RavenTreeItem *node = <span class="hljs-keyword">new</span> RavenTreeItem;
    node-&gt;name = name;
    node-&gt;fullPath = fullPath;
    node-&gt;absolutePath = absPath;
    node-&gt;flag = &amp;flag;
    node-&gt;binary = binary;
    node-&gt;children.clear();  <span class="hljs-comment">// Ensure the children list is initialized</span>
    <span class="hljs-keyword">return</span> node;
}
</code></pre>
<p>Let's go over each functions:</p>
<p><strong>index():</strong></p>
<pre><code class="lang-cpp"><span class="hljs-function">QModelIndex <span class="hljs-title">RavenTreeModel::index</span><span class="hljs-params">(<span class="hljs-keyword">int</span> row, <span class="hljs-keyword">int</span> column, <span class="hljs-keyword">const</span> QModelIndex &amp;parent)</span> <span class="hljs-keyword">const</span>
</span>{
    <span class="hljs-keyword">if</span> (!hasIndex(row, column, parent)) <span class="hljs-keyword">return</span> QModelIndex();

    RavenTreeItem *parentNode = parent.isValid() ? <span class="hljs-keyword">static_cast</span>&lt;RavenTreeItem*&gt;(parent.internalPointer()) : rootNode;
    RavenTreeItem *childNode = parentNode-&gt;children.value(row);

    <span class="hljs-comment">// Ensure that the node is valid before creating the index</span>
    <span class="hljs-keyword">if</span> (!childNode) QModelIndex();

    <span class="hljs-keyword">return</span> createIndex(row, column, childNode);
}
</code></pre>
<ol>
<li><p>In this code, I check whether we have a valid model index for the given row and column. If I do not, we create an invalid model index of our own.</p>
</li>
<li><p>Next, we grab the data struct <code>RavenTreeItem</code> from the parent model index.</p>
</li>
<li><p>We have to do this check in order to deal with “parent-less nodes” like the <code>A</code> node in my example above. It doesn't have a parent node (in our representation) however <code>QTreeView</code> expects a single root node at the top-most level.</p>
</li>
<li><p>Next, we compute the child node from the struct and return a model index for this newly created item for a given row and column.</p>
</li>
<li><p>Lastly, we use <code>createIndex</code> function to create a model index for the given <code>childNode</code> struct at the given row and column and return it.</p>
</li>
</ol>
<p>Next, let's see the <code>parent()</code> function implementation:</p>
<p><strong>parent():</strong></p>
<pre><code class="lang-cpp"><span class="hljs-function">QModelIndex <span class="hljs-title">RavenTreeModel::parent</span><span class="hljs-params">(<span class="hljs-keyword">const</span> QModelIndex &amp;child)</span> <span class="hljs-keyword">const</span>
</span>{
    <span class="hljs-keyword">if</span> (!child.isValid()) <span class="hljs-keyword">return</span> QModelIndex();

    RavenTreeItem *childNode = <span class="hljs-keyword">static_cast</span>&lt;RavenTreeItem*&gt;(child.internalPointer());
    RavenTreeItem *parentNode = <span class="hljs-literal">nullptr</span>;

    <span class="hljs-comment">// If it's rootNode, return invalid QModelIndex</span>
    <span class="hljs-keyword">if</span> (childNode == rootNode) <span class="hljs-keyword">return</span> QModelIndex();

    <span class="hljs-comment">// Find the parent node from `rootNode` children</span>
    <span class="hljs-keyword">for</span> (RavenTreeItem* node : <span class="hljs-keyword">static_cast</span>&lt;<span class="hljs-keyword">const</span> QList&lt;RavenTreeItem*&gt;&gt;(rootNode-&gt;children))
    {
        <span class="hljs-keyword">if</span> (node-&gt;children.contains(childNode)) {
            parentNode = node;
            <span class="hljs-keyword">break</span>;
        }
    }

    <span class="hljs-comment">// Handle invalid case</span>
    <span class="hljs-keyword">if</span> (!parentNode) <span class="hljs-keyword">return</span> QModelIndex();

    <span class="hljs-comment">// Found parentNode, compute row and return expected output</span>
    <span class="hljs-keyword">int</span> row = parentNode-&gt;children.indexOf(childNode);
    <span class="hljs-keyword">return</span> createIndex(row, <span class="hljs-number">0</span>, parentNode);
}
</code></pre>
<ol>
<li><p>We check if the model index is valid and if not, we return an invalid model index of our own (same as above).</p>
</li>
<li><p>We grab the custom data struct of the given model index and cast it to our custom struct type (same as above).</p>
</li>
<li><p>Now comes the interesting part, if the child node we just grabbed from model index is the same as our "Root" node (container node) we return an invalid model index. This is expected because <code>rootNode</code> is the top-most node with no parent node.</p>
</li>
<li><p>Next, we iterate over all the child nodes of <code>rootNode</code> to locate the parent node for the given model index (via <code>childNode</code>) and return new model index based on this information.</p>
</li>
<li><p>If the node couldn’t be located, we return an invalid model index.</p>
</li>
</ol>
<p><strong>rowCount()</strong> <strong>&amp; columnCount()<em>:</em></strong></p>
<pre><code class="lang-cpp"><span class="hljs-function"><span class="hljs-keyword">int</span> <span class="hljs-title">RavenTreeModel::rowCount</span><span class="hljs-params">(<span class="hljs-keyword">const</span> QModelIndex &amp;parent)</span> <span class="hljs-keyword">const</span>
</span>{
    <span class="hljs-comment">// Find row for given QModelIndex (if valid) or fallback to `rootNode`</span>
    <span class="hljs-keyword">auto</span> node = parent.isValid() ? <span class="hljs-keyword">static_cast</span>&lt;RavenTreeItem*&gt;(parent.internalPointer()) : rootNode;
    <span class="hljs-keyword">return</span> node-&gt;children.count();
}

<span class="hljs-function"><span class="hljs-keyword">int</span> <span class="hljs-title">RavenTreeModel::columnCount</span><span class="hljs-params">(<span class="hljs-keyword">const</span> QModelIndex &amp;parent)</span> <span class="hljs-keyword">const</span>
</span>{
    <span class="hljs-keyword">return</span> <span class="hljs-number">1</span>;
}
</code></pre>
<ol>
<li>In the case of row count, we return the number of child nodes for a given <em>valid</em> model index. If the given model index is invalid, we return <code>rootNode</code>'s child count as fallback.</li>
</ol>
<blockquote>
<p>I am assuming this means we have just started rendering the tree and we are currently at the top-most tree node. It might also be an invalid state but I am not sure how that can happen.</p>
<p>If you have more details, I am very interested in learning more. Please comment below or tag me on social media with the details. :)</p>
</blockquote>
<ol start="2">
<li>For <code>columnCount</code>, I return <code>1</code> because my UI doesn't require any more columns.</li>
</ol>
<blockquote>
<p>Note: If you change the column count, <em>my code won’t work for your use case</em>. You might need to make additional tweaks. See <a target="_blank" href="https://doc.qt.io/qt-6/qtwidgets-itemviews-stardelegate-example.html">Star Delegate example</a> implementation for more details.</p>
</blockquote>
<p><strong>data():</strong></p>
<pre><code class="lang-cpp"><span class="hljs-function">QVariant <span class="hljs-title">RavenTreeModel::data</span><span class="hljs-params">(<span class="hljs-keyword">const</span> QModelIndex &amp;index, <span class="hljs-keyword">int</span> role)</span> <span class="hljs-keyword">const</span>
</span>{
    <span class="hljs-keyword">if</span> (!index.isValid()) <span class="hljs-keyword">return</span> QVariant();

    RavenTreeItem *node = <span class="hljs-keyword">static_cast</span>&lt;RavenTreeItem*&gt;(index.internalPointer());

    <span class="hljs-keyword">if</span> (role == Qt::DisplayRole)
    {
        <span class="hljs-keyword">return</span> node-&gt;name;
    }

    <span class="hljs-keyword">if</span> (role == Qt::DecorationRole)
    {
        <span class="hljs-keyword">auto</span> iconName = node-&gt;children.size() &gt; <span class="hljs-number">0</span> ? <span class="hljs-string">"folder-symbolic"</span> : <span class="hljs-string">"filename-title-amarok"</span>;
        <span class="hljs-keyword">auto</span> iconProvider = QIcon::fromTheme(iconName);
        <span class="hljs-keyword">return</span> iconProvider;
    }

    <span class="hljs-keyword">if</span> (role == Qt::ToolTipRole)
    {
        <span class="hljs-keyword">return</span> node-&gt;fullPath;
    }

    <span class="hljs-comment">// Default value??</span>
    <span class="hljs-keyword">return</span> QVariant();
}
</code></pre>
<ol>
<li><p>We check if the model index is valid or return an invalid index of our own.</p>
</li>
<li><p>Grab the data from model index and cast it to our custom data struct.</p>
</li>
<li><p>Next, we need to know what <em>kind</em> of data is being rendered at the moment. These are described by <code>roles</code> where each role has it's own part to play. You can read more about <a target="_blank" href="https://doc.qt.io/qt-6/qt.html#ItemDataRole-enum">Roles in Qt here</a>.</p>
<p> I’m interested in only 3 roles:<br /> - <code>DisplayRole</code> for displaying text on each tree node<br /> - <code>DecorationRole</code> for showing a folder or file icon on each tree node<br /> - <code>ToolTipRole</code> for showing tooltip text when user hovers on a tree node</p>
</li>
</ol>
<p><strong>createNode():</strong></p>
<p>Lastly, here's <code>createNode</code> function which generates new custom nodes inside my model.</p>
<pre><code class="lang-cpp"><span class="hljs-function">RavenTreeModel::RavenTreeItem *<span class="hljs-title">RavenTreeModel::createNode</span><span class="hljs-params">(<span class="hljs-keyword">const</span> QString &amp;name, <span class="hljs-keyword">const</span> QString &amp;fullPath, <span class="hljs-keyword">const</span> QString &amp;absPath, <span class="hljs-keyword">int</span> flag, <span class="hljs-keyword">bool</span> binary)</span>
</span>{
    RavenTreeItem *node = <span class="hljs-keyword">new</span> RavenTreeItem;
    node-&gt;name = name;
    node-&gt;fullPath = fullPath;
    node-&gt;absolutePath = absPath;
    node-&gt;flag = &amp;flag;
    node-&gt;binary = binary;
    node-&gt;children.clear();  <span class="hljs-comment">// Ensure the children list is initialized</span>
    <span class="hljs-keyword">return</span> node;
}
</code></pre>
<h1 id="heading-results">Results</h1>
<p>Here’s a screenshot of the final result. I realize this isn’t much in terms of looks but it serves as a very good starting point for building something complex.</p>
<p>You will see how I customized this UI in future posts. :)</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1754742737739/89488abd-ee88-4769-8dbf-aa1846337d3c.png" alt="Custom QTreeView with a file and folder being rendered along with icon" class="image--center mx-auto" /></p>
<h1 id="heading-conclusion">Conclusion</h1>
<p>I hope you learned something new in this post. This is my first C++ project so there may be better way(s) to handle certain scenarios.</p>
<p>Please leave a comment below on your thoughts. You can also <code>@</code> me on Mastodon <a target="_blank" href="https://social.linux.pizza/@shanmukhateja/"><strong>here</strong></a>.</p>
<p>This project isn’t currently hosted anywhere yet. The code shown here is a few weeks old as of writing however it is good enough to be a starting point.</p>
<p>I don’t even use Git to manage the code. I have a bunch of <code>_final_final</code> files to sort. <em>I can see the irony here</em> but I have yet to decide on project’s license. More details in <a target="_blank" href="https://blog.suryatejak.in/gitraven-my-next-toy-project#heading-need-to-decide-on-approach">my previous post</a>.</p>
<p>Bye for now :)</p>
]]></content:encoded></item><item><title><![CDATA[GitRaven: My next toy project]]></title><description><![CDATA[Hi
It’s been a while since I built something for fun. I will be covering my journey on GitRaven. It’s a VCS GUI similar to GitHub Desktop or SourceTree without the code reviews, PR merge or other advanced capabilities.
The idea is to try to build thi...]]></description><link>https://blog.suryatejak.in/gitraven-my-next-toy-project</link><guid isPermaLink="true">https://blog.suryatejak.in/gitraven-my-next-toy-project</guid><category><![CDATA[Git]]></category><category><![CDATA[software development]]></category><category><![CDATA[C++]]></category><category><![CDATA[Qt]]></category><category><![CDATA[projects]]></category><dc:creator><![CDATA[Surya Teja Karra]]></dc:creator><pubDate>Fri, 01 Aug 2025 16:00:31 GMT</pubDate><content:encoded><![CDATA[<p>Hi</p>
<p>It’s been a while since I built something for fun. I will be covering my journey on GitRaven. It’s a VCS GUI similar to GitHub Desktop or SourceTree without the code reviews, PR merge or other advanced capabilities.</p>
<p>The idea is to try to build this project in a new language with little experience and take my readers along for the ride. This will be my interesting project of 2025 and beyond.</p>
<h1 id="heading-background">Background</h1>
<p>GitRaven will serve as MY replacement for VS Code’s Version Control feature. I use it on all projects even when using a different IDE. It is easy to work with (to me at least) and helps me visualize the <code>git status</code> output.</p>
<p>The project aims to help me maintain my projects wrt source code management. See my goals below pushing the changes to the remote Git server. This means no PRs, MRs, ticket management, etc. It’s a simple diff viewer + editor and general Git repo management tool.</p>
<p>The project is being built on C++ with Qt Widgets. This will be my first huge project on C++ however I have <a target="_blank" href="https://hashnode.com/post/cm8k2y60n000109jogb3khvk4">written C++ before</a>.</p>
<h1 id="heading-goals">Goals</h1>
<h2 id="heading-primary">Primary</h2>
<ol>
<li><p>Serve as my VS Code’s replacement tool for managing Git repos.</p>
</li>
<li><p>Cleaner design, simple to use. Always be “straight to the point”.</p>
</li>
<li><p>Use <code>libgit2</code> for dealing with Git stuff instead of relying on Git binary (unlike VS Code).</p>
</li>
<li><p>Use <code>KTextEditor</code> for text viewing and editing purposes. I have been wanting to try it ever since I used <code>GtkSourceView</code> on my other project.</p>
</li>
</ol>
<h2 id="heading-bonus">Bonus</h2>
<ol>
<li><p>Learn Git internals</p>
</li>
<li><p>Learn C++</p>
</li>
<li><p>Fewer progress bars, loading screens and “Please wait” messages when using the app.</p>
</li>
</ol>
<h1 id="heading-tech-stack">Tech Stack</h1>
<h2 id="heading-tldr">TL;DR</h2>
<ol>
<li><p><strong>C++</strong></p>
</li>
<li><p><strong>Qt</strong></p>
</li>
<li><p>KTextEditor</p>
</li>
<li><p>libgit2</p>
</li>
</ol>
<p>I originally intended to use Rust just like my <a target="_blank" href="https://blog.suryatejak.in/series/mystudio-ide">other toy project</a> and I did build a prototype with it. I went so far as fetching Git branch details from a given local directory using <code>libgit2</code> but I later realized it didn’t support <code>KTextEditor</code>.</p>
<p>I was leaning towards C++ by then as I have been itching to try it ever since I got a confidence boost when I created a my own KDE Dolphin plugin. You can read about it <a target="_blank" href="https://hashnode.com/post/cm8k2y60n000109jogb3khvk4">here</a>.</p>
<p>I did try using Qt with Rust with <code>cxx-qt</code> but I couldn’t figure out how to use a struct as a child property inside a parent struct. I opened a ticket on the Discussions section of <code>cxx-qt</code> <a target="_blank" href="https://github.com/KDAB/cxx-qt/discussions/1313">here</a>.</p>
<p>Meanwhile, I wanted to try C++ approach for testing purposes and so, I put my Rust+Qt prototype on hold and fired up Qt Creator to experiment with C++ with Qt. I was trying to replicate the prototype functionality in C++.</p>
<p>I managed to make good progress on it and go beyond the details. I was already having fun figuring out C++ and Qt nuances while working on the prototype like using a QSplitter for QTreeView and diff view, add mock data to render the tree items just like the prototype, etc.</p>
<p>Finally, I decided to pull the plug on Rust implementation and focus on the C++ app.</p>
<h1 id="heading-current-progress">Current Progress</h1>
<p>Here’s how the app looks like as of writing:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1753978549306/7f014cda-0b9a-40e4-8a3f-8c499b040482.png" alt="GitRaven Qt + C++ prototype" class="image--center mx-auto" /></p>
<p>I managed to write a custom delegate class for rendering the Git status text (<em>the “D” and “U” text on the right</em>)</p>
<p>You can see a basic diff view of the selected file using <code>KTextEditor</code> . It is still long ways before I can call it a “diff” without the quotes but its a start.</p>
<h1 id="heading-ai-usage">AI Usage</h1>
<p>I used ChatGPT and Gemini on this project to solve certain challenging problems after many hours of researching didn’t yield results. I have searched including but not limited to Qt forum, StackOverflow, dev blog posts, etc.</p>
<p>I personally am not a fan of AI tools. There is a potential use case in the future but right now it’s imitation imitating reality at the worst case and I do not like it. Hence, they are my last resort.</p>
<p>When I am stuck with an issue, I ask my query and look for some context. If it presents an answer which worked, I went back to compare my code with it’s proposed changes to figure that part.</p>
<p>It was a wild run and I am happy with my choices so far. I will list down these adventures in my future posts.</p>
<h1 id="heading-next-steps">Next Steps</h1>
<h2 id="heading-diff-viewer-amp-editor">Diff Viewer &amp; Editor</h2>
<h3 id="heading-need-to-decide-on-approach">Need to decide on approach</h3>
<ol>
<li><p>Build a diff viewer on top of <code>KTextEditor</code></p>
</li>
<li><p>Replace <code>KTextEditor</code> with <a target="_blank" href="https://doc.qt.io/qt-6/qtwebengine-index.html">QtWebEngine</a> + <a target="_blank" href="https://microsoft.github.io/monaco-editor/">monaco-editor</a>. Monaco provides an out-of-the-box diff viewer component which can be seen inside VS Code and it’s forks.</p>
<blockquote>
<p>Want to learn more about Monaco editor? I wrote about it some time ago <a target="_blank" href="https://blog.suryatejak.in/do-you-know-about-the-library-that-powers-vscode">here</a>.</p>
</blockquote>
</li>
</ol>
<p>I am personally not a fan of (2). It embeds a web browser into the project just for diff viewer component. I would much rather stick with (1) and figure out how to build a diff viewer out of <code>KTextEditor</code>. This will be a technical challenge due to my beginner C++ skill level.</p>
<p>Also, I am aware of <a target="_blank" href="https://apps.kde.org/kompare/">Kompare</a> app by KDE. It uses its own diff viewer component similar to my (1) approach. Kompare is a GPL project and I intend to license GitRaven as MIT just like all my other projects. This means, I need to <em>carefully</em> figure out how to integrate GPL code into an MIT project.</p>
<p>I was thinking of moving the diff viewer code into a GPL licensed <code>libravendiff</code> static library. I can dynamically link it to GitRaven’s executable.<br />This way, I need to make changes to the library while respecting both licenses. Is this feasible?</p>
<p>Lastly, what approach would you pick?</p>
<h2 id="heading-git-features">Git Features</h2>
<ol>
<li><p><code>git fetch</code></p>
</li>
<li><p>List all branches and checkout to different branch</p>
</li>
<li><p>Pull/Push changes</p>
</li>
<li><p>Generate or apply Patch files for selected files.</p>
</li>
<li><p>Stash</p>
</li>
<li><p>Rebase &amp; Merge support</p>
</li>
</ol>
<h1 id="heading-conclusion">Conclusion</h1>
<p>Thank you for reading my post. I plan on updating the blog when I’ve made some progress on the project.</p>
<p>You can @ me on <a target="_blank" href="https://social.linux.pizza/@shanmukhateja/">Mastodon</a> or leave a comment below if I missed something.</p>
<p>Bye for now :-)</p>
]]></content:encoded></item><item><title><![CDATA[How to use Ctrl+Shift+F keybinding in Neovim?]]></title><description><![CDATA[Hello,
This post is a reference on how to generate `Ctrl+Shift+<key>` keybindings for Neovim. As you know, Vim (apparently) doesn’t support this since some terminal emulators do not pass key combinations.
Theory

What we’ll be doing instead is asking...]]></description><link>https://blog.suryatejak.in/how-to-use-ctrlshiftf-keybinding-in-neovim</link><guid isPermaLink="true">https://blog.suryatejak.in/how-to-use-ctrlshiftf-keybinding-in-neovim</guid><category><![CDATA[neovim]]></category><category><![CDATA[alacritty]]></category><category><![CDATA[vim keybindings]]></category><category><![CDATA[Linux]]></category><dc:creator><![CDATA[Surya Teja Karra]]></dc:creator><pubDate>Thu, 10 Apr 2025 16:12:42 GMT</pubDate><content:encoded><![CDATA[<p>Hello,</p>
<p>This post is a reference on how to generate `Ctrl+Shift+&lt;key&gt;` keybindings for Neovim. As you know, Vim (apparently) doesn’t support this since some terminal emulators do not pass key combinations.</p>
<h1 id="heading-theory">Theory</h1>
<ol>
<li><p>What we’ll be doing instead is asking the terminal emulator (in my case Alacritty) to send a different character (<code>char</code>) when `Ctrl+Shift+&lt;key&gt;` is pressed.</p>
</li>
<li><p>So, when the key combination is pressed, a different character will be sent to Neovim.</p>
</li>
<li><p>At Neovim end, we will bind the required Neovim action to this character instead of <code>C&lt;-S-f&gt;</code> or something.</p>
</li>
</ol>
<p>For my usage, I will be triggering <code>Snacks.picker.grep()</code> whenever I press <code>Ctrl+Shift+F</code> on my keyboard.  </p>
<p>I use this particular keyboard shortcut very often in VSCode and so trying to retain the pattern in <code>nvim</code> (for muscle memory’s sake) if possible :)</p>
<h1 id="heading-implementation">Implementation</h1>
<blockquote>
<p>The steps are intended for Alacritty terminal emulator. If you’re using something else, please note, the instructions might be slightly different but you’ll be achieving the same goal here.</p>
</blockquote>
<ol>
<li><p>Open Alacritty config at <code>$USER/.config/alacritty/alacritty.toml</code></p>
</li>
<li><p>Add the following code snippet below:</p>
</li>
</ol>
<pre><code class="lang-ini"><span class="hljs-comment"># Global search in nvim</span>
<span class="hljs-comment"># I am using the `https://www.compart.com/en/unicode/U+058D` character as the replacement.</span>
<span class="hljs-section">[keyboard]</span>
<span class="hljs-attr">bindings</span> = [
  { key = <span class="hljs-string">"F"</span>, mods = <span class="hljs-string">"Control|Shift"</span>, chars = <span class="hljs-string">"֍"</span> },
]
</code></pre>
<ol start="3">
<li>Now, let’s go to nvim config file at <code>$HOME/.config/nvim/lua/config/keymaps.lua</code></li>
</ol>
<pre><code class="lang-plaintext">-- Global search
map("n", "֍", function()  Snacks.picker.grep() end, {desc="Global search", remap = true })
</code></pre>
<ol start="4">
<li>Save all changes and test. You should see the “Grep” window open inside nvim when you press <code>Ctrl+Shift+F</code></li>
</ol>
<h1 id="heading-conclusion">Conclusion</h1>
<p>This post was quickly put together for reference purposes. Sorry if it didn’t include your specific terminal emulator.</p>
<p>Thanks for reading!</p>
<p>Bye for now :)</p>
]]></content:encoded></item><item><title><![CDATA[KDE Dolphin Plugin for viewing Windows PE version info]]></title><description><![CDATA[Hello
In this blog post, I will be sharing how to create a plugin for KDE Dolphin to view “File version” info off a Windows EXE & DLL file.
Background
This project was born out of necessity to install mods for Skyrim on Linux. I needed to know the ve...]]></description><link>https://blog.suryatejak.in/kde-dolphin-plugin-for-viewing-windows-pe-version-info</link><guid isPermaLink="true">https://blog.suryatejak.in/kde-dolphin-plugin-for-viewing-windows-pe-version-info</guid><category><![CDATA[Linux]]></category><category><![CDATA[kde]]></category><category><![CDATA[C++]]></category><category><![CDATA[headers]]></category><category><![CDATA[advanced linux]]></category><dc:creator><![CDATA[Surya Teja Karra]]></dc:creator><pubDate>Sat, 22 Mar 2025 10:42:25 GMT</pubDate><content:encoded><![CDATA[<p>Hello</p>
<p>In this blog post, I will be sharing how to create a plugin for KDE Dolphin to view “File version” info off a Windows EXE &amp; DLL file.</p>
<h1 id="heading-background">Background</h1>
<p>This project was born out of necessity to install mods for Skyrim on Linux. I needed to know the version number of Skyrim’s binary to proceed and it wasn’t readily available on Dolphin.</p>
<p>I found out about the excellent <a target="_blank" href="https://pypi.org/project/pefile/">https://pypi.org/project/pefile/</a> Python library which helped me but I wanted to <em>embed</em> this functionality into Dolphin (if possible).</p>
<p>Later found out we can create custom plugins for Dolphin and the research began. As of writing, there is surprisingly very little information about building plugins for KDE Dolphin (not to be confused with “Services” which is available <a target="_blank" href="https://develop.kde.org/docs/apps/dolphin/service-menus/">here</a>).</p>
<h1 id="heading-ai-notice">AI Notice</h1>
<p>The use of AI was necessary for this particular project. Read on to find my reasoning.</p>
<p>Firstly, I am not a C++ developer and this was my first C++ project. I prefer Python or Rust to build stuff but KDE Dolphin plugins <strong>must</strong> be built out of C++ as far as I understand (correct me if I am wrong here).</p>
<p>I’ve used ChatGPT on this project for 3 things:</p>
<p>1. <strong>Setup the development environment with CMake</strong><br />This meant learning about <code>CMakeLists.txt</code> to detect required KDE &amp; Qt dependencies. <code>KDevelop</code> was chosen as the IDE for this project.  </p>
<p>2. <strong>KDE Dolphin plugin boilerplate code</strong><br />Got to know about <code>KPropertiesDialogPlugin</code> and saw boilerplate code which was meant for Qt5 but it was a good lead. I later found KDE API reference page which gave me confidence to continue the project.  </p>
<p>3. <strong>C++ Programming Language</strong>:<br />As a beginner in C++ with a big task of solve, I used ChatGPT to deal with low-level stuff like converting data into different type (<code>std::string</code> , <code>QString</code>, <code>const char *</code>), typecasting pointers, the use of <code>#pragma once</code>, <code>unique_ptr</code>, etc.</p>
<p>Lastly, the code on the project is largely based on my research including checking out “Version Control” plugin for KDE Dolphin (<a target="_blank" href="https://archlinux.org/packages/extra/x86_64/dolphin-plugins/">dolphin-plugins</a>), API reference pages for KDE &amp; Qt, Qt forums, <a target="_blank" href="https://grep.app">grep.app</a> to check implementation of a class and StackOverflow.</p>
<h1 id="heading-requirements">Requirements</h1>
<ol>
<li><p>Qt 6.8</p>
</li>
<li><p>KDE Dolphin 24.12.3 (might work on older versions)</p>
</li>
<li><p>pe-parse library from <a target="_blank" href="https://github.com/trailofbits/pe-parse">https://github.com/trailofbits/pe-parse</a></p>
</li>
<li><p>KDevelop (or any other IDE of your choice)</p>
</li>
</ol>
<blockquote>
<p>Refer to CMakeLists.txt listed below for the exact requirements.</p>
</blockquote>
<h1 id="heading-theory">Theory</h1>
<p>The idea is create a custom class which extends <code>KPropertiesDialogPlugin</code> that parses PE header of the Windows executables or DLL file (selected by the user) and dynamically places a “Version: “ <code>QLabel</code> in the “General” tab of Properties dialog.</p>
<p>We build a shared library out of this custom class which will be installed at <code>/usr/lib/qt6/plugins/kf6/propertiesdialog/</code> .<br />We will also create a manifest file so that KDE Dolphin can dynamically load the library when we right-click a file/folder.</p>
<h1 id="heading-implementation">Implementation</h1>
<blockquote>
<p>You can find the Git repo with latest changes <a target="_blank" href="https://github.com/shanmukhateja/kde-dolphin-peparse">here</a>.</p>
</blockquote>
<p>Here’s the project structure for reference:</p>
<pre><code class="lang-bash">helloworld
├── CMakeLists.txt                    <span class="hljs-comment"># CMake</span>
├── hello-dolphin-plugin.json         <span class="hljs-comment"># Manifest file</span>
├── helloworld.kdev4                  <span class="hljs-comment"># KDevelop related</span>
└── src
    ├── hello-dolphin-plugin.cpp      <span class="hljs-comment"># Plugin source</span>
    ├── hello-dolphin-plugin.h
    ├── pe-parser-wrapper.cpp         <span class="hljs-comment"># Deals with PE files</span>
    └── pe-parser-wrapper.h
</code></pre>
<p>Let’s setup CMake for the project. It will help us manage all dependencies.</p>
<pre><code class="lang-makefile"><span class="hljs-comment"># CMakeLists.txt</span>

<span class="hljs-comment"># Set minimum CMake version</span>
cmake_minimum_required(VERSION 3.16)
<span class="hljs-comment"># Set project name</span>
project(hello-dolphin-plugin)

set(QT_MIN_VERSION <span class="hljs-string">"6.8.2"</span>)
set(KF_MIN_VERSION <span class="hljs-string">"6.11.0"</span>)

<span class="hljs-comment"># Ensure the build directory is set early in the configuration</span>
set(CMAKE_BINARY_DIR <span class="hljs-string">"${CMAKE_SOURCE_DIR}/build"</span> CACHE PATH <span class="hljs-string">"Build directory"</span> FORCE)

<span class="hljs-comment"># ECM setup</span>
find_package(ECM ${KF_MIN_VERSION} CONFIG REQUIRED)
set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH} ${CMAKE_SOURCE_DIR}/cmake)
<span class="hljs-keyword">include</span>(QtVersionOption)
<span class="hljs-keyword">include</span>(ECMSetupVersion)
<span class="hljs-keyword">include</span>(KDEInstallDirs)
<span class="hljs-keyword">include</span>(KDECMakeSettings)
<span class="hljs-keyword">include</span>(KDECompilerSettings NO_POLICY_SCOPE)
<span class="hljs-keyword">include</span>(ECMDeprecationSettings)
<span class="hljs-keyword">include</span>(ECMOptionalAddSubdirectory)

find_package(pe-parse REQUIRED)

find_package(Qt6 ${QT_MIN_VERSION} REQUIRED COMPONENTS
    Core
    Core5Compat
    Widgets
    Network
    DBus
)
find_package(KF6 ${KF_MIN_VERSION} REQUIRED COMPONENTS
    XmlGui
    I18n
    KIO
    TextWidgets
    Config
    CoreAddons
    WidgetsAddons
    Solid
)

<span class="hljs-comment"># Add the shared library target</span>
add_library(
    hello-dolphin-plugin
    SHARED
    src/hello-dolphin-plugin.h
    src/hello-dolphin-plugin.cpp
    src/pe-parser-wrapper.h
    src/pe-parser-wrapper.cpp
)

<span class="hljs-comment"># Set properties for the shared library</span>
set_target_properties(hello-dolphin-plugin PROPERTIES
    VERSION <span class="hljs-string">"${RELEASE_SERVICE_VERSION}"</span>
    SOVERSION <span class="hljs-string">"1"</span>
)

target_link_libraries(hello-dolphin-plugin KF6::CoreAddons KF6::XmlGui)
target_link_libraries(hello-dolphin-plugin KF6::KIOCore KF6::KIOFileWidgets KF6::KIOWidgets)
target_link_libraries(hello-dolphin-plugin pe-parse::pe-parse)

<span class="hljs-comment"># Include directories for the project</span>
include_directories(
    ${CMAKE_CURRENT_SOURCE_DIR}
    ${Qt6Widgets_INCLUDE_DIRS}
    ${KF6_INCLUDE_DIRS}
)

<span class="hljs-comment"># Ensure that all output directories are set explicitly to the build directory</span>
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})

<span class="hljs-comment"># Set the destination for installation if needed</span>
install(TARGETS hello-dolphin-plugin DESTINATION ${KDE_INSTALL_PLUGINDIR}/kf6/propertiesdialog/)
install(FILES hello-dolphin-plugin.json DESTINATION  ${KDE_INSTALL_PLUGINDIR}/kf6/propertiesdialog/)
</code></pre>
<p>Let’s take a look at <code>PEWrapper</code> class. It’s main job is to parse an <code>.exe</code> or <code>.dll</code> file and extract it’s version number.</p>
<p><strong>src/pe-parser-wrapper.h:</strong></p>
<pre><code class="lang-cpp"><span class="hljs-meta">#<span class="hljs-meta-keyword">pragma</span> once</span>

<span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">&lt;pe-parse/parse.h&gt;</span></span>
<span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">&lt;pe-parse/nt-headers.h&gt;</span></span>

<span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">&lt;memory&gt;</span></span>

<span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">&lt;QString&gt;</span></span>

<span class="hljs-meta">#<span class="hljs-meta-keyword">define</span> MAX_MSG 81</span>

<span class="hljs-keyword">using</span> <span class="hljs-keyword">namespace</span> <span class="hljs-built_in">std</span>;

<span class="hljs-keyword">using</span> ParsedPeRef =
<span class="hljs-built_in">unique_ptr</span>&lt;peparse::parsed_pe, <span class="hljs-keyword">void</span> (*)(peparse::parsed_pe *)&gt;;

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">PEParserWrapper</span> {</span>

<span class="hljs-keyword">public</span>:
  <span class="hljs-comment">// This struct wasn't part of peparse library.</span>
  <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">VS_FIXEDFILEINFO</span> {</span>
    <span class="hljs-keyword">uint32_t</span> dwSignature;
    <span class="hljs-keyword">uint32_t</span> dwStrucVersion;
    <span class="hljs-keyword">uint32_t</span> dwFileVersionMS;
    <span class="hljs-keyword">uint32_t</span> dwFileVersionLS;
    <span class="hljs-keyword">uint32_t</span> dwProductVersionMS;
    <span class="hljs-keyword">uint32_t</span> dwProductVersionLS;
    <span class="hljs-keyword">uint32_t</span> dwFileFlagsMask;
    <span class="hljs-keyword">uint32_t</span> dwFileFlags;
    <span class="hljs-keyword">uint32_t</span> dwFileOS;
    <span class="hljs-keyword">uint32_t</span> dwFileType;
    <span class="hljs-keyword">uint32_t</span> dwFileSubtype;
    <span class="hljs-keyword">uint32_t</span> dwFileDateMS;
    <span class="hljs-keyword">uint32_t</span> dwFileDateLS;
  };

  PEParserWrapper();
  <span class="hljs-keyword">virtual</span> ~PEParserWrapper();
  <span class="hljs-function"><span class="hljs-built_in">string</span> <span class="hljs-title">parseFile</span><span class="hljs-params">(QString filePath)</span></span>;
};
</code></pre>
<p><strong>src/pe-parser-wrapper.cpp:</strong></p>
<pre><code class="lang-cpp"><span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">&lt;pe-parse/parse.h&gt;</span></span>

<span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">&lt;QString&gt;</span></span>
<span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">&lt;QDebug&gt;</span></span>
<span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">&lt;qlogging.h&gt;</span></span>

<span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">"pe-parser-wrapper.h"</span></span>

<span class="hljs-keyword">using</span> <span class="hljs-keyword">namespace</span> <span class="hljs-built_in">std</span>;

PEParserWrapper::PEParserWrapper()
{
}


PEParserWrapper::~PEParserWrapper()
{
}

<span class="hljs-comment">// This code is based off readpe (previously pev) project.</span>
<span class="hljs-comment">// Source: https://github.com/mentebinaria/readpe/blob/master/src/peres.c#L551</span>
<span class="hljs-function"><span class="hljs-keyword">int</span> <span class="hljs-title">resource_callback</span><span class="hljs-params">(<span class="hljs-keyword">void</span> *cbd, <span class="hljs-keyword">const</span> peparse::resource &amp;resource)</span>
</span>{
    <span class="hljs-keyword">if</span> (resource.type == peparse::RT_VERSION) {

        <span class="hljs-comment">// PE offset</span>
        <span class="hljs-keyword">uint8_t</span> pe_offset = <span class="hljs-number">32</span>;

        <span class="hljs-comment">// This offset allows us to skip to the required VS_FIXEDFILEINFO data</span>
        <span class="hljs-comment">// which can now be casted to struct.</span>
        <span class="hljs-comment">// If you have more info about this, please let me know. </span>
        <span class="hljs-keyword">uint8_t</span> my_offset = <span class="hljs-number">8</span>;

        <span class="hljs-comment">// <span class="hljs-doctag">NOTE:</span> KDevelop complains with `-Wclass-align` here.</span>
        <span class="hljs-comment">// I don't know what that is and Google hasn't been helpful.</span>
        <span class="hljs-comment">// If you know what this is and how to fix it, please let me know :)</span>
        <span class="hljs-keyword">auto</span> verInfo = (<span class="hljs-keyword">const</span> PEParserWrapper::VS_FIXEDFILEINFO *) (resource.buf-&gt;buf + pe_offset + my_offset);

        <span class="hljs-comment">// Additional check to make sure we're on the right track.</span>
        <span class="hljs-keyword">if</span> (verInfo-&gt;dwSignature == <span class="hljs-number">0xfeef04bd</span>)
        {
            <span class="hljs-keyword">char</span> value[MAX_MSG] = {<span class="hljs-number">0</span>};

            <span class="hljs-built_in">sprintf</span>(value, <span class="hljs-string">"%u.%u.%u.%u"</span>,
                    (<span class="hljs-keyword">uint32_t</span>)(verInfo-&gt;dwFileVersionMS &amp; <span class="hljs-number">0xffff0000</span>) &gt;&gt; <span class="hljs-number">16</span>,
                    (<span class="hljs-keyword">uint32_t</span>)verInfo-&gt;dwFileVersionMS &amp; <span class="hljs-number">0x0000ffff</span>,
                    (<span class="hljs-keyword">uint32_t</span>)(verInfo-&gt;dwFileVersionLS &amp; <span class="hljs-number">0xffff0000</span>) &gt;&gt; <span class="hljs-number">16</span>,
                    (<span class="hljs-keyword">uint32_t</span>)verInfo-&gt;dwFileVersionLS &amp; <span class="hljs-number">0x0000ffff</span>);

            <span class="hljs-comment">// This is used to pass the data back to calling function</span>
            <span class="hljs-built_in">vector</span>&lt;<span class="hljs-keyword">char</span>&gt;&amp; vecPointer = *(<span class="hljs-built_in">vector</span>&lt;<span class="hljs-keyword">char</span>&gt; *)cbd;

            <span class="hljs-comment">// This is used to store contents of `value`</span>
            <span class="hljs-built_in">vector</span>&lt;<span class="hljs-keyword">char</span>&gt; v2 = {};

            <span class="hljs-comment">// We insert at the end so that it will return</span>
            <span class="hljs-comment">// in correct (expected) order when the data is passed back</span>
            <span class="hljs-keyword">for</span>(<span class="hljs-keyword">char</span> c: value)
            {
                <span class="hljs-comment">// <span class="hljs-doctag">FIXME:</span> Need to ensure only valid ASCII characters are inserted here.</span>
                <span class="hljs-comment">//        Right now, we are allowing `\x00' chars (NULL bytes?).</span>
                v2.insert(v2.end(), c);
            }

            <span class="hljs-comment">// Update reference here so vecPointer points to v2;</span>
            vecPointer = v2;

            <span class="hljs-keyword">return</span> <span class="hljs-number">1</span>;
        }
    }

    <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;
}


<span class="hljs-function"><span class="hljs-built_in">string</span> <span class="hljs-title">PEParserWrapper::parseFile</span><span class="hljs-params">(QString filePath)</span>
</span>{
    <span class="hljs-comment">// The factory function does not throw exceptions!</span>
    <span class="hljs-function">ParsedPeRef <span class="hljs-title">ref</span><span class="hljs-params">(peparse::ParsePEFromFile(filePath.toUtf8().data()),
                    peparse::DestructParsedPE)</span></span>;
    <span class="hljs-keyword">if</span> (!ref) {
        qWarning() &lt;&lt; <span class="hljs-string">"Failed to parse file "</span> &lt;&lt; filePath;
        <span class="hljs-keyword">return</span> <span class="hljs-string">""</span>;
    }

    <span class="hljs-built_in">vector</span>&lt;<span class="hljs-keyword">char</span>&gt; result;

    peparse::IterRsrc(ref.get(), resource_callback, &amp;result);

    <span class="hljs-keyword">if</span> (result.size() == <span class="hljs-number">0</span>)
    {
        <span class="hljs-keyword">return</span> <span class="hljs-string">""</span>;
    }

    <span class="hljs-keyword">return</span> <span class="hljs-built_in">string</span>(result.begin(), result.end());
}
</code></pre>
<p>Now, let’s check out the actual KDE Dolphin plugin code.</p>
<p><strong>src/hello-dolphin-plugin.h:</strong></p>
<pre><code class="lang-cpp"><span class="hljs-meta">#<span class="hljs-meta-keyword">pragma</span> once</span>

<span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">&lt;QObject&gt;</span></span>
<span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">&lt;QString&gt;</span></span>

<span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">&lt;KFileItem&gt;</span></span>
<span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">&lt;KPropertiesDialogPlugin&gt;</span></span>

<span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">"pe-parser-wrapper.h"</span></span>

<span class="hljs-keyword">using</span> <span class="hljs-keyword">namespace</span> <span class="hljs-built_in">std</span>;

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">HelloDolphinPlugin</span> :</span> <span class="hljs-keyword">public</span> KPropertiesDialogPlugin
{
    Q_OBJECT

<span class="hljs-keyword">public</span>:
    HelloDolphinPlugin(QObject *parent);

<span class="hljs-keyword">protected</span>:
    PEParserWrapper *peWrapper;
    <span class="hljs-built_in">std</span>::<span class="hljs-built_in">string</span> version = <span class="hljs-string">""</span>;
    <span class="hljs-comment">// List the file types we're interested in.</span>
    <span class="hljs-keyword">const</span> <span class="hljs-built_in">std</span>::<span class="hljs-built_in">vector</span>&lt;<span class="hljs-built_in">std</span>::<span class="hljs-built_in">string</span>&gt; ALLOW_LIST = {<span class="hljs-string">".exe"</span>, <span class="hljs-string">".dll"</span>};

    <span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">init</span><span class="hljs-params">()</span></span>;
    <span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">injectFileVersionIntoGeneralTab</span><span class="hljs-params">(KPageWidgetItem *current, KPageWidgetItem *before)</span></span>;
    <span class="hljs-comment">// This should be in camelCase, isn't it?</span>
    <span class="hljs-function"><span class="hljs-keyword">bool</span> <span class="hljs-title">is_target_file_type</span><span class="hljs-params">(QString fileName)</span></span>;

};
</code></pre>
<p><strong>src/hello-dolphin-plugin.cpp:</strong></p>
<pre><code class="lang-cpp"><span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">&lt;QObject&gt;</span></span>
<span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">&lt;QWidget&gt;</span></span>
<span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">&lt;QString&gt;</span></span>
<span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">&lt;QVBoxLayout&gt;</span></span>
<span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">&lt;QLabel&gt;</span></span>
<span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">&lt;QUrl&gt;</span></span>

<span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">&lt;KPluginFactory&gt;</span></span>
<span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">&lt;kpluginfactory.h&gt;</span></span>
<span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">&lt;KFileItem&gt;</span></span>
<span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">&lt;KPageDialog&gt;</span></span>
<span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">&lt;KPropertiesDialog&gt;</span></span>
<span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">&lt;KSqueezedTextLabel&gt;</span></span>

<span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">&lt;regex&gt;</span></span>

<span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">"hello-dolphin-plugin.h"</span></span>
<span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">"pe-parser-wrapper.h"</span></span>

<span class="hljs-keyword">using</span> <span class="hljs-keyword">namespace</span> <span class="hljs-built_in">std</span>;

HelloDolphinPlugin::HelloDolphinPlugin(QObject *parent) : KPropertiesDialogPlugin(parent)
{
    <span class="hljs-keyword">this</span>-&gt;peWrapper = <span class="hljs-keyword">new</span> PEParserWrapper();
    <span class="hljs-keyword">this</span>-&gt;init();
}

<span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">HelloDolphinPlugin::init</span><span class="hljs-params">()</span>
</span>{
    <span class="hljs-keyword">if</span> (<span class="hljs-keyword">this</span>-&gt;properties-&gt;items().size() == <span class="hljs-number">1</span>)
    {

        QUrl itemUrl = <span class="hljs-keyword">this</span>-&gt;properties-&gt;item().url();
        QString filePath = itemUrl.path();
        QString fileName = itemUrl.fileName();

        <span class="hljs-keyword">if</span> (<span class="hljs-keyword">this</span>-&gt;is_target_file_type(fileName))
        {

            <span class="hljs-keyword">this</span>-&gt;version = <span class="hljs-keyword">this</span>-&gt;peWrapper-&gt;parseFile(filePath);

            <span class="hljs-keyword">if</span> (!<span class="hljs-keyword">this</span>-&gt;version.empty())
            {
                <span class="hljs-comment">// Injects "File Version" details in General tab.</span>
                connect(
                    properties,
                    &amp;KPropertiesDialog::currentPageChanged,
                    <span class="hljs-keyword">this</span>,
                    &amp;HelloDolphinPlugin::injectFileVersionIntoGeneralTab,
                    Qt::ConnectionType::SingleShotConnection
                );

            }
        }
    }
}

<span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">HelloDolphinPlugin::injectFileVersionIntoGeneralTab</span><span class="hljs-params">(KPageWidgetItem *current, [[maybe_unused]] KPageWidgetItem *before)</span>
</span>{
    <span class="hljs-built_in">string</span> tabName = current-&gt;name().toStdString();

    <span class="hljs-comment">// Remove '&amp;' character used for pneumatics.</span>
    <span class="hljs-comment">// There might be an easy way here :)</span>
    <span class="hljs-function"><span class="hljs-built_in">std</span>::regex <span class="hljs-title">pattern</span><span class="hljs-params">(<span class="hljs-string">"&amp;"</span>)</span></span>;
    tabName = <span class="hljs-built_in">std</span>::regex_replace(tabName, pattern, <span class="hljs-string">""</span>);

    <span class="hljs-comment">// Looking for General tab to add the File Version</span>
    <span class="hljs-keyword">if</span> (tabName == <span class="hljs-string">"General"</span>)
    {
        QVBoxLayout *vbox = (QVBoxLayout*) current-&gt;widget()-&gt;children().first();
        QGridLayout *gridLayout = (QGridLayout*) vbox-&gt;children().first();

        QLabel *fileVersionLabelKey = <span class="hljs-keyword">new</span> QLabel(QString::fromUtf8(<span class="hljs-string">"Version:"</span>));
        QLabel *fileVersionLabelValue = <span class="hljs-keyword">new</span> QLabel(QString::fromUtf8(<span class="hljs-keyword">this</span>-&gt;version));

        <span class="hljs-comment">// 18 refers to row where the widgets will be placed. It is not in use so </span>
        <span class="hljs-comment">// I am hoping this won't overlap any other widget.</span>
        <span class="hljs-comment">// Confirmed by browsing `kio` repo at invent.kde.org</span>
        gridLayout-&gt;addWidget(fileVersionLabelKey, <span class="hljs-number">18</span>, <span class="hljs-number">0</span>, Qt::AlignRight|Qt::AlignVCenter);
        gridLayout-&gt;addWidget(fileVersionLabelValue, <span class="hljs-number">18</span>, <span class="hljs-number">1</span>, Qt::AlignLeft|Qt::AlignVCenter);
    }
}

<span class="hljs-function"><span class="hljs-keyword">bool</span> <span class="hljs-title">HelloDolphinPlugin::is_target_file_type</span><span class="hljs-params">(QString fileName)</span>
</span>{
    <span class="hljs-keyword">for</span> (<span class="hljs-built_in">std</span>::<span class="hljs-built_in">string</span> ext: <span class="hljs-keyword">this</span>-&gt;ALLOW_LIST)
    {
        <span class="hljs-keyword">if</span> (fileName.endsWith(QString::fromUtf8(ext))) <span class="hljs-keyword">return</span> <span class="hljs-number">1</span>;
    }

    <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;
}


<span class="hljs-comment">// Here we register the class as a plugin with additional metadata</span>
<span class="hljs-comment">// is available at the JSON file</span>
K_PLUGIN_CLASS_WITH_JSON(HelloDolphinPlugin, <span class="hljs-string">"hello-dolphin-plugin.json"</span>)


<span class="hljs-comment">// MOC file</span>
<span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">"hello-dolphin-plugin.moc"</span></span>
</code></pre>
<p>Lastly, here’s the manifest file which ties all this together.</p>
<p><strong>hello-dolphin-plugin.json:</strong></p>
<pre><code class="lang-json">{
    <span class="hljs-attr">"KPlugin"</span>: {
        <span class="hljs-attr">"Description"</span>: <span class="hljs-string">"This plugin inserts 'Version' row in General tab for EXE/DLL files."</span>,
        <span class="hljs-attr">"Icon"</span>: <span class="hljs-string">"document-open"</span>,
        <span class="hljs-attr">"MimeTypes"</span>: [
            <span class="hljs-string">"application/octet-stream"</span>
        ],
        <span class="hljs-attr">"Name"</span>: <span class="hljs-string">"Hello Dolphin plugin"</span>,
        <span class="hljs-attr">"EnabledByDefault"</span>: <span class="hljs-literal">true</span>,
        <span class="hljs-attr">"MimeType"</span>: <span class="hljs-string">"application/octet-stream"</span>,
        <span class="hljs-attr">"X-KDE-Protocols"</span>: [
            <span class="hljs-string">"file"</span>,
            <span class="hljs-string">"desktop"</span>
        ]
    }
}
</code></pre>
<h1 id="heading-end-result">End Result</h1>
<p>You can see the version number for an EXE file:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1742637340554/8898ae52-0221-4882-b49e-3df1d5f64b6c.png" alt="KDE Dolphin properties now shows Version for EXE file" class="image--center mx-auto" /></p>
<p>Here’s the screenshot for a DLL file:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1742637641047/a5c494d2-1634-479e-a5ad-1621cdf2a32e.png" alt class="image--center mx-auto" /></p>
<p><strong>I have one nitpick with</strong> <code>QGridLayout</code><strong>:</strong><br />It doesn’t automatically push widgets down by 1 when we’re trying to place a new widget at it’s place.<br />This meant I could’ve shown “Version” info just below “Type” which would’ve made a lot of sense but <em>I have to take the win here, I guess.</em></p>
<h1 id="heading-conclusion">Conclusion</h1>
<p>This project was a lot of fun! I learned a few things:</p>
<ol>
<li><p>C++ is a language I am <em>mildly</em> comfortable with</p>
</li>
<li><p>Using CMake for a project,</p>
</li>
<li><p>File headers and dealing with offsets</p>
</li>
<li><p>Concept of pointers is much clearer</p>
</li>
<li><p>Qt can be a viable replacement to GTK3 (which I like)</p>
</li>
</ol>
<p>Thank you for taking the time to read the blog post in it’s entirety. I would appreciate if you could drop a <strong>Like</strong> to this post<strong>.</strong> It helps out the algorithm.</p>
<p>You can also @ me on Mastodon <a target="_blank" href="http://social.linux.pizza/@shanmukhateja">here</a> :)</p>
<p>Bye for now.</p>
]]></content:encoded></item><item><title><![CDATA[How to install open-webui the easy way on Linux]]></title><description><![CDATA[Hello,
I will show you how to quickly and easily setup open-webui in your Linux distro.
This will allow using Ollama models (which you have already installed locally) from a web user interface (similar to ChatGPT).
Prerequisites
Python 3.11 is recomm...]]></description><link>https://blog.suryatejak.in/how-to-install-open-webui-the-easy-way-on-linux</link><guid isPermaLink="true">https://blog.suryatejak.in/how-to-install-open-webui-the-easy-way-on-linux</guid><category><![CDATA[ollama]]></category><category><![CDATA[#webui]]></category><category><![CDATA[llm]]></category><category><![CDATA[Python]]></category><dc:creator><![CDATA[Surya Teja Karra]]></dc:creator><pubDate>Sun, 16 Feb 2025 06:10:47 GMT</pubDate><content:encoded><![CDATA[<p>Hello,</p>
<p>I will show you how to quickly and easily setup <a target="_blank" href="https://github.com/open-webui/open-webui">open-webui</a> in your Linux distro.</p>
<p>This will allow using Ollama models (which you have already installed locally) from a web user interface (similar to ChatGPT).</p>
<h1 id="heading-prerequisites">Prerequisites</h1>
<p>Python 3.11 is recommended for the project. You can try this with global Python installation but make sure the version numbers match.</p>
<blockquote>
<p>If you’d like to install Python 3.11 manually, please look into <a target="_blank" href="https://github.com/pyenv/pyenv"><strong>pyvenv</strong></a><strong>.</strong></p>
</blockquote>
<h1 id="heading-installation">Installation</h1>
<ol>
<li>I use a “Projects“ folder to store all active projects.</li>
</ol>
<pre><code class="lang-bash">$ <span class="hljs-built_in">cd</span> /home/<span class="hljs-variable">$USER</span>/Projects
</code></pre>
<ol start="2">
<li>Now, we’ll create a virtual environment to isolate open-webui project. (<strong>Recommended for Arch and Arch based distros)</strong></li>
</ol>
<blockquote>
<p>Note for Arch &amp; Arch-like distro users: As of writing, Arch and Arch-like distros are shipping Python 3.13 which open-webui doesn’t support yet. (tested on v0.5.10 <a target="_blank" href="https://github.com/open-webui/open-webui/blob/v0.5.10/pyproject.toml#L115">GitHub</a>)</p>
</blockquote>
<pre><code class="lang-bash">
<span class="hljs-comment"># Make a virtualenv for this project called "webui".</span>
$ python3 -m venv webui

<span class="hljs-comment"># In case of Pyenv</span>
$ pyenv <span class="hljs-built_in">exec</span> python -m venv webui
</code></pre>
<ol start="3">
<li>Install the <code>open-webui</code> package.</li>
</ol>
<pre><code class="lang-bash">
<span class="hljs-comment"># The below commands work in both global as well as pyenv installation of Python</span>

$ <span class="hljs-built_in">cd</span> /home/<span class="hljs-variable">$USER</span>/Projects/webui
$ <span class="hljs-built_in">source</span> bin/activate
$ pip install open-webui
</code></pre>
<p>This command will now install open-webui to your project This will take some time depending on your internet connection.</p>
<ol start="4">
<li>Run open-webui</li>
</ol>
<pre><code class="lang-bash">$ open-webui serve
</code></pre>
<p>This should initialize the software and start listening for connections at <code>http://localhost:8080</code>.</p>
<ol start="5">
<li><p>Open Firefox/Chrome and go to <code>http://localhost:8080</code>. Provide a name, email and password to login to your instance and that’s it!!</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1739684781931/c8575d32-f6bd-48b5-9ba0-094389f1a288.png" alt="Open WebUI Get Started page" class="image--center mx-auto" /></p>
<h1 id="heading-update-open-webui">Update open-webui</h1>
</li>
</ol>
<ol>
<li><p>To update open-webui to latest version, look for a notification from open-webui screen informing us there is a new version as shown below:</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1739685374138/1a457991-d59f-4b62-8c91-5b689dfc9093.png" alt class="image--center mx-auto" /></p>
</li>
<li><p>Now, stop all processes of <code>open-webui</code> locally. <strong>This step is important.</strong> In most cases, you simply need to close the terminal which is running <code>open-webui serve</code>.</p>
</li>
<li><p>Run the commands below:</p>
</li>
</ol>
<pre><code class="lang-bash">$ <span class="hljs-built_in">cd</span> /home/<span class="hljs-variable">$USER</span>/Projects/webui
$ <span class="hljs-built_in">source</span> bin/activate
<span class="hljs-comment"># Replace &lt;version&gt; with the version number from notification.</span>
<span class="hljs-comment"># In my case, it is `0.5.12`</span>
$ pip install open-webui==&lt;version&gt;
<span class="hljs-comment"># Assuming no errors, let's run the serve command now.</span>
$ open-webui serve
</code></pre>
<ol start="4">
<li>If you do not see any errors, try visiting <code>http://localhost:8080</code>. In most cases, this will work out-of-the-box.</li>
</ol>
<h1 id="heading-conclusion">Conclusion</h1>
<p>This blog post summarizes the steps involved in getting <code>open-webui</code> working on my machine. The instructions are generic <em>enough</em> to be used for other Linux distros.</p>
<p>Please leave a comment if I missed something.</p>
<p>You can also follow me on <a target="_blank" href="https://social.linux.pizza/@shanmukhateja/">Mastodon</a> for more updates.</p>
<p>Bye for now :)</p>
]]></content:encoded></item><item><title><![CDATA[GhostLLM: Add text rewrite functionality to GtkTextView]]></title><description><![CDATA[Hello,
This is final part of the GhostLLM project. The goal was to introduce “Rewrite Text” functionality to GTK3 TextView using Ollama.
If you haven’t been following this project, you can find the links below:Part 1Part 2Part 3
In this part, I have ...]]></description><link>https://blog.suryatejak.in/ghostllm-add-text-rewrite-functionality-to-gtktextview</link><guid isPermaLink="true">https://blog.suryatejak.in/ghostllm-add-text-rewrite-functionality-to-gtktextview</guid><category><![CDATA[llm]]></category><category><![CDATA[Linux]]></category><category><![CDATA[Artificial Intelligence]]></category><category><![CDATA[GTK]]></category><category><![CDATA[Open Source]]></category><dc:creator><![CDATA[Surya Teja Karra]]></dc:creator><pubDate>Sat, 30 Nov 2024 07:13:34 GMT</pubDate><content:encoded><![CDATA[<p>Hello,</p>
<p>This is final part of the GhostLLM project. The goal was to introduce “Rewrite Text” functionality to GTK3 TextView using Ollama.</p>
<p>If you haven’t been following this project, you can find the links below:<br /><a target="_blank" href="https://hashnode.com/post/cm3x9jvve000609kz1t9tcq53"><strong>Part 1</strong></a><br /><a target="_blank" href="https://hashnode.com/post/cm3ykmob1000509lhdw9mgrsv"><strong>Part 2</strong></a><br /><a target="_blank" href="https://hashnode.com/post/cm431x7bu003k09ic16jw7uct"><strong>Part 3</strong></a></p>
<p>In this part, I have updated <code>GtkTextView</code> widget with relevant GhostLLM code for the text rewrite functionality.</p>
<h1 id="heading-theory">Theory</h1>
<p>We need to adapt the GhostLLM related code from <code>gtkentry.c</code> to <code>gtktextview.c</code> and test the features. This code has proven to be working with GtkEntry in the last part so I am not expecting any issues now.</p>
<h1 id="heading-implementation">Implementation</h1>
<p>You will find latest changes on Github repo <a target="_blank" href="https://github.com/shanmukhateja/ghostllm-gtk3">here</a>.</p>
<p>Here’s the relevant code:</p>
<p><strong>gtktextview.c (GhostLLM callback code):</strong></p>
<pre><code class="lang-c">
<span class="hljs-function"><span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span>
<span class="hljs-title">ghostllm_rewrite_text_cb</span> <span class="hljs-params">(GtkWidget *widget)</span>
</span>{
  GtkTextView *text_view = GTK_TEXT_VIEW (widget);
  GtkTextBuffer *buffer;
  GtkTextIter start_iter, end_iter;

  buffer = text_view-&gt;priv-&gt;buffer;

  <span class="hljs-comment">// Get selection bounds for string</span>
  gtk_text_buffer_get_selection_bounds (buffer, &amp;start_iter, &amp;end_iter);

  <span class="hljs-comment">// Get selected text from selection bounds</span>
  <span class="hljs-keyword">char</span> *input_str = gtk_text_buffer_get_text (buffer, &amp;start_iter, &amp;end_iter, FALSE);

  <span class="hljs-comment">// Rewrite the input string</span>
  <span class="hljs-keyword">char</span>* response = ghostllm_rewrite_text(input_str);

  <span class="hljs-keyword">if</span> (response == <span class="hljs-literal">NULL</span>) {
    g_printerr(<span class="hljs-string">"Unable to generate GhostLLM response."</span>);
    <span class="hljs-keyword">return</span>;
  }

  <span class="hljs-comment">// <span class="hljs-doctag">HACK:</span> Delete user selected text using "Cut" operation</span>
  gtk_text_view_cut_clipboard(text_view);

  <span class="hljs-comment">// Insert rewritten text</span>
  gtk_text_view_insert_at_cursor(text_view, response);

  g_free (response);

}
</code></pre>
<p><strong>gtktextview.c (Context menu code)</strong></p>
<pre><code class="lang-c"><span class="hljs-comment">// This code goes after line number 9586</span>

<span class="hljs-comment">// GhostLLM stuff</span>

<span class="hljs-comment">// The root GtkMenuItem that shows "GhostLLM" option on the context menu</span>
GtkWidget *ghostllm_root_menuitem = gtk_menu_item_new_with_mnemonic (_(<span class="hljs-string">"GhostLLM"</span>));
gtk_widget_set_sensitive(ghostllm_root_menuitem, gtk_text_buffer_get_char_count (priv-&gt;buffer) &gt; <span class="hljs-number">0</span>);
<span class="hljs-comment">// The GtkMenu widget which holds the LLM menu options.</span>
GtkWidget *ghostllm_submenu = gtk_menu_new ();

menuitem = gtk_menu_item_new_with_mnemonic (_(<span class="hljs-string">"_Rewrite Text"</span>));
g_signal_connect_swapped (menuitem, <span class="hljs-string">"activate"</span>, G_CALLBACK (ghostllm_rewrite_text_cb), text_view);
gtk_widget_show (menuitem);
<span class="hljs-comment">// Append "Rewrite Text" option into LLM submenu</span>
gtk_menu_shell_append (GTK_MENU_SHELL (ghostllm_submenu), menuitem);
<span class="hljs-comment">// Set submenu options for GhostLLM root menu item</span>
gtk_menu_item_set_submenu ( GTK_MENU_ITEM (ghostllm_root_menuitem), ghostllm_submenu);
gtk_widget_show(menuitem);
gtk_widget_show(ghostllm_root_menuitem);
<span class="hljs-comment">// Append the GhostLLM root GtkMenuItem into GtkEntry's context menu</span>
gtk_menu_shell_append (GTK_MENU_SHELL (priv-&gt;popup_menu), ghostllm_root_menuitem);
</code></pre>
<p>I have tested my changes using the “<strong>sunny”</strong> example provided on the GTK repo. I had to make the following change in <code>meson.build</code> at the project root and compiled the whole thing.</p>
<pre><code class="lang-diff">diff --git a/meson.build b/meson.build
index 67b24e5e7d..9df8fae828 100644
<span class="hljs-comment">--- a/meson.build</span>
<span class="hljs-comment">+++ b/meson.build</span>
@@ -857,6 +857,7 @@ subdir('gdk')
 subdir('gtk')
 subdir('modules')
 subdir('libgail-util')
<span class="hljs-addition">+subdir('examples')</span>
 if get_option('demos')
   subdir('demos')
 endif
@@ -864,9 +865,8 @@ if get_option('tests')
   subdir('tests')
   subdir('testsuite')
 endif
<span class="hljs-deletion">-if get_option('examples')</span>
<span class="hljs-deletion">-  subdir('examples')</span>
<span class="hljs-deletion">-endif</span>
<span class="hljs-addition">+#if get_option('examples')</span>
<span class="hljs-addition">+#endif</span>

 # config.h
 configure_file(
</code></pre>
<h1 id="heading-screenshots">Screenshots</h1>
<p><strong>User input screen</strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1732949616109/5b1a03c3-9773-4d71-b7e2-07b62286992a.png" alt class="image--center mx-auto" /></p>
<p><strong>Output screen</strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1732949625731/0f40f1c9-b615-41b9-865e-dd8a2afb5901.png" alt class="image--center mx-auto" /></p>
<h1 id="heading-my-opinion-on-llms">My Opinion on LLMs</h1>
<p>I personally feel the current LLM “AI” stuff is just hype around a new technology and just like “dot com bubble” as well as the crypto crap a few years back, this too shall wither away in the future.</p>
<p>I do see the potential with this technology as a completely offline auto complete or auto suggestions like functionality based on this however that would involve using much smaller LLM models. In this example, I am using <strong>qwen2.5:3b</strong> which did the job but it’s 1.9GB in size and it took a few seconds for my GTX 1050 Ti to process the request. This would only increase with the input tokens.</p>
<h1 id="heading-conclusion">Conclusion</h1>
<p>This marks the end of the fun (and silly) GhostLLM project. I learned a lot working on this and has proven yet again why I prefer GTK+ on my projects.</p>
<p>Thanks for following along on this project. Please leave a Like on the post to show your appreciation.</p>
<p>Bye for now :-)</p>
]]></content:encoded></item><item><title><![CDATA[GhostLLM : RewriteText now works on GtkEntry!]]></title><description><![CDATA[Hello,
This is part three of my GhostLLM project. This project is about integrating “Rewrite Text” functionality natively into GTK3 widgets. Check the part one and part two in case you missed it.
Theory
We will be working with gtkentry.c for this par...]]></description><link>https://blog.suryatejak.in/ghostllm-rewritetext-now-works-on-gtkentry</link><guid isPermaLink="true">https://blog.suryatejak.in/ghostllm-rewritetext-now-works-on-gtkentry</guid><category><![CDATA[llm]]></category><category><![CDATA[opensource]]></category><category><![CDATA[Linux]]></category><category><![CDATA[GTK]]></category><category><![CDATA[Artificial Intelligence]]></category><category><![CDATA[development]]></category><dc:creator><![CDATA[Surya Teja Karra]]></dc:creator><pubDate>Fri, 29 Nov 2024 18:02:46 GMT</pubDate><content:encoded><![CDATA[<p>Hello,</p>
<p>This is part three of my GhostLLM project. This project is about integrating “Rewrite Text” functionality natively into GTK3 widgets. Check the <a target="_blank" href="https://hashnode.com/post/cm3x9jvve000609kz1t9tcq53">part one</a> and <a target="_blank" href="https://blog.suryatejak.in/ghostllm-creating-the-dbus-server-with-ollama-integration">part two</a> in case you missed it.</p>
<h1 id="heading-theory">Theory</h1>
<p>We will be working with <code>gtkentry.c</code> for this part. The goal is to implement <code>ghostllm_rewrite_text_cb</code> callback function to replace the user selected text.</p>
<p>The idea is to declare new files - <code>ghostllm.c</code> &amp; the header file <code>ghostllm.h</code> to deal with DBus related communications. We also update <code>meson.build</code> file to include them in the build system.</p>
<h1 id="heading-implementation">Implementation</h1>
<p>The code lives on GitHub repo <a target="_blank" href="https://github.com/shanmukhateja/ghostllm-gtk3">here</a>. Please refer the repo link for latest changes :)</p>
<p>Here’s the relevant code:</p>
<p><strong>ghostllm.h:</strong></p>
<pre><code class="lang-c"><span class="hljs-comment">// This is the only line in this file!</span>
<span class="hljs-function"><span class="hljs-keyword">char</span>* <span class="hljs-title">ghostllm_rewrite_text</span><span class="hljs-params">(<span class="hljs-keyword">char</span>* input_str)</span></span>;
</code></pre>
<p><strong>ghostllm.c</strong></p>
<pre><code class="lang-c"><span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">"config.h"</span></span>

<span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">&lt;gtk/gtk.h&gt;</span></span>

<span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">&lt;gio/gio.h&gt;</span></span>

<span class="hljs-function"><span class="hljs-keyword">char</span> *<span class="hljs-title">ghostllm_rewrite_text</span><span class="hljs-params">(<span class="hljs-keyword">char</span> *input_str)</span> </span>{
  <span class="hljs-comment">// Connect to DBus Session Bus</span>
  GDBusConnection *conn = g_bus_get_sync(G_BUS_TYPE_SESSION, <span class="hljs-literal">NULL</span>, <span class="hljs-literal">NULL</span>);

  <span class="hljs-comment">// Define the remote service and object path</span>
  <span class="hljs-keyword">char</span> *service_name = <span class="hljs-string">"in.suryatejak.ghostllm"</span>;
  <span class="hljs-keyword">char</span> *object_path = <span class="hljs-string">"/in/suryatejak/ghostllm"</span>;

  <span class="hljs-comment">// This is required for communicating with DBus.</span>
  GDBusMessage *call_message = <span class="hljs-literal">NULL</span>;
  GDBusMessage *reply_message = <span class="hljs-literal">NULL</span>;
  GError **error = <span class="hljs-literal">NULL</span>;

  <span class="hljs-comment">// Define the DBus message to be sent.</span>
  call_message = g_dbus_message_new_method_call(service_name, object_path,
                                                service_name, <span class="hljs-string">"RewriteText"</span>);

  <span class="hljs-comment">// Set arguments</span>
  g_dbus_message_set_body(call_message, g_variant_new(<span class="hljs-string">"(s)"</span>, input_str));

  <span class="hljs-comment">// Call the RewriteText method on the remote service</span>
  reply_message = g_dbus_connection_send_message_with_reply_sync(
      conn, call_message, G_DBUS_SEND_MESSAGE_FLAGS_NONE, <span class="hljs-number">-1</span>, <span class="hljs-literal">NULL</span>, <span class="hljs-literal">NULL</span>,
      error);

  <span class="hljs-comment">// Retrieve the response data from DBus</span>
  GDBusMessageType reply_type = g_dbus_message_get_message_type(reply_message);

  <span class="hljs-keyword">if</span> (reply_type == G_DBUS_MESSAGE_TYPE_ERROR) {
    <span class="hljs-comment">// <span class="hljs-doctag">TODO:</span> show error to user</span>
    g_dbus_message_to_gerror(reply_message, error);
    <span class="hljs-keyword">return</span> <span class="hljs-literal">NULL</span>;
  }

  <span class="hljs-keyword">if</span> (reply_type == G_DBUS_MESSAGE_TYPE_METHOD_RETURN) {
    <span class="hljs-comment">// We got something!</span>
    GVariant *response = g_dbus_message_get_body(reply_message);

    <span class="hljs-comment">/**
     * We receive a tuple which contains required string.
     * Now, we fetch the first item and extract the string.
     */</span>
    gsize index = {<span class="hljs-number">0</span>};
    response = g_variant_get_child_value(response, index);

    <span class="hljs-keyword">if</span> (response == <span class="hljs-literal">NULL</span>) {
      <span class="hljs-keyword">return</span> <span class="hljs-literal">NULL</span>;
    }

    <span class="hljs-keyword">char</span> *msg = g_variant_get_string(response, <span class="hljs-literal">NULL</span>);

    <span class="hljs-keyword">return</span> msg;
  }

  <span class="hljs-keyword">return</span> <span class="hljs-literal">NULL</span>;
}
</code></pre>
<p>As seen above, we call our <code>RewriteText</code> method with the user’s selected text. It returns a tuple (DBus type: <code>(s)</code>) from which we extract rewritten string.</p>
<p><strong>gtkentry.c:</strong></p>
<pre><code class="lang-c"><span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span>
<span class="hljs-comment">// If you've seen this function in part one, you'll</span>
<span class="hljs-comment">// notice the change in function arguments.</span>
ghostllm_rewrite_text_cb (GtkEntry *entry)
{

  <span class="hljs-comment">// Get selection bounds for string</span>
  GtkEditable *editable = GTK_EDITABLE (entry);
  gint start, end;
  gtk_editable_get_selection_bounds (editable, &amp;start, &amp;end);

  <span class="hljs-comment">// Get selected text from selection bounds</span>
  gchar *input_str = _gtk_entry_get_display_text (entry, start, end);

  <span class="hljs-comment">// Rewrite the input string</span>
  <span class="hljs-keyword">char</span>* response = ghostllm_rewrite_text(input_str);

  <span class="hljs-keyword">if</span> (response == <span class="hljs-literal">NULL</span>) {
    g_printerr(<span class="hljs-string">"Unable to generate GhostLLM response."</span>);
    <span class="hljs-keyword">return</span>;
  }

  <span class="hljs-comment">// <span class="hljs-doctag">HACK:</span> Delete user selected text using "Cut" operation</span>
  gtk_entry_cut_clipboard(entry);

  <span class="hljs-comment">// Insert rewritten text</span>
  gtk_entry_insert_text(editable, response, <span class="hljs-built_in">strlen</span>(response), &amp;start);

  g_free (response);

}
</code></pre>
<p>In this step, we simply generate new text from our server, then copy the user’s selected input text to the clipboard and lastly insert this new text at the cursor position.</p>
<p>It might take a few seconds depending on your machine but in the end, it works. I noticed this still works even when the window is inactive.</p>
<h1 id="heading-demo">Demo</h1>
<p><strong>User Input:</strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1732902483309/b7d840d5-315e-4655-81d9-f87e0b452c1b.png" alt class="image--center mx-auto" /></p>
<p><strong>Ollama response:</strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1732902517162/f1fcd19f-5cf7-4270-aa5b-cfcfb6599999.png" alt class="image--center mx-auto" /></p>
<p>As you can see, the output has changed. Now, this is just to demonstrate something like this is doable even when it isn’t practical.</p>
<blockquote>
<p>I was worried about the additional work involved in replacing the user selected text with the rewritten text but, as it turns out, I can cheat a little here using clipboard. :)</p>
</blockquote>
<h1 id="heading-next-steps">Next Steps</h1>
<p>The idea is to implement this feature to <code>GtkTextView</code> widget which was the original goal of this project. I will keep the <code>GtkEntry</code> changes as-is for my amusement.</p>
<h1 id="heading-conclusion">Conclusion</h1>
<p>Thanks for your time!</p>
<p>This was a very fun project however I think the next part would mark the end for this. As always, the code will live on my GitHub.</p>
<p>I hope you liked this post. Please give it a Like to show your appreciation. If you feel there’s something I missed or can do better, let me know in the comments.</p>
<p>Bye for now :-)</p>
]]></content:encoded></item><item><title><![CDATA[GhostLLM: Creating the DBus server with Ollama integration]]></title><description><![CDATA[Hello,
This is part two of my GhostLLM project. This project is about integrating “Rewrite Text” functionality natively into GTK3 widgets. Check the part one post if you missed it.
In this part, I will going over the DBus server code (written in Pyth...]]></description><link>https://blog.suryatejak.in/ghostllm-creating-the-dbus-server-with-ollama-integration</link><guid isPermaLink="true">https://blog.suryatejak.in/ghostllm-creating-the-dbus-server-with-ollama-integration</guid><category><![CDATA[llm]]></category><category><![CDATA[Python]]></category><category><![CDATA[Linux]]></category><category><![CDATA[dbus]]></category><category><![CDATA[ollama]]></category><dc:creator><![CDATA[Surya Teja Karra]]></dc:creator><pubDate>Tue, 26 Nov 2024 14:47:37 GMT</pubDate><content:encoded><![CDATA[<p>Hello,</p>
<p>This is part two of my GhostLLM project. This project is about integrating “Rewrite Text” functionality natively into GTK3 widgets. Check the <a target="_blank" href="https://hashnode.com/post/cm3x9jvve000609kz1t9tcq53">part one</a> post if you missed it.</p>
<p>In this part, I will going over the DBus server code (written in Python) which will be exposing DBus interface on the Session Bus with a method “RewriteText” which accepts user input (String) and returns the rewritten sentence as the output.</p>
<h1 id="heading-implementation">Implementation</h1>
<p>The code for this project lives on GitHub <a target="_blank" href="https://github.com/shanmukhateja/ghostllm-dbus/">here</a>.</p>
<p>The main part here is to setup DBus interface. I used <code>dbus-fast</code> to simplify this.</p>
<p><strong>server.py:</strong></p>
<pre><code class="lang-python"><span class="hljs-comment"># File: server.py</span>

<span class="hljs-comment"># DBus interface is declared here</span>
<span class="hljs-comment"># The `_` prefix is to indicate this is an internal class.</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">_GhostLLMDBusInterface</span>(<span class="hljs-params">ServiceInterface</span>):</span>

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__init__</span>(<span class="hljs-params">self</span>):</span>
        super().__init__(DBUS_ADDRESS_NAME)
        self.ollama = OllamaController()

<span class="hljs-meta">    @method()</span>
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">RewriteText</span>(<span class="hljs-params">self, input_text: <span class="hljs-string">'s'</span></span>) -&gt; 's':</span>
        <span class="hljs-keyword">return</span> self.ollama.chat(input_text)

<span class="hljs-comment"># Wrapper class that simplifies executing this code from `__main__.py`</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">GhostLLMDBus</span>():</span>

    <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">init</span>(<span class="hljs-params">self</span>):</span>
        <span class="hljs-comment"># Connect to Session Bus</span>
        bus = <span class="hljs-keyword">await</span> MessageBus(bus_type=BusType.SESSION).connect()
        <span class="hljs-comment"># Implement the DBus interface</span>
        interface = _GhostLLMDBusInterface()
        bus.export(DBUS_INTERFACE_NAME, interface)
        <span class="hljs-keyword">await</span> bus.request_name(DBUS_ADDRESS_NAME)

        print(<span class="hljs-string">f"DBUS Service initialized at <span class="hljs-subst">{DBUS_INTERFACE_NAME}</span>"</span>)

        <span class="hljs-keyword">await</span> bus.wait_for_disconnect()

    <span class="hljs-comment"># We call this in `__main__.py` that will kick things off!</span>
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">listen</span>(<span class="hljs-params">self</span>):</span>
        asyncio.run(self.init())
</code></pre>
<p><strong>ollama.py:</strong></p>
<pre><code class="lang-python"><span class="hljs-comment"># File: ollama.py</span>
<span class="hljs-keyword">from</span> ollama <span class="hljs-keyword">import</span> generate
<span class="hljs-keyword">from</span> ollama <span class="hljs-keyword">import</span> GenerateResponse

<span class="hljs-keyword">from</span> .constants <span class="hljs-keyword">import</span> OLLAMA_MODEL_NAME, OLLAMA_SYSTEM_PROMPT

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">OllamaController</span>():</span>

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">chat</span>(<span class="hljs-params">self, input_str: str</span>) -&gt; str:</span>
        response: GenerateResponse = generate(
            model=OLLAMA_MODEL_NAME, <span class="hljs-comment"># qwen2.5:3b</span>
            system=OLLAMA_SYSTEM_PROMPT,
            stream=<span class="hljs-literal">False</span>,
            prompt= <span class="hljs-string">f'rewrite "<span class="hljs-subst">{input_str}</span>"'</span>
        )

        <span class="hljs-keyword">return</span> response.response
</code></pre>
<p>This code is pretty straight-forward. I used the usage code from <a target="_blank" href="https://github.com/ollama/ollama-python">Ollama Python library</a>. I use <code>/generate</code> endpoint to customize the prompt to fit the needs. The constants here could be replaced with a config file for easier access.</p>
<blockquote>
<p>You should always use ‘system’ role to set your instructions. This way, your users wouldn’t be able to change or override your instructions. That’s called a prompt injection attack. Learn more about it on <a target="_blank" href="https://en.wikipedia.org/wiki/Prompt_injection">Wikipedia</a>.</p>
</blockquote>
<p><strong>__main__.py:</strong></p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> sys

<span class="hljs-keyword">from</span> .server <span class="hljs-keyword">import</span> GhostLLMDBus

<span class="hljs-keyword">if</span> __name__ == <span class="hljs-string">'__main__'</span>:
    <span class="hljs-keyword">try</span>:
        gh = GhostLLMDBus()
        gh.listen()
    <span class="hljs-keyword">except</span> KeyboardInterrupt:
        sys.exit(<span class="hljs-number">0</span>)
</code></pre>
<p>I tried this code with LLAMA3.2 model but it didn’t respect the system prompt. It always tried to process the user prompt rather than simply rewriting the text. Hence, I went with <strong>qwen2.5:3b</strong> model with the following prompt:</p>
<pre><code class="lang-markdown">// SYSTEM PROMPT:
Your work is to rewrite sentences to make them more professional. Reply with 1 rewritten sentence. do not say anything else.

// USER PROMPT:
rewrite "xxx"
</code></pre>
<p>You run the project using:</p>
<pre><code class="lang-bash">$ <span class="hljs-built_in">cd</span> &lt;project-root&gt; <span class="hljs-comment"># like cd ~/Projects/ghostllm-dbus</span>
<span class="hljs-comment"># You need to run `pip install -r requirements.txt` first!!</span>
$ python -m ghostllm-dbus
</code></pre>
<h1 id="heading-screenshot">Screenshot</h1>
<p>I used <a target="_blank" href="https://doc.qt.io/qt-6/qdbusviewer.html">QTDBusViewer</a> software to test the server. You can use any other software :)</p>
<p><strong>Input prompt:</strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1732632025718/93141a8a-7459-462b-b65e-91d25c5ee16a.png" alt class="image--center mx-auto" /></p>
<p><strong>Response:</strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1732632041205/7a731da0-d1c9-4433-8cf6-021f72ddcda9.png" alt class="image--center mx-auto" /></p>
<h1 id="heading-next-steps">Next Steps</h1>
<p>The next part is going to be difficult. I need to figure out C code that will interact with DBus instance and present it to the user.</p>
<p>I will also need to figure out a way to replace the selected text in buffer with GhostLLM’s response.</p>
<h1 id="heading-conclusion">Conclusion</h1>
<p>Thanks for reading! I would appreciate if you could leave a Like to this post.</p>
<p>Please leave a comment if you think I missed something :)</p>
<p>Bye for now :-)</p>
]]></content:encoded></item><item><title><![CDATA[GhostLLM: Add LLM magic directly into GTK3 GtkEntry]]></title><description><![CDATA[Hello,
This is part one of my series of fun project that felt challenging for me. Read on to know more :)
Background
I’ve been using Ollama for running LLM models locally on my Linux machine for more than a year. It allows us to use many LLM models f...]]></description><link>https://blog.suryatejak.in/ghostllm-add-llm-magic-directly-into-gtk3-gtkentry</link><guid isPermaLink="true">https://blog.suryatejak.in/ghostllm-add-llm-magic-directly-into-gtk3-gtkentry</guid><category><![CDATA[GTK]]></category><category><![CDATA[Linux]]></category><category><![CDATA[llm]]></category><category><![CDATA[ollama]]></category><dc:creator><![CDATA[Surya Teja Karra]]></dc:creator><pubDate>Mon, 25 Nov 2024 16:49:45 GMT</pubDate><content:encoded><![CDATA[<p>Hello,</p>
<p>This is part one of my series of fun project that felt challenging for me. Read on to know more :)</p>
<h1 id="heading-background">Background</h1>
<p>I’ve been using <a target="_blank" href="https://www.ollama.com/">Ollama</a> for running LLM models locally on my Linux machine for more than a year. It allows us to use many LLM models from the CLI.</p>
<p>During Apple’s Keynote this year, I was fascinated with “AI sentence rewriting” feature available starting from macOS Sequoia.</p>
<p><a target="_blank" href="https://www.google.com/search?sca_esv=c744cb070de47b7e&amp;q=macos+sequoia&amp;spell=1&amp;sa=X&amp;ved=2ahUKEwi854XF3PeJAxXdd2wGHSHJEBYQkeECKAB6BAgQEAE">This</a> led me down a rabbit hole for ways to extend existing input fields (at least ones that use GTK3) with LLM model for quickly rewriting sentences.</p>
<p>It is possible that things might not go my way however I picked up this project for 2 things:</p>
<ol>
<li><p>Better understanding of GTK text entry widgets</p>
</li>
<li><p>A silly project to keep my nerdy brain occupied :)</p>
</li>
</ol>
<p>I have decided to name this project “GhostLLM”. It is based on the word <a target="_blank" href="https://www.google.com/search?q=ghostwriter+meaning">ghostwriter</a> with “LLM” joined to the hip. I think it fits simply because the LLM models will be the ghostwriters every time I ask it to rewrite a sentence.</p>
<h1 id="heading-theory">Theory</h1>
<p>The idea is to fork GTK library and add a dedicated “GhostLLM” submenu for right-click context menu when text is selected. When user selects “Rewrite Text” option inside this menu, we will call DBus interface which will provide the alternative sentences.</p>
<p>This means I need to setup DBus address that can process the request. It would be written in Python (or Rust) and will be responsible to communicate with Ollama for models.</p>
<blockquote>
<p>I will be using GTK3 <strong>version 3.24.43 for this project</strong>. It’s what Manjaro Linux (my daily driver) shipped as of writing.</p>
</blockquote>
<h3 id="heading-gtkentry-instead-of-gtktextview">GtkEntry instead of GTKTextView</h3>
<p>As of writing, I could not find software that still used GTK3 + GTKTextView on Manjaro’s repos. Honestly, I didn't search thoroughly so I will revisit this in the future.</p>
<p>For the time-being, I chose <a target="_blank" href="https://docs.gtk.org/gtk3/class.Entry.html">GtkEntry</a> widget. It is used as the address bar on <a target="_blank" href="https://docs.xfce.org/xfce/thunar/start">Thunar</a> (which I have installed on my machine alongside Dolphin). It will make it easier to compile and test my changes.</p>
<h1 id="heading-code">Code</h1>
<p>I have written the necessary boilerplate code to get things moving. The callback for “Rewrite Text” option simply prints sample text to the console.</p>
<pre><code class="lang-diff">diff --git a/gtk/gtkentry.c b/gtk/gtkentry.c
index a71578218c..3bafbee079 100644
<span class="hljs-comment">--- a/gtk/gtkentry.c</span>
<span class="hljs-comment">+++ b/gtk/gtkentry.c</span>
@@ -5921,6 +5921,12 @@ gtk_entry_backspace (GtkEntry *entry)
   gtk_entry_pend_cursor_blink (entry);
 }

<span class="hljs-addition">+static void</span>
<span class="hljs-addition">+ghostllm_rewrite_text_cb (GtkMenuItem *menuitem, gpointer user_data)</span>
<span class="hljs-addition">+{</span>
<span class="hljs-addition">+  g_print("hello from rewrite text callback!!");</span>
<span class="hljs-addition">+}</span>
<span class="hljs-addition">+</span>
 static void
 gtk_entry_copy_clipboard (GtkEntry *entry)
 {
@@ -9601,6 +9607,26 @@ popup_targets_received (GtkClipboard     *clipboard,
                             mode == DISPLAY_NORMAL &amp;&amp;
                             info_entry_priv-&gt;current_pos != info_entry_priv-&gt;selection_bound);

<span class="hljs-addition">+      // GhostLLM stuff</span>
<span class="hljs-addition">+      </span>
<span class="hljs-addition">+      // The root GtkMenuItem that shows "GhostLLM" option on the context menu</span>
<span class="hljs-addition">+      GtkWidget *ghostllm_root_menuitem = gtk_menu_item_new_with_mnemonic (_("GhostLLM"));</span>
<span class="hljs-addition">+      gtk_widget_set_sensitive(ghostllm_root_menuitem, info_entry_priv-&gt;editable &amp;&amp; info_entry_priv-&gt;current_pos != info_entry_priv-&gt;selection_bound);</span>
<span class="hljs-addition">+</span>
<span class="hljs-addition">+      // The GtkMenu widget which holds the LLM menu options.</span>
<span class="hljs-addition">+      GtkWidget *ghostllm_submenu = gtk_menu_new ();</span>
<span class="hljs-addition">+      </span>
<span class="hljs-addition">+      menuitem = gtk_menu_item_new_with_mnemonic (_("_Rewrite Text"));</span>
<span class="hljs-addition">+</span>
<span class="hljs-addition">+      g_signal_connect_swapped (menuitem, "activate", G_CALLBACK (ghostllm_rewrite_text_cb), entry);</span>
<span class="hljs-addition">+      gtk_widget_show (menuitem);</span>
<span class="hljs-addition">+</span>
<span class="hljs-addition">+      // Append "Rewrite Text" option into LLM submenu</span>
<span class="hljs-addition">+      gtk_menu_shell_append (GTK_MENU_SHELL (ghostllm_submenu), menuitem);</span>
<span class="hljs-addition">+</span>
<span class="hljs-addition">+      // Set submenu options for GhostLLM root menu item</span>
<span class="hljs-addition">+      gtk_menu_item_set_submenu ( GTK_MENU_ITEM (ghostllm_root_menuitem), ghostllm_submenu);</span>
<span class="hljs-addition">+</span>
       append_action_signal (entry, menu, _("_Paste"), "paste-clipboard",
                             info_entry_priv-&gt;editable &amp;&amp; clipboard_contains_text);

@@ -9615,6 +9641,11 @@ popup_targets_received (GtkClipboard     *clipboard,
       gtk_widget_show (menuitem);
       gtk_menu_shell_append (GTK_MENU_SHELL (menu), menuitem);

<span class="hljs-addition">+      gtk_widget_show(menuitem);</span>
<span class="hljs-addition">+      gtk_widget_show(ghostllm_root_menuitem);</span>
<span class="hljs-addition">+      // Append the GhostLLM root GtkMenuItem into GtkEntry's context menu</span>
<span class="hljs-addition">+      gtk_menu_shell_append (GTK_MENU_SHELL (menu), ghostllm_root_menuitem);</span>
<span class="hljs-addition">+</span>
       menuitem = gtk_menu_item_new_with_mnemonic (_("Select _All"));
       gtk_widget_set_sensitive (menuitem, gtk_entry_buffer_get_length (info_entry_priv-&gt;buffer) &gt; 0);
       g_signal_connect_swapped (menuitem, "activate",
</code></pre>
<h2 id="heading-ldpreload">LD_PRELOAD</h2>
<p>After successfully compiling GTK library with the above changes, I found the modified GTK3 library at <code>./_build/gtk/libgtk-3.so</code>.</p>
<p>You can find building instructions on <code>INSTALL.md</code> file after checking out the <code>3.24.43</code> tag.</p>
<p>I tried <code>sudo meson install -C_build</code> to install this version globally on the system but it didn’t work. Hence, I went with `LD_PRELOAD` approach to test the changes.</p>
<pre><code class="lang-bash">$ LD_PRELOAD=/home/suryateja/Projects/gtk3/_build/gtk/libgtk-3.so thunar
</code></pre>
<h1 id="heading-whats-next">What’s Next?</h1>
<p>In the next part, I will write GhostLLM daemon which will create DBus address that will talk to LLM models via <a target="_blank" href="https://github.com/ollama/ollama/blob/main/docs/api.md">Ollama endpoints</a>.</p>
<p>I have yet to decide whether to go with Python or Rust for this part. Please add a comment to let me know your thoughts.</p>
<h1 id="heading-conclusion">Conclusion</h1>
<p>Thank you for reading. Give this post a Like to show your appreciation for this post.</p>
<p>You can follow me on <a target="_blank" href="https://social.linux.pizza/@shanmukhateja/">Mastodon</a> for more updates.</p>
<p>Bye for now :-)</p>
]]></content:encoded></item><item><title><![CDATA[fedimg: Shutting down the project]]></title><description><![CDATA[Hello,
It is with a heavy heart that I have decided to shut down the fedIMG project. This project started out of my curiosity towards the ActivityPub protocol. It began as a silly, fun and a “toy” server for educational purposes but as the developmen...]]></description><link>https://blog.suryatejak.in/fedimg-shutting-down-the-project</link><guid isPermaLink="true">https://blog.suryatejak.in/fedimg-shutting-down-the-project</guid><category><![CDATA[activitypub]]></category><category><![CDATA[Diary]]></category><category><![CDATA[Blogging]]></category><category><![CDATA[Node.js]]></category><dc:creator><![CDATA[Surya Teja Karra]]></dc:creator><pubDate>Wed, 20 Nov 2024 16:58:46 GMT</pubDate><content:encoded><![CDATA[<p>Hello,</p>
<p>It is with a heavy heart that I have decided to shut down the fedIMG project. This project started out of my curiosity towards the ActivityPub protocol. It began as a silly, fun and a “toy” server for educational purposes but as the development went on, I wanted to try my hand at turning into something serious (like <a target="_blank" href="https://fedidb.org/software/snac">snac</a>).</p>
<p>I had also planned to run a dev series on the blog (akin to MyStudio IDE) however I quit working on the project around the end of March 2023 because of my inability to solve several features, one of which was “Follow” user feature.</p>
<p>This bug haunted me for the coming months as I went through several stages to grief every time I saw activity related to ActivityPub software. After a 6 month hiatus, I returned to the project with a vengeance. I had managed to fix the bug (<a target="_blank" href="https://github.com/shanmukhateja/fedimg/commit/5b26b1537837ff2df7e3b99077465670e5beccc7#diff-ec2d07b6bc5eef9be8e900f4bd54ac144c330325f672f5353a311341903797dcR180">Link</a>) which was therapeutic and satisfying to say the least. <em>I slept like a baby that night!!</em></p>
<p>With the newly found interest, I continued development and managed to land some bug fixes and other misc. additions to the project but I soon realized the <strong>original</strong> <strong>spark</strong> had died. I wasn’t feeling the fun with this project and the work kept piling up.</p>
<p>Also, the project was costing me money regardless of my engagement towards it. Now, due to technical reasons, I created 2 AWS EC2 instances for this project - one for the NodeJS server and the other was dedicated MySQL database. I know I could’ve used something like <a target="_blank" href="https://github.com/inconshreveable/ngrok">ngrok</a> but their offerings needed a paid account for my needs. Also, I had to pay extra to my ISP for needing a static IP address so that I can SSH into the server to test things when they break.</p>
<p>Looking back, this project was a lot of fun. It was very educational and it taught me many things. After a lot of consideration, this project is being shutdown. I would rather see the project archived than let it die a slow death (abandoned).</p>
<p>The code will live on GitHub <a target="_blank" href="https://github.com/shanmukhateja/fedimg/">here</a> and the repo will be archived once the article is published. I hope this will help others venturing into ActivityPub.</p>
<p>A huge thanks to <a target="_blank" href="https://social.linux.pizza/@selea">https://social.linux.pizza/@selea</a> for allowing me to test fedIMG against his Mastodon server. I really appreciate his patience and compassion towards me and this project. ❤️</p>
<p>Here’s hoping my next passion project will succeed.</p>
<p>Thank you for your time.</p>
<p>Bye for now :-)</p>
]]></content:encoded></item><item><title><![CDATA[fedimg: ActivityPub server written in NodeJS]]></title><description><![CDATA[Background
I started the project just because I wanted to build something again. After looking at some projects which I use daily, I realized I wanted to build an ActivityPub server.
I go into detail in the article but this spec has been a fascinatin...]]></description><link>https://blog.suryatejak.in/fedimg-activitypub-server-written-in-nodejs</link><guid isPermaLink="true">https://blog.suryatejak.in/fedimg-activitypub-server-written-in-nodejs</guid><category><![CDATA[Node.js]]></category><category><![CDATA[activitypub]]></category><category><![CDATA[Mastodon]]></category><category><![CDATA[OpenSource Journey]]></category><dc:creator><![CDATA[Surya Teja Karra]]></dc:creator><pubDate>Sun, 29 Sep 2024 18:10:42 GMT</pubDate><content:encoded><![CDATA[<h1 id="heading-background">Background</h1>
<p>I started the project just because I wanted to build something again. After looking at some projects which I use daily, I realized I wanted to build an <a target="_blank" href="https://activitypub.rocks/">ActivityPub</a> server.</p>
<p>I go into detail in the article but this spec has been a fascinating concept ever since I learned about its existence back in 2022.</p>
<p>I believe in “learning by building stuff” ideology and as such, this project will help me understand a lot of concepts including ActivityPub, building sophisticated social media experiences and so on.</p>
<h2 id="heading-inspiration">Inspiration</h2>
<p>I have been following activity around <a target="_blank" href="https://activitypub.rocks/">ActivityPub</a> since 2022. It was also around the time when I migrated from Twitter to <a target="_blank" href="https://social.linux.pizza/@shanmukhateja/">Mastodon</a>.</p>
<p>I would highly recommend checking out Mastodon and ActivityPub. It could truly be the next-gen social media platform, if given a chance.</p>
<p>There are several projects which implement ActivityPub but the way they <em>currently</em> work is, each project manages one <em>type</em> of content - for example:</p>
<ol>
<li><p>Mastodon is a microblogging platform.</p>
</li>
<li><p>Lemmy &amp; Kbin are link aggregator platforms similar to Reddit.</p>
</li>
<li><p>PeerTube is a video sharing platform.</p>
</li>
</ol>
<blockquote>
<p>Unlike traditional social media platforms, these software have 2 fascinating features:</p>
<ol>
<li>Allow following users across different ActivityPub servers (collectively called the #Fediverse).</li>
</ol>
<ol start="2">
<li>Users are allowed to migrate to different server hosted at a different domain within the same project. For example, a user from <code>mastodonserver1.com</code> can migrate to another Mastodon server hosted at <code>techstuffonly.com</code>.</li>
</ol>
</blockquote>
<h1 id="heading-goals">Goals</h1>
<p>I aim to accomplish the following goals for this project:</p>
<ol>
<li><p>Multiple content types are supported - text, images and short videos (sometime in the near future)</p>
</li>
<li><p>“My Feed” section that is <strong>tuned according to your followers &amp; following collections.</strong> <em>I need to research this a lot before I begin!</em></p>
</li>
</ol>
<h2 id="heading-license">License</h2>
<p>I wanted to go with MIT license just like I do with all my personal projects but after some consideration, I decided to go with AGPLv3 for this project. It is identical to that of Mastodon. The rationale for this choice stems from the fact that I studied Mastodon’s code for many days for implementation purposes.</p>
<h2 id="heading-progress">Progress</h2>
<p>This project lives on GitHub <a target="_blank" href="https://github.com/shanmukhateja/fedimg">here</a>. It is bare-bones as of writing as I have prioritized building necessary features into the project.</p>
<blockquote>
<p>This is actively under development and things will change (and break) often.</p>
</blockquote>
<iframe src="https://social.linux.pizza/@shanmukhateja/113060374960269456/embed" class="mastodon-embed" style="width:100%;border:0" height="350"></iframe>

<p>I have recently returned to the project from a 7 month hiatus. This personal time has allowed me to think clearly and I am happy to report it’s been a good decision. The project lives!!! :)</p>
<iframe src="https://social.linux.pizza/@shanmukhateja/113114755696074592/embed" class="mastodon-embed" style="width:100%;border:0" height="460"></iframe>

<h3 id="heading-what-works">What works:</h3>
<ol>
<li><p>Webfinger endpoint [<a target="_blank" href="https://webfinger.net/">Ref</a>][<a target="_blank" href="https://github.com/shanmukhateja/fedimg/blob/main/src/routes/well-known.route.ts#L11">Code</a>]</p>
</li>
<li><p>NodeInfo endpoint [<a target="_blank" href="https://github.com/jhass/nodeinfo">Ref</a>] [<a target="_blank" href="https://github.com/shanmukhateja/fedimg/blob/main/src/routes/nodeinfo.route.ts">Code</a>]</p>
</li>
<li><p>User registration &amp; login</p>
</li>
<li><p>Follow a remote user</p>
</li>
<li><p>Accept remote user’s Follow request (can’t Reject though!)</p>
</li>
<li><p>Profile page with option to upload profile picture and set “display name”</p>
</li>
</ol>
<h3 id="heading-whats-missing">What’s missing:</h3>
<p>There are many things missing in my implementation but here’s an overview of what I think is important at the moment.</p>
<ol>
<li><p>Landing page</p>
</li>
<li><p>UI is terrible and in some cases, it doesn’t exist yet.</p>
</li>
<li><p>Reject a remote user’s follow - UI &amp; backend</p>
</li>
<li><p>Create Activity management</p>
</li>
<li><p>Undo activity management</p>
</li>
<li><p>Implement <code>/actor/outbox</code> endpoint</p>
</li>
<li><p>Severe lack of documentation for project</p>
</li>
</ol>
<h1 id="heading-challenges">Challenges</h1>
<ol>
<li><p>It was <strong>hell</strong> trying to implement HTTP Signatures. This was mandatory as all AP severs won’t accept certain requests without it. I remember spending several weeks trying different things to get the expected output.</p>
</li>
<li><p>I ran into trouble setting up generating public &amp; private keys for registered users.</p>
</li>
<li><p>Staying motivated was a <strong>HUGE</strong> problem. I always got cornered with <a target="_blank" href="https://en.wikipedia.org/wiki/Impostor_syndrome">imposter syndrome</a> when something fails.</p>
</li>
<li><p>The “do it right the first time” entity in my head was difficult to negotiate. We currently have several <code>FIXME</code> comments throughout the code because that’s the ONLY way I was able to move forward when building something this huge.</p>
</li>
</ol>
<h1 id="heading-conclusion">Conclusion</h1>
<p>I am excited about this project and the learning potential I gain from this experiment is immense. Consider following my Mastodon account <a target="_blank" href="https://social.linux.pizza/@shanmukhateja/">here</a> or following my project’s hashtag <a target="_blank" href="https://social.linux.pizza/tags/fedimg">here</a> for more updates.</p>
<p>I plan on making this a series provided I have something new to report!</p>
<p>Bye for now :)</p>
]]></content:encoded></item><item><title><![CDATA[Let's talk about Moment.js]]></title><description><![CDATA[Moment.js has been my go-to for many years. When I started learning and building websites, jQuery and moment.js were a god-send but over the years moment.js has gotten difficult to ship in my newer projects.
Now, don't get me wrong. It's a great libr...]]></description><link>https://blog.suryatejak.in/lets-talk-about-momentjs</link><guid isPermaLink="true">https://blog.suryatejak.in/lets-talk-about-momentjs</guid><category><![CDATA[MomentJS]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[temporal api]]></category><category><![CDATA[datetime]]></category><category><![CDATA[Node.js]]></category><dc:creator><![CDATA[Surya Teja Karra]]></dc:creator><pubDate>Thu, 21 Dec 2023 17:47:51 GMT</pubDate><content:encoded><![CDATA[<p>Moment.js has been my go-to for many years. When I started learning and building websites, jQuery and moment.js were a god-send but over the years moment.js has gotten difficult to ship in my newer projects.</p>
<p>Now, don't get me wrong. It's a great library but it has a huge problem. JavaScript's native <code>Date</code> class offers very little to what I need in terms of functionality and so I relied on <code>momentjs</code> for all this time.</p>
<p>The problem is <code>momentjs</code> has been <a target="_blank" href="https://momentjs.com/docs/#/-project-status/">deprecated</a> for a while. I didn't know about this until I stumbled upon it while searching for API reference in the documentation.</p>
<p>Now, let's talk alternatives - we have many including ones recommended by Moment.js</p>
<h1 id="heading-my-answer">My answer?</h1>
<p>I started using <code>date-fns</code> for smaller projects. I know its not a "drop-in" replacement but the learning curve isn't <em>that</em> bad in my opinion*.*</p>
<h2 id="heading-there-was-one-more">..there was one more!</h2>
<p>As recommended by Momentjs, for a brief time I gave another library a shot - Luxon. Check out Luxon project's <a target="_blank" href="https://moment.github.io/luxon/#/install?id=basic-browser-setup">website</a> for more details.</p>
<blockquote>
<p>It is authored by <a target="_blank" href="https://github.com/icambron">Isaac Cambron</a>, a long-time contributor to Moment.</p>
<p>--<br />Moment.js docs</p>
</blockquote>
<p>Just like moment.js, it shares a similar drawback - higher bundle size. There is an open issue (as of writing) on project's repo (<a target="_blank" href="https://github.com/moment/luxon/issues/703">link</a>) which has some solutions but I didn't want to meddle with that.</p>
<p>I just want to build my app!!! I wanted something that's lightweight and flexible. In the end, I started using <code>date-fns</code> in more projects.</p>
<p>Speaking of bundle size, <code>date-fns</code> has a <a target="_blank" href="https://date-fns.org/v3.0.0/docs/webpack">webpack section in their docs</a> to reduce the build size (if you're interested).</p>
<h2 id="heading-date-fns-quick-start">date-fns - Quick Start</h2>
<p>Let's get cracking with <code>date-fns</code>.</p>
<blockquote>
<p>Note: I am using date-fns v3.0.0 on the snippets below.</p>
</blockquote>
<pre><code class="lang-javascript"><span class="hljs-comment">// helloworld.js</span>

<span class="hljs-keyword">import</span> { format, compareAsc } <span class="hljs-keyword">from</span> <span class="hljs-string">"date-fns"</span>;

<span class="hljs-keyword">const</span> formattedDate = format(<span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>(), <span class="hljs-string">'dd-MM-yyyy'</span>);
<span class="hljs-built_in">console</span>.log(formattedDate);
<span class="hljs-comment">// Output: '18-12-2023'</span>
</code></pre>
<pre><code class="lang-javascript"><span class="hljs-comment">// difference in days</span>
<span class="hljs-keyword">import</span> { differenceInDays } <span class="hljs-keyword">from</span> <span class="hljs-string">"date-fns"</span>;

<span class="hljs-keyword">const</span> date = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>();

<span class="hljs-comment">// Did you know Month starts with 0 in JS's `Date`?</span>
<span class="hljs-keyword">const</span> dateDec1 = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>(<span class="hljs-number">2023</span>, <span class="hljs-number">11</span>, <span class="hljs-number">1</span>);
<span class="hljs-keyword">const</span> diffDays = differenceInDays(date, dateDec1);

<span class="hljs-built_in">console</span>.log(diffDays);
<span class="hljs-comment">// Output: 17</span>
</code></pre>
<p>There are other handy helper functions like <code>differenceInBusinessDays</code> , <code>differenceInHours</code>, etc. Be sure to check the <a target="_blank" href="https://date-fns.org/v3.0.0/docs/differenceInBusinessDays">documentation</a> for more info.</p>
<h1 id="heading-the-future">The future?</h1>
<p>If you're wondering why are we still thinking of third-party libraries for such an important feature, I have some good news!</p>
<p><a target="_blank" href="https://tc39.es/proposal-temporal/docs/index.html">Temporal</a> is a <em>work-in-progress</em> specification started 2 years ago and can be considered a much needed enhancement over <code>Date</code> .</p>
<p>It was originally drafted in 2017 and has received Stage 3 status back in 2021.</p>
<blockquote>
<p>Since it is only stage 3, it is considered experimental and not <strong>recommended for production usage just yet.</strong></p>
<p>If you'd like to report bugs, feel free to give it a try!<br />GitHub Link: <a target="_blank" href="https://github.com/tc39/proposal-temporal">https://github.com/tc39/proposal-temporal</a></p>
</blockquote>
<h2 id="heading-temporal-quick-start">Temporal - Quick Start</h2>
<p>Let's create an NPM project. <em>You will need Node.JS v14 or later.</em></p>
<pre><code class="lang-bash"><span class="hljs-comment"># Setup a folder to try the APIs</span>
$ mkdir temporal-research
$ <span class="hljs-built_in">cd</span> temporal-research
$ npm init -y

<span class="hljs-comment"># Install the polyfill (requires Node v14 or later)</span>
$ npm install @js-temporal/polyfill
</code></pre>
<p>Let's print the current date and time in ISO format.</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// index.js </span>
<span class="hljs-comment">// running on Node v18</span>

<span class="hljs-comment">// Import Temporal</span>
<span class="hljs-keyword">const</span> { Temporal } = <span class="hljs-built_in">require</span>(<span class="hljs-string">"@js-temporal/polyfill"</span>);
<span class="hljs-comment">// Get current time/date.</span>
<span class="hljs-keyword">const</span> now = Temporal.Now.instant().toString();
<span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Temporal: '</span> + now);
<span class="hljs-comment">// Output: "Temporal: 2023-09-29T13:47:49.625269601Z"</span>

<span class="hljs-comment">// I got curious comparing it with `Date` so here you go :)</span>
<span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Date: '</span> + <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>().toISOString());
<span class="hljs-comment">// Output: "Date: 2023-09-29T13:47:49.630Z"</span>
</code></pre>
<h1 id="heading-conclusion">Conclusion</h1>
<p>I hope you've learned something new here.</p>
<p>If you've liked this post, show your support by using the emojis on the right. It pleases the algorithm :^)</p>
<p>You can also @ me on Mastodon <a target="_blank" href="http://social.linux.pizza/@shanmukhateja">here</a>.</p>
<p>Bye for now.</p>
]]></content:encoded></item><item><title><![CDATA[How to export HTML table to PDF in Angular]]></title><description><![CDATA[Hello,
In this post, I will explain how to convert an HTML table to PDF. This applies to other HTML widgets but I will be focusing on table which has a large dataset and vertical scrolling to not clutter the page.
Prerequisites
We are going to use 2 ...]]></description><link>https://blog.suryatejak.in/how-to-export-html-table-to-pdf-in-angular</link><guid isPermaLink="true">https://blog.suryatejak.in/how-to-export-html-table-to-pdf-in-angular</guid><category><![CDATA[pdf]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[Angular]]></category><category><![CDATA[canvas]]></category><category><![CDATA[HTML5]]></category><dc:creator><![CDATA[Surya Teja Karra]]></dc:creator><pubDate>Mon, 13 Nov 2023 10:39:16 GMT</pubDate><content:encoded><![CDATA[<p>Hello,</p>
<p>In this post, I will explain how to convert an HTML table to PDF. This applies to other HTML widgets but I will be focusing on <code>table</code> which has a large dataset and vertical scrolling to not clutter the page.</p>
<h3 id="heading-prerequisites">Prerequisites</h3>
<p>We are going to use 2 libraries to achieve this.</p>
<ol>
<li><p><a target="_blank" href="https://github.com/niklasvh/html2canvas">html2canvas</a></p>
</li>
<li><p><a target="_blank" href="https://www.npmjs.com/package/jspdf">jspdf</a></p>
</li>
</ol>
<blockquote>
<p>I am using the following versions for this example taken from my package.json.</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"html2canvas"</span>: <span class="hljs-string">"^1.4.1"</span>,
  <span class="hljs-attr">"jspdf"</span>: <span class="hljs-string">"^2.5.1"</span>
}
</code></pre>
</blockquote>
<h3 id="heading-disclaimer">Disclaimer</h3>
<h4 id="heading-html2canvas"><strong>html2canvas:</strong></h4>
<p>As the name suggests, <code>html2canvas</code> processes and converts a given HTML DOM node into an image using <code>canvas</code> under the hood while keeping the styling intact.</p>
<ol>
<li><p>This library is considered "experimental" (as of writing) by its author and should not be used for production.</p>
</li>
<li><p>Please take a look at the <a target="_blank" href="https://html2canvas.hertzen.com/features">Features list</a> and verify whether this library supports all features that are required by your web app.</p>
</li>
</ol>
<h4 id="heading-jspdf">jsPDF:</h4>
<p>It is used to generate a PDF file based on the image (created from the <code>html2canvas</code> library).</p>
<p>Learn more on their GitHub <a target="_blank" href="https://github.com/parallax/jsPDF">here</a>.</p>
<h3 id="heading-theory">Theory</h3>
<p>The idea is very simple:</p>
<ol>
<li><p>We use html2canvas to generate an <code>canvas</code> object with the table.</p>
</li>
<li><p>We then use the <code>canvas#toDataURL</code> function (<a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toDataURL">MDN</a>) to generate a data URL.</p>
</li>
<li><p>We initialize <code>jsPDF</code>, use <code>addImage</code> function to set the image via data URL.</p>
</li>
<li><p>..</p>
</li>
<li><p>Profit?</p>
</li>
</ol>
<h3 id="heading-implementation">Implementation</h3>
<ol>
<li>Let's create a new Angular project.</li>
</ol>
<pre><code class="lang-bash">$ ng new testpdf
</code></pre>
<ol>
<li>Install both the libraries from NPM</li>
</ol>
<pre><code class="lang-bash">$ npm install jspdf html2canvas
</code></pre>
<ol>
<li>HTML code for Angular component</li>
</ol>
<pre><code class="lang-xml"><span class="hljs-comment">&lt;!-- app.component.html --&gt;</span>

  <span class="hljs-comment">&lt;!-- Export button --&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"my-3"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"btn btn-primary"</span> (<span class="hljs-attr">click</span>)=<span class="hljs-string">"export()"</span>&gt;</span>Export to PDF<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>

  <span class="hljs-comment">&lt;!-- Table --&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">h3</span>&gt;</span>Preview:<span class="hljs-tag">&lt;/<span class="hljs-name">h3</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"table-container"</span>&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-name">table</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"data-table"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"table table-bordered"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">thead</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">tr</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">th</span>&gt;</span>ID<span class="hljs-tag">&lt;/<span class="hljs-name">th</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">th</span>&gt;</span>First name<span class="hljs-tag">&lt;/<span class="hljs-name">th</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">th</span>&gt;</span>Last name<span class="hljs-tag">&lt;/<span class="hljs-name">th</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">tr</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">thead</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">tbody</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">tr</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>3<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>Cartman<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>Whateveryournameis<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">tr</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">tr</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>10<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>Cartman<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>Titi<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">tr</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">tr</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>11<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>Toto<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>Lara<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">tr</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">tr</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>22<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>Luke<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>Yoda<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">tr</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">tr</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>26<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>Foo<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>Moliku<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">tr</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">tr</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>31<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>Luke<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>Someone Last Name<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">tr</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">tr</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>32<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>Batman<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>Lara<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">tr</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">tr</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>37<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>Zed<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>Kyle<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">tr</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">tr</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>39<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>Louis<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>Whateveryournameis<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">tr</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">tr</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>41<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>Superman<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>Yoda<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">tr</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">tr</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>42<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>Batman<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>Moliku<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">tr</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">tr</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>43<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>Zed<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>Lara<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">tr</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">tr</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>46<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>Foo<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>Someone Last Name<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">tr</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">tr</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>47<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>Superman<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>Someone Last Name<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">tr</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">tr</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>48<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>Toto<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>Bar<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">tr</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">tr</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>48<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>Batman<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>Lara<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">tr</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">tr</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>54<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>Luke<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>Bar<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">tr</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">tr</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>62<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>Foo<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>Kyle<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">tr</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">tr</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>80<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>Zed<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>Kyle<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">tr</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">tr</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>87<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>Zed<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>Someone Last Name<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">tr</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">tr</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>87<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>Toto<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>Yoda<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">tr</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">tr</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>88<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>Toto<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>Titi<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">tr</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">tr</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>89<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>Luke<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>Whateveryournameis<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">tr</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">tr</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>97<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>Zed<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>Bar<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">tr</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">tr</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>101<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>Someone First Name<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>Someone Last Name<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">tr</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">tr</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>104<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>Toto<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>Kyle<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">tr</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">tr</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>105<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>Toto<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>Titi<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">tr</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">tr</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>107<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>Cartman<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>Whateveryournameis<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">tr</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">tr</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>107<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>Louis<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>Lara<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">tr</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">tr</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>113<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>Foo<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>Moliku<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">tr</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">tr</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>114<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>Someone First Name<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>Titi<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">tr</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">tr</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>119<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>Zed<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>Someone Last Name<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">tr</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">tr</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>121<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>Toto<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>Bar<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">tr</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">tr</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>131<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>Louis<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>Moliku<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">tr</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">tr</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>133<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>Cartman<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>Moliku<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">tr</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">tr</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>134<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>Someone First Name<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>Someone Last Name<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">tr</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">tr</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>134<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>Toto<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>Whateveryournameis<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">tr</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">tr</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>135<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>Superman<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>Whateveryournameis<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">tr</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">tr</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>144<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>Someone First Name<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>Yoda<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">tr</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">tr</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>154<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>Luke<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>Moliku<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">tr</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">tr</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>154<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>Batman<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>Bar<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">tr</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">tr</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>155<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>Louis<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>Whateveryournameis<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">tr</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">tr</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>156<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>Someone First Name<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>Lara<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">tr</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">tbody</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">table</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
</code></pre>
<ol>
<li>CSS styling (optional)</li>
</ol>
<pre><code class="lang-css"><span class="hljs-comment">/* app.component.css */</span>
<span class="hljs-selector-class">.table-container</span> {
  <span class="hljs-attribute">height</span>: <span class="hljs-number">50vh</span>;
  <span class="hljs-attribute">overflow</span>: auto;
}
</code></pre>
<ol>
<li>Attempt - 1:<br /> <code>exportPDF</code> function</li>
</ol>
<pre><code class="lang-typescript"><span class="hljs-comment">// app.component.ts</span>
<span class="hljs-keyword">export</span>() {
    <span class="hljs-comment">// 1. Get a reference to the DOM node.</span>
    <span class="hljs-keyword">const</span> component = <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">'data-table'</span>)!;
    <span class="hljs-comment">// 2. Get a reference to width, height of DOM node.</span>
    <span class="hljs-keyword">const</span> componentWidth = component.offsetWidth
    <span class="hljs-keyword">const</span> componentHeight = component.offsetHeight

    <span class="hljs-comment">// 3. Generate &lt;canvas&gt; from HTML node.</span>
    html2canvas(component).then(<span class="hljs-function">(<span class="hljs-params">canvas</span>) =&gt;</span> {
      <span class="hljs-comment">// 4. Generate Data URL from &lt;canvas&gt;</span>
      <span class="hljs-keyword">const</span> imgData = canvas.toDataURL(<span class="hljs-string">'image/png'</span>);
      <span class="hljs-comment">// 5. Create an instance of `jsPDF`</span>
        <span class="hljs-comment">// 5.1 `orientation`  -&gt; 'landscape' || 'portrait'</span>
        <span class="hljs-comment">// 5.2 `unit`         -&gt; 'px' (mandatory)</span>
      <span class="hljs-keyword">const</span> pdf = <span class="hljs-keyword">new</span> jsPDF({ orientation: <span class="hljs-string">'landscape'</span>, unit: <span class="hljs-string">'px'</span>});
      <span class="hljs-comment">// 6. Use `addImage` to render generated image into PDF page.</span>
      pdf.addImage(imgData, <span class="hljs-string">'PNG'</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, componentWidth, componentHeight)
      <span class="hljs-comment">// 7. Download PDF</span>
      pdf.save(<span class="hljs-string">'filename.pdf'</span>)
    })
  }
</code></pre>
<h3 id="heading-results-i">Results - I</h3>
<h4 id="heading-expected-output">Expected output:</h4>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1699869769360/be9a111d-80b0-4885-b775-0cc99f78a015.png" alt class="image--center mx-auto" /></p>
<h4 id="heading-actual-output">Actual output:</h4>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1699870070013/8419f72e-22b4-44d5-9eb8-1734676dd8c3.png" alt /></p>
<p>So, what went wrong? There are 3 observations to be made.</p>
<ol>
<li><p>The contents inside the PDF file are distorted.</p>
</li>
<li><p>The content hasn't scaled very well making it blurry (I mean look at the actual result screenshot!).</p>
</li>
<li><p>The third column is entirely missing.</p>
</li>
</ol>
<p>After researching this for a while, I came across <a target="_blank" href="https://stackoverflow.com/questions/36472094/how-to-set-image-to-fit-width-of-the-page-using-jspdf/64761137#64761137"><strong>this</strong></a> StackOverflow post which helped me understand the issue.</p>
<p>My theory is that the width and height properties of the jsPDF's internal page are much smaller than the <code>&lt;table&gt;</code> DOM node. This means we are trying to fit something so big inside something much smaller. So, when we try to fit in a "large" image inside a jsPDF page, it would (obviously) be cut off.</p>
<p>So, how do we fix this?</p>
<p>We need to somehow adjust the width and height properties of the PDF file to match the DOM's width and height.</p>
<p>Let's try this again.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// app.component.ts</span>
<span class="hljs-keyword">export</span>() {
    <span class="hljs-comment">// 1. Get a reference to the DOM node.</span>
    <span class="hljs-keyword">const</span> component = <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">'data-table'</span>)!;
    <span class="hljs-comment">// 2. Get a reference to width, height of DOM node.</span>
    <span class="hljs-keyword">const</span> componentWidth = component.offsetWidth
    <span class="hljs-keyword">const</span> componentHeight = component.offsetHeight

    <span class="hljs-comment">// 3. Generate &lt;canvas&gt; from HTML node.</span>
    html2canvas(component).then(<span class="hljs-function">(<span class="hljs-params">canvas</span>) =&gt;</span> {
      <span class="hljs-comment">// 4. Generate Data URL from &lt;canvas&gt;</span>
      <span class="hljs-keyword">const</span> imgData = canvas.toDataURL(<span class="hljs-string">'image/png'</span>);
      <span class="hljs-comment">// 5. Create an instance of `jsPDF`</span>
        <span class="hljs-comment">// 5.1 `orientation`  -&gt; 'landscape' || 'portrait'</span>
        <span class="hljs-comment">// 5.2 `unit`         -&gt; 'px' (mandatory)</span>
      <span class="hljs-keyword">const</span> pdf = <span class="hljs-keyword">new</span> jsPDF({ orientation: <span class="hljs-string">'landscape'</span>, unit: <span class="hljs-string">'px'</span>});

      <span class="hljs-comment">// NEW - set jsPDF page width/height the same as the DOM node.</span>
      pdf.internal.pageSize.width = componentWidth;
      pdf.internal.pageSize.height = componentHeight;

      <span class="hljs-comment">// 6. Use `addImage` to render generated image into PDF page.</span>
      pdf.addImage(imgData, <span class="hljs-string">'PNG'</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, componentWidth, componentHeight)
      <span class="hljs-comment">// 7. Download PDF</span>
      pdf.save(<span class="hljs-string">'filename.pdf'</span>)
    })
  }
</code></pre>
<h3 id="heading-results-ii">Results - II</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1699870834860/6ee902a8-3b3c-4ad1-8cbb-73334ecba46f.png" alt class="image--center mx-auto" /></p>
<p>Yup! It works.</p>
<blockquote>
<p>If you're wondering whether the "Actual Results" image from above was taken from the output of this code, then you're right :P</p>
</blockquote>
<h3 id="heading-bonus-content">Bonus content</h3>
<p>There is a chance you're seeing large file sizes on the generated PDF files. This could happen if your table uses too much CSS.<br />The solution is to enable <code>compress: true</code> option in <code>jsPDF</code> initialization options like so:</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// app.component.ts</span>
<span class="hljs-keyword">export</span>() {
    ...

    <span class="hljs-keyword">const</span> pdf = <span class="hljs-keyword">new</span> jsPDF({
        ...,
        compress: <span class="hljs-literal">true</span>    <span class="hljs-comment">// reduces PDF file size</span>
    });

    ...
 }
</code></pre>
<h3 id="heading-conclusion">Conclusion</h3>
<p>I hope you've learned something in this post.</p>
<p>If you've liked this post, show your support by using the emojis on the right. It pleases the algorithm :^)</p>
<p>You can also @ me on Mastodon <a target="_blank" href="http://social.linux.pizza/@shanmukhateja">here</a>.</p>
<p>Bye for now.</p>
]]></content:encoded></item><item><title><![CDATA[MyStudio IDE: Sunsetting the project]]></title><description><![CDATA[Hello,
I wanted to make a quick blog post regarding my decision to archive the MyStudio IDE project.
This project has been a huge learning opportunity for me in terms of developing GUI applications, GTK toolkit, and Rust programming language.
The IDE...]]></description><link>https://blog.suryatejak.in/mystudio-ide-sunsetting-the-project</link><guid isPermaLink="true">https://blog.suryatejak.in/mystudio-ide-sunsetting-the-project</guid><category><![CDATA[Rust]]></category><category><![CDATA[Open Source]]></category><category><![CDATA[IDEs]]></category><dc:creator><![CDATA[Surya Teja Karra]]></dc:creator><pubDate>Sat, 11 Nov 2023 15:49:19 GMT</pubDate><content:encoded><![CDATA[<p>Hello,</p>
<p>I wanted to make a quick blog post regarding my decision to archive the MyStudio IDE project.</p>
<p>This project has been a huge learning opportunity for me in terms of developing GUI applications, GTK toolkit, and Rust programming language.</p>
<p>The IDE is more than 2 years old and was a fun little project to pick up and work on on and off. As one might've noticed, there haven't been any new commits since February.</p>
<p>I would like to thank the "<em>Rust India Telegram group"</em> for guiding me with a feature I had originally planned. Their insight into the problem made me realise some major architecture-level problems needed to be fixed.</p>
<p>This means a rewrite of the project was necessary but that requires a deeper understanding of multi-threaded applications and async Rust.</p>
<p>After a lot of R&amp;D on this a few times in the past year, I realised I need to learn more to develop it at a level I can be proud of and make this IDE worth someone's time. I couldn't write a half-baked code on a project I love.</p>
<p>Hence, it is with a heavy heart, this project has reached its end on 11 November 2023. The project's GitHub repo has also been archived.</p>
<p>I might work on something else in the future and it <em>could</em> be an IDE but nothing's set in stone yet.</p>
<p>Thank you for your time!</p>
<p>Happy Diwali to you and your family! 🪔</p>
<p>Bye for now :-)</p>
]]></content:encoded></item><item><title><![CDATA[A quick start guide on dynamic JSON based HTML forms in Angular]]></title><description><![CDATA[Hello,
In this post, I am going to show you an easier way to generate Angular forms (with validations!) using a simple JSON object.
TL;DR
For example, to generate a form for user login, we would provide the following JSON payload.
fields = [
{
    ke...]]></description><link>https://blog.suryatejak.in/a-quick-start-guide-on-dynamic-json-based-html-forms-in-angular</link><guid isPermaLink="true">https://blog.suryatejak.in/a-quick-start-guide-on-dynamic-json-based-html-forms-in-angular</guid><category><![CDATA[Angular]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[forms]]></category><category><![CDATA[formly]]></category><dc:creator><![CDATA[Surya Teja Karra]]></dc:creator><pubDate>Wed, 13 Sep 2023 04:42:38 GMT</pubDate><content:encoded><![CDATA[<p>Hello,</p>
<p>In this post, I am going to show you an easier way to generate Angular forms (with validations!) using a simple JSON object.</p>
<h2 id="heading-tldr">TL;DR</h2>
<p>For example, to generate a form for user login, we would provide the following JSON payload.</p>
<pre><code class="lang-json">fields = [
{
    key: <span class="hljs-string">"email"</span>,         # &lt;-- unique identifier for the field
    type: <span class="hljs-string">"input"</span>,        # &lt;-- Inform library to render a text field
    props: {
        type: <span class="hljs-string">"text"</span>,     # &lt;-- Informs what kind of `&lt;input&gt;`    
        required: <span class="hljs-literal">true</span>,    # &lt;-- equivalent to `Validators.required`
        label: <span class="hljs-string">"Email"</span>,
        placeholder: <span class="hljs-string">"Enter email address"</span> 
    }
},
{
    key: <span class="hljs-string">"password"</span>,         # &lt;-- unique identifier for the field
    type: <span class="hljs-string">"input"</span>,        # &lt;-- Inform library to render a text field
    props: {
        type: <span class="hljs-string">"password"</span>,     # &lt;-- Declaring a password field    
        label: <span class="hljs-string">"Password"</span>,
        required: <span class="hljs-literal">true</span>,    # &lt;-- equivalent to `Validators.required`
        minLength: <span class="hljs-number">6</span>,    # &lt;-- equivalent to `Validators.minLength(6)`
    }
},

]
</code></pre>
<p>It's that simple! Do I have your attention now? Read on.</p>
<h2 id="heading-implementation">Implementation</h2>
<p>Let's create a new Angular project.</p>
<pre><code class="lang-bash">$ ng new json-forms
</code></pre>
<p>We need to install a couple of things:</p>
<ol>
<li><p><code>@ngx-formly/core</code> - This library provides us with a simple way to build JSON based forms</p>
</li>
<li><p>Styling the forms - I am using <a target="_blank" href="https://getbootstrap.com/">Bootstrap</a> as an example.<br /> 1. <code>bootstrap</code> - The will change depending on your chosen UI library.<br /> 2. <code>@ngx-formly/bootstrap</code> - Formly's wrapper UI library for Bootstrap</p>
<blockquote>
<p>There are a <a target="_blank" href="https://formly.dev/#supported-ui-libs">wide variety</a> of UI libraries supported by Formly. Check the link for more info.</p>
</blockquote>
</li>
</ol>
<pre><code class="lang-bash">$ npm install @ngx-formly/core @ngx-formly/bootstrap bootstrap@latest
</code></pre>
<p>We import <code>FormlyModule</code> and <code>FormlyBootstrapModule</code> into <code>app.module.ts</code>.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { NgModule } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/core'</span>;
<span class="hljs-keyword">import</span> { BrowserModule } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/platform-browser'</span>;

<span class="hljs-keyword">import</span> { ConfigOption, FormlyModule } <span class="hljs-keyword">from</span> <span class="hljs-string">'@ngx-formly/core'</span>;
<span class="hljs-keyword">import</span> { FormlyBootstrapModule } <span class="hljs-keyword">from</span> <span class="hljs-string">'@ngx-formly/bootstrap'</span>;

<span class="hljs-keyword">import</span> { AppComponent } <span class="hljs-keyword">from</span> <span class="hljs-string">'./app.component'</span>;
<span class="hljs-keyword">import</span> { FormsModule, ReactiveFormsModule } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/forms'</span>;
<span class="hljs-keyword">import</span> { FormlyController } <span class="hljs-keyword">from</span> <span class="hljs-string">'./formly.controller'</span>;

<span class="hljs-keyword">const</span> formlyConfig: ConfigOption = {
...
};

<span class="hljs-meta">@NgModule</span>({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    FormlyModule.forRoot(formlyConfig),
    FormlyBootstrapModule,
    FormsModule,
    ReactiveFormsModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> AppModule { }
</code></pre>
<p>Let's see what goes into Formly's config file. This is the real deal. We specify all your (global) validation controllers &amp; their error messages, custom Formly components for advanced usages, etc.</p>
<p>I found it easier in my projects to export all things Formly inside a <code>formly.controller.ts</code> file and simply import them into <code>AppModule</code>.</p>
<h3 id="heading-formlycontroller-class">FormlyController class</h3>
<p>This is a custom TypeScript class comprising static functions that help organize the root config of Formly. I found this to be easier when working on other projects.</p>
<p>Here is the final <code>formly.controller.ts</code> that works for setting up the user login page.</p>
<p>Feel free to adapt this to your needs.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// formly.controller.ts</span>
<span class="hljs-keyword">import</span> { Validators } <span class="hljs-keyword">from</span> <span class="hljs-string">"@angular/forms"</span>;
<span class="hljs-keyword">import</span> { TypeOption, ValidationMessageOption, ValidatorOption } <span class="hljs-keyword">from</span> <span class="hljs-string">"@ngx-formly/core/lib/models"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> FormlyController {

  <span class="hljs-comment">// A helper function containing all your required validators.</span>
  <span class="hljs-comment">// Here, you can specify any custom validations that you might need.</span>
  <span class="hljs-keyword">static</span> getValidators(): ValidatorOption[] {
    <span class="hljs-keyword">return</span> [
       {
        name: <span class="hljs-string">'email'</span>,
        validation: <span class="hljs-function">(<span class="hljs-params">control</span>) =&gt;</span> {
          <span class="hljs-keyword">const</span> result = Validators.email(control);
          <span class="hljs-keyword">if</span> (!result) <span class="hljs-keyword">return</span> {email: <span class="hljs-literal">true</span>};
          <span class="hljs-keyword">return</span> <span class="hljs-literal">null</span>
        }
      }
    ];
  }

  <span class="hljs-comment">// Declare a suitable error message for validation errors.</span>
  <span class="hljs-comment">// Note: In my case, I had to declare messages for </span>
  <span class="hljs-comment">// built-in Validation types.</span>
  <span class="hljs-keyword">static</span> getValidationMessages(): ValidationMessageOption[] {
    <span class="hljs-keyword">return</span> [
      {
        name: <span class="hljs-string">'required'</span>,
        message: <span class="hljs-string">'This field is required'</span>
      },
      {
        name: <span class="hljs-string">'minLength'</span>,
        message: <span class="hljs-function">(<span class="hljs-params">_, f</span>) =&gt;</span> <span class="hljs-string">'This field should be atleast '</span>+f.props?.minLength+<span class="hljs-string">' characters'</span>
      },
      {
        name: <span class="hljs-string">'email'</span>,
        message: <span class="hljs-string">'Invalid email address'</span>
      }
    ]
  }

  <span class="hljs-comment">// Here, we declare all your custom Angular components</span>
  <span class="hljs-comment">// that need to render inside &lt;formly-form&gt;.</span>
  <span class="hljs-keyword">static</span> getTypes(): TypeOption[] {
    <span class="hljs-keyword">return</span> []
  }
}
</code></pre>
<p>Let's look at <code>AppModule</code> where we initialize and use this config.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// Declare Formly config</span>
<span class="hljs-keyword">const</span> formlyConfig: ConfigOption = {
  validators: FormlyController.getValidators(),
  types: FormlyController.getTypes(),
  validationMessages: FormlyController.getValidationMessages()
};

<span class="hljs-comment">// Initialize Formly with the config</span>
<span class="hljs-meta">@NgModule</span>({
    ...,
    imports: [
    FormlyModule.forRoot(formlyConfig),
    ...,
    ],
})
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> AppModule { }
</code></pre>
<h3 id="heading-progress-1">Progress #1</h3>
<p>This code should be just enough to render the form. Let's quickly run <code>ng serve</code> on our test project and look at the output.</p>
<h4 id="heading-first-look">First look:</h4>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1689269638583/df687a34-d0d1-4ef0-b07b-59c7b8909846.png" alt="A screenshot of Formly rendering JSON based form. The email address and password fields are visible." class="image--center mx-auto" /></p>
<h4 id="heading-we-test-required-validator-below">We test <code>required</code> validator below:</h4>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1689269825135/c9b59093-3282-4cba-8fac-e2d24343987c.png" alt class="image--center mx-auto" /></p>
<h4 id="heading-we-test-the-invalid-email-validator-and-the-passwords-minlength-validator">We test the "invalid email" validator and the password's "minLength" validator:</h4>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1689271026058/204de305-c475-4273-9d01-9b931225dfa5.png" alt class="image--center mx-auto" /></p>
<p>Awesome! How do I submit this form?</p>
<h3 id="heading-how-to-submit-this-form">How to submit this form?</h3>
<p>There are two ways to do this:</p>
<ol>
<li><p>The easy way would be to add a <code>&lt;button&gt;</code> into the HTML file, bind <code>disabled</code> property (of the button) to the form and set up a click listener that processes your request.</p>
</li>
<li><p>Create a custom button widget in Formly which is reusable.</p>
</li>
</ol>
<blockquote>
<p>I will be covering the second way in another post. The link will be added here once it's up.</p>
</blockquote>
<p>To get started, let's create our HTML file.</p>
<pre><code class="lang-xml"><span class="hljs-comment">&lt;!-- app.component.html --&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"login-container"</span>&gt;</span>

  <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"card"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"card-body"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">h2</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"card-title text-center"</span>&gt;</span>Sign in to continue<span class="hljs-tag">&lt;/<span class="hljs-name">h2</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">form</span> [<span class="hljs-attr">formGroup</span>]=<span class="hljs-string">"formGroup"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">formly-form</span> [<span class="hljs-attr">fields</span>]=<span class="hljs-string">"fields"</span> [<span class="hljs-attr">form</span>]=<span class="hljs-string">"formGroup"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">formly-form</span>&gt;</span>

        <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"w-100 btn btn-primary"</span> [<span class="hljs-attr">disabled</span>]=<span class="hljs-string">"formGroup.invalid"</span> (<span class="hljs-attr">click</span>)=<span class="hljs-string">"processSignIn()"</span>&gt;</span>Sign In<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">form</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
</code></pre>
<p>And here is the source code for the TS file:</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// app.component.ts</span>

<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> AppComponent <span class="hljs-keyword">implements</span> OnInit {
  formGroup = <span class="hljs-keyword">new</span> FormGroup({});

  fields: FormlyFieldConfig[] = [
    {
      key: <span class="hljs-string">"email"</span>,
      <span class="hljs-keyword">type</span>: <span class="hljs-string">"input"</span>,
      props: {
        <span class="hljs-keyword">type</span>: <span class="hljs-string">"email"</span>,
        required: <span class="hljs-literal">true</span>,
        label: <span class="hljs-string">"Email"</span>,
        placeholder: <span class="hljs-string">"Enter email address"</span>
      },
      validators: [<span class="hljs-string">'email'</span>]
    },
    {
      key: <span class="hljs-string">"password"</span>,
      <span class="hljs-keyword">type</span>: <span class="hljs-string">"input"</span>,
      props: {
        <span class="hljs-keyword">type</span>: <span class="hljs-string">"password"</span>,
        label: <span class="hljs-string">"Password"</span>,
        required: <span class="hljs-literal">true</span>,
        minLength: <span class="hljs-number">6</span>,
        placeholder: <span class="hljs-string">'*******'</span>
      }
    },
  ]

  ngOnInit(): <span class="hljs-built_in">void</span> {
  }

  processSignIn() {
    alert(<span class="hljs-string">'Sign in called'</span>);
  }
}
</code></pre>
<p>Lastly, here is the stylesheet used</p>
<pre><code class="lang-css"><span class="hljs-selector-class">.login-container</span> {
  <span class="hljs-attribute">display</span>: flex;
  <span class="hljs-attribute">align-items</span>: center;
  <span class="hljs-attribute">justify-content</span>: center;
  <span class="hljs-attribute">height</span>: <span class="hljs-number">100vh</span>;
}

<span class="hljs-selector-class">.login-container</span> <span class="hljs-selector-class">.card</span> {
  <span class="hljs-attribute">min-width</span>: <span class="hljs-number">600px</span>;
}
</code></pre>
<h2 id="heading-end-result">End Result</h2>
<h4 id="heading-invalid-form-details">Invalid form details</h4>
<p>Notice the <code>button</code> is disabled when validation fails.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1689346943717/8329c304-7e0c-423f-92cc-4c6a57dced0e.png" alt class="image--center mx-auto" /></p>
<h4 id="heading-sign-in-button-is-clicked">Sign In button is clicked.</h4>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1689346846333/7d983670-df1d-4b68-8067-e83e9a3dcbfa.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>I hope you've learned something new in this post. Please feel free to add your thoughts below. I would love to know them!</p>
<p><strong>Formly</strong> is a great tool for cases where you know the web app has so many HTML forms to write.</p>
<p>Check out Formly by visiting their website <a target="_blank" href="https://formly.dev/">here</a>.</p>
<p>If you've liked this post, show your support by using the emojis on the right. It pleases the algorithm :^)</p>
<p>You can also @ me on Mastodon <a target="_blank" href="http://social.linux.pizza/@shanmukhateja">here</a>.</p>
<p>Bye for now.</p>
]]></content:encoded></item><item><title><![CDATA[auto-dark-theme: Let's build a CLI tool for convenience]]></title><description><![CDATA[Hello,
In my previous posts here and here, I talked about creating a Python app that switches my KDE Plasma's theme to light or dark according to the system time. This app uses a config file that lives in $HOME/.config/auto-dark-theme as per XDG user...]]></description><link>https://blog.suryatejak.in/auto-dark-theme-lets-build-a-cli-tool-for-convenience</link><guid isPermaLink="true">https://blog.suryatejak.in/auto-dark-theme-lets-build-a-cli-tool-for-convenience</guid><category><![CDATA[Python]]></category><category><![CDATA[command line]]></category><category><![CDATA[kde]]></category><category><![CDATA[Linux]]></category><category><![CDATA[Developer]]></category><dc:creator><![CDATA[Surya Teja Karra]]></dc:creator><pubDate>Mon, 05 Jun 2023 05:56:41 GMT</pubDate><content:encoded><![CDATA[<p>Hello,</p>
<p>In my previous posts <a target="_blank" href="https://hashnode.com/post/clh4dx437000p09l10u1v1hm2">here</a> and <a target="_blank" href="https://hashnode.com/post/clhmve1ly000109jt5leo1oso">here</a>, I talked about creating a Python app that switches my KDE Plasma's theme to light or dark according to the system time. This app uses a config file that lives in <code>$HOME/.config/auto-dark-theme</code> as per <a target="_blank" href="https://wiki.archlinux.org/title/XDG_Base_Directory">XDG user base specification</a>.</p>
<h2 id="heading-background">Background</h2>
<p>I wanted a way to "force" light or dark theme using my app for testing purposes without having to tweak the config file (and restart the systemd service).</p>
<p>This CLI will help me <em>temporarily</em> switch the theme irrespective of system time until the next trigger.</p>
<h2 id="heading-theory">Theory</h2>
<p>The idea is the CLI should allow users to forcefully switch from a light or dark theme with a simpler syntax.</p>
<p>Next, it should allow the user to view their current config preferably in a table on their terminal.</p>
<p>We should provide a helper shell script to call <code>cli.py</code> without having to use <code>python -m auto-dark-theme.cli</code> every time.</p>
<h3 id="heading-valid-commands">Valid commands</h3>
<ol>
<li><p>It should support a <code>--theme</code> option with <code>-t</code> as its shorthand that allows either <code>light</code> or <code>dark</code> as the supported parameters.</p>
</li>
<li><p>It should support a <code>--list-config</code> option that parse the config file and displays it in a table.</p>
</li>
</ol>
<h2 id="heading-implementation">Implementation</h2>
<blockquote>
<p>You can find the latest code for the CLI on the GitHub repo <a target="_blank" href="https://github.com/shanmukhateja/auto-dark-theme/blob/main/auto-dark-theme/cli.py">here</a>.</p>
</blockquote>
<p>I create a new file <code>cli.py</code> inside <code>auto-dark-theme</code> as shown below:</p>
<pre><code class="lang-bash">App_Root
├── auto-dark-theme
│   ├── cli.py          <span class="hljs-comment"># Hello, there.</span>
│   ├── config.py
│   ├── dbus.py
│   ├── __init__.py
│   ├── __main__.py
│   ├── __pycache__
│   ├── spawn.py
│   └── switcher.py
├── config
│   ├── autodarktheme   <span class="hljs-comment"># Helper shell script.</span>
│   ├── auto-dark-theme.service
│   └── config.sample.ini
├── LICENSE
├── pyvenv.cfg
├── README.md
├── requirements.txt
└── setup.cfg
</code></pre>
<p>I will be using the excellent <a target="_blank" href="https://docs.python.org/3/library/argparse.html">argparse</a> library for this as I have prior experience with it.</p>
<pre><code class="lang-python"><span class="hljs-comment"># cli.py</span>

<span class="hljs-keyword">import</span> argparse
<span class="hljs-comment"># Create an instance of ArgumentParser</span>
parser = argparse.ArgumentParser(
    prog=<span class="hljs-string">'auto-dark-theme'</span>, 
    description=<span class="hljs-string">'Switch your DE theme between light/dark themes acc. to specified time for KDE Plasma'</span>
)
</code></pre>
<p><code>ArgumentParser</code> takes care of managing the CLI interface and setting up some defaults like printing output similar to other CLIs, printing the usage section if user inputs were incorrect and type-casting user inputs (for <code>int</code>, <code>float</code>, etc.) among others.</p>
<pre><code class="lang-python"><span class="hljs-comment"># cli.py</span>

<span class="hljs-comment"># Add arguments supported by CLI</span>
<span class="hljs-comment"># -t || --theme</span>
parser.add_argument(
    <span class="hljs-string">'-t'</span>, <span class="hljs-string">'--theme'</span>, choices=[<span class="hljs-string">'light'</span>, <span class="hljs-string">'dark'</span>], required=<span class="hljs-literal">False</span>, help=<span class="hljs-string">'Forcefully switch between light/dark theme.'</span>)
<span class="hljs-comment"># List config</span>
parser.add_argument(
    <span class="hljs-string">'-l'</span>, <span class="hljs-string">'--list-config'</span>, action=<span class="hljs-string">'store_true'</span>, help=<span class="hljs-string">'List current config.'</span>)
</code></pre>
<p>We use <a target="_blank" href="https://docs.python.org/3/library/argparse.html#argparse.ArgumentParser.add_argument">add_argument</a> here to add arguments supported by our app. In this case, I need to support 2 arguments:</p>
<ol>
<li><p><code>-t</code> or <code>--theme</code> for theme switching</p>
</li>
<li><p><code>-l</code> or <code>--list-config</code> for listing current config</p>
</li>
</ol>
<pre><code class="lang-python"><span class="hljs-comment"># cli.py</span>

<span class="hljs-keyword">from</span> tabulate <span class="hljs-keyword">import</span> tabulate
<span class="hljs-keyword">from</span> .switcher <span class="hljs-keyword">import</span> ThemeSwitcher
<span class="hljs-keyword">from</span> .config <span class="hljs-keyword">import</span> AppConfig

<span class="hljs-comment"># Parse &amp; process</span>
result = parser.parse_args()

<span class="hljs-comment"># Trigger theme change if provided</span>
<span class="hljs-keyword">if</span> result.theme == <span class="hljs-string">'light'</span>:
    ThemeSwitcher().switch_to_light()
<span class="hljs-keyword">if</span> result.theme == <span class="hljs-string">'dark'</span>:
    ThemeSwitcher().switch_to_dark()

<span class="hljs-keyword">if</span> (result.list_config):
    app_config = AppConfig().list_config()
    print(tabulate(app_config, headers=[<span class="hljs-string">'Option'</span>, <span class="hljs-string">'Value'</span>], tablefmt=<span class="hljs-string">"outline"</span>))
</code></pre>
<p>In the above lines lies the core logic for driving the argument parsing and performing tasks according to user input.</p>
<p><a target="_blank" href="https://docs.python.org/3/library/argparse.html#argparse.ArgumentParser.parse_args"><code>parser.parse_args()</code></a> will run the parser on user input and place the resultant values inside the <code>result</code> object.</p>
<p>If <code>-t</code> or <code>--theme</code> are used, the first two <code>if</code> 's will be triggered according to user input.</p>
<p>If <code>-l</code> or <code>--list-config</code> is used, we create an instance of <code>AppConfig</code> which returns a list of items to be shown to the user. I am using <a target="_blank" href="https://pypi.org/project/tabulate/">tabulate</a> Python library for generating pretty tables for this. It's so nice :^)</p>
<p>Lastly, let's create a helper script for running auto-dark-theme CLI without calling the Python file directly.</p>
<pre><code class="lang-bash"><span class="hljs-meta">#!/bin/sh</span>

<span class="hljs-comment"># I know, I know. This is hardcoded and will be fixed </span>
<span class="hljs-comment"># once I figure out setup.py for distribution.</span>
/home/suryateja/Projects/auto-dark-theme/bin/python -m auto-dark-theme.cli <span class="hljs-variable">$1</span>
</code></pre>
<h2 id="heading-result">Result</h2>
<p>Here lies the screenshot of the usage section for the newly created CLI:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1685943212848/62252632-5472-4a8a-b8af-62ec26f6b462.png" alt="Terminal output for auto-dark-theme CLI tool usage" class="image--center mx-auto" /></p>
<p>And the output for the list config argument is shown below:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1685943618154/b691fea0-00b6-499e-ac2d-35d560117f1a.png" alt="Terminal output for auto-dark-theme CLI showing the current config" class="image--center mx-auto" /></p>
<h2 id="heading-next-steps">Next steps</h2>
<p>Create a <code>setup.py</code> file for two reasons:</p>
<ol>
<li><p>Installing the app should be a simple <code>pip install --user auto-dark-theme</code> away!</p>
</li>
<li><p>Get rid of all hardcoded paths that link to my local machine.</p>
</li>
</ol>
<h2 id="heading-conclusion">Conclusion</h2>
<p>I hope you've learned something new in this post. Please feel free to add your thoughts below. I would love to know them!</p>
<p>If you've liked this post, show your support by using the emojis on the right. It pleases the algorithm :^)</p>
<p>You can also @ me on Mastodon <a target="_blank" href="http://social.linux.pizza/@shanmukhateja">here</a>.</p>
<p>Bye for now.</p>
]]></content:encoded></item></channel></rss>