Gedit 3.14 Plugin

そろそろ新しい Fedora が出るので準備をしなければ。
ベータが出ているようなので試す、面倒だから久々に仮想マシン。

boxes

Boxes(qemu-kvm) はやはり超簡単というか全自動です。
Fedora on Fedora なら 3D デスクトップもストレスが無いのが嬉しい。
細かい指定はできないけどテスト目的には十分すぎる。

ただ Live 中はフル HD なのを認識したのにインストール後は SXGA に。
相変わらずよく解らない認識をする、フル HD 固定だとそれも困るわけですが。
筆者はウインドウモードで使うので 1440×900 に固定。

インストール直後に生 XML から iso を排除する必要はなくなったみたい。
Boxes の設定画面[デバイス]の CD/DVD の所で取り外しできる。

cd_dvd

Alt+Space がウインドウメニューに割り当てされているや。
US キーボードなのでコレで入力切り替えできないと不便なんだよ。
まあ設定で簡単に変えられるのでちゃっちゃと変更。

header_bar

いやぁ、見事に GtkHeaderBar 化されて…
っておい Eye of GNOME さん、あんたが一番メニューバー邪魔でしょ。
これ以上の変更点レビューは正式版の時に。

とにかく Gedit はメニューバーが無いので以前の自作プラグイン達は使えない。

Apps/Gedit/PythonPluginHowTo – GNOME Wiki!
残念ながら 2014.11.16 現在チュートリアルは以前のままだ。

Gedit 3.12 をスルーしたおかげで日本語の先人を見つけた。

うぇーん、GNOME 3.12 にしたら、自家製Linespacing が動かないよー | (まだ無題 ; そのうち変更するかも)

メニュー項目は GMenu 化、GtkApplication 側に登録。
ハンドラは GAction として GtkWindow 側に登録するみたい。
GtkUIManager 関連を消すのを忘れているみたいですけど。

なんか上手くいかないので QuickOpen のソースを参考に少し作り替え。
*.plugin ファイルは以前と同じでいいようです。

#-*- coding:utf-8 -*-

from gi.repository import GObject, Gedit, Gtk, Gio

class TestTestAppActivatable(GObject.Object, Gedit.AppActivatable):
    """
        Set GMenu and Accelerator
    """
    app = GObject.property(type=Gedit.App)

    def __init__(self):
        GObject.Object.__init__(self)

    def do_activate(self):
        # Set Accelerator
        # (key, action name[win.xxxxx], parameter or None)
        self.app.add_accelerator("<Control>B", "win.action_name", None)
        # Append Menu
        self.menu_ext = self.extend_menu("tools-section")
        item = Gio.MenuItem.new("imouto",  "win.action_name")
        self.menu_ext.append_menu_item(item)

    def do_deactivate(self):
        # Remove Accelerator
        self.app.remove_accelerator("win.action_name", None)

class TestTest(GObject.Object, Gedit.WindowActivatable):
    """
        Set GAction
    """
    __gtype_name__ = "TestTest"
    window = GObject.property(type=Gedit.Window)
    def __init__(self):
        GObject.Object.__init__(self)

    def do_activate(self):
        # Set Action
        # (action name, parameter or None)
        action = Gio.SimpleAction.new("action_name", None)
        action.connect('activate', self.on_activate)
        self.window.add_action(action)

    def do_deactivate(self):
        # Remove Action
        self.window.remove_action("action_name")

    def do_update_state(self):
        pass

    def on_activate(self, action, data=None):
        self.messagebox("Yamete Oni-chan")

    def messagebox(self, text):
        dlg = Gtk.MessageDialog(
                self.window,
                Gtk.DialogFlags.MODAL,
                Gtk.MessageType.WARNING,
                Gtk.ButtonsType.OK,
                text)
        r = dlg.run()
        dlg.destroy()

gedit314_plugin

こんな感じで。

とりあえずアクション名を決める。
GtkApplication 側で使う場合は win.xxxxx と接頭子を付ける。
こうしないとメニューがアクティブにならなかった。

次はアクション名にアクセラレータキーに紐付ける
そしてメニューに表示する文字列を決め GMenu を作成。
それをメニューのどこかに突っ込む。
file-section, tools-section, view-section-2 等々。

