aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorToshiaki Asai <toshi.alternative@gmail.com>2016-10-20 21:49:19 +0900
committerToshiaki Asai <toshi.alternative@gmail.com>2016-10-20 21:49:19 +0900
commitc610da913865e25d4f789014747fd315760a6f9d (patch)
tree7a57925a76fc7a762f56437703bbea77f444f072
parent1bcbace4f5ec391b780a05c9a01aaf2d0565d2f1 (diff)
parent8e403b697522adeb981d7c1a5ebe73348542fbbd (diff)
downloadmikutter-c610da913865e25d4f789014747fd315760a6f9d.tar.gz
Merge branch 'topic/905-auto-intent-selector' into develop
-rw-r--r--core/plugin/intent/intent.rb8
-rw-r--r--core/plugin/intent_selector/intent_selector.rb117
-rw-r--r--core/plugin/intent_selector/listview.rb108
-rw-r--r--core/userconfig.rb11
4 files changed, 230 insertions, 14 deletions
diff --git a/core/plugin/intent/intent.rb b/core/plugin/intent/intent.rb
index 862a1a84..12cc69fc 100644
--- a/core/plugin/intent/intent.rb
+++ b/core/plugin/intent/intent.rb
@@ -3,6 +3,10 @@ require_relative 'model/intent'
require_relative 'model/intent_token'
Plugin.create(:intent) do
+ # 全てのIntentを列挙するためのフィルタ
+ defevent :intent_catalog,
+ prototype: [:<<]
+
# _uri_ を開くことができる Model を列挙するためのフィルタ
defevent :model_of_uri,
prototype: [URI, :<<]
@@ -40,6 +44,10 @@ Plugin.create(:intent) do
end
[target_model_slug, intents]
end
+ filter_intent_all do |intents|
+ intents << my_intent
+ [intents]
+ end
add_event(:"intent_open_#{slug}", &proc)
self
end
diff --git a/core/plugin/intent_selector/intent_selector.rb b/core/plugin/intent_selector/intent_selector.rb
index 07bafcf8..3cc0e45f 100644
--- a/core/plugin/intent_selector/intent_selector.rb
+++ b/core/plugin/intent_selector/intent_selector.rb
@@ -1,14 +1,46 @@
# -*- coding: utf-8 -*-
+require_relative 'listview'
Plugin.create(:intent_selector) do
+ UserConfig[:intent_selector_rules] ||= []
+
on_intent_select do |intents, model|
case model
when Retriever::Model
- intent_choose_dialog(intents, model: model)
+ intent_open(intents, model: model)
when URI
- intent_choose_dialog(intents, uri: model)
+ intent_open(intents, uri: model)
when String
- intent_choose_dialog(intents, model: URI.parse(model))
+ intent_open(intents, uri: URI.parse(model))
+ end
+ end
+
+ settings(_('関連付け')) do
+ listview = Plugin::IntentSelector::IntentSelectorListView.new
+ pack_start(Gtk::VBox.new(false, 4).
+ closeup(listview.filter_entry).
+ add(Gtk::HBox.new(false, 4).
+ add(listview).
+ closeup(listview.buttons(Gtk::VBox))))
+ end
+
+ # _model:_ または _uri:_ を開くintentを _intents_ の中から選び出し、その方法で開く。
+ # このメソッドは、まず設定されたルールでintentを選出し、一つにintentが定まれば直ちにそれで開く。
+ # 候補が一つに絞れなかった場合は、intent選択ダイアログを表示して、ユーザに決定を仰ぐ。
+ # ==== Args
+ # [intents] Intent modelの配列
+ # [model:] 開くModel。 _uri:_ しかわからない場合は、省略してもよい
+ # [uri:] 開くURI。 _model:_ を渡している場合は、省略してもよい
+ def intent_open(intents, model: nil, uri: model.uri)
+ recommended, suggested = divide_intents(intents, uri, specified_model_slug(model))
+ if recommended.size == 1
+ Plugin::Intent::IntentToken.open(
+ uri: uri,
+ model: model,
+ intent: recommended.first,
+ parent: nil)
+ else
+ intent_choose_dialog(recommended + suggested, model: model, uri: uri)
end
end
@@ -17,36 +49,93 @@ Plugin.create(:intent_selector) do
dialog.window_position = Gtk::Window::POS_CENTER
dialog.add_button(Gtk::Stock::OK, Gtk::Dialog::RESPONSE_OK)
dialog.add_button(Gtk::Stock::CANCEL, Gtk::Dialog::RESPONSE_CANCEL)
- dialog.vbox.closeup(Gtk::Label.new("%{uri} \nを開こうとしています。どの方法で開きますか?" % {uri: uri}, false))
- selected_intent = nil
+ dialog.vbox.closeup(Gtk::Label.new("%{uri}\nを開こうとしています。どの方法で開きますか?" % {uri: uri}, false))
+ intent_token_builder = {
+ uri: uri,
+ model: model,
+ intent: nil,
+ parent: nil }
intents.inject(nil) do |group, intent|
if group
radio = Gtk::RadioButton.new(group, intent.label)
else
- selected_intent = intent
+ intent_token_builder[:intent] = intent
radio = Gtk::RadioButton.new(intent.label) end
radio.ssc(:toggled) do |w|
- selected_intent = intent
+ intent_token_builder[:intent] = intent
false
end
radio.ssc(:activate) do |w|
- selected_intent = intent
+ intent_token_builder[:intent] = intent
dialog.signal_emit(:response, Gtk::Dialog::RESPONSE_OK)
false
end
dialog.vbox.closeup(radio)
group || radio
end
+ saving_rule_checkbox(dialog, intent_token_builder, specified_model_slug(model))
dialog.ssc(:response) do |w, response_id|
- if response_id == Gtk::Dialog::RESPONSE_OK and selected_intent
- Plugin::Intent::IntentToken.open(
- uri: uri,
- model: model,
- intent: selected_intent,
- parent: nil)
+ if response_id == Gtk::Dialog::RESPONSE_OK and intent_token_builder[:intent]
+ Plugin::Intent::IntentToken.open(**intent_token_builder)
end
w.destroy
+ false
end
dialog.show_all
end
+
+ def saving_rule_checkbox(dialog, intent_token_builder, model_slug)
+ save_check = Gtk::CheckButton.new(_('次回から、次の内容から始まるURLはこの方法で開く'))
+ rule = Gtk::Entry.new.set_text(intent_token_builder[:uri].to_s)
+ rule.sensitive = false
+ save_check.ssc(:toggled) do |widget|
+ rule.sensitive = widget.active?
+ false
+ end
+ dialog.ssc(:response) do |w, response_id|
+ if response_id == Gtk::Dialog::RESPONSE_OK and intent_token_builder[:intent] and save_check.active?
+ add_intent_rule(intent: intent_token_builder[:intent],
+ str: rule.text,
+ rule: 'start',
+ model_slug: model_slug)
+ end
+ false
+ end
+ dialog.vbox.
+ closeup(save_check).
+ closeup(rule)
+ end
+
+ def add_intent_rule(intent:, str:, rule:, model_slug:)
+ unless UserConfig[:intent_selector_rules].any?{|r| r[:intent].to_sym == intent.slug && r[:str] == str && r[:rule] == rule }
+ UserConfig[:intent_selector_rules] += [{uuid: SecureRandom.uuid, intent: intent.slug, model: model_slug, str: str, rule: rule}]
+ end
+ end
+
+ # intent の配列を受け取り、ユーザが過去に入力したルールに基づき、
+ # recommendedとsuggestedに分ける
+ # ==== Args
+ # [intents] スキャン対象のintent
+ # [uri] リソースのURI
+ # [model_slug] 絞り込みに使うModelのslug。
+ # ==== Return
+ # 条件に対して推奨されるintentの配列と、intentsに指定されたそれ以外の値の配列
+ def divide_intents(intents, uri, model_slug)
+ intent_slugs = UserConfig[:intent_selector_rules].select{|record|
+ model_slug == record[:model].to_s && uri.to_s.start_with?(record[:str])
+ }.map{|record|
+ record[:intent].to_sym
+ }
+ intents.partition{|intent| intent_slugs.include?(intent.slug) }
+ end
+
+ # _model_ のmodel slugを文字列で得る。
+ # ==== Args
+ # [model] Retriever::Modelのインスタンス又はnil
+ # ==== Return
+ # [String] Modelのslug。 _model_ がnilだった場合は空文字列
+ def specified_model_slug(model)
+ model ? model.class.slug.to_s : ''
+ end
+
end
diff --git a/core/plugin/intent_selector/listview.rb b/core/plugin/intent_selector/listview.rb
new file mode 100644
index 00000000..ae37ba52
--- /dev/null
+++ b/core/plugin/intent_selector/listview.rb
@@ -0,0 +1,108 @@
+# -*- coding: utf-8 -*-
+
+module Plugin::IntentSelector
+ class IntentSelectorListView < ::Gtk::CRUD
+ COLUMN_INTENT_LABEL = 0
+ COLUMN_MODEL_LABEL = 1
+ COLUMN_STRING = 2
+ COLUMN_INTENT = 3
+ COLUMN_MODEL = 4
+ COLUMN_UUID = 5
+
+ def initialize
+ super
+ intents = intent_catalog
+ models = model_catalog
+ UserConfig[:intent_selector_rules].each do |record|
+ iter = model.model.append
+ iter[COLUMN_INTENT] = record[:intent]
+ iter[COLUMN_INTENT_LABEL] = intents[record[:intent].to_sym]
+ iter[COLUMN_MODEL] = record[:model]
+ iter[COLUMN_MODEL_LABEL] = models[record[:model].to_s]
+ iter[COLUMN_STRING] = record[:str]
+ iter[COLUMN_UUID] = record[:uuid]
+ end
+ end
+
+ def initialize_model
+ set_model(Gtk::TreeModelFilter.new(Gtk::ListStore.new(*column_schemer.flatten.map{|x| x[:type]})))
+ model.set_visible_func{ |model, iter|
+ if defined?(@filter_entry) and @filter_entry
+ [COLUMN_INTENT, COLUMN_INTENT_LABEL, COLUMN_MODEL, COLUMN_MODEL_LABEL, COLUMN_STRING].any?{ |column| iter[column].to_s.include?(@filter_entry.text) }
+ else
+ true end }
+ end
+
+ def column_schemer
+ [{kind: :text, type: Symbol, expand: true, label: _('開く方法')},
+ {kind: :text, type: String, expand: true, label: _('対象')},
+ {kind: :text, type: String, widget: :input, expand: true, label: _('条件')},
+ {type: Symbol, widget: :chooseone, args: [intent_catalog], label: _('開く方法')},
+ {type: String, widget: :chooseone, args: [model_catalog], label: _('対象')},
+ {type: String},
+ ].freeze
+ end
+
+ def on_created(iter)
+ iter[COLUMN_UUID] = SecureRandom.uuid
+ iter[COLUMN_INTENT_LABEL] = intent_catalog[iter[COLUMN_INTENT].to_sym]
+ iter[COLUMN_MODEL_LABEL] = model_catalog[iter[COLUMN_MODEL].to_s]
+ UserConfig[:intent_selector_rules] += [{
+ intent: iter[COLUMN_INTENT].to_sym,
+ model: iter[COLUMN_MODEL],
+ str: iter[COLUMN_STRING],
+ rule: 'start',
+ uuid: iter[COLUMN_UUID]
+ }]
+ end
+
+ def on_updated(iter)
+ iter[COLUMN_INTENT_LABEL] = intent_catalog[iter[COLUMN_INTENT].to_sym]
+ iter[COLUMN_MODEL_LABEL] = model_catalog[iter[COLUMN_MODEL].to_s]
+ UserConfig[:intent_selector_rules] = UserConfig[:intent_selector_rules].map do |record|
+ if record[:uuid] == iter[COLUMN_UUID]
+ record.merge(
+ intent: iter[COLUMN_INTENT].to_sym,
+ model: iter[COLUMN_MODEL],
+ str: iter[COLUMN_STRING])
+ else
+ record
+ end
+ end
+ end
+
+ def on_deleted(iter)
+ UserConfig[:intent_selector_rules] = UserConfig[:intent_selector_rules].reject do |record|
+ record[:uuid] == iter[COLUMN_UUID]
+ end
+ end
+
+ private
+
+ def filter_entry
+ @filter_entry ||= Gtk::Entry.new.tap do |entry|
+ entry.primary_icon_pixbuf = Gdk::WebImageLoader.pixbuf(MUI::Skin.get("search.png"), 24, 24)
+ entry.ssc(:changed, self, &gen_refilter)
+ end
+ end
+
+ def gen_refilter
+ proc do
+ model.refilter
+ end
+ end
+
+ def _(str)
+ Plugin[:intent_selector]._(str)
+ end
+
+ def intent_catalog
+ Hash[Plugin.filtering(:intent_all, []).first.map{|i|[i.slug, i.label]}]
+ end
+
+ def model_catalog
+ Hash[Plugin.filtering(:retrievers, []).first.map{|s|[s[:slug].to_s,s[:name]]}].merge('': _('(未定義)'))
+ end
+
+ end
+end
diff --git a/core/userconfig.rb b/core/userconfig.rb
index bbb82ca9..051dbf3c 100644
--- a/core/userconfig.rb
+++ b/core/userconfig.rb
@@ -139,6 +139,17 @@ class UserConfig
:quote_text_max_line_count => 10,
:reply_clicked_action => :open,
:quote_clicked_action => :open,
+
+ :intent_selector_rules => [{:uuid=>"8ab31d89-6d5f-4765-bb99-7a93ce5b1139",
+ :intent=>:user_detail_view_twitter_user,
+ :model=>"",
+ :str=>"https://twitter.com/",
+ :rule=>"start"},
+ {:uuid=>"c0095e59-75da-4177-98e0-d4955ece1d20",
+ :intent=>:message_detail_view_twitter_tweet,
+ :model=>"",
+ :str=>"https://twitter.com/",
+ :rule=>"start"}]
}
@@watcher = Hash.new{ [] }