diff --git a/HACKING.md b/HACKING.md index 72c52fb..2ba08cd 100644 --- a/HACKING.md +++ b/HACKING.md @@ -134,9 +134,9 @@ You need implement "scroll" interface in AppBuffer, such as like PDF Viewer does self.buffer_widget.scroll_down() ``` -scroll_direction is string, "up" mean scroll buffer up, "down" mean scroll buffer down. +Argument "scroll_direction" is string, "up" mean scroll buffer up, "down" mean scroll buffer down. -scroll_type is string, "page" mean scroll buffer by page, "line" mean scroll buffer by line. +Argument "scroll_type" is string, "page" mean scroll buffer by page, "line" mean scroll buffer by line. ### Save/Restore session We always need save and restore session for application, such as, save play position of video player. @@ -153,9 +153,22 @@ You need implement interfaces "save_session_data" and "restore_session_data", be self.buffer_widget.media_player.setPosition(position) ``` -session_data is string, you can put anything in it +Argument "session_data" is string, you can put anything in it All session data save at ~/.emacs.d/eaf/session.json file. +### Update buffer +If you need update buffer sometimes, such as update org-file previewer after save org-file. + +You need implement interfaces "update_with_data" , below is an example of Org Previewer does: + +```Python + def update_with_data(self, update_data): + self.load_org_html_file() + self.buffer_widget.reload() +``` + +Argument "update_data" is pass from elisp side. + ## Todolist [Some works you can hacking ;)](TODOLIST.md) diff --git a/README.md b/README.md index 4617478..9238093 100644 --- a/README.md +++ b/README.md @@ -29,10 +29,11 @@ Using this framework, you can use PyQt develop powerful graphics programs to ext | | | | | | -| Air Share | -| :--------: | -| | -| | + +| Air Share | Org Previewer | +| :--------: | :--------: | +| | | +| | | ## Installation @@ -79,13 +80,14 @@ There are mainly three obstacles: | :-------- | :---- | :-----: | :---- | | Browser | URL | Left Button | Open link current tab | | | | Ctrl + Left Button | Open link in new tab | -| Markdown previewer | Markdown file path | | | -| Image Viewer | Image file path | j | Load next image in current directory | +| Markdown previewer | Type 'eaf-open' RET markdown filepath | | | +| Org file previewer | Type 'eaf-open' RET org filepath | | | +| Image Viewer | Type 'eaf-open' RET IMAGE filepath | j | Load next image in current directory | | | | k | Load previous image in current directory | -| Video Player | Video file path | Space | Play or Pause | +| Video Player | Type 'eaf-open' RET video filepath | Space | Play or Pause | | | | h | Seek backward | | | | l | Seek forward | -| Pdf Viewer | Pdf file path | j | Scroll up | +| Pdf Viewer | Type 'eaf-open' RET PDF filepath | j | Scroll up | | | | k | Scroll down | | | | Space | Scroll up page | | | | b | Scroll down page | diff --git a/app/orgpreviewer/buffer.py b/app/orgpreviewer/buffer.py new file mode 100644 index 0000000..a59463e --- /dev/null +++ b/app/orgpreviewer/buffer.py @@ -0,0 +1,87 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# Copyright (C) 2018 Andy Stewart +# +# Author: Andy Stewart +# Maintainer: Andy Stewart +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +from PyQt5 import QtCore +from PyQt5.QtCore import QUrl, Qt +from PyQt5.QtGui import QColor +from PyQt5.QtWebKitWidgets import QWebView, QWebPage +from PyQt5.QtWidgets import QApplication +from PyQt5.QtWebKit import QWebSettings +from core.buffer import Buffer +from core.utils import PostGui +import socket +import subprocess +import threading +import os + +class AppBuffer(Buffer): + def __init__(self, buffer_id, url): + Buffer.__init__(self, buffer_id, url, False, QColor(255, 255, 255, 255)) + + self.url = url + self.add_widget(BrowserWidget()) + + self.load_org_html_file() + + def load_org_html_file(self): + self.buffer_widget.setUrl(QUrl.fromLocalFile(os.path.splitext(self.url)[0]+".html")) + + def update_with_data(self, update_data): + self.load_org_html_file() + self.buffer_widget.reload() + +class BrowserWidget(QWebView): + + def __init__(self): + super(QWebView, self).__init__() + + self.web_page = WebPage() + self.setPage(self.web_page) + + self.settings().setAttribute(QWebSettings.PluginsEnabled, True) + self.settings().setAttribute(QWebSettings.JavascriptEnabled, True) + self.settings().setAttribute(QWebSettings.JavascriptCanOpenWindows, True) + +class WebPage(QWebPage): + + open_url_in_new_tab = QtCore.pyqtSignal(str) + + def __init__(self): + super(WebPage, self).__init__() + + def acceptNavigationRequest(self, frame, request, type): + modifiers = QApplication.keyboardModifiers() + + # Handle myself if got user event. + if type == QWebPage.NavigationTypeLinkClicked: + if modifiers == Qt.ControlModifier: + self.open_url_in_new_tab.emit(request.url().toString()) + else: + self.view().load(request.url()) + + # Return False to stop default behavior. + return False + + # # Otherwise, use default behavior. + return QWebPage.acceptNavigationRequest(self, frame, request, type) + + def javaScriptConsoleMessage(self, msg, lineNumber, sourceID): + pass diff --git a/core/buffer.py b/core/buffer.py index 665f1e6..3c5d42f 100644 --- a/core/buffer.py +++ b/core/buffer.py @@ -79,3 +79,6 @@ class Buffer(QGraphicsScene): def restore_session_data(self, session_data): pass + + def update_with_data(self, update_data): + pass diff --git a/eaf.el b/eaf.el index 976ac78..d326cb5 100644 --- a/eaf.el +++ b/eaf.el @@ -114,6 +114,9 @@ (defvar eaf-title-length 30) +(defvar eaf-org-file-list '()) +(defvar eaf-org-killed-file-list '()) + (defcustom eaf-name "*eaf*" "Name of eaf buffer." :type 'string @@ -167,6 +170,11 @@ ;; Clean cache url and app name, avoid next start process to open buffer. (setq eaf-first-start-url nil) (setq eaf-first-start-app-name nil) + ;; Clean `eaf-org-file-list' + (dolist (org-file-name eaf-org-file-list) + (eaf-delete-org-preview-file org-file-name)) + (setq eaf-org-file-list nil) + (setq eaf-org-killed-file-list nil) )) (defun eaf-restart-process () @@ -225,7 +233,7 @@ We need calcuate render allocation to make sure no black border around render co (dolist (window (window-list)) (let ((buffer (window-buffer window))) (with-current-buffer buffer - (if (string= "eaf-mode" (format "%s" major-mode)) + (if (eq major-mode 'eaf-mode) (let* ((window-allocation (eaf-get-window-allocation window)) (x (nth 0 window-allocation)) (y (nth 1 window-allocation)) @@ -239,18 +247,53 @@ We need calcuate render allocation to make sure no black border around render co (eaf-call "update_views" (mapconcat 'identity view-infos ",")) ))) +(defun eaf-delete-org-preview-file (org-file) + (setq org-html-file (concat (file-name-sans-extension org-file) ".html")) + (when (file-exists-p org-html-file) + (delete-file org-html-file) + (message (format "Clean org preview file %s (%s)" org-html-file org-file)) + )) + +(defun eaf-org-killed-buffer-clean () + (dolist (org-killed-buffer eaf-org-killed-file-list) + (unless (get-file-buffer org-killed-buffer) + (setq eaf-org-file-list (remove org-killed-buffer eaf-org-file-list)) + (eaf-delete-org-preview-file org-killed-buffer) + )) + (setq eaf-org-killed-file-list nil)) + (defun eaf-monitor-buffer-kill () (ignore-errors (with-current-buffer (buffer-name) - (when (string= "eaf-mode" (format "%s" major-mode)) - (eaf-call "kill_buffer" buffer-id) - (message (format "Kill %s" buffer-id)) - )))) + (cond ((eq major-mode 'org-mode) + ;; NOTE: + ;; Because save org buffer will trigger `kill-buffer' action, + ;; but org buffer still live after do `kill-buffer' action. + ;; So i run a timer to check org buffer is live after `kill-buffer' aciton. + (when (member (buffer-file-name) eaf-org-file-list) + (unless (member (buffer-file-name) eaf-org-killed-file-list) + (push (buffer-file-name) eaf-org-killed-file-list)) + (run-with-timer 1 nil (lambda () (eaf-org-killed-buffer-clean))) + )) + ((eq major-mode 'eaf-mode) + (eaf-call "kill_buffer" buffer-id) + (message (format "Kill %s" buffer-id))) + )))) + +(defun eaf-monitor-buffer-save () + (ignore-errors + (with-current-buffer (buffer-name) + (cond ((and + (eq major-mode 'org-mode) + (member (buffer-file-name) eaf-org-file-list)) + (org-html-export-to-html) + (eaf-call "update_buffer_with_url" "app.orgpreviewer.buffer" (buffer-file-name) "") + (message (format "export %s to html" (buffer-file-name)))))))) (defun eaf-monitor-key-event () (ignore-errors (with-current-buffer (buffer-name) - (when (string= "eaf-mode" (format "%s" major-mode)) + (when (eq major-mode 'eaf-mode) (let* ((event last-command-event) (key (make-vector 1 event)) (key-command (format "%s" (key-binding key))) @@ -303,7 +346,7 @@ We need calcuate render allocation to make sure no black border around render co (dolist (window (window-list)) (let ((buffer (window-buffer window))) (with-current-buffer buffer - (if (string= "eaf-mode" (format "%s" major-mode)) + (if (eq major-mode 'eaf-mode) (let* ((window-allocation (eaf-get-window-allocation window)) (x (nth 0 window-allocation)) (y (nth 1 window-allocation)) @@ -341,7 +384,7 @@ We need calcuate render allocation to make sure no black border around render co (let ((buffer (window-buffer window))) (with-current-buffer buffer (when (and - (string= "eaf-mode" (format "%s" major-mode)) + (eq major-mode 'eaf-mode) (equal buffer-id bid)) (rename-buffer title) (throw 'find-buffer t) @@ -371,6 +414,7 @@ We need calcuate render allocation to make sure no black border around render co (add-hook 'window-configuration-change-hook #'eaf-monitor-configuration-change) (add-hook 'pre-command-hook #'eaf-monitor-key-event) (add-hook 'kill-buffer-hook #'eaf-monitor-buffer-kill) +(add-hook 'after-save-hook #'eaf-monitor-buffer-save) (defun eaf-open-internal (url app-name) (let* ((buffer (eaf-create-buffer url)) @@ -398,11 +442,32 @@ We need calcuate render allocation to make sure no black border around render co (cond ((member extension-name '("pdf" "xps" "oxps" "cbz" "epub" "fb2" "fbz")) (setq app-name "pdfviewer")) ((member extension-name '("md")) + ;; Split window to show file and previewer. + (delete-other-windows) + (find-file url) + (split-window-horizontally) + (other-window +1) (setq app-name "markdownpreviewer")) ((member extension-name '("jpg" "png" "bmp")) (setq app-name "imageviewer")) ((member extension-name '("avi" "rmvb" "ogg" "mp4")) - (setq app-name "videoplayer")))) + (setq app-name "videoplayer")) + ((member extension-name '("org")) + ;; Find file first, because `find-file' will trigger `kill-buffer' operation. + (save-excursion + (find-file url) + (with-current-buffer (buffer-name) + (org-html-export-to-html))) + ;; Add file name to `eaf-org-file-list' after command `find-file'. + (unless (member url eaf-org-file-list) + (push url eaf-org-file-list)) + ;; Split window to show file and previewer. + (delete-other-windows) + (find-file url) + (split-window-horizontally) + (other-window +1) + (setq app-name "orgpreviewer") + ))) (t (setq app-name "browser") (unless (string-prefix-p "http" url) diff --git a/eaf.py b/eaf.py index fad365f..1d95a2f 100755 --- a/eaf.py +++ b/eaf.py @@ -56,6 +56,13 @@ class EAF(dbus.service.Object): # otherwise some library will throw error, such as fitz library. return self.create_app(buffer_id, str(url), "app.{0}.buffer".format(str(app_name))) + @dbus.service.method(EAF_DBUS_NAME, in_signature="sss", out_signature="") + def update_buffer_with_url(self, module_path, buffer_url, update_data): + for buffer in list(self.buffer_dict.values()): + if buffer.module_path == module_path and buffer.url == buffer_url: + buffer.update_with_data(update_data) + break + @dbus.service.method(EAF_DBUS_NAME, in_signature="sss", out_signature="") def scroll_buffer(self, view_info, scroll_direction, scroll_type): (buffer_id, _, _, _, _) = view_info.split(":") diff --git a/screenshot/org_previewer.gif b/screenshot/org_previewer.gif new file mode 100644 index 0000000..68ec0ce Binary files /dev/null and b/screenshot/org_previewer.gif differ