本体側は先程のアクション名で GAction を作る。
シグナルハンドラをセットし本体に登録、コッチは簡単だね。
後は今迄どおりでいいみたい。

Eye of GNOME プラグインは変更しなくてもいいかな?と思ったけどダメだった。
と思ったら *.plugin の Loader=python3 書き換えだけでイケた。

eog314plugin

Python3 なので unicode 等の処理をお忘れなく。

GTK+ on Ubuntu 14.10

Ubuntu 14.10 が出たので Live を試す。
GTK+ 3.12 のようだが GNOME アプリは全部 3.10 のままだった。

ubuntu1410_gedit

Gedit 3.12 がどうなったか知っているよね。
Apps/Gedit/Screenshots – GNOME Wiki!

Gedit 3.12 向け自作プラグインのテストに使おうと思っていたけど当てが外れた。
Fedora がメインなので 3.12 をすっ飛ばしている状態なのよ。

DistroWatch.com: Ubuntu GNOME
GNOME 版さえ Nautilus 3.10 のまんまカヨ…
Fedora 21 を素直に待つことにする。

それより GMenu と GtkHeaderBar への対応はどうなった?
USB メモリに以下のコードを書いた Python ソースを入れてと。

#!/usr/bin/env python3

import sys
from gi.repository import Gtk, Gio

"""
    GtkHeaderBar on Ubuntu 14.10
    It is enabled.
"""

class Win(Gtk.ApplicationWindow):
    def __init__(self, app):
        Gtk.ApplicationWindow.__init__(self, application=app)
        # Back-Forward Button
        back = Gtk.Button.new_from_icon_name("go-previous-symbolic", Gtk.IconSize.MENU)
        forw = Gtk.Button.new_from_icon_name("go-next-symbolic", Gtk.IconSize.MENU)
        back.set_valign(Gtk.Align.CENTER)
        forw.set_valign(Gtk.Align.CENTER)
        # Back-Forward Containers
        npbox = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, 0)
        Gtk.StyleContext.add_class (npbox.get_style_context(), "linked")
        npbox.pack_start(back, False, False, 0)
        npbox.pack_start(forw, False, False, 0)
        # GtkHeaderBar
        hbar = Gtk.HeaderBar()
        hbar.pack_start(npbox)
        hbar.set_show_close_button(True)
        self.set_titlebar(hbar)
        self.resize(200, 200)
        self.show_all()

class App(Gtk.Application):
    def __init__(self):
        Gtk.Application.__init__(self)

    def do_activate(self):
        self.window.present()

    def do_startup(self):
        Gtk.Application.do_startup(self)
        # AppMenu
        menu = Gio.Menu()
        menu.append("Quit", "app.quit")
        self.set_app_menu(menu)
        # option "quit"
        quit_action = Gio.SimpleAction.new("quit", None)
        quit_action.connect("activate", self.on_quit)
        self.add_action(quit_action)
        self.window = Win(self)
 
    def on_quit(self, action, parameter):
        self.quit()
 
if __name__ == "__main__":
    app = App()
    app.run(sys.argv)

ubuntu1410_hbar

リサイズも移動も可能になっている、密かに対応していたのか。
ただし閉じるボタン位置は左にはならないみたいね。
アプリケーションメニューは Unknown Application Name のまま。

中途半端だなぁ、使えるようになったのは素直に喜ばしいけど。
まだまだ Ubuntu で GTK+ はイケそうに思うけど。

2014年10月17日号 小型ARMデスクトップ“imp”・14.10のFinal Beta・“POODLE”攻撃・OSC Tokyo/Fall・UWN#387:Ubuntu Weekly Topics|gihyo.jp … 技術評論社

次期版 Unity デスクトップは独自 SDK + Qt5 になる。
つまり Android のように Linux ベースだけど専用 API 必須になる???
タッチ無しのデスクトップ環境限定なら Qt アプリは問題なく動くと思うけど。
GTK+ はどうなんだろう?

とはいえ。

updateman

今も相変わらず Python, GTK+ に頼りまくっているようですけど。
本家も 14.04 を勧めているし移行まで中途半端なままだろうね。

GtkHeaderBar の為だけにインストールするのもなぁ。
本当に全然新しくなっていないので Ubuntu 系ブロガーは頭が痛いかも。

document.createElement

さて、カエル王子を引っ張ってピヨ吉を退治する JavaScript だ。
こんな感じにしてみた。

