Windowsアプリケーションで矢印キーとEnterでのフォーカス移動
というトンデモな仕様を実装しろとのこと。
明らかにWindows標準APIを無視した設計だったので、
仕様の再検討を!!ってまずお願いしたけど、一応調べてみた。
TextBoxなどでは、文字列にカーソル当たっている状態では左右キーは文字間を移動するのに使われる。
その為、キャレット位置が先頭のときに←を押すと前のコントロールへフォーカス
キャレット位置が最終のときに→を押すと次のコントロールへフォーカスという動きを考えた。
TextBoxには、SelectionStartというプロパティがあって簡単にキャレット位置を判定できたが、
こまったのはそれ以外のコントロールについて。
例えば、ComboBoxなんかも任意文字列が入力できるのでキャレット位置を取得したい。
TextBoxにあったSelectionStartプロパティが使えたらなーっと思ってたらこっちにもあった。
DRYじゃねぇ・・・。
とおもった。
TextBoxBaseクラスを継承してたり、Interfaceとかで提供されてて欲しかった。
一応コード
TextBoxのみ文字間移動を対応。その他については考慮していない
あと、実装したのは共通で利用するForm。(KeyPreviewプロパティをTrueにしておく)
共通で利用するFormを継承したFormで自動的に適用される。
private void CommonForm_KeyDown(object sender, KeyEventArgs e) { if (e.KeyCode == Keys.Left) { if (this.ActiveControl is TextBox) { TextBox textBox = (TextBox)this.ActiveControl; if (textBox.SelectionStart != 0) { return; } } this.SelectNextControl(this.ActiveControl, false, true, true, true); } if (e.KeyCode == Keys.Right) { if (this.ActiveControl is TextBox) { TextBox textBox = (TextBox)this.ActiveControl; if (textBox.SelectionStart != textBox.Text.Length) { return; } } this.SelectNextControl(this.ActiveControl, true, true, true, true); } if (e.KeyCode == Keys.Enter) { bool forward = e.Modifiers != Keys.Shift; this.SelectNextControl(this.ActiveControl, forward, true, true, true); } }
SVNリポジトリの使用方法について
Subversionの各リポジトリ(trunk、tags、branches)の運用方法について記載します。
次期バージョンの開発を行なう場合
次期バージョンの開発(新規機能追加や既存機能改修など)を行なう場合は trunk にて作業を行ないます。
その為、trunkは常に最も新しい状態(開発版)となっています。(不具合がある可能性も高い)
開発を終えたバージョンをリリースしたい
trunkでの開発を終えたバージョンをリリースする場合、 branchesにバージョン番号のディレクトリを作成 し、枝分かれさせます。
リリース作業はbranchesに作成したリポジトリで作業を行ないます。
(例)
sample_appシステムの場合、「sample_app/branches/2.1」といった具合
リリース後、不具合が発生したので対応したい
リリース後の不具合対応はbranchesで管理している該当のバージョンのリポジトリに対して修正を行ないます。
その為、 branchesでの修正内容は状況に応じてtrunkにマージ する必要があります。
(次期バージョンでは前のバージョンの不具合修正を反映させなくても良いなどのケースではマージする必要はないが、原則は常にbranchesの修正内容はマージすること。)
安定版として保存しておきたい
リリース後branchesでの不具合が収束し、安定版として保存しておきたい場合はbranchesからtagsへコピーします。
大幅な改修などメジャーバージョン番号が変わるような改修が入った場合、不測の事態に備えていつでも元に戻れるように前のバージョンを保存しておく必要があります。
(例)
大幅な業務変更のため、メジャーバージョン番号を1.Xから2.Xに変更する。
その際、1.Xの最終安定版のバージョンである1.9をtagsへスナップショットとして保存しておく。
万が一、2.X系リリース後に大きな不具合が発生し、1.X系に差し戻す場合はtagsで保存しておいた1.X系の最終安定版の1.9に置き換える。
sample_appシステムの場合、「sample_app/tags/1.0」といった具合。
proxy環境下でユーザ毎にrvmをインストールする
概要
proxy環境下でユーザ単位のrvmをインストールする。
curlのインストール
rvmインストール時に必要っぽいのでcurlが無い場合はインストールしておく。
念のためapt-getをupdateしておく。
$ sudo apt-get update $ sudo apt-get install curl
proxyの設定
ユーザ毎に有効なの環境変数を.bash_profileに記載する。
curlのmanを見るとhttp_proxyのみ小文字という制限があったので
メンドクサイからhttp、https、ftpの使われそうなプロトコルに関して小文字、大文字でproxyを設定しておく
$ cd $HOME $ vim ~/.bash_profile
.bash_profile
export http_proxy=": " export https_proxy=" : " export ftp_proxy=" : " export HTTP_PROXY=" : " export HTTPS_PROXY=" : " export FTP_PROXY=" : "
編集した.bash_profileをリロードする
$ source ~/.bash_profile
※.bash_profileがなければ作る
rvmのインストール
rvmのホームページを参考にコマンドを実行していく。
RVMのインストール
$ bash -s stable < <(curl -s https://raw.github.com/wayneeseguin/rvm/master/binscripts/rvm-installer)
.bash_profileのリロード
$ source ~/.bash_profile
rvmでrubyをインストールするのに必要なライブラリが表示されるので指示に従ってインストールする。
$ rvm requirements Additional Dependencies: # For Ruby / Ruby HEAD (MRI, Rubinius, & REE), install the following: ruby: /usr/bin/apt-get install build-essential openssl libreadline6 libreadlin e6-dev curl git-core zlib1g zlib1g-dev libssl-dev libyaml-dev libsqlite3-0 libsqlite3-dev sqlite3 libxml2-dev libxslt-dev autoconf libc6-dev ncurses-dev automake libtool bison subversion
$ sudo apt-get install build-essential openssl libreadline6 libreadlin e6-dev curl git-core zlib1g zlib1g-dev libssl-dev libyaml-dev libsqlite3-0 libsqlite3-dev sqlite3 libxml2-dev libxslt-dev autoconf libc6-dev ncurses-dev automake libtool bison subversion
ruby1.9.3のインストール
$ rvm install 1.9.3
Redmineのplugin開発
今回、Redmineのプラグインを開発してみてHookやPlugin用の設定ページの作成や独自rakeコマンド、Mail送信機能拡張など様々な機能を触ってみて思ったよりも便利に開発が出来るんだということがわかった。
できたものは超オレオレプログラムのごり押し機能だけど、勉強になったしとりあえず動くしまぁいいやと思った。
要望
チケットの開始時間単位のリマインダー機能。
Redmineには、リマインダー機能は既に存在するがチケット何時から開始とかいう場合に1時間前にリマインダーを送りたいとかができない。
ソース
GitHubに登録しています。
環境
- Redmine1.2
- Rails2.3.11
機能
まず、カスタムフィールドで開始時間を定義する。
ここを参考に「開始時間」のカスタムフィールドを定義しておく。
そして、それに関連するトラッカーを決める。
例えば「イベント」トラッカーにカスタムフィールド「開始時間」をひも付けておく。
そして、定義したトラッカーでチケットを作成した時に、OSのタスク機能を利用して開始時間の○時間前にメール送信用のタスクを登録する。
チケットが完了や却下など、完了を示すステータスで更新されたら登録しておいたタスクを削除する。
かなりごり押しのテクニックなのであまり参考になるようなところはないかもしれない。
実装
本体を傷つけないように、プラグインとして開発する。
まず、プラグインのスケルトンを作成する。
$ cd RAILS_ROOT $ ruby script/generate redmine_plugin ReminderTicket
これで雛形が作成されるので、個別に実装していく。
まずは、Modelを作成する。
既存のMailerモデルを継承して今回用にRemindMailerクラスとしてカスタマイズする。
self.reminder_ticket
このメソッドはrakeコマンドより呼び出される。
引数はチケットID(issue.id)を受け取るようにしておく。
reminder_time
このメソッドがメール送信時に呼び出されるメソッド。
呼び出し元はself.reminder_ticketでdeliver_をつけて呼び出している。
deliver_を頭に付けるとメールが送信されるみたいで、送信先(TOやCC)や件名、本文などを定義する。
render_multipartが呼び出されたところで、Viewに渡される。
app/models/remind_mailer.rb
class RemindMailer < Mailer def self.reminder_ticket(options={}) issue = Issue.find(options[:issue_id]) deliver_reminder_time(issue.assigned_to, issue) if issue.assigned_to && issue.assigned_to.active? end def reminder_time(user, issue) set_language_if_valid user.language recipients issue.recipients cc(issue.watcher_recipients - @recipients) subject l(:mail_subject_reminder_ticket, :start_time => issue.custom_field_values[Setting.plugin_redmine_reminder_ticket['target_custome_field_value_id'].to_i], :count => Setting.plugin_redmine_reminder_ticket['diff_time'].to_i / 3600.0) body :issue => issue, :start_time => issue.custom_field_values[Setting.plugin_redmine_reminder_ticket['target_custome_field_value_id'].to_i], :count => Setting.plugin_redmine_reminder_ticket['diff_time'].to_i / 3600.0, :issue_url => url_for(:controller => 'issues', :action => 'show', :id => issue.id) render_multipart('reminder', body) end end
次にメール送信時のViewと設定用のViewを作成する。
app/views/remind_mailerディレクトリに以下の2つのテンプレートを作成する。(このディレクトリ名はRailsの規約通りModel名と結びつけておく)
メール送信設定でテキスト形式とHTML形式が設定できると思うが、それら2つに対応させておく。
ファイル名はModelのrender_multipartの第一引数で定義した名前.text.html.rhtml | text.plain.rhtml としておく。
View内で使用できるインスタンス変数(@付きの変数)はModel内のbodyでシンボル定義した値となる。
app/views/remind_mailer/reminder.text.html.rhtml
<p><%= l(:mail_body_reminder_ticket, :start_time => @start_time, :count => @count) %></p> <ul> <li><%=h @issue.project %> - <%= "#{@issue.tracker} ##{@issue.id}"%>: <%=h @issue.subject %></li> </ul> <p><%= link_to l(:label_issue), @issue_url %></p>
app/veiws/remind_mailer/remainder.text.plain.rhtml
<%= l(:mail_body_reminder_ticket, :start_time => @start_time, :count => @count) %>: * <%= "#{@issue.project} - #{@issue.tracker} ##{@issue.id}: #{@issue.subject}" %> <%= @issue_url %>
2012.03.06追記
※Redmine1.3以降はテンプレート名が変更されているので注意!!
reminder.text.html.rhtml -> reminder.html.erb
reminder.text.plain.rhtml -> reminder.text.erb
設定用Viewを作成する。
プラグイン内で簡潔する設定内容を編集できる頁を作成する。
これは、Redmineの管理メニュー > プラグインでプラグインの一覧から個別に設定画面へ飛ぶことのできるものである。
app/views/settings/_redmine_reminder_ticket_settings.rhtml
<p> <table> <tr><td> Target Tracker ID </td><td> <%= text_field_tag('settings[target_tracker_id]', @settings['target_tracker_id'])%> </td></tr> <tr><td> Target Custome Field Value ID </td><td> <%= text_field_tag('settings[target_custome_field_value_id]', @settings['target_custome_field_value_id'])%> </td></tr> <tr><td> Diff Time </td><td> <%= text_field_tag('settings[diff_time]', @settings['diff_time'])%> </td></tr> <tr><td> SC </td><td> <%= text_field_tag('settings[sc]', @settings['sc'])%> </td></tr> <tr><td> RU </td><td> <%= text_field_tag('settings[ru]', @settings['ru'])%> </td></tr> <tr><td> RP </td><td> <%= text_field_tag('settings[rp]', @settings['rp'])%> </td></tr> </table> </p>
設定できる内容は、以下の通りとした。
target_tracker_id
- 対象となるトラッカーのID
target_custome_field_value_id
- 対象となる開始時間を定義したカスタムフィールドのID
diff_time
- 何時間前に送られるか
sc
- schtasksのオプション
ru
- schtasksのオプション
rp
- schtasksのオプション
一応多言語対応用にja.ymlを作成しておく。
日本語しか対応させていないけど。
ja: mail_subject_reminder_ticket: "本日%{start_time}から開始されるチケットが%{count}時間後に到来します" mail_body_reminder_ticket: "本日%{start_time}から開始されるチケットが%{count}時間後に到来します"
チケット作成時にタスクを登録して、そのタスクから呼び出されるバッチファイルを作成する。
どこに作成したらいいのかわからない&環境依存なのでもっといい方法があるはず。
rakeコマンド呼び出し用のバッチファイル(Windows専用)。
lib/bat/reminder_ticket.bat
cd C:\redmine rake redmine:send_reminder_ticket issue_id=%1 RAILS_ENV=production
次は要となる部分。
既存のチケット登録、更新処理の後に実行させたい機能をHookというのを使って実現する。
これは、既存コントローラで既に埋め込まれてるHookスクリプトを奪い取って(?)実行してしまう。という機能。
本体に影響を与えずに機能追加したいという場合に有効。
今回は、更新時(edit_after_save)と新規登録時(new_after_save)のHookを利用して
更新時に、完了ステータスなら(schtasksコマンドで)タスク削除
それ以外ならタスク削除&作成で更新処理
新規登録時に、タスク作成処理を実現した。
lib/reminder_ticket/hooks/controller_issues_edit_after_save_hook.rb
module ReminderTicket module Hooks class ControllerIssuesEditAfterSaveHook < Redmine::Hook::ViewListener def controller_issues_edit_after_save(context={}) begin issue = context[:issue] if issue.tracker_id == Setting.plugin_redmine_reminder_ticket['target_tracker_id'].to_i if issue.closing? or issue.closed? Schtasks.delete_from_issue(issue) else Schtasks.delete_from_issue(issue) Schtasks.create_from_issue(issue) end end rescue false end end end end end
lib/reminder_ticket/hooks/controller_issues_new_after_save_hook.rb
module ReminderTicket module Hooks class ControllerIssuesNewAfterSaveHooks < Redmine::Hook::ViewListener def controller_issues_new_after_save(context={}) begin issue = context[:issue] if issue.tracker_id == Setting.plugin_redmine_reminder_ticket['target_tracker_id'].to_i Schtasks.create_from_issue(issue) end rescue false end end end end end
rakeタスクファイルを作成する。
こいつがメール送信をするrakeコマンドを提供する。
バッチファイルで定義した以下のコマンドが打てるようになる。
引数はENV['引数名']で受け取れる。
Modelで定義したRemindMailerに投げてメールが送信される。
rake redmine:send_reminder_ticket issue_id=[issue_id] RAILS_ENV=production
lib/tasks/reminder_ticket.rake
namespace :redmine do task :send_reminder_ticket => :environment do options = {} options[:issue_id] = ENV['issue_id'].to_i if ENV['issue_id'] RemindMailer.reminder_ticket(options) end end
Windowsのタスク機能を提供する「schtasks」を呼び出すモジュールを定義する。
Hook時に呼び出されている。
lib/schtasks.rb
module Schtasks def self.create_from_issue(issue) options = {} options[:tn] = issue.id options[:tr] = "#{RAILS_ROOT}/vendor/plugins/redmine_reminder_ticket/lib/bat/reminder_ticket.bat #{issue.id}" options[:sc] = Setting.plugin_redmine_reminder_ticket['sc'] options[:sd] = issue.start_date.strftime("%Y/%m/%d") options[:st] = (Time.parse("#{options[:sd]} #{issue.custom_field_values[Setting.plugin_redmine_reminder_ticket['target_custome_field_value_id'].to_i]}") - Setting.plugin_redmine_reminder_ticket['diff_time'].to_i).strftime("%H:%M") options[:ru] = Setting.plugin_redmine_reminder_ticket['ru'] options[:rp] = Setting.plugin_redmine_reminder_ticket['rp'] create(options) end def self.create(options) return unless options system "schtasks /create /tn \"#{options[:tn]}\" /tr \"#{options[:tr]}\" /sc \"#{options[:sc]}\" /sd \"#{options[:sd]}\" /st \"#{options[:st]}\" /ru \"#{options[:ru]}\" /rp \"#{options[:rp]}\"" end def self.delete_from_issue(issue) options = {} options[:tn] = issue.id delete(options) end def self.delete(options) return unless options system "schtasks /delete /f /tn #{options[:tn]}" end end
大事なinit.rbファイル。
settingsで定義した:defaultが設定内容となる。
:partialは設定用Viewを指す。
settingsは@settingsとして設定用View内でインスタンス変数としてアクセスできる。
ただし、そのほかのスコープでは、Setting.plugin_redmine_reminder_ticket['設定項目名']でアクセスする必要がある。
命名規則は、Setting.plugin_[プラグイン名]となる。
設定用のModelとかは作成する必要がなく、init.rbでsettingsとして定義した内容が設定内容として保持されるというところに感動した。便利。
最初は個別に設定内容を定数として別ファイルに書こうかと思っていたがその必要がなくなった。
init.rb
require 'redmine' require 'time' require_dependency 'schtasks' require_dependency 'reminder_ticket/hooks/controller_issues_new_after_save_hook' require_dependency 'reminder_ticket/hooks/controller_issues_edit_after_save_hook' Redmine::Plugin.register :redmine_reminder_ticket do name 'Redmine Reminder Ticket plugin' author 'Souichi Saitou' description 'This is a plugin for Redmine' version '0.0.1' url '' author_url 'http://souichi.heroku.com' settings :default => {'target_tracker_id' => 4, 'target_custome_field_value_id' => 0, 'diff_time' => 3600, 'sc' => 'ONCE', 'ru' => nil, 'rp' => nil}, :partial => 'settings/redmine_reminder_ticket_settings' end
2012年はどんなサービスが流行るのかなぁ
2011年はソーシャルサービスブームがすごかったという印象を受けるが、
2012年はどのようなサービスが登場していくのだろうかというのを少し考えてみる。
2012年からはキュレーションサービスがもっと出てくるのではないかと思う。
ソーシャルサービスのお陰でネットをしている時間でもいつでも友人や家族に繋がることができるようになった。
そして、FaceBookやTwitterなど「タイムライン」で今、誰がどのような活動をしているのか知ることができるようになった。
また、スマートフォンなどの使い方にも慣れて自分の好きな情報も集められるようになってきた。
これまで「良いBlog」を探すことが多かったが、そこから一歩進んで「良い記事」をオンラインブックマークやお気に入り機能で集めるようになる。(もしくは、必要に応じてキュレーションサービスでまとめたり、カスタマイズしたり)
そうすると、「その人が集めている情報」から「その人がどんな趣味趣向を持っているか」がわかるようになる。
SNSとキュレーションサービスの普及でより自分に合わせた情報をリアルタイムで受け取ることができるようになる。
2012年はそうしたサービスがもっと盛り上がるのではないかぁ??というか盛り上がって欲しい。
もっと自分が欲しい情報をガンガン集められるようになりたい。
UMLの勉強
UMLモデリングレッスン 21の基本パターンでわかる要求モデルの作り方
- 作者: 平澤章
- 出版社/メーカー: 日経BP社
- 発売日: 2008/01/24
- メディア: 単行本(ソフトカバー)
- 購入: 18人 クリック: 284回
- この商品を含むブログ (25件) を見る
設計においてUML覚えておくと便利だなぁと最近思い出して、
色々調べてたけどやっぱり実践しながらじゃないと覚えないと思っていたところ
実際の事例を元に学べそうな良書にたどり着いた。
今日は休日なので一気に読み込んでみよう。