[フリーソフト | mac ] シンプルなデスクトップ用デジタル時計アプリ MinimalClockを作りました

2014/01/15

こんにちは。きんくまです。

light_theme

シンプルなデスクトップ用デジタル時計アプリを作りました。
MinimalClockという名前にしました。mac用です。

マルチディスプレイ環境だと、どうにも時計が目に入らないことがありまして。
Macの最新のOS Mervericsだと各ディスプレイに時計をつけられるので、そんなに困らないのですが、
もう少し目につくところに好きな用に置きたいなーと。

特徴

日付と時間のみを表示するシンプルなデジタル時計です。
大きすぎず、かといって小さくて見づらいわけでもないちょうど良いバランスの大きさです。
(上のキャプチャは実寸です。)
デスクトップ上の好きな位置に置けます。常に画面の最前面に設定しておけば、別のアプリの裏に隠れることもありません。
1秒ごとに時間経過をチェックして、分が変ったときのみ画面描画するようにしてあるので、CPUに負担をかけません。
Retinaをサポートしました。
無料です。

3つの機能

1. 明るいテーマ、暗めのテーマの2種類のテーマ
2. 12時間 or 24時間表記
3. ウインドウを常に最前面に

の3つの機能です。

テーマ

テーマは上のキャプチャにある
・白地で黒文字
と下のキャプチャにある
・黒字で白文字
です。

dark_theme

メニューのToggle Themeから選択してください。

12時間24時間表記

メニューからToggle 12-24 hoursを選択してください。

画面の常に最前面に

メニューからWindow always in frontを選択してください。

ダウンロード

>> ダウンロードはこちらから

注意事項など

・mac用です。
・たぶん最近のmacだと動きます。古いのはテストしてないです。

・Retinaをサポートしました。

・zipを解凍してできるMinimalClock.appをダブルクリックすれば起動します。
 インストーラーはありません。

・無料(フリー)ですが、無保証です。自己責任でご使用ください。

・CPUに負荷をかけないために、1秒間に1回のみ、以前と時間が経ったかチェックして、分が変ったときのみ画面を更新します。
 なので、端末の時計にぴったり正確ではなく、だいたい合ってるぐらいです。

・アンインストールする際は、ファイルを削除してください。
 設定を保存しないので、これ以外に設定ファイルを作りません。

開発者向け作り方情報

このアプリ自体はAIR製です。
一番最初にAIRが出たときって、必ずインストーラーからインストールさせる面倒くさいアプリしか作れなかったのですが、なんだか最近のAIRはダブルクリックですぐに立ち上がるアプリを作れるようになってました。

AIRのランタイムも同梱できるようになったので、ユーザーの端末に入っているランタイムバージョンのことも気にすることなくできるので便利です。

これのソースコード自体は、特にクラスも分けずに300行くらいの1枚のメインクラスからのみで、できてます。
なので、こんな感じの小さなフリーウェアは楽でよいなと開発して思いました。

以下興味あるひと向けソースコード

