Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
181cbf79b2 | ||
|
|
66cf21ec71 | ||
|
|
f28b5a8145 | ||
|
|
5c4affefbd | ||
|
|
e27c392078 | ||
|
|
effa685762 | ||
|
|
a30ce043f4 |
No files matched your search
@@ -21,7 +21,7 @@ jobs:
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y --no-install-recommends \
|
||||
build-essential pkg-config \
|
||||
libgtk-3-dev
|
||||
libgtk-3-dev libwebkit2gtk-4.1-dev
|
||||
|
||||
- name: Build
|
||||
run: make -j
|
||||
@@ -29,8 +29,8 @@ jobs:
|
||||
- name: ABI check (GLib)
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if nm -D viewmd | grep -q ' g_string_free_and_steal$'; then
|
||||
echo "Error: viewmd links against g_string_free_and_steal (too-new GLib ABI for jammy/mint 21.x)" >&2
|
||||
if nm -D seemd | grep -q ' g_string_free_and_steal$'; then
|
||||
echo "Error: seemd links against g_string_free_and_steal (too-new GLib ABI for jammy/mint 21.x)" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -41,7 +41,7 @@ jobs:
|
||||
ARCH="$(dpkg --print-architecture)"
|
||||
MAINTAINER="${GITHUB_REPOSITORY_OWNER} <noreply@github.com>"
|
||||
|
||||
PKGDIR="dist/deb/viewmd_${VERSION}_${ARCH}"
|
||||
PKGDIR="dist/deb/seemd_${VERSION}_${ARCH}"
|
||||
rm -rf "$PKGDIR"
|
||||
mkdir -p "$PKGDIR/DEBIAN"
|
||||
|
||||
@@ -57,7 +57,7 @@ jobs:
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: viewmd-deb
|
||||
name: seemd-deb
|
||||
path: dist/deb/*.deb
|
||||
|
||||
rpm:
|
||||
@@ -68,7 +68,7 @@ jobs:
|
||||
run: |
|
||||
dnf -y install \
|
||||
git make gcc rpm-build \
|
||||
gtk3-devel pkgconf-pkg-config
|
||||
gtk3-devel webkit2gtk4.1-devel pkgconf-pkg-config
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
@@ -87,7 +87,7 @@ jobs:
|
||||
TAG="${{ github.event.release.tag_name || github.ref_name }}"
|
||||
VERSION="${TAG#v}"
|
||||
mkdir -p dist
|
||||
git archive --format=tar.gz --prefix="viewmd-${VERSION}/" -o "dist/viewmd-${VERSION}.tar.gz" HEAD
|
||||
git archive --format=tar.gz --prefix="seemd-${VERSION}/" -o "dist/seemd-${VERSION}.tar.gz" HEAD
|
||||
|
||||
- name: Build .rpm
|
||||
run: |
|
||||
@@ -98,25 +98,25 @@ jobs:
|
||||
TOPDIR="$PWD/dist/rpmbuild"
|
||||
mkdir -p "$TOPDIR"/{BUILD,BUILDROOT,RPMS,SOURCES,SPECS,SRPMS}
|
||||
|
||||
cp "dist/viewmd-${VERSION}.tar.gz" "$TOPDIR/SOURCES/"
|
||||
cp "dist/seemd-${VERSION}.tar.gz" "$TOPDIR/SOURCES/"
|
||||
|
||||
sed \
|
||||
-e "s/@VERSION@/${VERSION}/g" \
|
||||
-e "s|@URL@|${URL}|g" \
|
||||
packaging/rpm/viewmd.spec.in > "$TOPDIR/SPECS/viewmd.spec"
|
||||
packaging/rpm/seemd.spec.in > "$TOPDIR/SPECS/seemd.spec"
|
||||
|
||||
rpmbuild \
|
||||
--define "_topdir ${TOPDIR}" \
|
||||
-bb "$TOPDIR/SPECS/viewmd.spec"
|
||||
-bb "$TOPDIR/SPECS/seemd.spec"
|
||||
|
||||
mkdir -p dist/rpm
|
||||
# Only publish the main runtime RPM (exclude -debuginfo/-debugsource).
|
||||
find "$TOPDIR/RPMS" -type f -name 'viewmd-[0-9]*.rpm' -exec cp -v -t dist/rpm {} +
|
||||
find "$TOPDIR/RPMS" -type f -name 'seemd-[0-9]*.rpm' -exec cp -v -t dist/rpm {} +
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: viewmd-rpm
|
||||
path: dist/rpm/viewmd-[0-9]*.rpm
|
||||
name: seemd-rpm
|
||||
path: dist/rpm/seemd-[0-9]*.rpm
|
||||
|
||||
release-assets:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -126,7 +126,7 @@ jobs:
|
||||
- name: Download build artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
pattern: viewmd-*
|
||||
pattern: seemd-*
|
||||
merge-multiple: true
|
||||
path: dist
|
||||
|
||||
@@ -135,4 +135,4 @@ jobs:
|
||||
with:
|
||||
files: |
|
||||
dist/*.deb
|
||||
dist/viewmd-[0-9]*.rpm
|
||||
dist/seemd-[0-9]*.rpm
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2026 Robert Fulton
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -1,6 +1,7 @@
|
||||
CC = gcc
|
||||
CFLAGS = -Wall -Wextra -O2 -g `pkg-config --cflags gtk+-3.0`
|
||||
LDFLAGS = `pkg-config --libs gtk+-3.0`
|
||||
PKGS = gtk+-3.0 webkit2gtk-4.1
|
||||
CFLAGS = -Wall -Wextra -O2 -g `pkg-config --cflags $(PKGS)`
|
||||
LDFLAGS = `pkg-config --libs $(PKGS)`
|
||||
|
||||
SRCDIR = src
|
||||
OBJDIR = obj
|
||||
@@ -8,7 +9,7 @@ BINDIR = .
|
||||
|
||||
SOURCES = $(wildcard $(SRCDIR)/*.c)
|
||||
OBJECTS = $(SOURCES:$(SRCDIR)/%.c=$(OBJDIR)/%.o) $(OBJDIR)/md4c.o
|
||||
TARGET = $(BINDIR)/viewmd
|
||||
TARGET = $(BINDIR)/seemd
|
||||
|
||||
PREFIX ?= /usr/local
|
||||
DESTDIR ?=
|
||||
@@ -37,12 +38,12 @@ clean:
|
||||
rm -rf $(OBJDIR) $(TARGET)
|
||||
|
||||
install: $(TARGET)
|
||||
install -Dm755 $(TARGET) $(DESTDIR)$(bindir)/viewmd
|
||||
install -Dm644 assets/viewmd.desktop $(DESTDIR)$(applicationsdir)/viewmd.desktop
|
||||
install -Dm755 $(TARGET) $(DESTDIR)$(bindir)/seemd
|
||||
install -Dm644 assets/seemd.desktop $(DESTDIR)$(applicationsdir)/seemd.desktop
|
||||
|
||||
uninstall:
|
||||
rm -f $(DESTDIR)$(bindir)/viewmd
|
||||
rm -f $(DESTDIR)$(applicationsdir)/viewmd.desktop
|
||||
rm -f $(DESTDIR)$(bindir)/seemd
|
||||
rm -f $(DESTDIR)$(applicationsdir)/seemd.desktop
|
||||
|
||||
# Header dependencies
|
||||
$(OBJDIR)/main.o: $(SRCDIR)/app.h $(SRCDIR)/window.h
|
||||
|
||||
@@ -1,18 +1,19 @@
|
||||
# ViewMD
|
||||
# 👁 SeeMD
|
||||
|
||||
A lightweight GTK 3 markdown viewer for desktop Linux. It is ideal as your default app for opening `.md` files. Uses the excellent library [md4c](https://github.com/mity/md4c) for markdown parsing. This project is sort of a companion app/fork of my other markdown project [TrayMD](https://github.com/rabfulton/TrayMD).
|
||||
A lightweight GTK 3 markdown viewer and editor for desktop Linux. It is ideal as your default app for opening `.md` files. Uses the excellent library [md4c](https://github.com/mity/md4c) for markdown parsing and WebKitGTK for GitHub-style preview rendering. This project is a fork of [ViewMD](https://github.com/rabfulton/ViewMD) by NotAlexNoyle.
|
||||
|
||||

|
||||

|
||||
|
||||
## Features
|
||||
|
||||
- **Native GTK viewer** - No webviews
|
||||
- **Read-only rendering** - Focused on viewing markdown files
|
||||
- **Minimal UI** - Clean toolbar with open and settings buttons
|
||||
- **Lightweight** - Pure C, no web technologies, fast startup
|
||||
- **GitHub-style preview** - Rendered Markdown and inline HTML are displayed through WebKitGTK with GitHub-like markdown styling
|
||||
- **Read-only preview** - Rendered Markdown stays non-editable until source mode is enabled
|
||||
- **Source editing mode** - Toggle between rendered preview and editable Markdown
|
||||
- **Minimal UI** - Clean toolbar with open, reload, edit, save, and settings buttons
|
||||
- **Lightweight** - Pure C with GTK and WebKitGTK, fast startup
|
||||
- **Hyperlink support** - Left click opens links and internal anchors
|
||||
- **Document search** - `Ctrl+F` with next/previous match navigation
|
||||
- **Local image support** - Local images are resized to fit the the document window
|
||||
- **Local image support** - Local images keep natural size unless they need to fit the document window
|
||||
|
||||
## Supported Markdown
|
||||
|
||||
@@ -30,38 +31,19 @@ A lightweight GTK 3 markdown viewer for desktop Linux. It is ideal as your defau
|
||||
| `[text](url)` | Link |
|
||||
| `~~strike~~` | Strikethrough |
|
||||
| `\| table \| row \|` | Markdown tables |
|
||||
| `<strong>HTML</strong>` | GitHub-style inline and block HTML rendered in preview mode |
|
||||
| `---` | Horizontal rule |
|
||||
|
||||
Code blocks currently support a beta version of keyword highlighting for fenced languages `c`, `java`, and `python`.
|
||||
|
||||
## Installation
|
||||
|
||||
### Arch Linux (AUR)
|
||||
|
||||
https://aur.archlinux.org/packages/viewmd
|
||||
|
||||
### Debian/Ubuntu (.deb)
|
||||
|
||||
Install a built `.deb` package with:
|
||||
|
||||
```bash
|
||||
sudo apt install ./viewmd_*.deb
|
||||
```
|
||||
|
||||
### Fedora/RHEL (.rpm)
|
||||
|
||||
Install a built `.rpm` package with:
|
||||
|
||||
```bash
|
||||
sudo dnf install ./viewmd-*.rpm
|
||||
```
|
||||
Fenced code blocks are syntax-highlighted GitHub-style in the preview for the languages `c`, `java`, `javascript`, `kotlin`, `python`, and `bash`, covering keywords, strings, numbers, and comments.
|
||||
|
||||
## Usage
|
||||
|
||||
Run `viewmd` to start the application.
|
||||
Run `seemd` to start the application.
|
||||
|
||||
- **Open button**: Open a markdown document
|
||||
- **Reload button**: Reload the currently open document from disk
|
||||
- **Edit button**: Toggle editable Markdown source mode (`Ctrl+E`)
|
||||
- **Save button**: Save the current Markdown source (`Ctrl+S`)
|
||||
- **Settings button**: Adjust theme, fonts, and markdown accent colors
|
||||
|
||||
### Find in Document
|
||||
@@ -73,11 +55,11 @@ Run `viewmd` to start the application.
|
||||
|
||||
### Set as Default `.md` Viewer
|
||||
|
||||
After installing, associate markdown MIME types with `viewmd.desktop`:
|
||||
After installing, associate markdown MIME types with `seemd.desktop`:
|
||||
|
||||
```bash
|
||||
xdg-mime default viewmd.desktop text/markdown
|
||||
xdg-mime default viewmd.desktop text/x-markdown
|
||||
xdg-mime default seemd.desktop text/markdown
|
||||
xdg-mime default seemd.desktop text/x-markdown
|
||||
```
|
||||
|
||||
Verify the current default:
|
||||
@@ -101,8 +83,8 @@ sudo make install
|
||||
```
|
||||
|
||||
This installs:
|
||||
- Binary to `/usr/local/bin/viewmd`
|
||||
- Desktop file to `/usr/local/share/applications/viewmd.desktop`
|
||||
- Binary to `/usr/local/bin/seemd`
|
||||
- Desktop file to `/usr/local/share/applications/seemd.desktop`
|
||||
|
||||
### Uninstallation
|
||||
|
||||
@@ -113,32 +95,19 @@ sudo make uninstall
|
||||
|
||||
### Arch Linux
|
||||
```bash
|
||||
sudo pacman -S gtk3
|
||||
sudo pacman -S gtk3 webkit2gtk-4.1
|
||||
```
|
||||
|
||||
### Ubuntu/Debian
|
||||
```bash
|
||||
sudo apt install libgtk-3-dev
|
||||
sudo apt install libgtk-3-dev libwebkit2gtk-4.1-dev
|
||||
```
|
||||
|
||||
### Fedora
|
||||
```bash
|
||||
sudo dnf install gtk3-devel
|
||||
sudo dnf install gtk3-devel webkit2gtk4.1-devel
|
||||
```
|
||||
|
||||
## Packaging Templates
|
||||
|
||||
- Arch: `packaging/arch/PKGBUILD`
|
||||
- Debian: `packaging/deb/control.in`
|
||||
- RPM: `packaging/rpm/viewmd.spec.in`
|
||||
## License
|
||||
|
||||
MIT License
|
||||
|
||||
## Other Useful Projects
|
||||
- TrayMD is an app for taking notes in markdown with live editing [TrayMD](https://github.com/rabfulton/TrayMD)
|
||||
- Preditor is an image viewer with a similar philosophy to viewmd [preditor](https://github.com/rabfulton/preditor)
|
||||
- Try my AI panel plugin for XFCE [XFCE Ask](https://github.com/rabfulton/xfce-ask)
|
||||
- For a feature complete AI application try out [ChatGTK](https://github.com/rabfulton/ChatGTK)
|
||||
- A lightweight speech to text implementation [Auriscribe](https://github.com/rabfulton/Auriscribe)
|
||||
- A lightweight local movie database and browser [ReelVault](https://github.com/rabfulton/ReelVault)
|
||||
Released under the [MIT License](LICENSE).
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 112 KiB |
@@ -1,7 +1,7 @@
|
||||
[Desktop Entry]
|
||||
Name=ViewMD
|
||||
Name=SeeMD
|
||||
Comment=Lightweight markdown viewer
|
||||
Exec=viewmd %F
|
||||
Exec=seemd %F
|
||||
Icon=accessories-text-editor
|
||||
Terminal=false
|
||||
Type=Application
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 38 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
@@ -1,11 +1,11 @@
|
||||
pkgname=viewmd
|
||||
pkgname=seemd
|
||||
pkgver=0.r0.g0000000
|
||||
pkgrel=1
|
||||
pkgdesc="Lightweight GTK3 markdown viewer"
|
||||
arch=('x86_64')
|
||||
url="https://github.com/rabfulton/ViewMD"
|
||||
url="https://github.com/NotAlexNoyle/SeeMD"
|
||||
license=('MIT')
|
||||
depends=('gtk3')
|
||||
depends=('gtk3' 'webkit2gtk-4.1')
|
||||
makedepends=('git' 'gcc' 'make' 'pkgconf')
|
||||
source=("$pkgname::git+$url.git")
|
||||
sha256sums=('SKIP')
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
Package: viewmd
|
||||
Package: seemd
|
||||
Version: @VERSION@
|
||||
Section: utils
|
||||
Priority: optional
|
||||
Architecture: @ARCH@
|
||||
Maintainer: @MAINTAINER@
|
||||
Depends: libgtk-3-0
|
||||
Description: ViewMD - lightweight GTK3 markdown viewer
|
||||
ViewMD is a lightweight native GTK3 markdown viewer.
|
||||
Depends: libgtk-3-0, libwebkit2gtk-4.1-0
|
||||
Description: SeeMD - lightweight GTK3 markdown viewer
|
||||
SeeMD is a lightweight GTK3 markdown viewer with GitHub-style WebKit preview rendering.
|
||||
@@ -1,24 +1,26 @@
|
||||
Name: viewmd
|
||||
Name: seemd
|
||||
Version: @VERSION@
|
||||
Release: 1%{?dist}
|
||||
Summary: Lightweight GTK3 markdown viewer
|
||||
|
||||
License: MIT
|
||||
URL: @URL@
|
||||
Source0: viewmd-%{version}.tar.gz
|
||||
Source0: seemd-%{version}.tar.gz
|
||||
|
||||
BuildRequires: gcc
|
||||
BuildRequires: make
|
||||
BuildRequires: pkgconfig
|
||||
BuildRequires: gtk3-devel
|
||||
BuildRequires: webkit2gtk4.1-devel
|
||||
|
||||
Requires: gtk3
|
||||
Requires: webkit2gtk4.1
|
||||
|
||||
%description
|
||||
ViewMD is a lightweight native GTK3 markdown viewer.
|
||||
SeeMD is a lightweight GTK3 markdown viewer with GitHub-style WebKit preview rendering.
|
||||
|
||||
%prep
|
||||
%autosetup -n viewmd-%{version}
|
||||
%autosetup -n seemd-%{version}
|
||||
|
||||
%build
|
||||
make %{?_smp_mflags}
|
||||
@@ -28,9 +30,9 @@ rm -rf %{buildroot}
|
||||
make install DESTDIR=%{buildroot} PREFIX=%{_prefix}
|
||||
|
||||
%files
|
||||
%{_bindir}/viewmd
|
||||
%{_datadir}/applications/viewmd.desktop
|
||||
%{_bindir}/seemd
|
||||
%{_datadir}/applications/seemd.desktop
|
||||
|
||||
%changelog
|
||||
* Thu Jan 01 1970 ViewMD Packager <noreply@example.com> - @VERSION@-1
|
||||
* Thu Jan 01 1970 SeeMD Packager <noreply@example.com> - @VERSION@-1
|
||||
- Initial package
|
||||
@@ -27,7 +27,7 @@ MarkydApp *markyd_app_new(void) {
|
||||
flags = (GApplicationFlags)(flags | G_APPLICATION_NON_UNIQUE |
|
||||
G_APPLICATION_HANDLES_OPEN);
|
||||
|
||||
self->gtk_app = gtk_application_new("org.viewmd.app", flags);
|
||||
self->gtk_app = gtk_application_new("org.seemd.app", flags);
|
||||
self->current_file_path = NULL;
|
||||
|
||||
g_signal_connect(self->gtk_app, "activate", G_CALLBACK(on_activate), self);
|
||||
@@ -69,10 +69,10 @@ static void markyd_app_update_window_title(MarkydApp *self) {
|
||||
|
||||
if (self->current_file_path && self->current_file_path[0] != '\0') {
|
||||
gchar *base = g_path_get_basename(self->current_file_path);
|
||||
title = g_strdup_printf("ViewMD - %s", base);
|
||||
title = g_strdup_printf("SeeMD - %s", base);
|
||||
g_free(base);
|
||||
} else {
|
||||
title = g_strdup("ViewMD");
|
||||
title = g_strdup("SeeMD");
|
||||
}
|
||||
|
||||
gtk_window_set_title(GTK_WINDOW(self->window->window), title);
|
||||
@@ -111,7 +111,7 @@ static void on_open(GtkApplication *gtk_app, GFile **files, gint n_files,
|
||||
}
|
||||
|
||||
if (!opened && n_files > 0) {
|
||||
g_printerr("ViewMD: unable to open provided file(s)\n");
|
||||
g_printerr("SeeMD: unable to open provided file(s)\n");
|
||||
}
|
||||
|
||||
markyd_window_show(self->window);
|
||||
@@ -126,7 +126,7 @@ static void markyd_app_ensure_window(MarkydApp *self) {
|
||||
self->editor = self->window->editor;
|
||||
|
||||
markyd_editor_set_content(self->editor,
|
||||
"# ViewMD\n\nUse the Open button to load a markdown document.");
|
||||
"# SeeMD\n\nUse the Open button to load a markdown document.");
|
||||
markyd_app_update_window_title(self);
|
||||
}
|
||||
|
||||
@@ -156,6 +156,45 @@ gboolean markyd_app_open_file(MarkydApp *self, const gchar *path) {
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
gboolean markyd_app_save_file(MarkydApp *self, const gchar *path) {
|
||||
gchar *content;
|
||||
GError *error = NULL;
|
||||
|
||||
if (!self || !self->editor || !path || path[0] == '\0') {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
content = markyd_editor_get_content(self->editor);
|
||||
if (!g_file_set_contents(path, content ? content : "", -1, &error)) {
|
||||
if (error) {
|
||||
g_printerr("Failed to save markdown file '%s': %s\n", path, error->message);
|
||||
g_error_free(error);
|
||||
}
|
||||
g_free(content);
|
||||
return FALSE;
|
||||
}
|
||||
g_free(content);
|
||||
|
||||
g_free(self->current_file_path);
|
||||
self->current_file_path = g_strdup(path);
|
||||
markyd_app_update_window_title(self);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
gboolean markyd_app_save_current_file(MarkydApp *self) {
|
||||
gchar *path;
|
||||
gboolean ok;
|
||||
|
||||
if (!self || !self->current_file_path || self->current_file_path[0] == '\0') {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
path = g_strdup(self->current_file_path);
|
||||
ok = markyd_app_save_file(self, path);
|
||||
g_free(path);
|
||||
return ok;
|
||||
}
|
||||
|
||||
const gchar *markyd_app_get_current_path(MarkydApp *self) {
|
||||
if (!self) {
|
||||
return NULL;
|
||||
|
||||
@@ -25,6 +25,8 @@ int markyd_app_run(MarkydApp *app, int argc, char **argv);
|
||||
|
||||
/* Document management */
|
||||
gboolean markyd_app_open_file(MarkydApp *app, const gchar *path);
|
||||
gboolean markyd_app_save_file(MarkydApp *app, const gchar *path);
|
||||
gboolean markyd_app_save_current_file(MarkydApp *app);
|
||||
|
||||
/* Utility */
|
||||
const gchar *markyd_app_get_current_path(MarkydApp *app);
|
||||
|
||||
+218
-6
@@ -76,6 +76,84 @@ static const MarkydKeywordGroup py_groups[] = {
|
||||
{MARKYD_TAG_CODE_KW_C, py_kw_group_c, G_N_ELEMENTS(py_kw_group_c)},
|
||||
};
|
||||
|
||||
static const gchar *const js_kw_group_a[] = {
|
||||
"await", "break", "case", "catch", "continue", "default",
|
||||
"do", "else", "finally", "for", "if", "in",
|
||||
"instanceof", "of", "return", "switch", "throw", "try",
|
||||
"while", "with", "yield",
|
||||
};
|
||||
|
||||
static const gchar *const js_kw_group_b[] = {
|
||||
"async", "class", "const", "debugger", "delete", "export",
|
||||
"extends","function","import", "let", "new", "static",
|
||||
"super", "this", "typeof", "var", "void",
|
||||
};
|
||||
|
||||
static const gchar *const js_kw_group_c[] = {
|
||||
"false", "Infinity", "NaN", "null", "true", "undefined",
|
||||
};
|
||||
|
||||
static const MarkydKeywordGroup js_groups[] = {
|
||||
{MARKYD_TAG_CODE_KW_A, js_kw_group_a, G_N_ELEMENTS(js_kw_group_a)},
|
||||
{MARKYD_TAG_CODE_KW_B, js_kw_group_b, G_N_ELEMENTS(js_kw_group_b)},
|
||||
{MARKYD_TAG_CODE_KW_C, js_kw_group_c, G_N_ELEMENTS(js_kw_group_c)},
|
||||
};
|
||||
|
||||
static const gchar *const kotlin_kw_group_a[] = {
|
||||
"break", "catch", "continue", "do", "else", "finally",
|
||||
"for", "if", "in", "is", "return","throw",
|
||||
"try", "when", "while",
|
||||
};
|
||||
|
||||
static const gchar *const kotlin_kw_group_b[] = {
|
||||
"abstract", "actual", "annotation", "as", "by",
|
||||
"class", "companion", "const", "constructor","crossinline",
|
||||
"data", "enum", "expect", "external", "final",
|
||||
"fun", "import", "infix", "init", "inline",
|
||||
"inner", "interface", "internal", "lateinit", "noinline",
|
||||
"object", "open", "operator", "out", "override",
|
||||
"package", "private", "protected", "public", "reified",
|
||||
"sealed", "super", "suspend", "tailrec", "this",
|
||||
"typealias", "val", "var", "vararg", "where",
|
||||
};
|
||||
|
||||
static const gchar *const kotlin_kw_group_c[] = {
|
||||
"false", "null", "true", "Any", "Boolean", "Byte", "Char",
|
||||
"Double","Float","Int", "Long", "Nothing", "Short", "String",
|
||||
"Unit",
|
||||
};
|
||||
|
||||
static const MarkydKeywordGroup kotlin_groups[] = {
|
||||
{MARKYD_TAG_CODE_KW_A, kotlin_kw_group_a, G_N_ELEMENTS(kotlin_kw_group_a)},
|
||||
{MARKYD_TAG_CODE_KW_B, kotlin_kw_group_b, G_N_ELEMENTS(kotlin_kw_group_b)},
|
||||
{MARKYD_TAG_CODE_KW_C, kotlin_kw_group_c, G_N_ELEMENTS(kotlin_kw_group_c)},
|
||||
};
|
||||
|
||||
static const gchar *const bash_kw_group_a[] = {
|
||||
"case", "do", "done", "elif", "else", "esac", "fi",
|
||||
"for", "function","if", "in", "select","then", "time",
|
||||
"until","while",
|
||||
};
|
||||
|
||||
static const gchar *const bash_kw_group_b[] = {
|
||||
"alias", "bg", "break", "cd", "command", "continue",
|
||||
"declare", "echo", "eval", "exec", "exit", "export",
|
||||
"getopts", "hash", "jobs", "kill", "let", "local",
|
||||
"printf", "pwd", "read", "readonly","return", "set",
|
||||
"shift", "source", "test", "trap", "type", "typeset",
|
||||
"ulimit", "umask", "unalias", "unset", "wait",
|
||||
};
|
||||
|
||||
static const gchar *const bash_kw_group_c[] = {
|
||||
"false", "true",
|
||||
};
|
||||
|
||||
static const MarkydKeywordGroup bash_groups[] = {
|
||||
{MARKYD_TAG_CODE_KW_A, bash_kw_group_a, G_N_ELEMENTS(bash_kw_group_a)},
|
||||
{MARKYD_TAG_CODE_KW_B, bash_kw_group_b, G_N_ELEMENTS(bash_kw_group_b)},
|
||||
{MARKYD_TAG_CODE_KW_C, bash_kw_group_c, G_N_ELEMENTS(bash_kw_group_c)},
|
||||
};
|
||||
|
||||
static gboolean is_ascii_identifier_char(gchar c) {
|
||||
return (c == '_') || g_ascii_isalnum((guchar)c);
|
||||
}
|
||||
@@ -554,23 +632,43 @@ static void scan_line_c_like(const MarkydLanguageHighlight *language,
|
||||
}
|
||||
|
||||
if (in_block_comment) {
|
||||
if (p[0] == '*' && p[1] == '/') {
|
||||
p += 2;
|
||||
char_index += 2;
|
||||
in_block_comment = FALSE;
|
||||
continue;
|
||||
gint start_char_index = char_index;
|
||||
while (*p) {
|
||||
if (p[0] == '*' && p[1] == '/') {
|
||||
p += 2;
|
||||
char_index += 2;
|
||||
in_block_comment = FALSE;
|
||||
break;
|
||||
}
|
||||
advance_utf8_char(&p, &char_index);
|
||||
}
|
||||
advance_utf8_char(&p, &char_index);
|
||||
on_token(start_char_index, char_index, MARKYD_TAG_CODE_COMMENT, user_data);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (p[0] == '/' && p[1] == '/') {
|
||||
gint start_char_index = char_index;
|
||||
while (*p) {
|
||||
advance_utf8_char(&p, &char_index);
|
||||
}
|
||||
on_token(start_char_index, char_index, MARKYD_TAG_CODE_COMMENT, user_data);
|
||||
break;
|
||||
}
|
||||
if (p[0] == '/' && p[1] == '*') {
|
||||
gint start_char_index = char_index;
|
||||
p += 2;
|
||||
char_index += 2;
|
||||
in_block_comment = TRUE;
|
||||
while (*p) {
|
||||
if (p[0] == '*' && p[1] == '/') {
|
||||
p += 2;
|
||||
char_index += 2;
|
||||
in_block_comment = FALSE;
|
||||
break;
|
||||
}
|
||||
advance_utf8_char(&p, &char_index);
|
||||
}
|
||||
on_token(start_char_index, char_index, MARKYD_TAG_CODE_COMMENT, user_data);
|
||||
continue;
|
||||
}
|
||||
if (allow_java_text_blocks && starts_with_triple_quote(p)) {
|
||||
@@ -704,6 +802,11 @@ static void scan_line_python(const MarkydLanguageHighlight *language,
|
||||
}
|
||||
|
||||
if (p[0] == '#') {
|
||||
gint start_char_index = char_index;
|
||||
while (*p) {
|
||||
advance_utf8_char(&p, &char_index);
|
||||
}
|
||||
on_token(start_char_index, char_index, MARKYD_TAG_CODE_COMMENT, user_data);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -827,11 +930,120 @@ static void scan_line_python(const MarkydLanguageHighlight *language,
|
||||
}
|
||||
}
|
||||
|
||||
/* Bash: '#' comments (position-sensitive), single/double-quoted strings, and
|
||||
* keyword/builtin identifiers. No multi-line state is tracked. */
|
||||
static void scan_line_bash(const MarkydLanguageHighlight *language,
|
||||
const gchar *line, MarkydCodeScanState *state,
|
||||
MarkydCodeTokenCallback on_token,
|
||||
gpointer user_data) {
|
||||
const gchar *p;
|
||||
gint char_index = 0;
|
||||
gchar prev = '\0';
|
||||
|
||||
if (!language || !line || !state || !on_token) {
|
||||
return;
|
||||
}
|
||||
|
||||
p = line;
|
||||
while (*p) {
|
||||
/* A '#' starts a comment only at the start of a word (line start or after
|
||||
* whitespace), so tokens like "$#" and "a#b" are left alone. */
|
||||
if (p[0] == '#' && (p == line || prev == ' ' || prev == '\t')) {
|
||||
gint start_char_index = char_index;
|
||||
while (*p) {
|
||||
advance_utf8_char(&p, &char_index);
|
||||
}
|
||||
on_token(start_char_index, char_index, MARKYD_TAG_CODE_COMMENT, user_data);
|
||||
break;
|
||||
}
|
||||
|
||||
if (p[0] == '\'') {
|
||||
gint start_char_index = char_index;
|
||||
p++;
|
||||
char_index++;
|
||||
while (*p && p[0] != '\'') {
|
||||
advance_utf8_char(&p, &char_index);
|
||||
}
|
||||
if (*p == '\'') {
|
||||
p++;
|
||||
char_index++;
|
||||
}
|
||||
on_token(start_char_index, char_index, MARKYD_TAG_CODE_LITERAL, user_data);
|
||||
prev = '\'';
|
||||
continue;
|
||||
}
|
||||
|
||||
if (p[0] == '"') {
|
||||
gint start_char_index = char_index;
|
||||
p++;
|
||||
char_index++;
|
||||
while (*p) {
|
||||
if (p[0] == '\\' && p[1]) {
|
||||
p++;
|
||||
char_index++;
|
||||
advance_utf8_char(&p, &char_index);
|
||||
continue;
|
||||
}
|
||||
if (p[0] == '"') {
|
||||
p++;
|
||||
char_index++;
|
||||
break;
|
||||
}
|
||||
advance_utf8_char(&p, &char_index);
|
||||
}
|
||||
on_token(start_char_index, char_index, MARKYD_TAG_CODE_LITERAL, user_data);
|
||||
prev = '"';
|
||||
continue;
|
||||
}
|
||||
|
||||
{
|
||||
gunichar c = g_utf8_get_char(p);
|
||||
const gchar *next = g_utf8_next_char(p);
|
||||
|
||||
if (is_identifier_start(c)) {
|
||||
const gchar *token_start = p;
|
||||
gint start_char_index = char_index;
|
||||
|
||||
p = next;
|
||||
char_index++;
|
||||
while (*p) {
|
||||
gunichar tc = g_utf8_get_char(p);
|
||||
const gchar *tnext = g_utf8_next_char(p);
|
||||
if (!is_identifier_char(tc)) {
|
||||
break;
|
||||
}
|
||||
p = tnext;
|
||||
char_index++;
|
||||
}
|
||||
|
||||
gsize token_len = (gsize)(p - token_start);
|
||||
const gchar *tag_name =
|
||||
lookup_keyword_tag(language, token_start, token_len);
|
||||
if (tag_name) {
|
||||
on_token(start_char_index, char_index, tag_name, user_data);
|
||||
}
|
||||
prev = 'a';
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
prev = *p;
|
||||
advance_utf8_char(&p, &char_index);
|
||||
}
|
||||
}
|
||||
|
||||
static const MarkydLanguageHighlight languages[] = {
|
||||
{"c", c_groups, G_N_ELEMENTS(c_groups), scan_line_c},
|
||||
{"java", java_groups, G_N_ELEMENTS(java_groups), scan_line_java},
|
||||
{"python", py_groups, G_N_ELEMENTS(py_groups), scan_line_python},
|
||||
{"py", py_groups, G_N_ELEMENTS(py_groups), scan_line_python},
|
||||
{"javascript", js_groups, G_N_ELEMENTS(js_groups), scan_line_c},
|
||||
{"js", js_groups, G_N_ELEMENTS(js_groups), scan_line_c},
|
||||
{"kotlin", kotlin_groups, G_N_ELEMENTS(kotlin_groups), scan_line_java},
|
||||
{"kt", kotlin_groups, G_N_ELEMENTS(kotlin_groups), scan_line_java},
|
||||
{"bash", bash_groups, G_N_ELEMENTS(bash_groups), scan_line_bash},
|
||||
{"sh", bash_groups, G_N_ELEMENTS(bash_groups), scan_line_bash},
|
||||
{"shell", bash_groups, G_N_ELEMENTS(bash_groups), scan_line_bash},
|
||||
};
|
||||
|
||||
const MarkydLanguageHighlight *
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#define MARKYD_TAG_CODE_KW_B "code_kw_b"
|
||||
#define MARKYD_TAG_CODE_KW_C "code_kw_c"
|
||||
#define MARKYD_TAG_CODE_LITERAL "code_literal"
|
||||
#define MARKYD_TAG_CODE_COMMENT "code_comment"
|
||||
|
||||
typedef struct _MarkydKeywordGroup {
|
||||
const gchar *tag_name;
|
||||
|
||||
+1
-1
@@ -50,7 +50,7 @@ void config_free(MarkydConfig *cfg) {
|
||||
const gchar *config_get_path(void) {
|
||||
if (!config_path) {
|
||||
const gchar *config_dir = g_get_user_config_dir();
|
||||
gchar *new_app_dir = g_build_filename(config_dir, "viewmd", NULL);
|
||||
gchar *new_app_dir = g_build_filename(config_dir, "seemd", NULL);
|
||||
|
||||
g_mkdir_with_parents(new_app_dir, 0755);
|
||||
config_path = g_build_filename(new_app_dir, "config.ini", NULL);
|
||||
|
||||
+511
-47
@@ -2,6 +2,7 @@
|
||||
#include "app.h"
|
||||
#include "markdown.h"
|
||||
#include <string.h>
|
||||
#include <webkit2/webkit2.h>
|
||||
|
||||
static gboolean on_button_release(GtkWidget *widget, GdkEventButton *event,
|
||||
gpointer user_data);
|
||||
@@ -18,6 +19,15 @@ static gboolean resolve_image_source_path(MarkydEditor *self, const gchar *src,
|
||||
gchar **out_path);
|
||||
static void on_text_view_size_allocate(GtkWidget *widget, GtkAllocation *allocation,
|
||||
gpointer user_data);
|
||||
static void on_buffer_changed(GtkTextBuffer *buffer, gpointer user_data);
|
||||
static void sync_source_from_buffer(MarkydEditor *self);
|
||||
static void show_source_buffer(MarkydEditor *self);
|
||||
static void render_preview(MarkydEditor *self);
|
||||
static gchar *get_preview_base_uri(MarkydEditor *self);
|
||||
static gboolean on_preview_decide_policy(WebKitWebView *web_view,
|
||||
WebKitPolicyDecision *decision,
|
||||
WebKitPolicyDecisionType type,
|
||||
gpointer user_data);
|
||||
|
||||
static gchar *get_url_from_iter_tags(GtkTextIter *iter) {
|
||||
GSList *tags;
|
||||
@@ -31,7 +41,7 @@ static gchar *get_url_from_iter_tags(GtkTextIter *iter) {
|
||||
for (GSList *node = tags; node != NULL; node = node->next) {
|
||||
GtkTextTag *tag = GTK_TEXT_TAG(node->data);
|
||||
const gchar *stored =
|
||||
(const gchar *)g_object_get_data(G_OBJECT(tag), VIEWMD_LINK_URL_DATA);
|
||||
(const gchar *)g_object_get_data(G_OBJECT(tag), SEEMD_LINK_URL_DATA);
|
||||
if (stored && stored[0] != '\0') {
|
||||
url = g_strdup(stored);
|
||||
break;
|
||||
@@ -110,24 +120,43 @@ static gboolean scroll_to_markdown_anchor(MarkydEditor *self,
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void set_link_cursor(MarkydEditor *self, gboolean active) {
|
||||
GdkWindow *win = gtk_text_view_get_window(GTK_TEXT_VIEW(self->text_view),
|
||||
GTK_TEXT_WINDOW_TEXT);
|
||||
static void set_editor_mouse_cursor(MarkydEditor *self, gboolean over_link) {
|
||||
GdkWindow *win;
|
||||
GdkDisplay *display;
|
||||
GdkCursor *cursor;
|
||||
|
||||
if (!self || !self->text_view) {
|
||||
return;
|
||||
}
|
||||
|
||||
win = gtk_text_view_get_window(GTK_TEXT_VIEW(self->text_view),
|
||||
GTK_TEXT_WINDOW_TEXT);
|
||||
|
||||
if (!win) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (active) {
|
||||
GdkDisplay *display = gdk_window_get_display(win);
|
||||
GdkCursor *cursor = gdk_cursor_new_from_name(display, "pointer");
|
||||
if (!cursor) {
|
||||
cursor = gdk_cursor_new_for_display(display, GDK_HAND2);
|
||||
}
|
||||
gdk_window_set_cursor(win, cursor);
|
||||
if (cursor) {
|
||||
g_object_unref(cursor);
|
||||
}
|
||||
} else {
|
||||
display = gdk_window_get_display(win);
|
||||
cursor = gdk_cursor_new_from_name(display, over_link ? "pointer" : "text");
|
||||
if (!cursor) {
|
||||
cursor = gdk_cursor_new_for_display(display, over_link ? GDK_HAND2 : GDK_XTERM);
|
||||
}
|
||||
gdk_window_set_cursor(win, cursor);
|
||||
if (cursor) {
|
||||
g_object_unref(cursor);
|
||||
}
|
||||
}
|
||||
|
||||
static void clear_editor_mouse_cursor(MarkydEditor *self) {
|
||||
GdkWindow *win;
|
||||
|
||||
if (!self || !self->text_view) {
|
||||
return;
|
||||
}
|
||||
|
||||
win = gtk_text_view_get_window(GTK_TEXT_VIEW(self->text_view),
|
||||
GTK_TEXT_WINDOW_TEXT);
|
||||
if (win) {
|
||||
gdk_window_set_cursor(win, NULL);
|
||||
}
|
||||
}
|
||||
@@ -136,6 +165,11 @@ static void apply_markdown(MarkydEditor *self) {
|
||||
if (!self) {
|
||||
return;
|
||||
}
|
||||
if (self->edit_mode) {
|
||||
return;
|
||||
}
|
||||
|
||||
render_preview(self);
|
||||
|
||||
self->updating_tags = TRUE;
|
||||
markdown_apply_tags(self->buffer,
|
||||
@@ -190,16 +224,66 @@ static gboolean resolve_image_source_path(MarkydEditor *self, const gchar *src,
|
||||
|
||||
static gint get_image_max_width(MarkydEditor *self) {
|
||||
GtkAllocation alloc;
|
||||
GdkRectangle visible = {0, 0, 0, 0};
|
||||
GtkTextView *view;
|
||||
gint width;
|
||||
|
||||
if (!self || !self->text_view) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
gtk_widget_get_allocation(self->text_view, &alloc);
|
||||
width = alloc.width - gtk_text_view_get_left_margin(GTK_TEXT_VIEW(self->text_view)) -
|
||||
gtk_text_view_get_right_margin(GTK_TEXT_VIEW(self->text_view)) - 24;
|
||||
return MAX(width, 64);
|
||||
view = GTK_TEXT_VIEW(self->text_view);
|
||||
gtk_text_view_get_visible_rect(view, &visible);
|
||||
width = visible.width;
|
||||
|
||||
if (width <= 0) {
|
||||
gtk_widget_get_allocation(self->text_view, &alloc);
|
||||
width = alloc.width;
|
||||
}
|
||||
|
||||
width -= gtk_text_view_get_left_margin(view);
|
||||
width -= gtk_text_view_get_right_margin(view);
|
||||
return MAX(width, 0);
|
||||
}
|
||||
|
||||
static void compute_image_display_size(gint orig_width, gint orig_height,
|
||||
gint requested_width,
|
||||
gint requested_height, gint max_width,
|
||||
gint *out_width, gint *out_height) {
|
||||
gint width = orig_width;
|
||||
gint height = orig_height;
|
||||
|
||||
if (orig_width <= 0 || orig_height <= 0) {
|
||||
width = MAX(requested_width, 1);
|
||||
height = MAX(requested_height, 1);
|
||||
} else if (requested_width > 0 && requested_height > 0) {
|
||||
width = requested_width;
|
||||
height = requested_height;
|
||||
} else if (requested_width > 0) {
|
||||
width = requested_width;
|
||||
height = (gint)(((gdouble)orig_height * (gdouble)width) /
|
||||
(gdouble)orig_width + 0.5);
|
||||
} else if (requested_height > 0) {
|
||||
height = requested_height;
|
||||
width = (gint)(((gdouble)orig_width * (gdouble)height) /
|
||||
(gdouble)orig_height + 0.5);
|
||||
}
|
||||
|
||||
width = MAX(width, 1);
|
||||
height = MAX(height, 1);
|
||||
|
||||
if (max_width > 0 && width > max_width) {
|
||||
height = (gint)(((gdouble)height * (gdouble)max_width) /
|
||||
(gdouble)width + 0.5);
|
||||
width = max_width;
|
||||
}
|
||||
|
||||
if (out_width) {
|
||||
*out_width = MAX(width, 1);
|
||||
}
|
||||
if (out_height) {
|
||||
*out_height = MAX(height, 1);
|
||||
}
|
||||
}
|
||||
|
||||
static void scale_image_widget(GtkWidget *widget, gint max_width) {
|
||||
@@ -207,13 +291,17 @@ static void scale_image_widget(GtkWidget *widget, gint max_width) {
|
||||
GdkPixbuf *orig;
|
||||
gint ow;
|
||||
gint oh;
|
||||
gint requested_width;
|
||||
gint requested_height;
|
||||
gint display_width;
|
||||
gint display_height;
|
||||
|
||||
if (!widget || max_width <= 0) {
|
||||
if (!widget) {
|
||||
return;
|
||||
}
|
||||
|
||||
image = g_object_get_data(G_OBJECT(widget), "viewmd-image-widget-child");
|
||||
orig = g_object_get_data(G_OBJECT(widget), "viewmd-image-orig-pixbuf");
|
||||
image = g_object_get_data(G_OBJECT(widget), "seemd-image-widget-child");
|
||||
orig = g_object_get_data(G_OBJECT(widget), "seemd-image-orig-pixbuf");
|
||||
if (!GTK_IS_IMAGE(image) || !GDK_IS_PIXBUF(orig)) {
|
||||
return;
|
||||
}
|
||||
@@ -224,16 +312,23 @@ static void scale_image_widget(GtkWidget *widget, gint max_width) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (ow > max_width) {
|
||||
gint nh = (gint)(((gdouble)oh * (gdouble)max_width) / (gdouble)ow);
|
||||
GdkPixbuf *scaled =
|
||||
gdk_pixbuf_scale_simple(orig, max_width, MAX(nh, 1), GDK_INTERP_BILINEAR);
|
||||
gtk_image_set_from_pixbuf(GTK_IMAGE(image), scaled);
|
||||
requested_width = GPOINTER_TO_INT(
|
||||
g_object_get_data(G_OBJECT(widget), "seemd-image-requested-width"));
|
||||
requested_height = GPOINTER_TO_INT(
|
||||
g_object_get_data(G_OBJECT(widget), "seemd-image-requested-height"));
|
||||
|
||||
compute_image_display_size(ow, oh, requested_width, requested_height, max_width,
|
||||
&display_width, &display_height);
|
||||
|
||||
if (display_width == ow && display_height == oh) {
|
||||
gtk_image_set_from_pixbuf(GTK_IMAGE(image), orig);
|
||||
} else {
|
||||
GdkPixbuf *scaled = gdk_pixbuf_scale_simple(
|
||||
orig, display_width, display_height, GDK_INTERP_BILINEAR);
|
||||
gtk_image_set_from_pixbuf(GTK_IMAGE(image), scaled ? scaled : orig);
|
||||
if (scaled) {
|
||||
g_object_unref(scaled);
|
||||
}
|
||||
} else {
|
||||
gtk_image_set_from_pixbuf(GTK_IMAGE(image), orig);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -251,9 +346,9 @@ static void refresh_image_widget_scales(MarkydEditor *self) {
|
||||
while (!gtk_text_iter_equal(&iter, &end)) {
|
||||
GtkTextChildAnchor *anchor = gtk_text_iter_get_child_anchor(&iter);
|
||||
if (anchor &&
|
||||
g_object_get_data(G_OBJECT(anchor), VIEWMD_IMAGE_ANCHOR_DATA) != NULL) {
|
||||
g_object_get_data(G_OBJECT(anchor), SEEMD_IMAGE_ANCHOR_DATA) != NULL) {
|
||||
GtkWidget *image_widget =
|
||||
g_object_get_data(G_OBJECT(anchor), VIEWMD_IMAGE_WIDGET_DATA);
|
||||
g_object_get_data(G_OBJECT(anchor), SEEMD_IMAGE_WIDGET_DATA);
|
||||
if (image_widget) {
|
||||
scale_image_widget(image_widget, max_width);
|
||||
}
|
||||
@@ -276,14 +371,18 @@ static void render_image_widgets(MarkydEditor *self) {
|
||||
while (!gtk_text_iter_equal(&iter, &end)) {
|
||||
GtkTextChildAnchor *anchor = gtk_text_iter_get_child_anchor(&iter);
|
||||
if (anchor &&
|
||||
g_object_get_data(G_OBJECT(anchor), VIEWMD_IMAGE_ANCHOR_DATA) != NULL) {
|
||||
g_object_get_data(G_OBJECT(anchor), SEEMD_IMAGE_ANCHOR_DATA) != NULL) {
|
||||
GtkWidget *image_widget =
|
||||
g_object_get_data(G_OBJECT(anchor), VIEWMD_IMAGE_WIDGET_DATA);
|
||||
g_object_get_data(G_OBJECT(anchor), SEEMD_IMAGE_WIDGET_DATA);
|
||||
if (!image_widget) {
|
||||
const gchar *src =
|
||||
g_object_get_data(G_OBJECT(anchor), VIEWMD_IMAGE_SRC_DATA);
|
||||
g_object_get_data(G_OBJECT(anchor), SEEMD_IMAGE_SRC_DATA);
|
||||
const gchar *alt =
|
||||
g_object_get_data(G_OBJECT(anchor), VIEWMD_IMAGE_ALT_DATA);
|
||||
g_object_get_data(G_OBJECT(anchor), SEEMD_IMAGE_ALT_DATA);
|
||||
gint requested_width = GPOINTER_TO_INT(
|
||||
g_object_get_data(G_OBJECT(anchor), SEEMD_IMAGE_WIDTH_DATA));
|
||||
gint requested_height = GPOINTER_TO_INT(
|
||||
g_object_get_data(G_OBJECT(anchor), SEEMD_IMAGE_HEIGHT_DATA));
|
||||
gchar *path = NULL;
|
||||
|
||||
if (resolve_image_source_path(self, src, &path)) {
|
||||
@@ -295,10 +394,14 @@ static void render_image_widgets(MarkydEditor *self) {
|
||||
gtk_event_box_set_visible_window(GTK_EVENT_BOX(event_box), FALSE);
|
||||
gtk_widget_set_halign(event_box, GTK_ALIGN_START);
|
||||
gtk_container_add(GTK_CONTAINER(event_box), image);
|
||||
g_object_set_data(G_OBJECT(event_box), "viewmd-image-widget-child",
|
||||
g_object_set_data(G_OBJECT(event_box), "seemd-image-widget-child",
|
||||
image);
|
||||
g_object_set_data_full(G_OBJECT(event_box), "viewmd-image-orig-pixbuf",
|
||||
g_object_set_data_full(G_OBJECT(event_box), "seemd-image-orig-pixbuf",
|
||||
orig, g_object_unref);
|
||||
g_object_set_data(G_OBJECT(event_box), "seemd-image-requested-width",
|
||||
GINT_TO_POINTER(requested_width));
|
||||
g_object_set_data(G_OBJECT(event_box), "seemd-image-requested-height",
|
||||
GINT_TO_POINTER(requested_height));
|
||||
scale_image_widget(event_box, max_width);
|
||||
image_widget = event_box;
|
||||
} else if (err) {
|
||||
@@ -317,7 +420,7 @@ static void render_image_widgets(MarkydEditor *self) {
|
||||
gtk_text_view_add_child_at_anchor(GTK_TEXT_VIEW(self->text_view),
|
||||
image_widget, anchor);
|
||||
gtk_widget_show_all(image_widget);
|
||||
g_object_set_data(G_OBJECT(anchor), VIEWMD_IMAGE_WIDGET_DATA, image_widget);
|
||||
g_object_set_data(G_OBJECT(anchor), SEEMD_IMAGE_WIDGET_DATA, image_widget);
|
||||
g_free(path);
|
||||
} else {
|
||||
scale_image_widget(image_widget, max_width);
|
||||
@@ -339,16 +442,16 @@ static void render_table_widgets(MarkydEditor *self) {
|
||||
while (!gtk_text_iter_equal(&iter, &end)) {
|
||||
GtkTextChildAnchor *anchor = gtk_text_iter_get_child_anchor(&iter);
|
||||
if (anchor &&
|
||||
g_object_get_data(G_OBJECT(anchor), VIEWMD_TABLE_ANCHOR_DATA) != NULL) {
|
||||
g_object_get_data(G_OBJECT(anchor), SEEMD_TABLE_ANCHOR_DATA) != NULL) {
|
||||
GtkWidget *table =
|
||||
g_object_get_data(G_OBJECT(anchor), VIEWMD_TABLE_WIDGET_DATA);
|
||||
g_object_get_data(G_OBJECT(anchor), SEEMD_TABLE_WIDGET_DATA);
|
||||
if (!table) {
|
||||
table = markdown_create_table_widget(anchor);
|
||||
if (table) {
|
||||
gtk_text_view_add_child_at_anchor(GTK_TEXT_VIEW(self->text_view), table,
|
||||
anchor);
|
||||
gtk_widget_show_all(table);
|
||||
g_object_set_data(G_OBJECT(anchor), VIEWMD_TABLE_WIDGET_DATA, table);
|
||||
g_object_set_data(G_OBJECT(anchor), SEEMD_TABLE_WIDGET_DATA, table);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -367,20 +470,274 @@ static void schedule_markdown_apply(MarkydEditor *self) {
|
||||
if (!self || self->updating_tags || self->markdown_idle_id != 0) {
|
||||
return;
|
||||
}
|
||||
if (self->edit_mode) {
|
||||
return;
|
||||
}
|
||||
|
||||
self->markdown_idle_id =
|
||||
g_idle_add_full(G_PRIORITY_LOW, apply_markdown_idle, self, NULL);
|
||||
}
|
||||
|
||||
void markyd_editor_refresh(MarkydEditor *self) { schedule_markdown_apply(self); }
|
||||
static void sync_source_from_buffer(MarkydEditor *self) {
|
||||
GtkTextIter start;
|
||||
GtkTextIter end;
|
||||
gchar *text;
|
||||
|
||||
if (!self || !self->buffer) {
|
||||
return;
|
||||
}
|
||||
|
||||
gtk_text_buffer_get_bounds(self->buffer, &start, &end);
|
||||
text = gtk_text_buffer_get_text(self->buffer, &start, &end, FALSE);
|
||||
g_free(self->source_content);
|
||||
self->source_content = text ? text : g_strdup("");
|
||||
}
|
||||
|
||||
static void show_source_buffer(MarkydEditor *self) {
|
||||
if (!self || !self->buffer) {
|
||||
return;
|
||||
}
|
||||
|
||||
self->updating_tags = TRUE;
|
||||
gtk_text_buffer_set_text(self->buffer,
|
||||
self->source_content ? self->source_content : "", -1);
|
||||
self->updating_tags = FALSE;
|
||||
}
|
||||
|
||||
static void on_buffer_changed(GtkTextBuffer *buffer, gpointer user_data) {
|
||||
MarkydEditor *self = (MarkydEditor *)user_data;
|
||||
(void)buffer;
|
||||
|
||||
if (!self || self->updating_tags || !self->edit_mode) {
|
||||
return;
|
||||
}
|
||||
|
||||
sync_source_from_buffer(self);
|
||||
}
|
||||
|
||||
static gchar *get_preview_base_uri(MarkydEditor *self) {
|
||||
const gchar *current_path;
|
||||
gchar *dir;
|
||||
GFile *dir_file;
|
||||
gchar *uri;
|
||||
gchar *base_uri;
|
||||
|
||||
current_path = self && self->app ? markyd_app_get_current_path(self->app) : NULL;
|
||||
if (current_path && current_path[0] != '\0') {
|
||||
dir = g_path_get_dirname(current_path);
|
||||
} else {
|
||||
dir = g_get_current_dir();
|
||||
}
|
||||
|
||||
dir_file = g_file_new_for_path(dir);
|
||||
uri = g_file_get_uri(dir_file);
|
||||
base_uri = g_str_has_suffix(uri, "/") ? g_strdup(uri) : g_strconcat(uri, "/", NULL);
|
||||
|
||||
g_free(uri);
|
||||
g_object_unref(dir_file);
|
||||
g_free(dir);
|
||||
return base_uri;
|
||||
}
|
||||
|
||||
static void render_preview(MarkydEditor *self) {
|
||||
gchar *base_uri;
|
||||
gchar *html;
|
||||
|
||||
if (!self || !self->preview_view) {
|
||||
return;
|
||||
}
|
||||
|
||||
base_uri = get_preview_base_uri(self);
|
||||
html = markdown_render_github_html(self->source_content ? self->source_content : "",
|
||||
base_uri);
|
||||
|
||||
g_free(self->preview_base_uri);
|
||||
self->preview_base_uri = g_strdup(base_uri);
|
||||
webkit_web_view_load_html(WEBKIT_WEB_VIEW(self->preview_view), html, base_uri);
|
||||
|
||||
g_free(html);
|
||||
g_free(base_uri);
|
||||
}
|
||||
|
||||
static gboolean preview_uri_is_same_document(MarkydEditor *self,
|
||||
const gchar *uri) {
|
||||
const gchar *fragment;
|
||||
|
||||
if (!uri || uri[0] == '\0') {
|
||||
return TRUE;
|
||||
}
|
||||
if (uri[0] == '#') {
|
||||
return TRUE;
|
||||
}
|
||||
if (g_str_has_prefix(uri, "about:")) {
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
fragment = strchr(uri, '#');
|
||||
if (fragment && self && self->preview_base_uri &&
|
||||
g_str_has_prefix(uri, self->preview_base_uri)) {
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static gboolean path_is_markdown_file(const gchar *path) {
|
||||
gchar *lower;
|
||||
gboolean result;
|
||||
|
||||
if (!path) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
lower = g_ascii_strdown(path, -1);
|
||||
result = g_str_has_suffix(lower, ".md") ||
|
||||
g_str_has_suffix(lower, ".markdown");
|
||||
g_free(lower);
|
||||
return result;
|
||||
}
|
||||
|
||||
static gboolean open_local_markdown_link(MarkydEditor *self, const gchar *uri) {
|
||||
gchar *path;
|
||||
gchar *fragment;
|
||||
gboolean opened = FALSE;
|
||||
|
||||
if (!self || !self->app || !uri || !g_str_has_prefix(uri, "file://")) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
path = g_filename_from_uri(uri, NULL, NULL);
|
||||
if (!path) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
fragment = strchr(path, '#');
|
||||
if (fragment) {
|
||||
*fragment = '\0';
|
||||
}
|
||||
|
||||
if (path_is_markdown_file(path) && g_file_test(path, G_FILE_TEST_EXISTS)) {
|
||||
opened = markyd_app_open_file(self->app, path);
|
||||
}
|
||||
|
||||
g_free(path);
|
||||
return opened;
|
||||
}
|
||||
|
||||
static void open_preview_uri_externally(GtkWidget *widget, const gchar *uri) {
|
||||
GtkWidget *toplevel;
|
||||
GError *error = NULL;
|
||||
|
||||
if (!uri || uri[0] == '\0') {
|
||||
return;
|
||||
}
|
||||
|
||||
toplevel = gtk_widget_get_toplevel(widget);
|
||||
if (!gtk_show_uri_on_window(GTK_IS_WINDOW(toplevel) ? GTK_WINDOW(toplevel)
|
||||
: NULL,
|
||||
uri, GDK_CURRENT_TIME, &error)) {
|
||||
if (error) {
|
||||
g_printerr("Failed to open link '%s': %s\n", uri, error->message);
|
||||
g_clear_error(&error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static gboolean on_preview_decide_policy(WebKitWebView *web_view,
|
||||
WebKitPolicyDecision *decision,
|
||||
WebKitPolicyDecisionType type,
|
||||
gpointer user_data) {
|
||||
MarkydEditor *self = (MarkydEditor *)user_data;
|
||||
WebKitNavigationPolicyDecision *navigation_decision;
|
||||
WebKitNavigationAction *action;
|
||||
WebKitURIRequest *request;
|
||||
const gchar *uri;
|
||||
|
||||
if (type != WEBKIT_POLICY_DECISION_TYPE_NAVIGATION_ACTION) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
navigation_decision = WEBKIT_NAVIGATION_POLICY_DECISION(decision);
|
||||
action = webkit_navigation_policy_decision_get_navigation_action(
|
||||
navigation_decision);
|
||||
if (!action ||
|
||||
webkit_navigation_action_get_navigation_type(action) !=
|
||||
WEBKIT_NAVIGATION_TYPE_LINK_CLICKED) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
request = webkit_navigation_action_get_request(action);
|
||||
uri = request ? webkit_uri_request_get_uri(request) : NULL;
|
||||
if (preview_uri_is_same_document(self, uri)) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (open_local_markdown_link(self, uri)) {
|
||||
webkit_policy_decision_ignore(decision);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
open_preview_uri_externally(GTK_WIDGET(web_view), uri);
|
||||
webkit_policy_decision_ignore(decision);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
void markyd_editor_refresh(MarkydEditor *self) {
|
||||
if (!self) {
|
||||
return;
|
||||
}
|
||||
if (self->edit_mode) {
|
||||
return;
|
||||
}
|
||||
schedule_markdown_apply(self);
|
||||
}
|
||||
|
||||
void markyd_editor_set_edit_mode(MarkydEditor *self, gboolean edit_mode) {
|
||||
if (!self || self->edit_mode == edit_mode) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (edit_mode) {
|
||||
if (self->markdown_idle_id != 0) {
|
||||
g_source_remove(self->markdown_idle_id);
|
||||
self->markdown_idle_id = 0;
|
||||
}
|
||||
self->edit_mode = TRUE;
|
||||
show_source_buffer(self);
|
||||
gtk_text_view_set_editable(GTK_TEXT_VIEW(self->text_view), TRUE);
|
||||
gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(self->text_view), TRUE);
|
||||
gtk_stack_set_visible_child_name(GTK_STACK(self->widget), "source");
|
||||
gtk_widget_grab_focus(self->text_view);
|
||||
} else {
|
||||
sync_source_from_buffer(self);
|
||||
self->edit_mode = FALSE;
|
||||
gtk_text_view_set_editable(GTK_TEXT_VIEW(self->text_view), FALSE);
|
||||
gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(self->text_view), FALSE);
|
||||
gtk_stack_set_visible_child_name(GTK_STACK(self->widget), "preview");
|
||||
schedule_markdown_apply(self);
|
||||
}
|
||||
}
|
||||
|
||||
gboolean markyd_editor_get_edit_mode(MarkydEditor *self) {
|
||||
return self ? self->edit_mode : FALSE;
|
||||
}
|
||||
|
||||
MarkydEditor *markyd_editor_new(MarkydApp *app) {
|
||||
MarkydEditor *self = g_new0(MarkydEditor, 1);
|
||||
WebKitSettings *settings;
|
||||
|
||||
self->app = app;
|
||||
self->source_content = g_strdup("");
|
||||
self->updating_tags = FALSE;
|
||||
self->markdown_idle_id = 0;
|
||||
self->edit_mode = FALSE;
|
||||
self->preview_base_uri = NULL;
|
||||
|
||||
self->widget = gtk_stack_new();
|
||||
gtk_stack_set_transition_type(GTK_STACK(self->widget),
|
||||
GTK_STACK_TRANSITION_TYPE_NONE);
|
||||
gtk_widget_set_hexpand(self->widget, TRUE);
|
||||
gtk_widget_set_vexpand(self->widget, TRUE);
|
||||
|
||||
self->text_view = gtk_text_view_new();
|
||||
gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(self->text_view),
|
||||
@@ -392,8 +749,28 @@ MarkydEditor *markyd_editor_new(MarkydApp *app) {
|
||||
gtk_text_view_set_editable(GTK_TEXT_VIEW(self->text_view), FALSE);
|
||||
gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(self->text_view), FALSE);
|
||||
|
||||
self->source_scroll = gtk_scrolled_window_new(NULL, NULL);
|
||||
gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(self->source_scroll),
|
||||
GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
|
||||
gtk_container_add(GTK_CONTAINER(self->source_scroll), self->text_view);
|
||||
|
||||
self->preview_view = webkit_web_view_new();
|
||||
gtk_widget_set_hexpand(self->preview_view, TRUE);
|
||||
gtk_widget_set_vexpand(self->preview_view, TRUE);
|
||||
settings = webkit_web_view_get_settings(WEBKIT_WEB_VIEW(self->preview_view));
|
||||
webkit_settings_set_enable_javascript(settings, TRUE);
|
||||
webkit_settings_set_enable_javascript_markup(settings, FALSE);
|
||||
webkit_settings_set_auto_load_images(settings, TRUE);
|
||||
g_signal_connect(self->preview_view, "decide-policy",
|
||||
G_CALLBACK(on_preview_decide_policy), self);
|
||||
|
||||
gtk_stack_add_named(GTK_STACK(self->widget), self->preview_view, "preview");
|
||||
gtk_stack_add_named(GTK_STACK(self->widget), self->source_scroll, "source");
|
||||
gtk_stack_set_visible_child_name(GTK_STACK(self->widget), "preview");
|
||||
|
||||
self->buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(self->text_view));
|
||||
markdown_init_tags(self->buffer);
|
||||
g_signal_connect(self->buffer, "changed", G_CALLBACK(on_buffer_changed), self);
|
||||
|
||||
gtk_widget_add_events(self->text_view, GDK_POINTER_MOTION_MASK |
|
||||
GDK_LEAVE_NOTIFY_MASK |
|
||||
@@ -427,6 +804,7 @@ void markyd_editor_free(MarkydEditor *self) {
|
||||
self->markdown_idle_id = 0;
|
||||
}
|
||||
g_free(self->source_content);
|
||||
g_free(self->preview_base_uri);
|
||||
g_free(self);
|
||||
}
|
||||
|
||||
@@ -437,24 +815,42 @@ void markyd_editor_set_content(MarkydEditor *self, const gchar *content) {
|
||||
|
||||
g_free(self->source_content);
|
||||
self->source_content = g_strdup(content ? content : "");
|
||||
schedule_markdown_apply(self);
|
||||
if (self->edit_mode) {
|
||||
show_source_buffer(self);
|
||||
} else {
|
||||
schedule_markdown_apply(self);
|
||||
}
|
||||
}
|
||||
|
||||
gchar *markyd_editor_get_content(MarkydEditor *self) {
|
||||
if (!self) {
|
||||
return g_strdup("");
|
||||
}
|
||||
if (self->edit_mode) {
|
||||
sync_source_from_buffer(self);
|
||||
}
|
||||
return g_strdup(self->source_content ? self->source_content : "");
|
||||
}
|
||||
|
||||
GtkWidget *markyd_editor_get_widget(MarkydEditor *self) {
|
||||
return self->text_view;
|
||||
return self ? self->widget : NULL;
|
||||
}
|
||||
|
||||
void markyd_editor_focus(MarkydEditor *self) {
|
||||
GtkTextMark *insert_mark;
|
||||
|
||||
if (!self || !self->text_view || !self->buffer) {
|
||||
if (!self) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!self->edit_mode) {
|
||||
if (self->preview_view) {
|
||||
gtk_widget_grab_focus(self->preview_view);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (!self->text_view || !self->buffer) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -466,6 +862,74 @@ void markyd_editor_focus(MarkydEditor *self) {
|
||||
}
|
||||
}
|
||||
|
||||
void markyd_editor_scroll_top(MarkydEditor *self) {
|
||||
GtkTextIter start;
|
||||
|
||||
if (!self) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!self->edit_mode && self->preview_view) {
|
||||
webkit_web_view_evaluate_javascript(WEBKIT_WEB_VIEW(self->preview_view),
|
||||
"window.scrollTo(0, 0);", -1, NULL,
|
||||
NULL, NULL, NULL, NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!self->buffer || !self->text_view) {
|
||||
return;
|
||||
}
|
||||
|
||||
gtk_text_buffer_get_start_iter(self->buffer, &start);
|
||||
gtk_text_buffer_place_cursor(self->buffer, &start);
|
||||
gtk_text_view_scroll_to_iter(GTK_TEXT_VIEW(self->text_view), &start, 0.0,
|
||||
FALSE, 0.0, 0.0);
|
||||
}
|
||||
|
||||
void markyd_editor_find(MarkydEditor *self, const gchar *query) {
|
||||
WebKitFindController *controller;
|
||||
|
||||
if (!self || self->edit_mode || !self->preview_view || !query ||
|
||||
query[0] == '\0') {
|
||||
return;
|
||||
}
|
||||
|
||||
controller = webkit_web_view_get_find_controller(
|
||||
WEBKIT_WEB_VIEW(self->preview_view));
|
||||
webkit_find_controller_search(controller, query,
|
||||
WEBKIT_FIND_OPTIONS_CASE_INSENSITIVE |
|
||||
WEBKIT_FIND_OPTIONS_WRAP_AROUND,
|
||||
G_MAXUINT);
|
||||
}
|
||||
|
||||
void markyd_editor_find_next(MarkydEditor *self, gboolean previous) {
|
||||
WebKitFindController *controller;
|
||||
|
||||
if (!self || self->edit_mode || !self->preview_view) {
|
||||
return;
|
||||
}
|
||||
|
||||
controller = webkit_web_view_get_find_controller(
|
||||
WEBKIT_WEB_VIEW(self->preview_view));
|
||||
if (previous) {
|
||||
webkit_find_controller_search_previous(controller);
|
||||
} else {
|
||||
webkit_find_controller_search_next(controller);
|
||||
}
|
||||
}
|
||||
|
||||
void markyd_editor_find_clear(MarkydEditor *self) {
|
||||
WebKitFindController *controller;
|
||||
|
||||
if (!self || !self->preview_view) {
|
||||
return;
|
||||
}
|
||||
|
||||
controller = webkit_web_view_get_find_controller(
|
||||
WEBKIT_WEB_VIEW(self->preview_view));
|
||||
webkit_find_controller_search_finish(controller);
|
||||
}
|
||||
|
||||
static gboolean on_button_release(GtkWidget *widget, GdkEventButton *event,
|
||||
gpointer user_data) {
|
||||
MarkydEditor *self = (MarkydEditor *)user_data;
|
||||
@@ -533,7 +997,7 @@ static gboolean on_motion_notify(GtkWidget *widget, GdkEventMotion *event,
|
||||
(gint)event->y, &bx, &by);
|
||||
gtk_text_view_get_iter_at_location(GTK_TEXT_VIEW(widget), &iter, bx, by);
|
||||
|
||||
set_link_cursor(self, get_link_url_at_iter(self->buffer, &iter, &url));
|
||||
set_editor_mouse_cursor(self, get_link_url_at_iter(self->buffer, &iter, &url));
|
||||
g_free(url);
|
||||
return FALSE;
|
||||
}
|
||||
@@ -543,6 +1007,6 @@ static gboolean on_leave_notify(GtkWidget *widget, GdkEventCrossing *event,
|
||||
MarkydEditor *self = (MarkydEditor *)user_data;
|
||||
(void)widget;
|
||||
(void)event;
|
||||
set_link_cursor(self, FALSE);
|
||||
clear_editor_mouse_cursor(self);
|
||||
return FALSE;
|
||||
}
|
||||
@@ -6,7 +6,10 @@
|
||||
typedef struct _MarkydApp MarkydApp;
|
||||
|
||||
typedef struct _MarkydEditor {
|
||||
GtkWidget *widget;
|
||||
GtkWidget *source_scroll;
|
||||
GtkWidget *text_view;
|
||||
GtkWidget *preview_view;
|
||||
GtkTextBuffer *buffer;
|
||||
MarkydApp *app;
|
||||
|
||||
@@ -18,6 +21,11 @@ typedef struct _MarkydEditor {
|
||||
|
||||
/* Coalesce markdown re-rendering to idle to avoid invalidating GTK iterators. */
|
||||
guint markdown_idle_id;
|
||||
|
||||
/* TRUE when the buffer is showing editable markdown source instead of preview. */
|
||||
gboolean edit_mode;
|
||||
|
||||
gchar *preview_base_uri;
|
||||
} MarkydEditor;
|
||||
|
||||
/* Lifecycle */
|
||||
@@ -28,9 +36,19 @@ void markyd_editor_free(MarkydEditor *editor);
|
||||
void markyd_editor_set_content(MarkydEditor *editor, const gchar *content);
|
||||
gchar *markyd_editor_get_content(MarkydEditor *editor);
|
||||
|
||||
/* Source editing mode */
|
||||
void markyd_editor_set_edit_mode(MarkydEditor *editor, gboolean edit_mode);
|
||||
gboolean markyd_editor_get_edit_mode(MarkydEditor *editor);
|
||||
|
||||
/* Widget access */
|
||||
GtkWidget *markyd_editor_get_widget(MarkydEditor *editor);
|
||||
void markyd_editor_focus(MarkydEditor *editor);
|
||||
void markyd_editor_scroll_top(MarkydEditor *editor);
|
||||
|
||||
/* Preview search */
|
||||
void markyd_editor_find(MarkydEditor *editor, const gchar *query);
|
||||
void markyd_editor_find_next(MarkydEditor *editor, gboolean previous);
|
||||
void markyd_editor_find_clear(MarkydEditor *editor);
|
||||
|
||||
/* Force a refresh of markdown styling/rendering (e.g., after settings change). */
|
||||
void markyd_editor_refresh(MarkydEditor *editor);
|
||||
|
||||
+2264
-45
File diff suppressed because it is too large.
Load diff
+22
-17
@@ -4,7 +4,7 @@
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
/* GObject data key used to mark hrule child anchors inserted into the buffer. */
|
||||
#define TRAYMD_HRULE_ANCHOR_DATA "viewmd-hr-anchor"
|
||||
#define TRAYMD_HRULE_ANCHOR_DATA "seemd-hr-anchor"
|
||||
|
||||
/* Initialize markdown tags on a text buffer */
|
||||
void markdown_init_tags(GtkTextBuffer *buffer);
|
||||
@@ -13,42 +13,44 @@ void markdown_init_tags(GtkTextBuffer *buffer);
|
||||
void markdown_update_accent_tags(GtkTextBuffer *buffer);
|
||||
|
||||
/* Data key set on per-link metadata tags to store resolved URL/href. */
|
||||
#define VIEWMD_LINK_URL_DATA "viewmd-link-url"
|
||||
#define SEEMD_LINK_URL_DATA "seemd-link-url"
|
||||
|
||||
/* Prefix for named text marks used as internal heading anchors. */
|
||||
#define VIEWMD_ANCHOR_MARK_PREFIX "viewmd-anchor-"
|
||||
#define SEEMD_ANCHOR_MARK_PREFIX "seemd-anchor-"
|
||||
|
||||
/* GObject data key used to mark table child anchors with parsed table data. */
|
||||
#define VIEWMD_TABLE_ANCHOR_DATA "viewmd-table-anchor"
|
||||
#define SEEMD_TABLE_ANCHOR_DATA "seemd-table-anchor"
|
||||
/* GObject data key set on table anchors for hidden searchable index metadata. */
|
||||
#define VIEWMD_TABLE_SEARCH_INDEX_DATA "viewmd-table-search-index"
|
||||
#define SEEMD_TABLE_SEARCH_INDEX_DATA "seemd-table-search-index"
|
||||
/* GObject data key set on table anchors for attached table widget instance. */
|
||||
#define VIEWMD_TABLE_WIDGET_DATA "viewmd-table-widget"
|
||||
#define SEEMD_TABLE_WIDGET_DATA "seemd-table-widget"
|
||||
/* GObject data keys set on each table cell widget. */
|
||||
#define VIEWMD_TABLE_CELL_ROW_DATA "viewmd-table-cell-row"
|
||||
#define VIEWMD_TABLE_CELL_COL_DATA "viewmd-table-cell-col"
|
||||
#define SEEMD_TABLE_CELL_ROW_DATA "seemd-table-cell-row"
|
||||
#define SEEMD_TABLE_CELL_COL_DATA "seemd-table-cell-col"
|
||||
/* CSS classes for table search highlight states. */
|
||||
#define VIEWMD_TABLE_CELL_MATCH_CLASS "viewmd-table-cell-match"
|
||||
#define VIEWMD_TABLE_CELL_CURRENT_CLASS "viewmd-table-cell-current"
|
||||
#define SEEMD_TABLE_CELL_MATCH_CLASS "seemd-table-cell-match"
|
||||
#define SEEMD_TABLE_CELL_CURRENT_CLASS "seemd-table-cell-current"
|
||||
|
||||
/* GObject data keys for image anchors and metadata. */
|
||||
#define VIEWMD_IMAGE_ANCHOR_DATA "viewmd-image-anchor"
|
||||
#define VIEWMD_IMAGE_SRC_DATA "viewmd-image-src"
|
||||
#define VIEWMD_IMAGE_ALT_DATA "viewmd-image-alt"
|
||||
#define VIEWMD_IMAGE_WIDGET_DATA "viewmd-image-widget"
|
||||
#define SEEMD_IMAGE_ANCHOR_DATA "seemd-image-anchor"
|
||||
#define SEEMD_IMAGE_SRC_DATA "seemd-image-src"
|
||||
#define SEEMD_IMAGE_ALT_DATA "seemd-image-alt"
|
||||
#define SEEMD_IMAGE_WIDGET_DATA "seemd-image-widget"
|
||||
#define SEEMD_IMAGE_WIDTH_DATA "seemd-image-width"
|
||||
#define SEEMD_IMAGE_HEIGHT_DATA "seemd-image-height"
|
||||
|
||||
typedef struct {
|
||||
gint row;
|
||||
gint col;
|
||||
gint start_offset;
|
||||
gint end_offset;
|
||||
} ViewmdTableSearchCellRange;
|
||||
} SeemdTableSearchCellRange;
|
||||
|
||||
typedef struct {
|
||||
gint start_offset;
|
||||
gint end_offset;
|
||||
GArray *cells; /* ViewmdTableSearchCellRange */
|
||||
} ViewmdTableSearchIndex;
|
||||
GArray *cells; /* SeemdTableSearchCellRange */
|
||||
} SeemdTableSearchIndex;
|
||||
|
||||
/* Normalize heading/link text into anchor slug form. Caller owns result. */
|
||||
gchar *markdown_normalize_anchor_slug(const gchar *text);
|
||||
@@ -59,6 +61,9 @@ gchar *markdown_anchor_mark_name(const gchar *fragment);
|
||||
/* Render markdown source into the buffer and apply markdown styling. */
|
||||
void markdown_apply_tags(GtkTextBuffer *buffer, const gchar *source);
|
||||
|
||||
/* Render markdown source to a complete GitHub-style HTML document. */
|
||||
gchar *markdown_render_github_html(const gchar *source, const gchar *base_uri);
|
||||
|
||||
/* Build a GTK widget for a table anchor, or NULL if not a table anchor. */
|
||||
GtkWidget *markdown_create_table_widget(GtkTextChildAnchor *anchor);
|
||||
|
||||
|
||||
+379
-64
@@ -12,12 +12,16 @@ typedef struct {
|
||||
gint table_col;
|
||||
} SearchMatch;
|
||||
|
||||
#define TAG_SEARCH_MATCH "viewmd_search_match"
|
||||
#define TAG_SEARCH_CURRENT "viewmd_search_current"
|
||||
#define TAG_SEARCH_MATCH "seemd_search_match"
|
||||
#define TAG_SEARCH_CURRENT "seemd_search_current"
|
||||
#define TOOLBAR_LABEL_DATA "seemd-toolbar-label"
|
||||
|
||||
static void on_open_clicked(GtkButton *button, gpointer user_data);
|
||||
static void on_refresh_clicked(GtkButton *button, gpointer user_data);
|
||||
static void on_edit_clicked(GtkButton *button, gpointer user_data);
|
||||
static void on_save_clicked(GtkButton *button, gpointer user_data);
|
||||
static void on_settings_clicked(GtkButton *button, gpointer user_data);
|
||||
static void on_about_clicked(GtkButton *button, gpointer user_data);
|
||||
static void on_search_changed(GtkEditable *editable, gpointer user_data);
|
||||
static void on_search_prev_clicked(GtkButton *button, gpointer user_data);
|
||||
static void on_search_next_clicked(GtkButton *button, gpointer user_data);
|
||||
@@ -62,9 +66,16 @@ static gchar *rgba_to_hex(const GdkRGBA *rgba);
|
||||
static void init_color_button(GtkColorButton *btn, const gchar *color_str);
|
||||
static void on_color_set(GtkColorButton *btn, gpointer user_data);
|
||||
static GtkWidget *create_settings_dialog(MarkydApp *app);
|
||||
static gboolean prompt_save_as(MarkydWindow *self);
|
||||
static void update_edit_button_state(MarkydWindow *self);
|
||||
static void set_edit_mode(MarkydWindow *self, gboolean edit_mode);
|
||||
static GtkWidget *create_toolbar_button(const gchar *icon_name, const gchar *label,
|
||||
const gchar *tooltip);
|
||||
static void set_toolbar_button_label(GtkWidget *button, const gchar *label);
|
||||
static void restore_editor_focus(MarkydWindow *self);
|
||||
|
||||
static gboolean geometry_debug_enabled(void) {
|
||||
const gchar *v = g_getenv("VIEWMD_DEBUG_GEOMETRY");
|
||||
const gchar *v = g_getenv("SEEMD_DEBUG_GEOMETRY");
|
||||
if (!v) {
|
||||
v = g_getenv("TRAYMD_DEBUG_GEOMETRY");
|
||||
}
|
||||
@@ -128,6 +139,8 @@ static void clear_search_matches(MarkydWindow *self) {
|
||||
return;
|
||||
}
|
||||
|
||||
markyd_editor_find_clear(self->editor);
|
||||
|
||||
gtk_text_buffer_get_bounds(self->editor->buffer, &start, &end);
|
||||
gtk_text_buffer_remove_tag_by_name(self->editor->buffer, TAG_SEARCH_MATCH,
|
||||
&start, &end);
|
||||
@@ -161,15 +174,15 @@ static void set_table_cell_highlight(GtkWidget *cell, gboolean match,
|
||||
|
||||
style = gtk_widget_get_style_context(cell);
|
||||
if (match) {
|
||||
gtk_style_context_add_class(style, VIEWMD_TABLE_CELL_MATCH_CLASS);
|
||||
gtk_style_context_add_class(style, SEEMD_TABLE_CELL_MATCH_CLASS);
|
||||
} else {
|
||||
gtk_style_context_remove_class(style, VIEWMD_TABLE_CELL_MATCH_CLASS);
|
||||
gtk_style_context_remove_class(style, SEEMD_TABLE_CELL_MATCH_CLASS);
|
||||
}
|
||||
|
||||
if (current) {
|
||||
gtk_style_context_add_class(style, VIEWMD_TABLE_CELL_CURRENT_CLASS);
|
||||
gtk_style_context_add_class(style, SEEMD_TABLE_CELL_CURRENT_CLASS);
|
||||
} else {
|
||||
gtk_style_context_remove_class(style, VIEWMD_TABLE_CELL_CURRENT_CLASS);
|
||||
gtk_style_context_remove_class(style, SEEMD_TABLE_CELL_CURRENT_CLASS);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -234,9 +247,9 @@ static GtkWidget *lookup_table_cell_widget(GtkWidget *table_widget, gint row,
|
||||
for (GList *g = grid_children; g != NULL; g = g->next) {
|
||||
GtkWidget *cell = GTK_WIDGET(g->data);
|
||||
gint cell_row = GPOINTER_TO_INT(
|
||||
g_object_get_data(G_OBJECT(cell), VIEWMD_TABLE_CELL_ROW_DATA));
|
||||
g_object_get_data(G_OBJECT(cell), SEEMD_TABLE_CELL_ROW_DATA));
|
||||
gint cell_col = GPOINTER_TO_INT(
|
||||
g_object_get_data(G_OBJECT(cell), VIEWMD_TABLE_CELL_COL_DATA));
|
||||
g_object_get_data(G_OBJECT(cell), SEEMD_TABLE_CELL_COL_DATA));
|
||||
if (cell_row == row && cell_col == col) {
|
||||
found = cell;
|
||||
break;
|
||||
@@ -262,9 +275,9 @@ static void clear_table_search_highlight(MarkydWindow *self, gboolean clear_matc
|
||||
while (!gtk_text_iter_equal(&iter, &end)) {
|
||||
GtkTextChildAnchor *anchor = gtk_text_iter_get_child_anchor(&iter);
|
||||
if (anchor &&
|
||||
g_object_get_data(G_OBJECT(anchor), VIEWMD_TABLE_ANCHOR_DATA) != NULL) {
|
||||
g_object_get_data(G_OBJECT(anchor), SEEMD_TABLE_ANCHOR_DATA) != NULL) {
|
||||
GtkWidget *table_widget =
|
||||
g_object_get_data(G_OBJECT(anchor), VIEWMD_TABLE_WIDGET_DATA);
|
||||
g_object_get_data(G_OBJECT(anchor), SEEMD_TABLE_WIDGET_DATA);
|
||||
if (table_widget && GTK_IS_CONTAINER(table_widget)) {
|
||||
GList *wrapper_children =
|
||||
gtk_container_get_children(GTK_CONTAINER(table_widget));
|
||||
@@ -278,11 +291,11 @@ static void clear_table_search_highlight(MarkydWindow *self, gboolean clear_matc
|
||||
GtkWidget *cell = GTK_WIDGET(g->data);
|
||||
if (clear_match) {
|
||||
gtk_style_context_remove_class(gtk_widget_get_style_context(cell),
|
||||
VIEWMD_TABLE_CELL_MATCH_CLASS);
|
||||
SEEMD_TABLE_CELL_MATCH_CLASS);
|
||||
}
|
||||
if (clear_current) {
|
||||
gtk_style_context_remove_class(gtk_widget_get_style_context(cell),
|
||||
VIEWMD_TABLE_CELL_CURRENT_CLASS);
|
||||
SEEMD_TABLE_CELL_CURRENT_CLASS);
|
||||
}
|
||||
}
|
||||
g_list_free(grid_children);
|
||||
@@ -320,8 +333,8 @@ static gboolean resolve_table_match_location(MarkydWindow *self, gint start_offs
|
||||
while (!gtk_text_iter_equal(&iter, &end)) {
|
||||
GtkTextChildAnchor *anchor = gtk_text_iter_get_child_anchor(&iter);
|
||||
if (anchor) {
|
||||
ViewmdTableSearchIndex *index = g_object_get_data(
|
||||
G_OBJECT(anchor), VIEWMD_TABLE_SEARCH_INDEX_DATA);
|
||||
SeemdTableSearchIndex *index = g_object_get_data(
|
||||
G_OBJECT(anchor), SEEMD_TABLE_SEARCH_INDEX_DATA);
|
||||
if (index && start_offset < index->end_offset &&
|
||||
end_offset > index->start_offset) {
|
||||
if (out_anchor) {
|
||||
@@ -329,10 +342,10 @@ static gboolean resolve_table_match_location(MarkydWindow *self, gint start_offs
|
||||
}
|
||||
|
||||
if (index->cells) {
|
||||
ViewmdTableSearchCellRange *overlap_cell = NULL;
|
||||
SeemdTableSearchCellRange *overlap_cell = NULL;
|
||||
for (guint i = 0; i < index->cells->len; i++) {
|
||||
ViewmdTableSearchCellRange *cell =
|
||||
&g_array_index(index->cells, ViewmdTableSearchCellRange, i);
|
||||
SeemdTableSearchCellRange *cell =
|
||||
&g_array_index(index->cells, SeemdTableSearchCellRange, i);
|
||||
if (start_offset >= cell->start_offset && start_offset < cell->end_offset) {
|
||||
if (out_row) {
|
||||
*out_row = cell->row;
|
||||
@@ -383,7 +396,7 @@ static void apply_table_search_match_highlight(MarkydWindow *self) {
|
||||
}
|
||||
|
||||
table_widget =
|
||||
g_object_get_data(G_OBJECT(match->table_anchor), VIEWMD_TABLE_WIDGET_DATA);
|
||||
g_object_get_data(G_OBJECT(match->table_anchor), SEEMD_TABLE_WIDGET_DATA);
|
||||
cell = lookup_table_cell_widget(table_widget, match->table_row, match->table_col);
|
||||
if (cell) {
|
||||
set_table_cell_highlight(cell, TRUE, FALSE);
|
||||
@@ -415,7 +428,7 @@ static void jump_to_search_match(MarkydWindow *self, gint index,
|
||||
match = &g_array_index(self->search_matches, SearchMatch, index);
|
||||
if (match->table_anchor && match->table_row >= 0 && match->table_col >= 0) {
|
||||
GtkWidget *table_widget =
|
||||
g_object_get_data(G_OBJECT(match->table_anchor), VIEWMD_TABLE_WIDGET_DATA);
|
||||
g_object_get_data(G_OBJECT(match->table_anchor), SEEMD_TABLE_WIDGET_DATA);
|
||||
GtkWidget *cell =
|
||||
lookup_table_cell_widget(table_widget, match->table_row, match->table_col);
|
||||
if (cell) {
|
||||
@@ -436,7 +449,7 @@ static void jump_to_search_match(MarkydWindow *self, gint index,
|
||||
gboolean scrolled_to_cell = FALSE;
|
||||
if (match->table_row >= 0 && match->table_col >= 0) {
|
||||
GtkWidget *table_widget = g_object_get_data(G_OBJECT(match->table_anchor),
|
||||
VIEWMD_TABLE_WIDGET_DATA);
|
||||
SEEMD_TABLE_WIDGET_DATA);
|
||||
GtkWidget *cell = lookup_table_cell_widget(table_widget, match->table_row,
|
||||
match->table_col);
|
||||
if (cell) {
|
||||
@@ -480,6 +493,14 @@ static void update_search_matches(MarkydWindow *self) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!markyd_editor_get_edit_mode(self->editor)) {
|
||||
markyd_editor_find(self->editor, query);
|
||||
gtk_widget_set_sensitive(self->btn_search_prev, TRUE);
|
||||
gtk_widget_set_sensitive(self->btn_search_next, TRUE);
|
||||
gtk_label_set_text(GTK_LABEL(self->lbl_search_status), "Preview search");
|
||||
return;
|
||||
}
|
||||
|
||||
ensure_search_tags(self);
|
||||
gtk_text_buffer_get_start_iter(self->editor->buffer, &iter);
|
||||
gtk_text_buffer_get_end_iter(self->editor->buffer, &end);
|
||||
@@ -548,6 +569,11 @@ static void on_search_prev_clicked(GtkButton *button, gpointer user_data) {
|
||||
(void)button;
|
||||
|
||||
if (!self || !self->search_matches || self->search_matches->len == 0) {
|
||||
if (self && self->editor && !markyd_editor_get_edit_mode(self->editor) &&
|
||||
self->search_entry &&
|
||||
gtk_entry_get_text_length(GTK_ENTRY(self->search_entry)) > 0) {
|
||||
markyd_editor_find_next(self->editor, TRUE);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -565,6 +591,11 @@ static void on_search_next_clicked(GtkButton *button, gpointer user_data) {
|
||||
(void)button;
|
||||
|
||||
if (!self || !self->search_matches || self->search_matches->len == 0) {
|
||||
if (self && self->editor && !markyd_editor_get_edit_mode(self->editor) &&
|
||||
self->search_entry &&
|
||||
gtk_entry_get_text_length(GTK_ENTRY(self->search_entry)) > 0) {
|
||||
markyd_editor_find_next(self->editor, FALSE);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -624,16 +655,121 @@ static void on_editor_buffer_changed(GtkTextBuffer *buffer, gpointer user_data)
|
||||
update_search_matches(self);
|
||||
}
|
||||
|
||||
static GtkWidget *create_toolbar_button_internal(const gchar *icon_name,
|
||||
const gchar *label,
|
||||
const gchar *tooltip,
|
||||
gboolean toggle) {
|
||||
GtkWidget *button;
|
||||
GtkWidget *box;
|
||||
GtkWidget *image;
|
||||
GtkWidget *label_widget;
|
||||
|
||||
button = toggle ? gtk_toggle_button_new() : gtk_button_new();
|
||||
box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 4);
|
||||
image = gtk_image_new_from_icon_name(icon_name, GTK_ICON_SIZE_BUTTON);
|
||||
label_widget = gtk_label_new(label);
|
||||
|
||||
gtk_widget_set_halign(box, GTK_ALIGN_CENTER);
|
||||
gtk_widget_set_valign(box, GTK_ALIGN_CENTER);
|
||||
gtk_box_pack_start(GTK_BOX(box), image, FALSE, FALSE, 0);
|
||||
gtk_box_pack_start(GTK_BOX(box), label_widget, FALSE, FALSE, 0);
|
||||
gtk_container_add(GTK_CONTAINER(button), box);
|
||||
gtk_widget_set_focus_on_click(button, FALSE);
|
||||
|
||||
if (tooltip) {
|
||||
gtk_widget_set_tooltip_text(button, tooltip);
|
||||
}
|
||||
g_object_set_data(G_OBJECT(button), TOOLBAR_LABEL_DATA, label_widget);
|
||||
return button;
|
||||
}
|
||||
|
||||
static GtkWidget *create_toolbar_button(const gchar *icon_name, const gchar *label,
|
||||
const gchar *tooltip) {
|
||||
return create_toolbar_button_internal(icon_name, label, tooltip, FALSE);
|
||||
}
|
||||
|
||||
static void set_toolbar_button_label(GtkWidget *button, const gchar *label) {
|
||||
GtkWidget *label_widget;
|
||||
|
||||
if (!button || !label) {
|
||||
return;
|
||||
}
|
||||
|
||||
label_widget = g_object_get_data(G_OBJECT(button), TOOLBAR_LABEL_DATA);
|
||||
if (GTK_IS_LABEL(label_widget)) {
|
||||
gtk_label_set_text(GTK_LABEL(label_widget), label);
|
||||
}
|
||||
}
|
||||
|
||||
static void restore_editor_focus(MarkydWindow *self) {
|
||||
if (self && self->editor) {
|
||||
markyd_editor_focus(self->editor);
|
||||
}
|
||||
}
|
||||
|
||||
static void on_about_popover_closed(GtkPopover *popover, gpointer user_data) {
|
||||
MarkydWindow *self = (MarkydWindow *)user_data;
|
||||
|
||||
restore_editor_focus(self);
|
||||
gtk_widget_destroy(GTK_WIDGET(popover));
|
||||
}
|
||||
|
||||
static void on_about_clicked(GtkButton *button, gpointer user_data) {
|
||||
MarkydWindow *self = (MarkydWindow *)user_data;
|
||||
GtkWidget *popover;
|
||||
GtkWidget *box;
|
||||
GtkWidget *title;
|
||||
GtkWidget *summary;
|
||||
GtkWidget *details;
|
||||
|
||||
popover = gtk_popover_new(GTK_WIDGET(button));
|
||||
gtk_popover_set_position(GTK_POPOVER(popover), GTK_POS_BOTTOM);
|
||||
gtk_container_set_border_width(GTK_CONTAINER(popover), 12);
|
||||
g_signal_connect(popover, "closed", G_CALLBACK(on_about_popover_closed), self);
|
||||
|
||||
box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 8);
|
||||
gtk_widget_set_size_request(box, 300, -1);
|
||||
gtk_container_add(GTK_CONTAINER(popover), box);
|
||||
|
||||
title = gtk_label_new(NULL);
|
||||
gtk_label_set_markup(
|
||||
GTK_LABEL(title),
|
||||
"<a href=\"https://github.com/NotAlexNoyle/SeeMD\"><b>SeeMD</b></a>");
|
||||
gtk_label_set_xalign(GTK_LABEL(title), 0.0f);
|
||||
gtk_box_pack_start(GTK_BOX(box), title, FALSE, FALSE, 0);
|
||||
|
||||
summary = gtk_label_new("A lightweight GTK markdown viewer and editor.");
|
||||
gtk_label_set_xalign(GTK_LABEL(summary), 0.0f);
|
||||
gtk_label_set_line_wrap(GTK_LABEL(summary), TRUE);
|
||||
gtk_box_pack_start(GTK_BOX(box), summary, FALSE, FALSE, 0);
|
||||
|
||||
details = gtk_label_new(NULL);
|
||||
gtk_label_set_markup(
|
||||
GTK_LABEL(details),
|
||||
"This is a fork of "
|
||||
"<a href=\"https://github.com/rabfulton/ViewMD\">ViewMD</a> by "
|
||||
"NotAlexNoyle, with additional features, including GitHub-style preview "
|
||||
"rendering, HTML-in-Markdown support, editing, saving, toolbar labels, "
|
||||
"and improved image sizing.");
|
||||
gtk_label_set_xalign(GTK_LABEL(details), 0.0f);
|
||||
gtk_label_set_line_wrap(GTK_LABEL(details), TRUE);
|
||||
gtk_label_set_max_width_chars(GTK_LABEL(details), 42);
|
||||
gtk_box_pack_start(GTK_BOX(box), details, FALSE, FALSE, 0);
|
||||
|
||||
gtk_widget_show_all(popover);
|
||||
gtk_popover_popup(GTK_POPOVER(popover));
|
||||
}
|
||||
|
||||
MarkydWindow *markyd_window_new(MarkydApp *app) {
|
||||
MarkydWindow *self = g_new0(MarkydWindow, 1);
|
||||
GtkWidget *left_buttons;
|
||||
GtkWidget *toolbar_buttons;
|
||||
GtkWidget *main_box;
|
||||
GtkWidget *search_box;
|
||||
|
||||
self->app = app;
|
||||
|
||||
self->window = gtk_application_window_new(app->gtk_app);
|
||||
gtk_window_set_title(GTK_WINDOW(self->window), "ViewMD");
|
||||
gtk_window_set_title(GTK_WINDOW(self->window), "SeeMD");
|
||||
|
||||
gtk_window_set_default_size(GTK_WINDOW(self->window), config->window_width,
|
||||
config->window_height);
|
||||
@@ -644,7 +780,7 @@ MarkydWindow *markyd_window_new(MarkydApp *app) {
|
||||
}
|
||||
|
||||
if (geometry_debug_enabled()) {
|
||||
g_printerr("ViewMD geometry init: x=%d y=%d w=%d h=%d maximized=%d\n",
|
||||
g_printerr("SeeMD geometry init: x=%d y=%d w=%d h=%d maximized=%d\n",
|
||||
config->window_x, config->window_y, config->window_width,
|
||||
config->window_height, config->window_maximized);
|
||||
}
|
||||
@@ -660,40 +796,63 @@ MarkydWindow *markyd_window_new(MarkydApp *app) {
|
||||
g_signal_connect(self->window, "window-state-event",
|
||||
G_CALLBACK(on_window_state_event), self);
|
||||
|
||||
self->header_bar = gtk_header_bar_new();
|
||||
gtk_header_bar_set_show_close_button(GTK_HEADER_BAR(self->header_bar), TRUE);
|
||||
gtk_window_set_titlebar(GTK_WINDOW(self->window), self->header_bar);
|
||||
|
||||
self->lbl_title = gtk_label_new("ViewMD");
|
||||
gtk_widget_set_halign(self->lbl_title, GTK_ALIGN_CENTER);
|
||||
gtk_header_bar_set_custom_title(GTK_HEADER_BAR(self->header_bar),
|
||||
self->lbl_title);
|
||||
self->lbl_title = gtk_button_new_from_icon_name("view-reveal-symbolic",
|
||||
GTK_ICON_SIZE_BUTTON);
|
||||
gtk_button_set_relief(GTK_BUTTON(self->lbl_title), GTK_RELIEF_NORMAL);
|
||||
gtk_widget_set_valign(self->lbl_title, GTK_ALIGN_CENTER);
|
||||
gtk_widget_set_focus_on_click(self->lbl_title, FALSE);
|
||||
gtk_widget_set_tooltip_text(self->lbl_title, "About SeeMD");
|
||||
gtk_widget_set_halign(self->lbl_title, GTK_ALIGN_END);
|
||||
gtk_widget_set_margin_start(self->lbl_title, 12);
|
||||
gtk_widget_set_margin_end(self->lbl_title, 12);
|
||||
g_signal_connect(self->lbl_title, "clicked", G_CALLBACK(on_about_clicked),
|
||||
self);
|
||||
|
||||
self->btn_open =
|
||||
gtk_button_new_from_icon_name("document-open-symbolic", GTK_ICON_SIZE_BUTTON);
|
||||
gtk_widget_set_tooltip_text(self->btn_open, "Open Markdown Document");
|
||||
create_toolbar_button("document-open-symbolic", "Open",
|
||||
"Open Markdown Document");
|
||||
g_signal_connect(self->btn_open, "clicked", G_CALLBACK(on_open_clicked), self);
|
||||
|
||||
self->btn_refresh =
|
||||
gtk_button_new_from_icon_name("view-refresh-symbolic", GTK_ICON_SIZE_BUTTON);
|
||||
gtk_widget_set_tooltip_text(self->btn_refresh, "Reload Current Document");
|
||||
create_toolbar_button("view-refresh-symbolic", "Reload",
|
||||
"Reload Current Document");
|
||||
g_signal_connect(self->btn_refresh, "clicked", G_CALLBACK(on_refresh_clicked),
|
||||
self);
|
||||
|
||||
self->btn_edit =
|
||||
create_toolbar_button("accessories-text-editor-symbolic", "Edit",
|
||||
"Edit Markdown Source");
|
||||
g_signal_connect(self->btn_edit, "clicked", G_CALLBACK(on_edit_clicked), self);
|
||||
|
||||
self->btn_save =
|
||||
create_toolbar_button("document-save-symbolic", "Save",
|
||||
"Save Markdown Document");
|
||||
g_signal_connect(self->btn_save, "clicked", G_CALLBACK(on_save_clicked), self);
|
||||
|
||||
self->btn_settings =
|
||||
gtk_button_new_from_icon_name("emblem-system-symbolic", GTK_ICON_SIZE_BUTTON);
|
||||
gtk_widget_set_tooltip_text(self->btn_settings, "Settings");
|
||||
create_toolbar_button("emblem-system-symbolic", "Settings", "Settings");
|
||||
g_signal_connect(self->btn_settings, "clicked", G_CALLBACK(on_settings_clicked),
|
||||
self);
|
||||
|
||||
left_buttons = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
|
||||
gtk_box_pack_start(GTK_BOX(left_buttons), self->btn_open, FALSE, FALSE, 0);
|
||||
gtk_box_pack_start(GTK_BOX(left_buttons), self->btn_refresh, FALSE, FALSE, 0);
|
||||
gtk_box_pack_start(GTK_BOX(left_buttons), self->btn_settings, FALSE, FALSE, 0);
|
||||
gtk_header_bar_pack_start(GTK_HEADER_BAR(self->header_bar), left_buttons);
|
||||
toolbar_buttons = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
|
||||
gtk_widget_set_halign(toolbar_buttons, GTK_ALIGN_CENTER);
|
||||
gtk_box_pack_start(GTK_BOX(toolbar_buttons), self->btn_open, FALSE, FALSE, 0);
|
||||
gtk_box_pack_start(GTK_BOX(toolbar_buttons), self->btn_refresh, FALSE, FALSE, 0);
|
||||
gtk_box_pack_start(GTK_BOX(toolbar_buttons), self->btn_edit, FALSE, FALSE, 0);
|
||||
gtk_box_pack_start(GTK_BOX(toolbar_buttons), self->btn_save, FALSE, FALSE, 0);
|
||||
gtk_box_pack_start(GTK_BOX(toolbar_buttons), self->btn_settings, FALSE, FALSE, 0);
|
||||
|
||||
/* Plain in-window toolbar (no GtkHeaderBar/CSD) so the window manager draws
|
||||
* server-side decorations with full window controls (e.g. KDE/KWin). */
|
||||
self->header_bar = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
|
||||
gtk_style_context_add_class(gtk_widget_get_style_context(self->header_bar),
|
||||
"toolbar");
|
||||
gtk_box_set_center_widget(GTK_BOX(self->header_bar), toolbar_buttons);
|
||||
gtk_box_pack_end(GTK_BOX(self->header_bar), self->lbl_title, FALSE, FALSE, 0);
|
||||
|
||||
main_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
|
||||
gtk_container_add(GTK_CONTAINER(self->window), main_box);
|
||||
gtk_box_pack_start(GTK_BOX(main_box), self->header_bar, FALSE, FALSE, 0);
|
||||
|
||||
self->search_revealer = gtk_revealer_new();
|
||||
gtk_revealer_set_transition_type(GTK_REVEALER(self->search_revealer),
|
||||
@@ -737,14 +896,10 @@ MarkydWindow *markyd_window_new(MarkydApp *app) {
|
||||
gtk_widget_set_halign(self->lbl_search_status, GTK_ALIGN_END);
|
||||
gtk_box_pack_start(GTK_BOX(search_box), self->lbl_search_status, FALSE, FALSE, 0);
|
||||
|
||||
self->scroll = gtk_scrolled_window_new(NULL, NULL);
|
||||
gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(self->scroll),
|
||||
GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
|
||||
gtk_box_pack_start(GTK_BOX(main_box), self->scroll, TRUE, TRUE, 0);
|
||||
|
||||
self->editor = markyd_editor_new(app);
|
||||
gtk_container_add(GTK_CONTAINER(self->scroll),
|
||||
markyd_editor_get_widget(self->editor));
|
||||
self->scroll = self->editor->source_scroll;
|
||||
gtk_box_pack_start(GTK_BOX(main_box), markyd_editor_get_widget(self->editor),
|
||||
TRUE, TRUE, 0);
|
||||
self->search_matches = g_array_new(FALSE, FALSE, sizeof(SearchMatch));
|
||||
self->search_current_index = -1;
|
||||
g_signal_connect(self->editor->buffer, "changed",
|
||||
@@ -755,6 +910,10 @@ MarkydWindow *markyd_window_new(MarkydApp *app) {
|
||||
|
||||
gtk_widget_show_all(self->window);
|
||||
|
||||
/* Focus the document instead of letting the first toolbar button (Open)
|
||||
* grab default keyboard focus. */
|
||||
markyd_editor_focus(self->editor);
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
@@ -836,28 +995,28 @@ void markyd_window_apply_css(MarkydWindow *self) {
|
||||
"window {"
|
||||
" background-color: %s;"
|
||||
"}"
|
||||
".viewmd-table-cell {"
|
||||
".seemd-table-cell {"
|
||||
" background-color: %s;"
|
||||
" border-style: solid;"
|
||||
" border-width: 1px;"
|
||||
" border-color: %s;"
|
||||
"}"
|
||||
".viewmd-table-header-cell {"
|
||||
".seemd-table-header-cell {"
|
||||
" background-color: %s;"
|
||||
"}"
|
||||
".viewmd-table-cell label {"
|
||||
".seemd-table-cell label {"
|
||||
" color: %s;"
|
||||
"}"
|
||||
".viewmd-table-cell." VIEWMD_TABLE_CELL_MATCH_CLASS " {"
|
||||
".seemd-table-cell." SEEMD_TABLE_CELL_MATCH_CLASS " {"
|
||||
" background-color: %s;"
|
||||
"}"
|
||||
".viewmd-table-cell." VIEWMD_TABLE_CELL_MATCH_CLASS " label {"
|
||||
".seemd-table-cell." SEEMD_TABLE_CELL_MATCH_CLASS " label {"
|
||||
" color: %s;"
|
||||
"}"
|
||||
".viewmd-table-cell." VIEWMD_TABLE_CELL_CURRENT_CLASS " {"
|
||||
".seemd-table-cell." SEEMD_TABLE_CELL_CURRENT_CLASS " {"
|
||||
" background-color: %s;"
|
||||
"}"
|
||||
".viewmd-table-cell." VIEWMD_TABLE_CELL_CURRENT_CLASS " label {"
|
||||
".seemd-table-cell." SEEMD_TABLE_CELL_CURRENT_CLASS " label {"
|
||||
" color: %s;"
|
||||
"}",
|
||||
config->font_family, config->font_size, bg, fg, fg, bg, fg, fg, sel_bg, bg,
|
||||
@@ -918,6 +1077,94 @@ gboolean markyd_window_is_visible(MarkydWindow *self) {
|
||||
return gtk_widget_get_visible(self->window);
|
||||
}
|
||||
|
||||
static void update_edit_button_state(MarkydWindow *self) {
|
||||
gboolean editing;
|
||||
|
||||
if (!self || !self->editor || !self->btn_edit) {
|
||||
return;
|
||||
}
|
||||
|
||||
editing = markyd_editor_get_edit_mode(self->editor);
|
||||
set_toolbar_button_label(self->btn_edit, editing ? "Preview" : "Edit");
|
||||
gtk_widget_set_tooltip_text(self->btn_edit,
|
||||
editing ? "Preview Markdown" : "Edit Markdown Source");
|
||||
}
|
||||
|
||||
static void set_edit_mode(MarkydWindow *self, gboolean edit_mode) {
|
||||
if (!self || !self->editor) {
|
||||
return;
|
||||
}
|
||||
|
||||
markyd_editor_set_edit_mode(self->editor, edit_mode);
|
||||
update_edit_button_state(self);
|
||||
markyd_editor_focus(self->editor);
|
||||
if (self->search_revealer &&
|
||||
gtk_revealer_get_reveal_child(GTK_REVEALER(self->search_revealer)) &&
|
||||
self->search_entry &&
|
||||
gtk_entry_get_text_length(GTK_ENTRY(self->search_entry)) > 0) {
|
||||
update_search_matches(self);
|
||||
}
|
||||
}
|
||||
|
||||
static gboolean prompt_save_as(MarkydWindow *self) {
|
||||
GtkWidget *dialog;
|
||||
GtkFileFilter *md_filter;
|
||||
gint response;
|
||||
gboolean saved = FALSE;
|
||||
|
||||
if (!self || !self->window) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
dialog = gtk_file_chooser_dialog_new(
|
||||
"Save Markdown Document", GTK_WINDOW(self->window),
|
||||
GTK_FILE_CHOOSER_ACTION_SAVE, "_Cancel", GTK_RESPONSE_CANCEL, "_Save",
|
||||
GTK_RESPONSE_ACCEPT, NULL);
|
||||
gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(dialog), TRUE);
|
||||
|
||||
md_filter = gtk_file_filter_new();
|
||||
gtk_file_filter_set_name(md_filter, "Markdown files (*.md, *.markdown)");
|
||||
gtk_file_filter_add_pattern(md_filter, "*.md");
|
||||
gtk_file_filter_add_pattern(md_filter, "*.markdown");
|
||||
gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog), md_filter);
|
||||
|
||||
md_filter = gtk_file_filter_new();
|
||||
gtk_file_filter_set_name(md_filter, "All files");
|
||||
gtk_file_filter_add_pattern(md_filter, "*");
|
||||
gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog), md_filter);
|
||||
|
||||
{
|
||||
const gchar *current = markyd_app_get_current_path(self->app);
|
||||
if (current && current[0] != '\0') {
|
||||
gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(dialog), current);
|
||||
} else {
|
||||
gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog), "untitled.md");
|
||||
}
|
||||
}
|
||||
|
||||
response = gtk_dialog_run(GTK_DIALOG(dialog));
|
||||
if (response == GTK_RESPONSE_ACCEPT) {
|
||||
gchar *path = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
|
||||
if (path) {
|
||||
saved = markyd_app_save_file(self->app, path);
|
||||
if (!saved) {
|
||||
GtkWidget *error_dialog = gtk_message_dialog_new(
|
||||
GTK_WINDOW(self->window),
|
||||
GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_ERROR,
|
||||
GTK_BUTTONS_CLOSE, "Failed to save document");
|
||||
gtk_message_dialog_format_secondary_text(
|
||||
GTK_MESSAGE_DIALOG(error_dialog), "%s", path);
|
||||
gtk_dialog_run(GTK_DIALOG(error_dialog));
|
||||
gtk_widget_destroy(error_dialog);
|
||||
}
|
||||
g_free(path);
|
||||
}
|
||||
}
|
||||
|
||||
gtk_widget_destroy(dialog);
|
||||
return saved;
|
||||
}
|
||||
|
||||
static void on_open_clicked(GtkButton *button, gpointer user_data) {
|
||||
MarkydWindow *self = (MarkydWindow *)user_data;
|
||||
GtkWidget *dialog;
|
||||
@@ -961,19 +1208,23 @@ static void on_open_clicked(GtkButton *button, gpointer user_data) {
|
||||
}
|
||||
|
||||
gtk_widget_destroy(dialog);
|
||||
restore_editor_focus(self);
|
||||
}
|
||||
|
||||
static void on_refresh_clicked(GtkButton *button, gpointer user_data) {
|
||||
MarkydWindow *self = (MarkydWindow *)user_data;
|
||||
const gchar *path;
|
||||
const gchar *current;
|
||||
gchar *path;
|
||||
|
||||
(void)button;
|
||||
|
||||
path = markyd_app_get_current_path(self->app);
|
||||
if (!path || path[0] == '\0') {
|
||||
current = markyd_app_get_current_path(self->app);
|
||||
if (!current || current[0] == '\0') {
|
||||
restore_editor_focus(self);
|
||||
return;
|
||||
}
|
||||
|
||||
path = g_strdup(current);
|
||||
if (!markyd_app_open_file(self->app, path)) {
|
||||
GtkWidget *error_dialog = gtk_message_dialog_new(
|
||||
GTK_WINDOW(self->window),
|
||||
@@ -984,6 +1235,46 @@ static void on_refresh_clicked(GtkButton *button, gpointer user_data) {
|
||||
gtk_dialog_run(GTK_DIALOG(error_dialog));
|
||||
gtk_widget_destroy(error_dialog);
|
||||
}
|
||||
g_free(path);
|
||||
restore_editor_focus(self);
|
||||
}
|
||||
|
||||
static void on_edit_clicked(GtkButton *button, gpointer user_data) {
|
||||
MarkydWindow *self = (MarkydWindow *)user_data;
|
||||
(void)button;
|
||||
if (!self || !self->editor) {
|
||||
return;
|
||||
}
|
||||
set_edit_mode(self, !markyd_editor_get_edit_mode(self->editor));
|
||||
}
|
||||
|
||||
static void on_save_clicked(GtkButton *button, gpointer user_data) {
|
||||
MarkydWindow *self = (MarkydWindow *)user_data;
|
||||
const gchar *current;
|
||||
|
||||
(void)button;
|
||||
|
||||
if (!self || !self->app) {
|
||||
return;
|
||||
}
|
||||
|
||||
current = markyd_app_get_current_path(self->app);
|
||||
if (!current || current[0] == '\0') {
|
||||
prompt_save_as(self);
|
||||
restore_editor_focus(self);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!markyd_app_save_current_file(self->app)) {
|
||||
GtkWidget *error_dialog = gtk_message_dialog_new(
|
||||
GTK_WINDOW(self->window), GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
|
||||
GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE, "Failed to save document");
|
||||
gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(error_dialog),
|
||||
"%s", current);
|
||||
gtk_dialog_run(GTK_DIALOG(error_dialog));
|
||||
gtk_widget_destroy(error_dialog);
|
||||
}
|
||||
restore_editor_focus(self);
|
||||
}
|
||||
|
||||
static void on_settings_clicked(GtkButton *button, gpointer user_data) {
|
||||
@@ -1004,6 +1295,7 @@ static void on_settings_clicked(GtkButton *button, gpointer user_data) {
|
||||
}
|
||||
|
||||
gtk_widget_destroy(dialog);
|
||||
restore_editor_focus(self);
|
||||
}
|
||||
|
||||
static gboolean on_key_press_event(GtkWidget *widget, GdkEventKey *event,
|
||||
@@ -1020,6 +1312,18 @@ static gboolean on_key_press_event(GtkWidget *widget, GdkEventKey *event,
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
if ((event->state & GDK_CONTROL_MASK) != 0 &&
|
||||
(event->keyval == GDK_KEY_e || event->keyval == GDK_KEY_E)) {
|
||||
set_edit_mode(self, !markyd_editor_get_edit_mode(self->editor));
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
if ((event->state & GDK_CONTROL_MASK) != 0 &&
|
||||
(event->keyval == GDK_KEY_s || event->keyval == GDK_KEY_S)) {
|
||||
on_save_clicked(NULL, self);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
if (event->keyval == GDK_KEY_Escape &&
|
||||
self->search_revealer &&
|
||||
gtk_revealer_get_reveal_child(GTK_REVEALER(self->search_revealer))) {
|
||||
@@ -1027,12 +1331,23 @@ static gboolean on_key_press_event(GtkWidget *widget, GdkEventKey *event,
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
if (event->keyval == GDK_KEY_Escape && self->editor &&
|
||||
markyd_editor_get_edit_mode(self->editor)) {
|
||||
set_edit_mode(self, FALSE);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
if (event->keyval == GDK_KEY_Escape) {
|
||||
gtk_window_close(GTK_WINDOW(widget));
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
if (event->keyval == GDK_KEY_Home || event->keyval == GDK_KEY_KP_Home) {
|
||||
if (self->editor && !markyd_editor_get_edit_mode(self->editor)) {
|
||||
markyd_editor_scroll_top(self->editor);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
if (self->editor && self->editor->buffer && self->editor->text_view) {
|
||||
GtkTextIter start;
|
||||
GtkAdjustment *hadj = NULL;
|
||||
@@ -1102,15 +1417,15 @@ static gboolean on_configure_event(GtkWidget *widget, GdkEventConfigure *event,
|
||||
if (geometry_debug_enabled()) {
|
||||
if (have_extents) {
|
||||
g_printerr(
|
||||
"ViewMD configure: event=%dx%d gtk=%dx%d saved=%dx%d frame=%dx%d at (%d,%d)\n",
|
||||
"SeeMD configure: event=%dx%d gtk=%dx%d saved=%dx%d frame=%dx%d at (%d,%d)\n",
|
||||
event->width, event->height, gtk_w, gtk_h, width, height,
|
||||
frame_extents.width, frame_extents.height, x, y);
|
||||
} else {
|
||||
g_printerr("ViewMD configure: event=%dx%d gtk=%dx%d saved=%dx%d at (%d,%d)\n",
|
||||
g_printerr("SeeMD configure: event=%dx%d gtk=%dx%d saved=%dx%d at (%d,%d)\n",
|
||||
event->width, event->height, gtk_w, gtk_h, width, height, x,
|
||||
y);
|
||||
}
|
||||
g_printerr("ViewMD saved: x=%d y=%d w=%d h=%d\n", config->window_x,
|
||||
g_printerr("SeeMD saved: x=%d y=%d w=%d h=%d\n", config->window_x,
|
||||
config->window_y, config->window_width, config->window_height);
|
||||
}
|
||||
}
|
||||
@@ -1185,7 +1500,7 @@ static GtkWidget *create_settings_dialog(MarkydApp *app) {
|
||||
gint row = 0;
|
||||
|
||||
dialog = gtk_dialog_new_with_buttons(
|
||||
"ViewMD Settings", GTK_WINDOW(app->window->window),
|
||||
"SeeMD Settings", GTK_WINDOW(app->window->window),
|
||||
GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT, "_Cancel",
|
||||
GTK_RESPONSE_CANCEL, "_Apply", GTK_RESPONSE_APPLY, NULL);
|
||||
|
||||
|
||||
@@ -11,6 +11,8 @@ typedef struct _MarkydWindow {
|
||||
GtkWidget *header_bar;
|
||||
GtkWidget *btn_open;
|
||||
GtkWidget *btn_refresh;
|
||||
GtkWidget *btn_edit;
|
||||
GtkWidget *btn_save;
|
||||
GtkWidget *btn_settings;
|
||||
GtkWidget *search_revealer;
|
||||
GtkWidget *search_entry;
|
||||
|
||||
Reference in new issue
Block a user