※canvas サイズは 320×400 に固定
※ピヨ吉構造体を用意、HP や X,Y 座標メンバを保持
※ソレを画面に配置する数だけ配列にしておく
※タイマーでカエルが動く毎に for 文で全数チェック
※ピヨ吉にヒットしていれば HP を減らし 0 なら非表示にして通過する
※ピヨ吉すべての HP がゼロになればクリア

とりあえず二回ヒットするとピヨ吉が消える暫定コードに。
カエル王子は 32x32px にしてみたけど iPhone では少し引っ張り辛いかも。

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>ひよこを倒す 01</title>
<!-- for Smart Phone -->
<meta name="viewport" content="
    width=device-width,
    initial-scale=1.0,
    minimum-scale=1.0,
    maximum-scale=1.0,
    user-scalable=no" />
<style>
body {
    margin: 0;
    overflow: hidden;
    -webkit-text-size-adjust: 100%;
}
#ID_CANVAS {
    background-color: #FF7700;
}
</style>
<script type="text/javascript"><!--

    var canvas = null;
    var context = null;
    var ball = null;
    var piyos = [];
    // Constant
    const INIT_X = 160;  // X origin of the image
    const INIT_Y = 280;  // Y origin of the image
    const OFFSET = 16;   // Half of the image size
    const INTERVAL = 20; // n/1000 ms
    const LENGTH = 20;   // Distance the image moves
    const COUNT  = 250;  // INTERVAL*n ms
    // struct
    var piyokichi = function(x, y, hp) {
        this.x = x;
        this.y = y;
        this.hp = hp;
        this.img = null;
    }
    var ballData = function() {
        this.timer_id = -1;
        this.timer_count = 0;
        this.x = 0;
        this.y = 0;
        this.rotation = 0;
    }
    var BallData = new ballData();

    /*
     * Touch Event Handler
    **/
    var onBallTouchStart = function(e) {
        e.preventDefault();
    }
    var onBallTouchMove = function(e) {
        if (BallData.timer_id == -1) {
            context.clearRect(0, 0, canvas.width, canvas.height);
            BallData.x = e.touches[0].pageX;
            BallData.y = e.touches[0].pageY;
            if (BallData.y > 400 - OFFSET)
                BallData.y = 400 - OFFSET;
            context.beginPath();
            context.moveTo(INIT_X, INIT_Y);
            context.lineTo(BallData.x, BallData.y);
            context.stroke();
            moveBall(BallData.x, BallData.y);
        }
    }
    var onBallTouchEnd = function(e) {
        if (BallData.timer_id == -1) {
            BallData.rotation = Math.atan2(BallData.y - INIT_Y, BallData.x - INIT_X)
            BallData.timer_count = COUNT;
            BallData.timer_id = setInterval(onTimer, INTERVAL);
            context.clearRect(0, 0, canvas.width, canvas.height);
        }
    }
    /*
     * Timer Handler
    **/
    var onTimer = function() {
        // Clear ?
        var clear = piyos.length;
        for (var i=0; i<piyos.length; i++) {
            if (piyos[i].hp == 0) clear -= 1;
        }
        if (clear == 0) {
            context.font = "64pt 'Arial'";
            context.fillText("Great!", 10, 100);
            clearInterval(BallData.timer_id);
            BallData.timer_id = -1;
            return;
        }
        if (BallData.timer_count > 0) {
            // Hit the Rival ?
            if (!getRivalHit()) {
                // Hit the Wall ?
                if (BallData.x < OFFSET || BallData.x > canvas.width - OFFSET)
                    BallData.rotation = Math.PI - BallData.rotation;
                if (BallData.y < OFFSET || BallData.y > canvas.height - OFFSET)
                    BallData.rotation = -BallData.rotation;
            }
            // Move Image
            BallData.x -= Math.cos(BallData.rotation) * LENGTH;
            BallData.y -= Math.sin(BallData.rotation) * LENGTH;
            moveBall(BallData.x, BallData.y);
            BallData.timer_count--;
        } else {
            clearInterval(BallData.timer_id);
            BallData.timer_id = -1;
            moveBall(INIT_X, INIT_Y);
        }
    }
    /*
     * Hit Function
    **/
    var getRivalHit = function() {
        for (var i=0; i<piyos.length; i++) {
            if (BallData.x > piyos[i].x - OFFSET &&
                BallData.x < piyos[i].x + OFFSET &&
                BallData.y > piyos[i].y - OFFSET &&
                BallData.y < piyos[i].y + OFFSET) {
                // HP == 0?
                piyos[i].hp -= 5;
                if (piyos[i].hp <= 0) {
                    piyos[i].hp = 0;
                    piyos[i].img.style.display = "none";
                    continue;
                }
                if (BallData.x > piyos[i].x - OFFSET && BallData.x < piyos[i].x + OFFSET)
                    BallData.rotation = Math.PI - BallData.rotation;
                if (BallData.y > piyos[i].y - OFFSET && BallData.y < piyos[i].y + OFFSET)
                    BallData.rotation = -BallData.rotation;
                return true;
            }
        }
        return false;
    }
    /*
     * Move Function
    **/
    var moveBall = function(x, y) {
        ball.style.left = x - OFFSET + "px";
        ball.style.top  = y - OFFSET + "px";
    }
    /*
     * Initialize
    **/
    var init = function() {
        if (window.TouchEvent) {
            canvas = document.getElementById("ID_CANVAS");
            canvas.width = window.innerWidth;
            canvas.height = 400;
            context = canvas.getContext("2d");
            ball = document.getElementById("ID_BALL");
            ball.addEventListener("touchstart",onBallTouchStart);
            ball.addEventListener("touchmove",onBallTouchMove);
            ball.addEventListener("touchend",onBallTouchEnd);
            moveBall(INIT_X, INIT_Y);
            // Back Button
            var back = document.getElementById("ID_BACK");
            back.style.top = canvas.height + "px";
            back.addEventListener("touchend", function() {
                document.location = ".";
            });
            // Create Piyokichi
            piyos.push(new piyokichi(20, 200, 10));
            piyos.push(new piyokichi(50, 30, 10));
            piyos.push(new piyokichi(60, 100, 10));
            piyos.push(new piyokichi(170, 80, 10));
            piyos.push(new piyokichi(280, 120, 10));
            for (var i=0; i<piyos.length; i++) {
                var img = document.createElement("img");
                img.alt = "image";
                img.src = "kaeru01/hiyo02_32x32.gif";
                img.width = 32;
                img.height = 32;
                img.style.position = "absolute";
                img.style.left = piyos[i].x - OFFSET + "px";
                img.style.top  = piyos[i].y - OFFSET + "px";
                document.body.appendChild(img);
                piyos[i].img = img;
            }
        }
    }
    //-->