package
{
	import flash.desktop.NativeApplication;
	import flash.display.Graphics;
	import flash.display.NativeMenu;
	import flash.display.NativeMenuItem;
	import flash.display.Shape;
	import flash.display.Sprite;
	import flash.display.StageAlign;
	import flash.display.StageScaleMode;
	import flash.events.Event;
	import flash.events.InvokeEvent;
	import flash.events.KeyboardEvent;
	import flash.events.MouseEvent;
	import flash.events.TimerEvent;
	import flash.text.TextField;
	import flash.text.TextFormat;
	import flash.text.TextFormatAlign;
	import flash.ui.Keyboard;
	import flash.utils.Timer;
	
	[SWF(width="88",height="50",frameRate="1", backgroundColor="0x333333")]
	public class MinimalClock extends Sprite
	{
		[Embed(source="/assets/Arial Rounded Bold.ttf"
				,fontName="Arial Rounded MT Bold Regular",
				,embedAsCFF = "false"
				,unicodeRange = "U+0020-U+007e"
				,mimeType='application/x-font')]
		private static const ClockFontClass:Class;

		public static const THEME_LIGHT:int = 0;
		public static const THEME_DARK:int = 1;
		
		private var _is24HourText:Boolean = false;
		
		private var _timeLabel:TextField;
		private var _dateLabel:TextField; 
		private var _bg:Shape;
		private var _theme:int = THEME_LIGHT;
		private var _baseTimer:Timer;
		private var _isCommandPressing:Boolean = false;
		
		private var _lastDate:Date = new Date();
		
		public function MinimalClock()
		{
			stage.scaleMode = StageScaleMode.NO_SCALE;
			stage.align = StageAlign.TOP_LEFT;
			initBg();
			initLabels();
			initTimer();
			initMenu();
			NativeApplication.nativeApplication.addEventListener(InvokeEvent.INVOKE, appInvokeHandler);
			NativeApplication.nativeApplication.addEventListener(Event.DEACTIVATE, appDeactivateHandler);
			NativeApplication.nativeApplication.addEventListener(Event.ACTIVATE, appActivateHandler);
			updateLabels(new Date());
			registerMouseEvents();
			registerKeyboardEvents();
			this.theme = THEME_LIGHT;
		}
		
		private function registerKeyboardEvents():void
		{
			stage.addEventListener(KeyboardEvent.KEY_DOWN, keyboardDownHandler);
			stage.addEventListener(KeyboardEvent.KEY_UP, keyboardUpHandler);
		}
		
		protected function keyboardUpHandler(event:KeyboardEvent):void
		{
			if(event.keyCode == Keyboard.COMMAND){
				_isCommandPressing = false;
			}
		}
		
		protected function keyboardDownHandler(event:KeyboardEvent):void
		{
			if(event.keyCode == Keyboard.COMMAND){
				_isCommandPressing = true;
			}else if(event.keyCode == Keyboard.Q){
				if(_isCommandPressing){
					NativeApplication.nativeApplication.exit(0);
				}
			}
		}
		
		private function initMenu():void
		{
			var menuRoot:NativeMenu = new NativeMenu();
			if(NativeApplication.supportsMenu){
				NativeApplication.nativeApplication.menu = menuRoot;
			}
			
			var mainMenu:NativeMenu = new NativeMenu();
			menuRoot.addSubmenu(mainMenu, "Minimal Clock");
			
			var toggleThemeMenu:NativeMenuItem = mainMenu.addItem(new NativeMenuItem("Toggle Theme"));
			toggleThemeMenu.addEventListener(Event.SELECT, toggleThemeSelectHandler);
			
			var toggle24hoursMenu:NativeMenuItem = mainMenu.addItem(new NativeMenuItem("Toggle 12-24 Hours"));
			toggle24hoursMenu.addEventListener(Event.SELECT, toggle24HoursMenuSelectHandler);
			
			var toggleWindowAlwaysInfrontMenu:NativeMenuItem = mainMenu.addItem(new NativeMenuItem("Window always in front"));
			toggleWindowAlwaysInfrontMenu.addEventListener(Event.SELECT, toggleAlwaysInfrontOfWindowMenuSelectHandler);
			
			mainMenu.addItem(new NativeMenuItem("", true));
			var quitMenu:NativeMenuItem = mainMenu.addItem(new NativeMenuItem("Quit"));
			quitMenu.addEventListener(Event.SELECT, quitMenuSelectHandler);
		}
		
		protected function toggleAlwaysInfrontOfWindowMenuSelectHandler(event:Event):void
		{
			this.windowAlwaysInFront = !this.windowAlwaysInFront;
			NativeMenuItem(event.currentTarget).checked = this.windowAlwaysInFront;
		}
		
		protected function toggle24HoursMenuSelectHandler(event:Event):void
		{
			toggle24Hours();
		}
		
		protected function quitMenuSelectHandler(event:Event):void
		{
			NativeApplication.nativeApplication.exit(0);
		}
		
		protected function toggleThemeSelectHandler(event:Event):void
		{
			toggleTheme();
		}
		
		private function toggleTheme():void
		{
			if(_theme == THEME_LIGHT){
				this.theme = THEME_DARK;
				
			}else if(_theme == THEME_DARK){
				this.theme = THEME_LIGHT;
			}
		}
		
		private function initBg():void
		{
			_bg = new Shape();
			addChild(_bg);
		}
		
		private function registerMouseEvents():void
		{
			stage.addEventListener(MouseEvent.MOUSE_DOWN, stageMouseDownHandler);
			//stage.addEventListener(MouseEvent.CLICK, stageMouseClickHandler);
		}
		
		protected function stageMouseClickHandler(event:MouseEvent):void
		{

		}
		
		protected function stageMouseDownHandler(event:MouseEvent):void
		{
			stage.nativeWindow.startMove();
		}
		
		protected function appActivateHandler(event:Event):void
		{
			//trace("activate");
		}
		
		protected function appDeactivateHandler(event:Event):void
		{
			//trace("deativate");
		}
		
		protected function appInvokeHandler(event:InvokeEvent):void
		{
			//trace("invoke");
		}
		
		private function initTimer():void
		{
			_baseTimer = new Timer(1000/1, 0);
			_baseTimer.addEventListener(TimerEvent.TIMER, baseTimerHandler, false, 0, true);
			_baseTimer.start();
		}
		
		protected function baseTimerHandler(event:TimerEvent):void
		{
			var date:Date = new Date();
			if(date.minutes != _lastDate.minutes){
				updateLabels(date);
				_lastDate = date;
			}
		}
		
		private function toggle24Hours():void
		{
			this.is24HourText = !_is24HourText;
			updateLabels(new Date());
		}
		
		private function updateLabels(date:Date):void
		{
			showCurrentTime(date);
			showCurrentDate(date);			
		}
		
		private function showCurrentDate(date:Date):void
		{
			var dateText:String = date.fullYear + " / " 
				+ paddingZeroString(date.month + 1) + " / "
				+ paddingZeroString(date.date);
			_dateLabel.text = dateText;
		}
		
		private function showCurrentTime(date:Date):void
		{
			var hours:int;
			var hoursStr:String;
			if(_is24HourText){
				hours = date.hours;
				if(hours == 0){
					hoursStr = hours.toString(10);
				}else{
					hoursStr = paddingZeroString(hours);
				}
			}else{
				hours = get12Hours(date.hours);
				hoursStr = hours.toString(10);
			}

			var timeText:String = hoursStr
				+ ":" + paddingZeroString(date.minutes);
			_timeLabel.text = timeText;
		}
		
		private function get12Hours(hours:int):int
		{
			if(hours >= 12){
				hours = hours - 12;
			}
			return hours;
		}
		
		private function paddingZeroString(num:Number):String
		{
			var numStr:String = num.toString(10);
			if(numStr.length < 2){
				numStr = "0" + numStr;
			}
			return numStr;
		}
		
		private function initLabels():void
		{
			_timeLabel = new TextField();
			_timeLabel.embedFonts = true;
			_timeLabel.text = "";
			_timeLabel.x = 4;
			_timeLabel.y = 15;
			_timeLabel.selectable = false;
			_timeLabel.width = 79;
			addChild(_timeLabel);
			
			_dateLabel = new TextField();
			_dateLabel.embedFonts = true;
			_dateLabel.text = "";
			_dateLabel.x = 7;
			_dateLabel.y = 4;
			_dateLabel.selectable = false;
			addChild(_dateLabel);
		}
		
		private function set theme(newTheme:int):void
		{
			_theme = newTheme;
			var textFormat:TextFormat = new TextFormat();
			var dateTextFormat:TextFormat = new TextFormat();
			textFormat.size = 26;
			textFormat.align = TextFormatAlign.CENTER;
			textFormat.font = "Arial Rounded MT Bold Regular";
			
			dateTextFormat.size = 11;
			dateTextFormat.font = textFormat.font;
			
			var bgColor:int;
			if(_theme == THEME_LIGHT){
				dateTextFormat.color = textFormat.color = 0x000000;
				bgColor = 0xffffff;
				
			}else if(_theme == THEME_DARK){
				dateTextFormat.color = textFormat.color = 0xffffff;
				bgColor = 0x000000;

			}
			_timeLabel.setTextFormat(textFormat);
			_timeLabel.defaultTextFormat = textFormat;
			
			_dateLabel.setTextFormat(dateTextFormat);
			_dateLabel.defaultTextFormat = dateTextFormat;
			
			var g:Graphics = _bg.graphics;
			g.clear();
			g.beginFill(bgColor,0.25);
			g.drawRect(0,0,stage.stageWidth,stage.stageHeight);
			g.endFill();
		}

		public function set is24HourText(value:Boolean):void
		{
			_is24HourText = value;
		}
		
		public function set windowAlwaysInFront(value:Boolean):void
		{
			stage.nativeWindow.alwaysInFront = value;
		}
		
		public function get windowAlwaysInFront():Boolean
		{
			return stage.nativeWindow.alwaysInFront;
		}
	}
}
LINEで送る
Pocket

自作iPhoneアプリ 好評発売中!
フォルメモ - シンプルなフォルダつきメモ帳
ジッピー電卓 - 消費税や割引もサクサク計算!

ページトップへ戻る