Tkinterで、image付きListbox的なモノを実装してみた( Tkinter List with image and text

f:id:lynmock:20220214095512p:plain
初めてのPythonソフトとして、こんなのを作った、という話です

先日、Raspberry Pi4にSSDを奢ってみたので、せっかくだからPythonを使えるようになって積極的にオモチャにしてやろうと勉強することにしました。

f:id:lynmock:20220214140418j:plain

まずは軽くTwitterクライアント*1を作ってみようと考えたものの、Pythonの主流GUIライブラリであるTkinterには、画像の使えるListboxがない様子。
Stack Overflowを見ても「Listboxで画像を使う方法を教えて」「そんな機能はない」などというやり取りが見受けられ、軽く検索してみても、それを実現するコードは見つかりませんでした。なんか大昔にTwitterクライアントを作ろうとAWTの勉強をしてみたものの同様のことで躓き、Swingに転がったのを思い出しますね。
問題は今回はライセンスとか考えると、そういう都合のいい別のライブラリがないところですが。

てなわけで、先の3連休の最終日を潰して画像の使えるListbox的なモノを丁稚UPしてみました。まあ多分本格的にやってるヒト達はこんなのホイホイと作ってるのでしょうが、検索した時に何も出てこないのは初心者には辛かろう、という意味での公開です…。

import tkinter as tk
import tkinter.ttk as ttk
from PIL import Image, ImageTk

class ScrollableFrame(ttk.Frame):
    # based https://daeudaeu.com/scrollbar/
    def __init__(self, container):
        super().__init__(container)
        self.canvas = tk.Canvas(self)
        self.scrollable_frame = ttk.Frame(self.canvas)
        self.scrollable_frame.bind(
            "<Configure>",
            lambda e: self.canvas.configure(
                scrollregion=self.canvas.bbox("all")
            )
        )
        self.canvas.bind("<MouseWheel>", self.mouse_y_scroll)
        self.canvas.create_window((0, 0), window=self.scrollable_frame, anchor="nw")
        self.scrollbar_y = ttk.Scrollbar(self, orient="vertical", command=self.canvas.yview)
        self.scrollbar_y.pack(side=tk.RIGHT, fill="y")
        self.canvas.configure(yscrollcommand=self.scrollbar_y.set)
        self.canvas.pack(side=tk.LEFT, fill="both", expand=True)
        self.canvas.focus_set()
    def mouse_y_scroll(self, event):
        unitCount = 0
        if event.delta > 0:
            unitCount = -1
        elif event.delta < 0:
            unitCount = 1
        self.canvas.yview_scroll(unitCount, 'units')

class WrappingLabel(ttk.Label):
    def __init__(self, master=None, **kwargs):
        ttk.Label.__init__(self, master, **kwargs)
        # -90 is a correction value to avoid overlapping Label and scrollbar.
        self.bind('<Configure>', lambda e: self.config(wraplength=frame.winfo_width() - 90))
        self.bind("<MouseWheel>", frame.mouse_y_scroll)
        self.bind("<Button-1>", self.click)
    def setIndex(self, i):
        self.rowIndex = i
    def click(self, event):
        global selectedIndex,label_list
        if selectedIndex == self.rowIndex:
            return
        set_list_background(0, self.rowIndex)            


def press_down_key(self):
    global selectedIndex,label_list
    if (selectedIndex < 0 or selectedIndex == len(label_list) -1):
        return
    set_list_background(1)
   
def press_up_key(self):
    global selectedIndex,label_list
    if (selectedIndex <= 0):
        return
    set_list_background(-1)

def set_list_background(indexCorrect, newIndex = -1):
    global selectedIndex, label_list
     # Return the selected label to the original background color
    selectedLabel = label_list[selectedIndex]
    selectedLabel.config(foreground = 'black', background = get_list_background(selectedIndex)) 
    # Change the background color of the newly selected label to blue
    if newIndex >= 0:
        selectedIndex = newIndex #click
    else:
        selectedIndex = selectedIndex + indexCorrect
    selectedLabel = label_list[selectedIndex]
    selectedLabel.config(foreground = 'white', background = '#2080ff')

def get_list_background(index):
    if  index%2 == 0:
        return "#FFFFFF"
    else:
        return "#DDDDDD"


root = tk.Tk()
root.geometry("500x700+0+0")
root.bind("<Down>", press_down_key)
root.bind("<Up>", press_up_key)

style = ttk.Style()
style.theme_use("classic")

frame = ScrollableFrame(root)
frame.pack(fill="both", expand=True)

image_list = []
label_list = []
selectedIndex = -1

for i in range(100):

    img = Image.open('icon.png')
    img = img.resize((48, 48))
    img = ImageTk.PhotoImage(img)
    image_list.append(img)

    label = WrappingLabel(
        frame.scrollable_frame, 
        text = str (i) +":Good morning. In less than an hour, aircraft from here will join others from around the world. And you will be launching the largest aerial battle in the history of mankind. Mankind. That word should have new meaning for all of us today. ", 
        image = image_list[i], 
        foreground = "black",
        background = get_list_background(i),
        compound = "left",
        padding = [10],
        wraplength = 100)
    label.pack(expand=True, fill=tk.X) 
    label.setIndex(i)
    label.bind("<MouseWheel>", frame.mouse_y_scroll)

    label_list.append(label)
    
root.mainloop()

Labelデザインやデータの構造の部分は都合のいいように変更して使ってください。挙動はこちらに動画を上げてみましたのでご確認ください。

www.youtube.com

開発&動作確認はMac OS Monterey、Python 3.10.2(2.7.18かも)で行いました(RaspberryPiじゃないのかよ)
何しろPythonについては素人なので、修正すべきところがあったら教えてくれると嬉しいです。

あ、あと、気に入ったヒトは、お財布が温かい時に気が向いたら、こちらの欲しい物リストから何かくれると私が超喜びますw

ほんじゃね。

※ScrollableFrameの部分は https://daeudaeu.com/scrollbar/ に掲載されたコードを元にしています。


追伸
本当はカーソルの位置に追従してスクロールするのを実装していたのですが、スクロールバーをクリックした時にどうしたらいいのかわからんので諦めました

www.youtube.com
下に行く時だけできた…まあ気が向いたら完成させます

考えてみたらコイツらを使えばスマートにできたんだ…

*1:Twitterクライアントって色々な機能が要求されるので、一つの言語やフレームワークを覚えるのに都合がいいのです

日立のMSX2の仕様に翻弄された

1986年、パナソニックソニーは、それまで5万円以上が標準だったMSX2の価格を一気に引き下げる、FS-A1、HB-F1をリリース。その安さから、当時の「パソコンが気になっていた少年少女達(我々のことだ、わかっとるか)」の支持を受けて一気に普及台数を伸ばすことになった。

f:id:lynmock:20210827103119j:plain f:id:lynmock:20210827102211j:plain

 が、新しく安いモノが出るならば、古くて高いモノが残るのは世の常。当時の家電量販店には、「型遅れ」となってしまったMSX2達が並び、定価を無視して、場合によってはFS-A1、HB-F1以下のプライスタグを付けられていた。

となると…今度は逆にそっちに目をつける捻くれたヤツも出てくる。そう、当時の私のように。
私は(やめればいいのに)、19800円というそれらのMSX2の中から、日立のMB-H3、キヤノンのV-25という機種に目を付けた。前者は手書きタブレット付き(!)、後者はとにかく格好いいデザインがウリである。

f:id:lynmock:20210827103346j:plain f:id:lynmock:20210827103407j:plain

結局、数日悩んだ上でV-25を選択したのだが、マトモに知識のない中でこれを選んだことを、その後1年悔み続けることになる。
ご存じの方ならば既に数行前でニヤニヤされていることであろう。そう、MB-H3と共に、この機種はVRAMが通常128KBの半分、64KBしかないのである!!!
しかも、MB-H3の方はまだ日立によるVRAMアップデートサービスが行われていたにも関わらず、V-25の方は放置。友人達には散々「新しいMSX2のゲーム買ったケド…あ、お前のでは遊べないね」とからかわれたのだった。
その後、私は雑誌の売買コーナーを駆使して、毎年のようにリリースされるMSX系の新型に買い替えることになるのだけど、まあそれはおいといて…この30年、ずっと気になっていたことがある。
先のMB-H3、VRAMが増やせるのならばそのように設計されているハズ。ならば自分の手でそれをやってみたい! 幸いにして情報はこちらにあったので、あとはモノを手に入れるだけである。早速オークションにてジャンク品のMB-H3を入手してみた。
どっこい、いざRGB to HDMI接続で繋いでみたら画面が映らない。

f:id:lynmock:20210827104229j:plain

電解コンデンサが逝っているのでは?と言われたので、せっせと基板上のコンデンサの仕様を調べたりしてみたが、試しにビデオ出力で繋いでみたらちゃんと動く。

f:id:lynmock:20210827104305j:plain

RGB信号はVDPで生成して、そこからゴニョゴニョしてビデオ信号にしているハズ。ということは、RGB端子周りが怪しい?
…とここまで考えたところで、私がMSX2を手に入れるより前は、同じRGBでもピン配列が異なるモノがある、という話を思い出した。
果たしてMSX Resource Centerで調べたらご覧の通り。見事にピン配列が違っていましたorz
これはRGBケーブルを入手して改造する必要がありますね…。

f:id:lynmock:20210827104118p:plain

とりあえずまたオークションでRGBケーブルを入手しました…

 

Androidアプリ・ジフスタ〜GifStampをリリースしました

Androidユーザの皆さん、Twitterにネタとしてアニメーションgifをもっと使ってみたいと思ったことないだろうか。いや、別にTwitterに限った話ではないのだけど。
あるいは、ゲームをプレイしていて、「ここを投稿したい」と思ったことは?

もちろん、数多のAndroidアプリの中には、その用途に使えるモノが多数ある。だが、大抵は録画後にクロップしたりトリミングしたりと面倒な操作が伴うものだ。

違う、そうじゃない。僕たちはもっと手軽にアニメgifを作成して投稿して遊びたいのだ。(※個人の感想です)

と、言うわけで今回作成してリリースしたのがこのジフスタ GifStampだ。


DLはこちらから
※1/9にDLされたヒトは、一旦削除して、こちらからDLしてください

使い方は簡単。例えばYoutubeの動画から作成する場合の手順は以下の通り

STEP.1 ジフスタを起動する

STEP.2 Youtubeアプリでネタ元動画を再生する

STEP.3 gifに含めたいポイントがきたら録画ボタンを押す(録画コントローラが消えます)
STEP.4 録画したいポイントが終わったら指を画面を離す(離さなくても6秒を過ぎると自動的に録画終了する)
STEP.5 TwitterSNSなどに投稿する(できあがったgifに納得がいかなかったらCANCELボタンを押してやり直す

勿論、上に上げた手順ではゲームプレイ中には録画できない。その場合は、アプリを起動した時に表示される画面にある「オート録画機能」にチェックを入れればいい。そうすると、録画ボタンを押してから6秒間の録画を行うようになる。


ここね

また、当然ながら録画コントローラは移動可能だ。☓ボタンを長押しすると、コントローラ移動モードになるので、指で自由に移動させられる。これで投稿したいアプリのUIに干渉しないようにすることができる。


移動モード


どうかな。これだけgifアニメを作る敷居を下げれば、気軽にL○NEスタンプのようにサクサクと作成して投稿することができるんじゃないかな。

というわけでAndroidユーザの皆さんがガンガンダウンロードするといいと思いますね。今のところは広告もない無料アプリなので、是非!

こういうのをガンガン作って遊ぼうぜ!

皆さんはもう予約しました? 私は絶賛Forza Horizon4とHorizonZero Dawnプレイ中なので一旦様子見です…

【オボエガキ】bitriseでUnityをBuildしたら通らなかった

bitriseでUnityプロジェクトをビルドしたい。
こちらを参考にしてbitrise.ymlを作成(要所にあるバージョンはアップデート、$UNITY_SERIAL、$UNITY_EMAIL、$UNITY_PWは下記画像を参考にして設定)してbuildしたが、"Activate Unity"で下記のエラーが出て通らない。

DisplayProgressbar: Unity Package Manager
[Package Manager] Done resolving packages in 0.27s seconds
[Package Manager] Failed to resolve packages: [Packages] directory does not exist. No packages loaded.

A re-import of the project may be required to fix the issue or a manual modification of Packages/manifest.json file.
[Package Manager] Server::Kill – Server was shutdown

PackageManagerなんて使ってないのに何故…

とりあえず今回はPackageManagerを使う予定はないので、「-noUpm」オプションを付けて回避することにした。

/Applications/Unity/Unity.app/Contents/MacOS/Unity -quit batchmode -serial $UNITY_SERIAL -username $UNITY_EMAIL -password $UNITY_PW -logfile

/Applications/Unity/Unity.app/Contents/MacOS/Unity -quit -noUpm -batchmode -serial $UNITY_SERIAL -username $UNITY_EMAIL -password $UNITY_PW -logfile

【オボエガキ】Bitriseでビルド中に落ちた

:app:transformNativeLibsWithStripDebugSymbolForDevelopmentDebug FAILED
FAILURE: Build failed with an exception.
What went wrong:
Execution failed for task ':app:transformNativeLibsWithStripDebugSymbolForDevelopmentDebug'.
> A problem occurred starting process 'command '/opt/android-ndk/toolchains/mips64el-linux-android-4.9/prebuilt/linux-x86_64/bin/mips64el-linux-android-strip''
Try:
Run with --info or --debug option to get more log output.
Exception is:
org.gradle.api.tasks.TaskExecutionException: Execution failed for task ':app:transformNativeLibsWithStripDebugSymbolForDevelopmentDebug'.

というメッセージでコケた場合、ndkを削除する必要がある*1
ワークフローの「Script step」に「rm -rf /opt/android-ndk」を追加すると解決する

起きろメガドライバー。俺たちの祭りが始まるぞ

先日年内発売が発表されたMEGA DRIVEミニ。リアルタイムで*1メガドライブを楽しんだ者としては期待が大きいのだけど、心配なのはそれに内蔵されるゲームだよね。
ベースになるのはアメリカのAtGamesから発売されているSega Genesis Flashbackと言われてるけど、その中身は以下の通り。

他、MKIIIやGGタイトルも含まれるケド割愛

…なんだろうなこれは。とにかくセガのタイトルを無作為に突っ込みましたという感じ。特に名作揃いってわけでもないところがなんともアレ。愛が足りないぜ!
折角だから今回はアタマを20代の頃にロールバックして何が含まれるかを楽しく妄想してみた。根拠はセガのVCのリスト最近の版権動向。そこに個人的な希望を入れ込んでみた。本数はとりあえず40本。

ふむ…とりあえず40本あれば満足のいくリストができることが判明したw
当初公開時はシャイニングシリーズを入れてたけど、私はあまりあのシリーズが好きくないのでベアナックルシリーズと差し替えたw いいよねベアナックル。ゲームはファイn(ry*3
スーパー32Xのタイトルが混じってるのは、どうせエミュレータなんだから32X程度の再現は余裕だろうということで入れてみた次第w いや、だって「メガドライブ」でやってみたいじゃない、体感ゲーム勢ぞろいっての。

個人的にはここに更に

みたいなレアゲームなんかが含まれてると最高なんだけど、無理だろうなあ…

ま、今回はこんな感じで。実質ちょっと縛りのある『俺のメカドライブゲーム40選』みたいになってるケド気にしない!
皆も発表まで好きにラインナップを考えてまとめて公表するといいんじゃないかな、折角だから盛り上がろうぜ、私も皆の考えた妄想ゲームリストを見てみたいんだ。
そんじゃね。

*1:とかいいつつ私がメガドライブを手に入れたのは1989年のスーパー忍発売直前なんだけどね。SEGA専門店サンタで買いました

*2:時期的にSwitch版と被るなら外される可能性もあるな…

*3:お里の知れる発言

*4:版権的に無理そげ

Twitterに発言したことの解説

「安倍政権を倒す」のではなく「日本を良くする」と言わなければ広い支持を得ることはできないことに、野党連中はいつ気づくんだろな。*1
https://twitter.com/lynmock/status/914824957292507138

自分のヘイトを他人に聞かせて、それで支持が得られると思ってるヒト達ってどんなアタマしてるんだろうか。


国民は良きにしろ悪きにしろ国に保障されて生活している。
故に求めるのはその保障のリセットでも革命でもなく「アップグレード」*2。勿論ダウングレードなんて以ての外。
我々有権者が聞きたいのはそのアップグレード内容…HOPEであるハズなのに、野党連中が叫ぶのは現政権へのHATEばかり。
他人を蹴落とすことばかり口にするヒトに、安心して国の運営を任せられるわけがない。

想像してみよう。すべての政党や候補が他政党への憎悪を口にせずに、自分達が理想とする政策だけを説く選挙戦を。
それだけでもかなり希望に溢れた楽しい選挙になるんじゃなかろうか。


ちなみに2012年に自民党が政権を奪い返した時の自民党のスローガンは「日本を、取り戻す。」
「打倒 hogehoge」ではない辺りに安心感があるよね、野党連中もいい加減これ位の品を身につけて欲しいものです。


強い言葉は身内の団結は強固にできるかも知れないけれど、外野から見たら気持ち悪いだけですよ。一言余分なんです。

以上、どうも左右ともにツイートの意味を誤解されてるヒトが多いので解説でした。

可変レギオス エータタイプ ノンスケール ダイキャスト&ABS製 塗装済み 完成品 可動フィギュア

可変レギオス エータタイプ ノンスケール ダイキャスト&ABS製 塗装済み 完成品 可動フィギュア

どこでもいい、私がこういうのをサックリ買える位に景気を良くしてくれ

*1:一部修正

*2:もし破壊をお望みの方は公安に連絡してあげますのでご一報ください