</script>

</head>
<body onLoad="init()">

<canvas id="ID_CANVAS"></canvas>
<img id="ID_BALL" src="kaeru01/kaeru02_32x32.gif" style="position:absolute" alt="ball">
<img id="ID_BACK" src="back.png" style="position:absolute" alt="back">

</body>
</html>

kaeru01

ひよこを倒す 01

敵の攻撃やターン数、障害物その他についてはまた今度。
長くなってきたので次回から[リンク先で Ctrl+U してね!]にするつもり。

他に今回やってみたこと。

canvas に文字列を表示するには fillText
[MS Pゴシック]なんてこのスマートフォン時代に指定しないで下さい。

context.font = "64pt 'Arial'";
context.fillText("Great!", 10, 100);

img タグを動的に作るには createElement
その img を非表示にするには img.style.display = “none”;

var img = document.createElement("img");
document.body.appendChild(img);

後は前回までのコードを少し応用しただけでココまで作れた。
しかしヒット判定は四方チェックしてから振り分けしか手段が無いのかな?
ネットに転がっているブロック崩しのコードを色々見るもイマイチ参考にならないし。
全部グローバル変数で読み辛い人多過ぎだし、構造体やクラスって凄く便利だよ。

今回は[フリー素材 動物アイコン]で適当に検索して以下をお借りしました。
1キロバイトの素材屋さん-無料素材の配布/可愛いアイコン・動く顔文字などのフリー素材集-

カエルとひよこがあって丁度よかった、32x32px に変更しただけ。
本格的に作る時があったらオリジナルキャラにしますが筆者は絵心が…

Girlfriend

スマホ学園恋愛ゲーム「ガールフレンド(仮)」からPC向けのブラウザ版がリリース – ねとらぼ

girlfrend_pc

