~tristan957/pigeon

b6e265e6d6c3af78fff3be319848cd2e5ba284e0 — Tristan Partin 2 months ago 1f0dbfb master
Backup...
M .gitignore => .gitignore +6 -0
@@ 1,2 1,8 @@
/build
/.vscode
/.clangd
/subprojects/*
!/subprojects/gio.wrap
!/subprojects/gtk.wrap
!/subprojects/gtksourceview.wrap
compile_commands.json

M README.md => README.md +19 -0
@@ 12,3 12,22 @@ to soldiers. In this case, we are sending messages to servers.
## Motivation

I am tired of Electron apps ruling my life.

#0  0x00007ffff7afd896 in gtk_widget_constructed (object=0x5bfac0) at ../subprojects/gtk-3.98.5/gtk/gtkwidget.c:881
#1  0x00007ffff75ef2e7 in g_object_new_internal () at /lib64/libgobject-2.0.so.0
#2  0x00007ffff75f06ad in g_object_new_with_properties () at /lib64/libgobject-2.0.so.0
#3  0x00007ffff75f1331 in g_object_new () at /lib64/libgobject-2.0.so.0
#4  0x0000000000404179 in pigeon_page_new () at ../src/pigeon-page.c:76
#5  0x0000000000403b1d in pigeon_application_window_init (self=0x5522e0) at ../src/pigeon-application-window.c:136
#6  0x00007ffff7609498 in g_type_create_instance () at /lib64/libgobject-2.0.so.0
#7  0x00007ffff75ef205 in g_object_new_internal () at /lib64/libgobject-2.0.so.0
#8  0x00007ffff75f0fac in g_object_new_valist () at /lib64/libgobject-2.0.so.0
#9  0x00007ffff75f130d in g_object_new () at /lib64/libgobject-2.0.so.0
#10 0x0000000000403f80 in pigeon_application_window_new (app=<optimized out>) at ../src/pigeon-application-window.c:145
#11 0x0000000000403981 in pigeon_application_activate (app=<optimized out>) at ../src/pigeon-application.c:41
#12 0x00007ffff75e9ae6 in _g_closure_invoke_va () at /lib64/libgobject-2.0.so.0
#13 0x00007ffff76026e9 in g_signal_emit_valist () at /lib64/libgobject-2.0.so.0
#14 0x00007ffff7602c63 in g_signal_emit () at /lib64/libgobject-2.0.so.0
#15 0x00007ffff7714518 in g_application_real_local_command_line () at /lib64/libgio-2.0.so.0
#16 0x00007ffff7714696 in g_application_run () at /lib64/libgio-2.0.so.0
#17 0x0000000000403611 in main (argc=1, argv=0x7fffffffccc8) at ../src/main.c:19

M data/io.partin.tristan.Pigeon.appdata.xml.in => data/io.partin.tristan.Pigeon.appdata.xml.in +2 -3
@@ 1,8 1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<?xml version="1.1" encoding="UTF-8"?>
<component type="desktop">
	<id>io.partin.tristan.Pigeon.desktop</id>
	<metadata_license>CC0-1.0</metadata_license>
	<project_license>GPL-3.0-or-later</project_license>
	<description>
	</description>
	<description></description>
</component>

M data/io.partin.tristan.Pigeon.desktop.in => data/io.partin.tristan.Pigeon.desktop.in +1 -1
@@ 1,5 1,5 @@
[Desktop Entry]
Name=pigeon
Name=Pigeon
Exec=pigeon
Terminal=false
Type=Application

M data/io.partin.tristan.Pigeon.gschema.xml => data/io.partin.tristan.Pigeon.gschema.xml +2 -3
@@ 1,5 1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<?xml version="1.1" encoding="UTF-8"?>
<schemalist gettext-domain="pigeon">
	<schema id="io.partin.tristan.Pigeon" path="/io/partin/tristan/Pigeon/">
	</schema>
	<schema id="io.partin.tristan.Pigeon" path="/io/partin/tristan/Pigeon/"></schema>
</schemalist>

M data/pigeon-resources.gresource.xml => data/pigeon-resources.gresource.xml +6 -1
@@ 1,6 1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<?xml version="1.1" encoding="UTF-8"?>
<gresources>
  <gresource prefix="/io/partin/tristan/Pigeon">
    <file preprocess="xml-stripblanks">ui/pigeon-application-window.ui</file>
    <file preprocess="xml-stripblanks">ui/pigeon-page.ui</file>
    <file preprocess="xml-stripblanks">ui/pigeon-page-tab.ui</file>
    <file preprocess="xml-stripblanks">ui/pigeon-request-explorer.ui</file>
    <!-- <file preprocess="xml-stripblanks">ui/pigeon-response-attributes.ui</file> -->
    <file preprocess="xml-stripblanks">ui/pigeon-shortcuts-window.ui</file>
  </gresource>
</gresources>

M data/ui/pigeon-application-window.ui => data/ui/pigeon-application-window.ui +104 -23
@@ 1,25 1,106 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
  <requires lib="gtk+" version="3.24"/>
    <template class="PigeonApplicationWindow" parent="GtkApplicationWindow">
      <property name="default-width">600</property>
      <property name="default-height">300</property>
      <child type="titlebar">
        <object class="GtkHeaderBar" id="header_bar">
          <property name="visible">True</property>
          <property name="show-close-button">True</property>
          <property name="title">Hello, World!</property>
        </object>
      </child>
      <child>
        <object class="GtkLabel" id="label">
          <property name="label">Hello, World!</property>
          <property name="visible">True</property>
          <attributes>
            <attribute name="weight" value="bold"/>
            <attribute name="scale" value="2"/>
          </attributes>
        </object>
      </child>
    </template>
  </interface>
  <requires lib="gtk" version="3.98" />
  <menu id="application_menu_model">
    <section>
      <item>
        <attribute name="label" translatable="yes">Preferences</attribute>
        <attribute name="action">app.preferences</attribute>
      </item>
      <item>
        <attribute name="label" translatable="yes">Keyboard Shortcuts</attribute>
        <attribute name="action">win.shortcuts</attribute>
      </item>
      <item>
        <attribute name="label" translatable="yes">About Pigeon</attribute>
        <attribute name="action">app.about</attribute>
      </item>
      <item>
        <attribute name="label" translatable="yes">Quit Pigeon</attribute>
        <attribute name="action">app.quit</attribute>
      </item>
    </section>
  </menu>
  <object class="GtkPopoverMenu" id="application_menu">
    <property name="menu_model">application_menu_model</property>
  </object>
  <template class="PigeonApplicationWindow" parent="GtkApplicationWindow">
    <property name="default-width">600</property>
    <property name="default-height">300</property>
    <child type="titlebar">
      <object class="GtkHeaderBar" id="header_bar">
        <property name="show-title-buttons">True</property>
        <child type="start">
          <object class="GtkButton">
            <property name="icon_name">list-add-symbolic</property>
            <signal name="clicked" handler="new_request_cb" object="PigeonApplicationWindow" swapped="no" />
            <child>
              <object class="GtkShortcutController">
                <property name="scope">managed</property>
                <child>
                  <object class="GtkShortcut">
                    <property name="trigger">&lt;Control&gt;t</property>
                    <property name="action">activate</property>
                  </object>
                </child>
              </object>
            </child>
          </object>
        </child>
        <child type="end">
          <object class="GtkMenuButton">
            <property name="icon_name">open-menu-symbolic</property>
            <property name="popover">application_menu</property>
          </object>
        </child>
      </object>
    </child>
    <child>
      <object class="GtkNotebook" id="tab_notebook">
        <property name="scrollable">True</property>
        <property name="show_tabs">True</property>
      </object>
    </child>
    <child>
      <object class="GtkShortcutController">
        <property name="scope">global</property>
        <child>
          <object class="GtkShortcut">
            <property name="trigger">&lt;Control&gt;comma</property>
            <property name="action">action(app.preferences)</property>
          </object>
        </child>
        <child>
          <object class="GtkShortcut">
            <property name="trigger">&lt;Control&gt;k</property>
            <property name="action">action(win.shortcuts)</property>
          </object>
        </child>
        <child>
          <object class="GtkShortcut">
            <property name="trigger">&lt;Control&gt;b</property>
            <property name="action">action(app.about)</property>
          </object>
        </child>
        <child>
          <object class="GtkShortcut">
            <property name="trigger">&lt;Control&gt;q</property>
            <property name="action">action(app.quit)</property>
          </object>
        </child>
        <child>
          <object class="GtkShortcut">
            <property name="trigger">&lt;Alt&gt;Left</property>
            <property name="action">action(win.shift-left)</property>
          </object>
        </child>
        <child>
          <object class="GtkShortcut">
            <property name="trigger">&lt;Alt&gt;Right</property>
            <property name="action">action(win.shift-right)</property>
          </object>
        </child>
      </object>
    </child>
  </template>
</interface>

A data/ui/pigeon-page-tab.ui => data/ui/pigeon-page-tab.ui +44 -0
@@ 0,0 1,44 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
	<requires lib="gtk" version="3.98" />
	<template class="PigeonPageTab" parent="GtkBox">
		<child>
			<object class="GtkStack" id="label_stack">
				<property name="transition_type">crossfade</property>
				<child>
					<object class="GtkStackPage">
						<property name="name">readonly</property>
						<property name="child">
							<object class="GtkBox">
								<child>
									<object class="GtkLabel" id="label">
										<property name="has_tooltip">True</property>
										<property name="halign">start</property>
										<property name="use_markup">True</property>
									</object>
								</child>
							</object>
						</property>
					</object>
				</child>
				<child>
					<object class="GtkStackPage">
						<property name="name">edit</property>
						<property name="child">
							<object class="GtkText" id="label_entry"></object>
						</property>
					</object>
				</child>
			</object>
		</child>
		<child>
			<object class="GtkButton" id="close_button">
				<property name="icon_name">window-close-symbolic</property>
				<property name="action_name">win.close-tab</property>
				<style>
					<class name="flat" />
				</style>
			</object>
		</child>
	</template>
</interface>

A data/ui/pigeon-page.ui => data/ui/pigeon-page.ui +425 -0
@@ 0,0 1,425 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
	<requires lib="gtk" version="3.98" />
	<menu id="request_menu_model">
		<section>
			<item>
				<attribute name="label" translatable="yes">Save</attribute>
				<attribute name="action">win.save-tab</attribute>
			</item>
			<item>
				<attribute name="label" translatable="yes">Save as...</attribute>
				<attribute name="action">win.save-tab-as</attribute>
			</item>
		</section>
		<section>
			<item>
				<attribute name="label" translatable="yes">Close</attribute>
				<attribute name="action">win.close-tab</attribute>
			</item>
		</section>
	</menu>
	<object class="GtkPopoverMenu" id="request_menu">
		<property name="menu-model">request_menu_model</property>
	</object>
	<object class="GtkListStore" id="header_store">
		<columns>
			<column type="gboolean" />
			<column type="gchararray" />
			<column type="gchararray" />
			<column type="gchararray" />
		</columns>
		<data>
			<row>
				<col id="0">False</col>
				<col id="1">"Hello"</col>
				<col id="2">"World"</col>
				<col id="3">"Fuck"</col>
			</row>
			<row>
				<col id="0">False</col>
				<col id="1">"Hello"</col>
				<col id="2">"World"</col>
				<col id="3">"Fuck"</col>
			</row>
		</data>
	</object>
	<object class="GtkListStore" id="header_key_store">
		<columns>
			<column type="gchararray" />
		</columns>
		<data>
			<row>
				<col id="0">Accept</col>
			</row>
			<row>
				<col id="0">Accept-Charset</col>
			</row>
			<row>
				<col id="0">Accept-Datetime</col>
			</row>
			<row>
				<col id="0">Accept-Encoding</col>
			</row>
			<row>
				<col id="0">Accept-Language</col>
			</row>
			<row>
				<col id="0">Access-Control-Request-Headers</col>
			</row>
			<row>
				<col id="0">Access-Control-Request-Method</col>
			</row>
			<row>
				<col id="0">A-IM</col>
			</row>
			<row>
				<col id="0">Authorization</col>
			</row>
			<row>
				<col id="0">Cache-Control</col>
			</row>
			<row>
				<col id="0">Connection</col>
			</row>
			<row>
				<col id="0">Content-Encoding</col>
			</row>
			<row>
				<col id="0">Content-Length</col>
			</row>
			<row>
				<col id="0">Content-MD</col>
			</row>
			<row>
				<col id="0">Content-Type</col>
			</row>
			<row>
				<col id="0">Cookie</col>
			</row>
			<row>
				<col id="0">Date</col>
			</row>
			<row>
				<col id="0">DNT</col>
			</row>
			<row>
				<col id="0">Expect</col>
			</row>
			<row>
				<col id="0">Forwarded</col>
			</row>
			<row>
				<col id="0">From</col>
			</row>
			<row>
				<col id="0">Front-End-Https</col>
			</row>
			<row>
				<col id="0">Host</col>
			</row>
			<row>
				<col id="0">HTTP</col>
			</row>
			<row>
				<col id="0">If-Match</col>
			</row>
			<row>
				<col id="0">If-Modified-Since</col>
			</row>
			<row>
				<col id="0">If-None-Match</col>
			</row>
			<row>
				<col id="0">If-Range</col>
			</row>
			<row>
				<col id="0">If-Unmodified-Since</col>
			</row>
			<row>
				<col id="0">Max-Forwards</col>
			</row>
			<row>
				<col id="0">Origin</col>
			</row>
			<row>
				<col id="0">Pragma</col>
			</row>
			<row>
				<col id="0">Proxy-Authorization</col>
			</row>
			<row>
				<col id="0">Proxy-Connection</col>
			</row>
			<row>
				<col id="0">Range</col>
			</row>
			<row>
				<col id="0">Referer</col>
			</row>
			<row>
				<col id="0">Save-Data</col>
			</row>
			<row>
				<col id="0">TE</col>
			</row>
			<row>
				<col id="0">Trailer</col>
			</row>
			<row>
				<col id="0">Transfer-Encoding</col>
			</row>
			<row>
				<col id="0">Upgrade</col>
			</row>
			<row>
				<col id="0">Upgrade-Insecure-Requests</col>
			</row>
			<row>
				<col id="0">User-Agent</col>
			</row>
			<row>
				<col id="0">X-ATT-DeviceId</col>
			</row>
			<row>
				<col id="0">X-Correlation-ID</col>
			</row>
			<row>
				<col id="0">X-Csrf-Token</col>
			</row>
			<row>
				<col id="0">X-Forwarded-For</col>
			</row>
			<row>
				<col id="0">X-Forwarded-Host</col>
			</row>
			<row>
				<col id="0">X-Forwarded-Proto</col>
			</row>
			<row>
				<col id="0">X-Http-Method-Override</col>
			</row>
			<row>
				<col id="0">X-Requested-With</col>
			</row>
			<row>
				<col id="0">X-Request-ID</col>
			</row>
			<row>
				<col id="0">X-UIDH</col>
			</row>
			<row>
				<col id="0">X-Wap-Profile</col>
			</row>
		</data>
	</object>
	<template class="PigeonPage" parent="GtkGrid">
		<property name="margin-top">5</property>
		<property name="margin-bottom">5</property>
		<property name="margin-start">5</property>
		<property name="margin-end">5</property>
		<property name="row-spacing">5</property>
		<child>
			<object class="GtkBox">
				<property name="spacing">5</property>
				<property name="hexpand">True</property>
				<child>
					<object class="GtkBox">
						<child>
							<object class="GtkComboBoxText" id="verb_dropdown">
								<property name="has_entry">True</property>
								<property name="hexpand">False</property>
								<property name="active">0</property>
								<items>
									<item translatable="yes" id="GET">GET</item>
									<item translatable="yes" id="HEAD">HEAD</item>
									<item translatable="yes" id="POST">POST</item>
									<item translatable="yes" id="PUT">PUT</item>
									<item translatable="yes" id="DELETE">DELETE</item>
									<item translatable="yes" id="CONNECT">CONNECT</item>
									<item translatable="yes" id="OPTIONS">OPTIONS</item>
									<item translatable="yes" id="TRACE">TRACE</item>
									<item translatable="yes" id="PATCH">PATCH</item>
								</items>
							</object>
						</child>
						<child>
							<object class="GtkEntry" id="resource_entry">
								<property name="hexpand">True</property>
							</object>
						</child>
						<child>
							<object class="GtkButton">
								<property name="label" translatable="yes">Execute</property>
								<style>
									<class name="suggested-action" />
								</style>
							</object>
						</child>
						<style>
							<class name="linked" />
						</style>
					</object>
				</child>
				<child>
					<object class="GtkMenuButton">
						<property name="icon-name">view-more-symbolic</property>
						<property name="popover">request_menu</property>
					</object>
				</child>
				<layout>
					<property name="left-attach">0</property>
					<property name="top-attach">0</property>
				</layout>
			</object>
		</child>
		<child>
			<object class="GtkStackSwitcher">
				<property name="stack">request_attributes_stack</property>
				<property name="halign">center</property>
				<layout>
					<property name="top-attach">1</property>
					<property name="left-attach">0</property>
				</layout>
			</object>
		</child>
		<child>
			<object class="GtkStack" id="request_attributes_stack">
				<property name="hhomogeneous">True</property>
				<property name="vhomogeneous">True</property>
				<property name="transition-type">slide-left-right</property>
				<child>
					<object class="GtkStackPage">
						<property name="name">headers</property>
						<property name="title" translatable="yes">Headers</property>
						<property name="child">
							<object class="GtkTreeView">
								<property name="enable-grid-lines">both</property>
								<property name="fixed-height-mode">True</property>
								<property name="headers-clickable">False</property>
								<property name="model">header_store</property>
								<property name="show-expanders">False</property>
								<child internal-child="selection">
									<object class="GtkTreeSelection" id="selection">
										<property name="mode">none</property>
									</object>
								</child>
								<child>
									<object class="GtkTreeViewColumn">
										<property name="sizing">fixed</property>
										<property name="title">Enabled</property>
										<child>
											<object class="GtkCellRendererToggle"></object>
										</child>
									</object>
								</child>
								<child>
									<object class="GtkTreeViewColumn">
										<property name="sizing">fixed</property>
										<property name="title">Key</property>
										<child>
											<object class="GtkCellRendererCombo">
												<property name="editable">True</property>
												<property name="has-entry">True</property>
												<property name="model">header_key_store</property>
												<property name="single-paragraph-mode">True</property>
												<property name="text-column">0</property>
											</object>
											<attributes>
												<attribute name="text">1</attribute>
											</attributes>
										</child>
									</object>
								</child>
								<child>
									<object class="GtkTreeViewColumn">
										<property name="sizing">fixed</property>
										<property name="title">Value</property>
										<child>
											<object class="GtkCellRendererText">
												<property name="editable">True</property>
												<property name="single-paragraph-mode">True</property>
											</object>
											<attributes>
												<attribute name="text">2</attribute>
											</attributes>
										</child>
									</object>
								</child>
								<child>
									<object class="GtkTreeViewColumn">
										<property name="sizing">fixed</property>
										<property name="title">Description</property>
										<child>
											<object class="GtkCellRendererText">
												<property name="editable">True</property>
												<property name="single-paragraph-mode">True</property>
											</object>
											<attributes>
												<attribute name="text">3</attribute>
											</attributes>
										</child>
									</object>
								</child>
							</object>
						</property>
					</object>
				</child>
				<child>
					<object class="GtkStackPage">
						<property name="name">body</property>
						<property name="title" translatable="yes">Body</property>
						<property name="child">
							<object class="GtkSourceView" id="request_body_source_view">
								<property name="auto-indent">True</property>
								<property name="highlight-current-line">True</property>
								<property name="tab-width">2</property>
								<property name="indent-width">2</property>
								<property name="indent-on-tab">True</property>
								<property name="show-line-numbers">True</property>
								<property name="show-line-marks">True</property>
								<property name="smart-backspace">True</property>
								<property name="smart-home-end">before</property>
								<!-- <child internal-child="buffer">
									<object class="GtkSourceBuffer" id="source_buffer">
										<property name="can-redo">True</property>
										<property name="can-undo">True</property>
										<property name="highlight-matching-brackets">True</property>
										<property name="highlight-syntax">True</property>
									</object>
								</child> -->
							</object>
						</property>
					</object>
				</child>
				<layout>
					<property name="top-attach">2</property>
					<property name="left-attach">0</property>
				</layout>
			</object>
		</child>
		<child>
			<object class="GtkShortcutController">
				<property name="scope">global</property>
				<child>
					<object class="GtkShortcut">
						<property name="trigger">&lt;Control&gt;s</property>
						<property name="action">action(win.save-tab)</property>
					</object>
				</child>
				<child>
					<object class="GtkShortcut">
						<property name="trigger">&lt;Control&gt;&lt;Shift&gt;s</property>
						<property name="action">action(win.save-tab-as)</property>
					</object>
				</child>
				<child>
					<object class="GtkShortcut">
						<property name="trigger">&lt;Control&gt;d</property>
						<property name="action">action(win.close-tab)</property>
					</object>
				</child>
			</object>
		</child>
	</template>
</interface>

A data/ui/pigeon-request-explorer.ui => data/ui/pigeon-request-explorer.ui +20 -0
@@ 0,0 1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
	<requires lib="gtk" version="3.98" />
	<template class="PigeonRequestExplorer" parent="GtkBox">
		<property name="orientation">vertical</property>
		<child>
			<object class="GtkScrolledWindow">
				<property name="hscrollbar_policy">automatic</property>
				<property name="vscrollbar_policy">automatic</property>
				<child>
					<object class="GtkViewport">
						<child>
							<object class="GtkListView" id="request_tree"></object>
						</child>
					</object>
				</child>
			</object>
		</child>
	</template>
</interface>

A data/ui/pigeon-response-attributes.ui => data/ui/pigeon-response-attributes.ui +1 -0
@@ 0,0 1,1 @@
<?xml version="1.0" encoding="UTF-8"?>

A data/ui/pigeon-shortcuts-window.ui => data/ui/pigeon-shortcuts-window.ui +105 -0
@@ 0,0 1,105 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
	<requires lib="gtk" version="3.98" />
	<object class="GtkShortcutsWindow" id="shortcuts_window">
		<property name="modal">True</property>
		<child>
			<object class="GtkShortcutsSection">
				<property name="section-name">shortcuts</property>
				<child>
					<object class="GtkShortcutsGroup">
						<property name="title" translatable="yes">Application</property>
						<child>
							<object class="GtkShortcutsShortcut">
								<property name="accelerator">&lt;Control&gt;comma</property>
								<property name="title" translatable="yes">Open preferences</property>
							</object>
						</child>
						<child>
							<object class="GtkShortcutsShortcut">
								<property name="accelerator">&lt;Control&gt;k</property>
								<property name="title" translatable="yes">Open shortcuts window</property>
							</object>
						</child>
						<child>
							<object class="GtkShortcutsShortcut">
								<property name="accelerator">&lt;Control&gt;b</property>
								<property name="title" translatable="yes">Open about</property>
							</object>
						</child>
						<child>
							<object class="GtkShortcutsShortcut">
								<property name="accelerator">&lt;Control&gt;q</property>
								<property name="title" translatable="yes">Quit application</property>
							</object>
						</child>
					</object>
				</child>
				<child>
					<object class="GtkShortcutsGroup">
						<property name="title" translatable="yes">Tab Management</property>
						<child>
							<object class="GtkShortcutsShortcut">
								<property name="shortcut-type">gesture-two-finger-swipe-right</property>
								<property name="title" translatable="yes">Switch to the next tab</property>
							</object>
						</child>
						<child>
							<object class="GtkShortcutsShortcut">
								<property name="shortcut-type">gesture-two-finger-swipe-left</property>
								<property name="title" translatable="yes">Switch to the previous tab</property>
							</object>
						</child>
						<child>
							<object class="GtkShortcutsShortcut">
								<property name="shortcut-type">gesture-two-finger-swipe-right</property>
								<property name="title" translatable="yes">Switch to the next tab</property>
							</object>
						</child>
						<child>
							<object class="GtkShortcutsShortcut">
								<property name="accelerator">&lt;Alt&gt;leftarrow</property>
								<property name="title" translatable="yes">Switch to the previous tab</property>
							</object>
						</child>
						<child>
							<object class="GtkShortcutsShortcut">
								<property name="accelerator">&lt;Alt&gt;rightarrow</property>
								<property name="title" translatable="yes">Switch to the next tab</property>
							</object>
						</child>
						<child>
							<object class="GtkShortcutsShortcut">
								<property name="accelerator">&lt;Control&gt;t</property>
								<property name="title" translatable="yes">Create new tab</property>
							</object>
						</child>
						<child>
							<object class="GtkShortcutsShortcut">
								<property name="accelerator">&lt;Control&gt;d</property>
								<property name="title" translatable="yes">Close current tab</property>
							</object>
						</child>
					</object>
				</child>
				<child>
					<object class="GtkShortcutsGroup">
						<property name="title" translatable="yes">Request Management</property>
						<child>
							<object class="GtkShortcutsShortcut">
								<property name="accelerator">&lt;Control&gt;s</property>
								<property name="title" translatable="yes">Save request</property>
							</object>
						</child>
						<child>
							<object class="GtkShortcutsShortcut">
								<property name="accelerator">&lt;Control&gt;&lt;Shift&gt;s</property>
								<property name="title" translatable="yes">Save request as</property>
							</object>
						</child>
					</object>
				</child>
			</object>
		</child>
	</object>
</interface>

M dist/flatpak/io.partin.tristan.Pigeon.json => dist/flatpak/io.partin.tristan.Pigeon.json +1 -1
@@ 1,7 1,7 @@
{
    "app-id" : "io.partin.tristan.Pigeon",
    "runtime" : "org.gnome.Platform",
    "runtime-version" : "3.34",
    "runtime-version" : "3.36",
    "sdk" : "org.gnome.Sdk",
    "command" : "pigeon",
    "finish-args" : [

M meson.build => meson.build +2 -1
@@ 15,7 15,8 @@ i18n = import('i18n')
gnome = import('gnome')

libgio_dep = dependency('gio-2.0', version: '>= 2.64.3', required: true, fallback: ['gio', 'libgio_dep'])
libgtk_dep = dependency('gtk+-3.0', version: '>= 3.24.20', required: true, fallback: ['gtk', 'libgtk_dep'])
libgtk_dep = dependency('gtk4', version: '>= 3.99.0', required: true, fallback: ['gtk', 'libgtk_dep'])
gtksource_dep = dependency('gtksourceview-4', version: '>= 4.6.2', fallback: ['gtksourceview', 'gtksource_dep'])
libcurl_dep = dependency('libcurl', version: '>= 7.69.1', required: true)

subdir('data')

M src/config.h.in => src/config.h.in +2 -0
@@ 1,5 1,7 @@
#pragma once

#define G_LOG_DOMAIN "@log_domain@"

#include <gtk/gtk.h>

#define PROJECT_NAME	"@project_name@"

M src/meson.build => src/meson.build +7 -1
@@ 1,4 1,5 @@
config_h = configuration_data({
  'log_domain': 'Pigeon',
  'project_name': 'Pigeon',
  'project_version': meson.project_version(),
  'project_license': 'GTK_LICENSE_GPL_3_0',


@@ 19,16 20,21 @@ pigeon_sources = [
  'main.c',
  'pigeon-application.c',
  'pigeon-application-window.c',
  'pigeon-page.c',
  'pigeon-page-tab.c',
  'pigeon-request-explorer.c',
]

pigeon_deps = [
  libgio_dep,
  libgtk_dep,
  libcurl_dep,
  gtksource_dep,
]

pigeon_c_args = [
  '-DG_LOG_DOMAIN="Pigeon"',
  # Forking g_clear_pointer
  '-Wno-incompatible-pointer-types',
]

if get_option('buildtype') == 'release'

M src/pigeon-application-window.c => src/pigeon-application-window.c +108 -0
@@ 1,34 1,142 @@
#include "config.h"

#include <glib-object.h>
#include <gtk/gtk.h>
#include <math.h>

#include "pigeon-application-window.h"
#include "pigeon-application.h"
#include "pigeon-page-tab.h"
#include "pigeon-page.h"

struct _PigeonApplicationWindow
{
	GtkApplicationWindow parent_object;

	unsigned int num_untitled_pages;
};

typedef struct PigeonApplicationWindowPrivate
{
	GtkNotebook *tab_notebook;
} PigeonApplicationWindowPrivate;

G_DEFINE_TYPE_WITH_PRIVATE(
	PigeonApplicationWindow, pigeon_application_window, GTK_TYPE_APPLICATION_WINDOW)

static void
win_shortcuts(
	GtkWidget *widget, G_GNUC_UNUSED const char *action_name, G_GNUC_UNUSED GVariant *parameter)
{
	PigeonApplicationWindow *self = PIGEON_APPLICATION_WINDOW(widget);

	g_autoptr(GtkBuilder) builder
		= gtk_builder_new_from_resource("/io/partin/tristan/Pigeon/ui/pigeon-shortcuts-window.ui");
	GtkShortcutsWindow *shortcuts_window
		= GTK_SHORTCUTS_WINDOW(gtk_builder_get_object(builder, "shortcuts_window"));
	gtk_window_set_transient_for(GTK_WINDOW(shortcuts_window), GTK_WINDOW(self));

	gtk_window_present(GTK_WINDOW(shortcuts_window));
}

static void
win_save_tab(G_GNUC_UNUSED GtkWidget *widget, G_GNUC_UNUSED const char *action_name,
	G_GNUC_UNUSED GVariant *parameter)
{
	g_print("save\n");
}

static void
win_save_tab_as(G_GNUC_UNUSED GtkWidget *widget, G_GNUC_UNUSED const char *action_name,
	G_GNUC_UNUSED GVariant *parameter)
{
	g_print("save as\n");
}

static void
win_close_tab(G_GNUC_UNUSED GtkWidget *widget, G_GNUC_UNUSED const char *action_name,
	G_GNUC_UNUSED GVariant *parameter)
{
	PigeonApplicationWindow *self		 = PIGEON_APPLICATION_WINDOW(widget);
	PigeonApplicationWindowPrivate *priv = pigeon_application_window_get_instance_private(self);

	const int page_num = gtk_notebook_get_current_page(priv->tab_notebook);
	gtk_notebook_remove_page(priv->tab_notebook, page_num);
}

static void
win_shift_left(G_GNUC_UNUSED GtkWidget *widget, G_GNUC_UNUSED const char *action_name,
	G_GNUC_UNUSED GVariant *parameter)
{
	PigeonApplicationWindow *self		 = PIGEON_APPLICATION_WINDOW(widget);
	PigeonApplicationWindowPrivate *priv = pigeon_application_window_get_instance_private(self);

	const int page_num	  = gtk_notebook_get_current_page(priv->tab_notebook);
	const int total_pages = gtk_notebook_get_n_pages(priv->tab_notebook);
	if (total_pages > 0 && page_num > 0)
		gtk_notebook_set_current_page(priv->tab_notebook, page_num - 1);
}

static void
win_shift_right(G_GNUC_UNUSED GtkWidget *widget, G_GNUC_UNUSED const char *action_name,
	G_GNUC_UNUSED GVariant *parameter)
{
	PigeonApplicationWindow *self		 = PIGEON_APPLICATION_WINDOW(widget);
	PigeonApplicationWindowPrivate *priv = pigeon_application_window_get_instance_private(self);

	const int page_num	  = gtk_notebook_get_current_page(priv->tab_notebook);
	const int total_pages = gtk_notebook_get_n_pages(priv->tab_notebook);
	if (total_pages > 0 && page_num < total_pages - 1)
		gtk_notebook_set_current_page(priv->tab_notebook, page_num + 1);
}

static void
new_request_cb(G_GNUC_UNUSED GtkButton *button, gpointer user_data)
{
	PigeonApplicationWindow *self		 = PIGEON_APPLICATION_WINDOW(user_data);
	PigeonApplicationWindowPrivate *priv = pigeon_application_window_get_instance_private(self);

	GString *label = g_string_new("Untitled ");
	g_string_append_printf(label, "%u", self->num_untitled_pages);
	PigeonPage *page		= pigeon_page_new();
	PigeonPageTab *page_tab = pigeon_page_tab_new(label->str);
	gtk_notebook_append_page(priv->tab_notebook, GTK_WIDGET(page), GTK_WIDGET(page_tab));
	self->num_untitled_pages++;
}

static void
pigeon_application_window_class_init(PigeonApplicationWindowClass *klass)
{
	GtkWidgetClass *wid_class = GTK_WIDGET_CLASS(klass);

	gtk_widget_class_set_template_from_resource(
		wid_class, "/io/partin/tristan/Pigeon/ui/pigeon-application-window.ui");
	gtk_widget_class_bind_template_child_private(wid_class, PigeonApplicationWindow, tab_notebook);
	gtk_widget_class_bind_template_callback(wid_class, new_request_cb);

	gtk_widget_class_install_action(wid_class, "win.shift-left", NULL, win_shift_left);
	gtk_widget_class_install_action(wid_class, "win.shift-right", NULL, win_shift_right);
	gtk_widget_class_install_action(wid_class, "win.shortcuts", NULL, win_shortcuts);
	gtk_widget_class_install_action(wid_class, "win.save-tab", NULL, win_save_tab);
	gtk_widget_class_install_action(wid_class, "win.save-tab-as", NULL, win_save_tab_as);
	gtk_widget_class_install_action(wid_class, "win.close-tab", NULL, win_close_tab);
}

static void
pigeon_application_window_init(PigeonApplicationWindow *self)
{
	PigeonApplicationWindowPrivate *priv = pigeon_application_window_get_instance_private(self);

	gtk_widget_init_template(GTK_WIDGET(self));

	self->num_untitled_pages = 0;

	GString *label = g_string_new("Untitled ");
	g_string_append_printf(label, "%u", self->num_untitled_pages);
	PigeonPage *page		= pigeon_page_new();
	PigeonPageTab *page_tab = pigeon_page_tab_new(label->str);
	gtk_notebook_append_page(priv->tab_notebook, GTK_WIDGET(page), GTK_WIDGET(page_tab));
	self->num_untitled_pages++;
}

PigeonApplicationWindow *

M src/pigeon-application.c => src/pigeon-application.c +39 -4
@@ 4,6 4,7 @@

#include <curl/curl.h>
#include <glib-object.h>
#include <gtksourceview/gtksource.h>

#include "pigeon-application-window.h"
#include "pigeon-application.h"


@@ 35,9 36,11 @@ pigeon_application_activate(GApplication *app)
#endif
		if (code != CURLE_OK) {
			g_critical("Unable to initialize libcurl. Try to start the application again.");
			exit(1);
			exit(EXIT_FAILURE);
		}

		gtk_source_init();

		priv->app_window = pigeon_application_window_new(self);
	}



@@ 54,8 57,16 @@ pigeon_application_startup(GApplication *app)
}

static void
pigeon_application_about(
	G_GNUC_UNUSED GSimpleAction *action, G_GNUC_UNUSED GVariant *param, gpointer data)
pigeon_application_shutdown(GApplication *app)
{
	curl_global_cleanup();
	gtk_source_finalize();

	G_APPLICATION_CLASS(pigeon_application_parent_class)->shutdown(app);
}

static void
app_about(G_GNUC_UNUSED GSimpleAction *action, G_GNUC_UNUSED GVariant *param, gpointer data)
{
	static const char *authors[] = {"Tristan Partin"};



@@ 68,12 79,35 @@ pigeon_application_about(
		NULL);
}

static void
app_preferences(
	G_GNUC_UNUSED GSimpleAction *action, G_GNUC_UNUSED GVariant *param, G_GNUC_UNUSED gpointer data)
{
	g_print("Showing preferences\n");
}

static void
app_quit(
	G_GNUC_UNUSED GSimpleAction *action, G_GNUC_UNUSED GVariant *param, G_GNUC_UNUSED gpointer data)
{
	GApplication *app = g_application_get_default();
	g_application_quit(app);
}

// clang-format off
static const GActionEntry app_entries[] = {
	{
		.name	  = "about",
		.activate = pigeon_application_about
		.activate = app_about
	},
	{
		.name	  = "preferences",
		.activate = app_preferences
	},
	{
		.name	  = "quit",
		.activate = app_quit
	}
};
// clang-format on



@@ 84,6 118,7 @@ pigeon_application_class_init(PigeonApplicationClass *klass)

	app_class->activate = pigeon_application_activate;
	app_class->startup	= pigeon_application_startup;
	app_class->shutdown = pigeon_application_shutdown;
}

static void

A src/pigeon-page-tab.c => src/pigeon-page-tab.c +150 -0
@@ 0,0 1,150 @@
#include "config.h"

#include <string.h>

#include <glib-object.h>
#include <glib/gi18n.h>
#include <gtk/gtk.h>

#include "pigeon-page-tab.h"

struct _PigeonPageTab
{
	GtkBox parent_instance;
};

typedef struct PigeonPageTabPrivate
{
	GtkStack *label_stack;
	GtkLabel *label;
	GtkText *label_entry;
	GtkButton *close_button;
} PigeonPageTabPrivate;

G_DEFINE_TYPE_WITH_PRIVATE(PigeonPageTab, pigeon_page_tab, GTK_TYPE_BOX)

enum
{
	PROP_0,
	PROP_LABEL,
	N_PROPS,
};

static GParamSpec *obj_properties[N_PROPS];

static void
click_cb(G_GNUC_UNUSED GtkGestureClick *gesture, int n_press, G_GNUC_UNUSED double x,
	G_GNUC_UNUSED double y, gpointer user_data)
{
	if (n_press != 2)
		return;

	PigeonPageTab *self		   = PIGEON_PAGE_TAB(user_data);
	PigeonPageTabPrivate *priv = pigeon_page_tab_get_instance_private(self);

	GtkEntryBuffer *buffer = gtk_text_get_buffer(priv->label_entry);
	const char *curr_label = gtk_label_get_text(priv->label);
	gtk_entry_buffer_set_text(buffer, curr_label, strlen(curr_label));
	gtk_stack_set_visible_child_name(GTK_STACK(priv->label_stack), "edit");
	gtk_widget_grab_focus(GTK_WIDGET(priv->label_entry));
}

static void
key_released_cb(G_GNUC_UNUSED GtkEventControllerKey *controller, guint keyval,
	G_GNUC_UNUSED guint keycode, G_GNUC_UNUSED GdkModifierType state, gpointer user_data)
{
	PigeonPageTab *self		   = PIGEON_PAGE_TAB(user_data);
	PigeonPageTabPrivate *priv = pigeon_page_tab_get_instance_private(self);

	GtkEntryBuffer *buffer = gtk_text_get_buffer(priv->label_entry);

	if (keyval == GDK_KEY_Escape)
		goto finish;

	if (keyval != GDK_KEY_Return)
		return;

	const char *new_label = gtk_entry_buffer_get_text(buffer);
	gtk_label_set_label(priv->label, new_label);
	gtk_widget_set_tooltip_text(GTK_WIDGET(priv->label), new_label);
finish:
	gtk_entry_buffer_delete_text(buffer, 0, -1);
	gtk_stack_set_visible_child_name(priv->label_stack, "readonly");
}

static void
pigeon_page_tab_get_property(GObject *obj, guint prop_id, GValue *val, GParamSpec *pspec)
{
	PigeonPageTab *self		   = PIGEON_PAGE_TAB(obj);
	PigeonPageTabPrivate *priv = pigeon_page_tab_get_instance_private(self);

	switch (prop_id) {
	case PROP_LABEL:
		g_value_set_string(val, gtk_label_get_label(priv->label));
		break;
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, pspec);
	}
}

static void
pigeon_page_tab_set_property(GObject *obj, guint prop_id, const GValue *val, GParamSpec *pspec)
{
	PigeonPageTab *self		   = PIGEON_PAGE_TAB(obj);
	PigeonPageTabPrivate *priv = pigeon_page_tab_get_instance_private(self);

	switch (prop_id) {
	case PROP_LABEL:
		gtk_label_set_text(priv->label, g_value_get_string(val));
		gtk_widget_set_tooltip_text(GTK_WIDGET(priv->label), g_value_get_string(val));
		break;
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, pspec);
	}
}

static void
pigeon_page_tab_class_init(PigeonPageTabClass *klass)
{
	GObjectClass *obj_class	  = G_OBJECT_CLASS(klass);
	GtkWidgetClass *wid_class = GTK_WIDGET_CLASS(klass);

	obj_class->get_property = pigeon_page_tab_get_property;
	obj_class->set_property = pigeon_page_tab_set_property;

	obj_properties[PROP_LABEL]
		= g_param_spec_string("label", _("Label"), _("Label of the tab"), NULL, G_PARAM_READWRITE);

	g_object_class_install_properties(obj_class, N_PROPS, obj_properties);

	gtk_widget_class_set_template_from_resource(
		wid_class, "/io/partin/tristan/Pigeon/ui/pigeon-page-tab.ui");
	gtk_widget_class_bind_template_child_private(wid_class, PigeonPageTab, label_stack);
	gtk_widget_class_bind_template_child_private(wid_class, PigeonPageTab, label);
	gtk_widget_class_bind_template_child_private(wid_class, PigeonPageTab, label_entry);
	gtk_widget_class_bind_template_child_private(wid_class, PigeonPageTab, close_button);

	// gtk_widget_class_set_layout_manager_type(wid_class, GTK_TYPE_BOX_LAYOUT);
}

static void
pigeon_page_tab_init(PigeonPageTab *self)
{
	PigeonPageTabPrivate *priv = pigeon_page_tab_get_instance_private(self);

	gtk_widget_init_template(GTK_WIDGET(self));

	GtkGesture *click_gesture = gtk_gesture_click_new();
	g_signal_connect(click_gesture, "pressed", G_CALLBACK(click_cb), self);
	gtk_widget_add_controller(GTK_WIDGET(priv->label), GTK_EVENT_CONTROLLER(click_gesture));

	GtkEventController *key_controller = gtk_event_controller_key_new();
	g_signal_connect(key_controller, "key-released", G_CALLBACK(key_released_cb), self);
	gtk_widget_add_controller(GTK_WIDGET(priv->label_entry), key_controller);
}

PigeonPageTab *
pigeon_page_tab_new(const char *label)
{
	return g_object_new(PIGEON_TYPE_PAGE_TAB, "label", label, NULL);
}

A src/pigeon-page-tab.h => src/pigeon-page-tab.h +11 -0
@@ 0,0 1,11 @@
#include <glib-object.h>
#include <gtk/gtk.h>

#define PIGEON_TYPE_PAGE_TAB (pigeon_page_tab_get_type())
G_DECLARE_FINAL_TYPE(PigeonPageTab, pigeon_page_tab, PIGEON, PAGE_TAB, GtkBox)

G_BEGIN_DECLS

PigeonPageTab *pigeon_page_tab_new(const char *label) G_GNUC_WARN_UNUSED_RESULT;

G_END_DECLS

A src/pigeon-page.c => src/pigeon-page.c +56 -0
@@ 0,0 1,56 @@
#include "config.h"

#include <curl/curl.h>
#include <glib-object.h>
#include <gtk/gtk.h>

#include "pigeon-page.h"

struct _PigeonPage
{
	GtkGrid parent_instance;

	CURL *handle;
};

typedef struct PigeonPagePrivate
{
} PigeonPagePrivate;

G_DEFINE_TYPE(PigeonPage, pigeon_page, GTK_TYPE_GRID)

static void
pigeon_page_finalize(GObject *obj)
{
	PigeonPage *self = PIGEON_PAGE(obj);

	curl_easy_cleanup(self->handle);

	G_OBJECT_CLASS(pigeon_page_parent_class)->finalize(obj);
}

static void
pigeon_page_class_init(PigeonPageClass *klass)
{
	GObjectClass *obj_class	  = G_OBJECT_CLASS(klass);
	GtkWidgetClass *wid_class = GTK_WIDGET_CLASS(klass);

	obj_class->finalize = pigeon_page_finalize;

	gtk_widget_class_set_template_from_resource(
		wid_class, "/io/partin/tristan/Pigeon/ui/pigeon-page.ui");
}

static void
pigeon_page_init(PigeonPage *self)
{
	gtk_widget_init_template(GTK_WIDGET(self));

	self->handle = curl_easy_init();
}

PigeonPage *
pigeon_page_new()
{
	return g_object_new(PIGEON_TYPE_PAGE, NULL);
}

A src/pigeon-page.h => src/pigeon-page.h +13 -0
@@ 0,0 1,13 @@
#pragma once

#include <glib-object.h>
#include <gtk/gtk.h>

#define PIGEON_TYPE_PAGE (pigeon_page_get_type())
G_DECLARE_FINAL_TYPE(PigeonPage, pigeon_page, PIGEON, PAGE, GtkGrid)

G_BEGIN_DECLS

PigeonPage *pigeon_page_new() G_GNUC_WARN_UNUSED_RESULT;

G_END_DECLS

A src/pigeon-request-explorer.c => src/pigeon-request-explorer.c +41 -0
@@ 0,0 1,41 @@
#include "config.h"

#include <glib-object.h>
#include <glib.h>
#include <gtk/gtk.h>

#include "pigeon-request-explorer.h"

struct _PigeonRequestExplorer
{
	GtkBox parent_instance;
};

typedef struct PigeonRequestExplorerPrivate
{
	GtkListView *request_tree;
} PigeonRequestExplorerPrivate;

G_DEFINE_TYPE_WITH_PRIVATE(PigeonRequestExplorer, pigeon_request_explorer, GTK_TYPE_BOX)

static void
pigeon_request_explorer_class_init(PigeonRequestExplorerClass *klass)
{
	GtkWidgetClass *wid_class = GTK_WIDGET_CLASS(klass);

	gtk_widget_class_set_template_from_resource(
		wid_class, "/io/partin/tristan/Pigeon/ui/pigeon-request-explorer.ui");
	gtk_widget_class_bind_template_child_private(wid_class, PigeonRequestExplorer, request_tree);
}

static void
pigeon_request_explorer_init(PigeonRequestExplorer *self)
{
	gtk_widget_init_template(GTK_WIDGET(self));
}

PigeonRequestExplorer *
pigeon_request_explorer_new()
{
	return g_object_new(PIGEON_TYPE_REQUEST_EXPLORER, NULL);
}

A src/pigeon-request-explorer.h => src/pigeon-request-explorer.h +12 -0
@@ 0,0 1,12 @@
#include <glib-object.h>
#include <gtk/gtk.h>

#define PIGEON_TYPE_REQUEST_EXPLORER (pigeon_request_explorer_get_type())
G_DECLARE_FINAL_TYPE(
	PigeonRequestExplorer, pigeon_request_explorer, PIGEON, REQUEST_EXPLORER, GtkBox)

G_BEGIN_DECLS

PigeonRequestExplorer *pigeon_request_explorer_new() G_GNUC_WARN_UNUSED_RESULT;

G_END_DECLS

M subprojects/gtk.wrap => subprojects/gtk.wrap +4 -4
@@ 1,6 1,6 @@
[wrap-file]
directory = gtk-3.24.20
directory = gtk-3.99.0

source_url = https://gitlab.gnome.org/GNOME/gtk/-/archive/3.24.20/gtk-3.24.20.tar.gz
source_filename = gtk-3.24.20.tar.gz
source_hash = 54e78a4eb81d38f887701bddddb8755002aa81bfce1a53852286b331fd47b5ff
source_url = https://gitlab.gnome.org/GNOME/gtk/-/archive/3.99.0/gtk-3.99.0.tar.gz
source_filename = gtk-3.99.0.tar.gz
source_hash = e46a28408595517b21c88c71d1856aee7d87d254450ebce848a8daedc7afba4b

A subprojects/gtksourceview.wrap => subprojects/gtksourceview.wrap +3 -0
@@ 0,0 1,3 @@
[wrap-git]
url = https://gitlab.gnome.org/GNOME/gtksourceview
revision = wip/chergert/gsv-gtk4