Linux 版 Chrome でも普通にログインできた。
レベルが結構進んでいたり HR クロエをコンプしているのは気にしないで。
ゲームは全然面白くないけど[ウチ姫]と頻繁にコラボするからヤメられない。

ちなみにコイツに課金する気は皆無なので SR がちっとも当たらないw
部活(という名のギルド)に入る気もないし横取りイベントとかは完全不参加w
たまにくれるコインは sim 無し Xperia を起動してウチ姫のガチャに使うw
それでも HR クロエ等はコンプできるしまあいいかと。

rorikaguya

ちなみに少し前のコラボでもらった月のカケラでロリカグヤをゲット。
ロリィナが来なかったので称号クエストはソコソコにアリスを集めているけど。
水 SR が何故か全然当たらないのでシンディ取りたいけど進化無理っぽいんで。

しかしランキングを見るとロリィナだらけで引く、みなドンダケ課金してんの?
ウチ姫の知名度でこれだとパズドラやモンストはいったい…
ってこれは関係ないね。

YouTube とかを見れば解るけど JavaScript コンソールでエミュレートすれば実は以前から可能だったというオチがありますが。
せっかく普通にパソコンで見れるので Ctrl+U して中身を見てみよう。

gf_320px

viewport 指定はお約束どおり。
パソコン版も見事に css にて 320px に指定しているんだね。
スゲェちいさいけどスマートフォンとつじつまを合わせるにはやはりこれしかないか。

buttle

バトルシーンや劇場等のスクロールしない画面は 320×400 になっているっぽい。
しかし女の子画像を「名前を付けて保存」すると 640×800 になる。
Android and iPhone word-break | PaePoi
max-width: 100%; で縮小表示しているようだ。

トップページはリロード毎に女の子の画像とセリフが変わる。
試すと毎回ランダムに読み込んでいるので Java か PHP だろうね。

他にも色々と参考になったけど後は各自で。
簡単にソースを参考にできる環境にしてくれた運営に感謝。

面白くないとケナしておいて参考にする私もアレだと思うけど。
ウチ姫面白いです、で許してください。

GtkMenuButton F10

Y901x 1.1.2 公開、日は変わってしまったけど。

y901x112

メニューバーを取り払ったよ。
ラジオメニューじゃ解りづらい再生速度アスペクト比切り替えもステータスバーに移した。

GtkHeaderBar and GStreamer | PaePoi
Mini GtkButton | PaePoi

つーか、今頃になってやっと上記を実装した、半年も前だったのか。
使い道の例だと思ってください、多分作った本人しか使っていないアプリなので。

所で F10 キーでメインメニューがドロップするのは知っているよね。
GtkMenuButton ではこれを自分で実装しないといけないみたい。
こんな感じでいいみたい、F10 ってシステム管理じゃなかったんだ。
ちょっと具体的に追記

#!/usr/bin/env python3

class Win(Gtk.ApplicationWindow):
    def __init__(self, app):
        Gtk.ApplicationWindow.__init__(self, application=app)
        # GtkAccelGroup
        accelgroup = Gtk.AccelGroup.new()
        self.add_accel_group(accelgroup)
        # Preference button
        preferencebutton = Gtk.MenuButton.new()
        image_gear = Gtk.Image.new_from_icon_name("emblem-system-symbolic", Gtk.IconSize.MENU)
        preferencebutton.set_image(image_gear)
        # Set F10 Accel
        preferencebutton.add_accelerator("clicked", accelgroup, Gdk.KEY_F10, 0, Gtk.AccelFlags.VISIBLE)
        # HeaderBar
        headerbar = Gtk.HeaderBar()
        headerbar.pack_end(preferencebutton)

ついでに今頃気が付いた。
Alt+F10 で最大化切り替えできる、これは便利だ。
まあシステム設定で変更できるんだけど。

後 Y901 伝統の Z キーでフルスクリーンは実装できなかった。
一応強引な手段もあるけど GNOME デフォルトの F11 のみでいいかなと。

次は Alt+Enter でプロパティ表示を実装したいな。
一度弄り始めると急に色々思い付くんだよね。

Ubuntu での動作確認は明日やろう(ぉい!
個人的には超有名な某ゲームエンジンと同じ名前であるあの糞デスクトップ環境はガン無視したいんだけどユーザー数がなぁ…