tk_component 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: '0588f1482fd807ba8c250d1e3098f2a0271bda795a0264fba9ccdced638cbdb5'
4
+ data.tar.gz: 2360a3b2cb6bb52aa9ae17815d3f345782da42f1f563c357d35685f13153d468
5
+ SHA512:
6
+ metadata.gz: 89b625e7097f5109bc4fbf903c098284c857ec6aece46fadb9f2d598004191ee9a5825010e05bd86342a515cea97f6b342d9d6f619f61f33d24ff93b192b5620
7
+ data.tar.gz: a647f65cc892c511cc5d6bd693b4d6865a3355b5839add9cd7c8fd1acde6e7a30ba0a5e73f2b456f32e3005b45ca025cf589a568a401d3607db94eaa3437b1bf
@@ -0,0 +1,11 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+
10
+ # rspec failure tracking
11
+ .rspec_status
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
@@ -0,0 +1,7 @@
1
+ ---
2
+ sudo: false
3
+ language: ruby
4
+ cache: bundler
5
+ rvm:
6
+ - 2.5.3
7
+ before_install: gem install bundler -v 1.16.6
@@ -0,0 +1,74 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ In the interest of fostering an open and welcoming environment, we as
6
+ contributors and maintainers pledge to making participation in our project and
7
+ our community a harassment-free experience for everyone, regardless of age, body
8
+ size, disability, ethnicity, gender identity and expression, level of experience,
9
+ nationality, personal appearance, race, religion, or sexual identity and
10
+ orientation.
11
+
12
+ ## Our Standards
13
+
14
+ Examples of behavior that contributes to creating a positive environment
15
+ include:
16
+
17
+ * Using welcoming and inclusive language
18
+ * Being respectful of differing viewpoints and experiences
19
+ * Gracefully accepting constructive criticism
20
+ * Focusing on what is best for the community
21
+ * Showing empathy towards other community members
22
+
23
+ Examples of unacceptable behavior by participants include:
24
+
25
+ * The use of sexualized language or imagery and unwelcome sexual attention or
26
+ advances
27
+ * Trolling, insulting/derogatory comments, and personal or political attacks
28
+ * Public or private harassment
29
+ * Publishing others' private information, such as a physical or electronic
30
+ address, without explicit permission
31
+ * Other conduct which could reasonably be considered inappropriate in a
32
+ professional setting
33
+
34
+ ## Our Responsibilities
35
+
36
+ Project maintainers are responsible for clarifying the standards of acceptable
37
+ behavior and are expected to take appropriate and fair corrective action in
38
+ response to any instances of unacceptable behavior.
39
+
40
+ Project maintainers have the right and responsibility to remove, edit, or
41
+ reject comments, commits, code, wiki edits, issues, and other contributions
42
+ that are not aligned to this Code of Conduct, or to ban temporarily or
43
+ permanently any contributor for other behaviors that they deem inappropriate,
44
+ threatening, offensive, or harmful.
45
+
46
+ ## Scope
47
+
48
+ This Code of Conduct applies both within project spaces and in public spaces
49
+ when an individual is representing the project or its community. Examples of
50
+ representing a project or community include using an official project e-mail
51
+ address, posting via an official social media account, or acting as an appointed
52
+ representative at an online or offline event. Representation of a project may be
53
+ further defined and clarified by project maintainers.
54
+
55
+ ## Enforcement
56
+
57
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
58
+ reported by contacting the project team at jes@josepegea.com. All
59
+ complaints will be reviewed and investigated and will result in a response that
60
+ is deemed necessary and appropriate to the circumstances. The project team is
61
+ obligated to maintain confidentiality with regard to the reporter of an incident.
62
+ Further details of specific enforcement policies may be posted separately.
63
+
64
+ Project maintainers who do not follow or enforce the Code of Conduct in good
65
+ faith may face temporary or permanent repercussions as determined by other
66
+ members of the project's leadership.
67
+
68
+ ## Attribution
69
+
70
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71
+ available at [http://contributor-covenant.org/version/1/4][version]
72
+
73
+ [homepage]: http://contributor-covenant.org
74
+ [version]: http://contributor-covenant.org/version/1/4/
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source "https://rubygems.org"
2
+
3
+ git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
4
+
5
+ # Specify your gem's dependencies in tk_component.gemspec
6
+ gemspec
@@ -0,0 +1,52 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ tk_component (0.1.0)
5
+ activesupport (~> 6.0.3)
6
+ tk (~> 0.3.0)
7
+
8
+ GEM
9
+ remote: https://rubygems.org/
10
+ specs:
11
+ activesupport (6.0.3.4)
12
+ concurrent-ruby (~> 1.0, >= 1.0.2)
13
+ i18n (>= 0.7, < 2)
14
+ minitest (~> 5.1)
15
+ tzinfo (~> 1.1)
16
+ zeitwerk (~> 2.2, >= 2.2.2)
17
+ concurrent-ruby (1.1.7)
18
+ diff-lcs (1.4.4)
19
+ i18n (1.8.5)
20
+ concurrent-ruby (~> 1.0)
21
+ minitest (5.14.2)
22
+ rake (10.5.0)
23
+ rspec (3.10.0)
24
+ rspec-core (~> 3.10.0)
25
+ rspec-expectations (~> 3.10.0)
26
+ rspec-mocks (~> 3.10.0)
27
+ rspec-core (3.10.0)
28
+ rspec-support (~> 3.10.0)
29
+ rspec-expectations (3.10.0)
30
+ diff-lcs (>= 1.2.0, < 2.0)
31
+ rspec-support (~> 3.10.0)
32
+ rspec-mocks (3.10.0)
33
+ diff-lcs (>= 1.2.0, < 2.0)
34
+ rspec-support (~> 3.10.0)
35
+ rspec-support (3.10.0)
36
+ thread_safe (0.3.6)
37
+ tk (0.3.0)
38
+ tzinfo (1.2.8)
39
+ thread_safe (~> 0.1)
40
+ zeitwerk (2.4.2)
41
+
42
+ PLATFORMS
43
+ ruby
44
+
45
+ DEPENDENCIES
46
+ bundler (~> 1.16)
47
+ rake (~> 10.0)
48
+ rspec (~> 3.0)
49
+ tk_component!
50
+
51
+ BUNDLED WITH
52
+ 1.16.6
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2020 TODO: Write your name
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,43 @@
1
+ # TkComponent
2
+
3
+ TkComponent allows you to create desktop UIs using a component structure taking advantadge of TK, the UI toolkit created for TCL and used by Python in TkInter
4
+
5
+ Still very much a work in progress.
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ ```ruby
12
+ gem 'tk_component'
13
+ ```
14
+
15
+ And then execute:
16
+
17
+ $ bundle
18
+
19
+ Or install it yourself as:
20
+
21
+ $ gem install tk_component
22
+
23
+ ## Usage
24
+
25
+ TODO: Nothing written yet.
26
+
27
+ ## Development
28
+
29
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
30
+
31
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
32
+
33
+ ## Contributing
34
+
35
+ Bug reports and pull requests are welcome on GitHub at https://github.com/josepegea/tk_component. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
36
+
37
+ ## License
38
+
39
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
40
+
41
+ ## Code of Conduct
42
+
43
+ Everyone interacting in the TkComponent project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/josepegea/tk_component/blob/master/CODE_OF_CONDUCT.md).
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "tk_component"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,6 @@
1
+ require "tk_component/version"
2
+ require "active_support/all"
3
+
4
+ require_relative 'tk_component/base'
5
+ require_relative 'tk_component/window'
6
+ require_relative 'tk_component/builder'
@@ -0,0 +1,67 @@
1
+ require 'tk'
2
+ require 'tkextlib/tile'
3
+
4
+ module TkComponent
5
+ class Base
6
+
7
+ attr_accessor :tk_item
8
+ attr_accessor :parent
9
+ attr_accessor :parent_node
10
+ attr_accessor :children
11
+ attr_accessor :node
12
+
13
+ def initialize(options = {})
14
+ @parent = options[:parent]
15
+ @parent_node = options[:parent_node]
16
+ @children = []
17
+ end
18
+
19
+ def parse_component(parent_component, options = {})
20
+ raise "You need to provide a block" unless block_given?
21
+ @node = Builder::Node.new(:top, options)
22
+ yield(@node)
23
+ binding.pry if @node.sub_nodes.size != 1
24
+ raise "Components need to have a single root node" unless @node.sub_nodes.size == 1
25
+ @node.prepare_option_events(self)
26
+ @node.prepare_grid
27
+ @node = @node.sub_nodes.first # Get rid of the dummy top node
28
+ end
29
+
30
+ def build(parent_component)
31
+ @node.build(@parent_node, parent_component)
32
+ component_did_build
33
+ children.each do |c|
34
+ c.build(self)
35
+ end
36
+ end
37
+
38
+ def regenerate
39
+ old_node = @node
40
+ generate(parent)
41
+ rebuild(old_node)
42
+ children.each do |c|
43
+ c.regenerate
44
+ end
45
+ end
46
+
47
+ def rebuild(old_node)
48
+ build(parent)
49
+ end
50
+
51
+ def name
52
+ self.class.name
53
+ end
54
+
55
+ def emit(event_name)
56
+ TkComponent::Builder::Event.emit('ParamChanged', parent_node.native_item, self.object_id)
57
+ end
58
+
59
+ def component_did_build
60
+ end
61
+
62
+ def add_child(child)
63
+ binding.pry if children.nil?
64
+ children << child
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,5 @@
1
+ require_relative 'builder/node'
2
+ require_relative 'builder/grid_map'
3
+ require_relative 'builder/event'
4
+ require_relative 'builder/event_handler'
5
+ require_relative 'builder/tk_item'
@@ -0,0 +1,97 @@
1
+ module TkComponent
2
+ module Builder
3
+ class Event
4
+ attr_accessor :name
5
+ attr_accessor :sender
6
+ attr_accessor :button_index
7
+ attr_accessor :key_code
8
+ attr_accessor :key_string
9
+ attr_accessor :mouse_x
10
+ attr_accessor :mouse_y
11
+ attr_accessor :root_mouse_x
12
+ attr_accessor :root_mouse_y
13
+ attr_accessor :mouse_wheel_delta
14
+ attr_accessor :data
15
+
16
+ EVENT_ATTRS = "%x %y %X %Y %b %D %A %k %d"
17
+
18
+ def initialize(name, sender)
19
+ @name = name.to_sym
20
+ @sender = sender
21
+ end
22
+
23
+ def self.emit(name, source, data)
24
+ Tk.event_generate(source, "<#{name}>", data: data)
25
+ end
26
+
27
+ def self.bind_command(name, sender, options, lambda)
28
+ sender.native_item.command do
29
+ event = self.new(name, sender)
30
+ lambda.call(event)
31
+ end
32
+ end
33
+
34
+ def self.bind_variable(name, sender, options, lambda)
35
+ handler = proc do
36
+ event = self.new(name, sender)
37
+ lambda.call(event)
38
+ end
39
+ sender.tk_variable.trace('write', handler)
40
+ end
41
+
42
+ def self.bind_event(name, sender, options, lambda, pre_lambda = nil, post_lambda = nil)
43
+ event_string = self.event_string_for(name, options)
44
+ handler = proc do |x, y, rx, ry, bi, mw, ks, kc, data|
45
+ event = self.new(name, sender)
46
+ event.mouse_x = x
47
+ event.mouse_y = y
48
+ event.root_mouse_x = rx
49
+ event.root_mouse_y = ry
50
+ event.button_index = bi
51
+ event.mouse_wheel_delta = mw
52
+ event.key_string = ks
53
+ event.key_code = kc
54
+ event.data = data
55
+ # The pre_lambda returns true if it wants to prevent the event from firing
56
+ return if pre_lambda.present? && pre_lambda.call(event)
57
+ lambda.call(event)
58
+ post_lambda.call(event) if post_lambda.present?
59
+ end
60
+ sender.native_item.bind(event_string, handler, EVENT_ATTRS)
61
+ end
62
+
63
+ def data_object
64
+ @data_object ||= begin
65
+ ObjectSpace._id2ref(self.data.to_i)
66
+ rescue
67
+ nil
68
+ end
69
+ end
70
+
71
+ private
72
+
73
+ def self.event_string_for(name, options)
74
+ event_name = self.resolve_event_alias(name).to_s.camelize
75
+ event_prefix = ''
76
+ if button = options[:button]
77
+ event_prefix << "B#{button}"
78
+ end
79
+ event_name = event_prefix + '-' + event_name if event_prefix.present?
80
+ event_name
81
+ end
82
+
83
+ EVENT_ALIASES = {
84
+ mouse_drag: :motion,
85
+ mouse_down: :button_press,
86
+ mouse_up: :button_release
87
+ }
88
+
89
+ def self.resolve_event_alias(name)
90
+ if (found = EVENT_ALIASES[name])
91
+ name = found
92
+ end
93
+ name
94
+ end
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,15 @@
1
+ module TkComponent
2
+ module Builder
3
+ class EventHandler
4
+ attr_accessor :name
5
+ attr_accessor :lambda
6
+ attr_accessor :options
7
+
8
+ def initialize(name, lambda, options = {})
9
+ @name = name.to_sym
10
+ @lambda = lambda
11
+ @options = options
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,73 @@
1
+ module TkComponent
2
+ module Builder
3
+ class GridMap
4
+ def initialize
5
+ @rows = []
6
+ @row_weights = []
7
+ @column_weights = []
8
+ end
9
+
10
+ def get(row, col)
11
+ return nil if row >= @rows.size
12
+ return nil if (cols = @rows[row]).nil? || col >= cols.size
13
+ cols[col]
14
+ end
15
+
16
+ def set(row, col, val)
17
+ @rows[row] = [] if row > @rows.size || @rows[row].nil?
18
+ @rows[row][col] = val
19
+ end
20
+
21
+ def fill(row, col, rowspan, columnspan, val)
22
+ for r in (row .. row + rowspan - 1) do
23
+ for c in (col .. col + columnspan - 1) do
24
+ set(r, c, val)
25
+ end
26
+ end
27
+ end
28
+
29
+ def row_weight(row)
30
+ @row_weights[row] || 0
31
+ end
32
+
33
+ def column_weight(col)
34
+ @column_weights[col] || 0
35
+ end
36
+
37
+ def set_weights(row, col, weights = {})
38
+ hw = weights[:h_weight]
39
+ @row_weights[row] = ((rw = @row_weights[row]).present? ? [rw, hw].max : hw) if hw
40
+ vw = weights[:v_weight]
41
+ @column_weights[col] = ((cw = @column_weights[col]).present? ? [cw, vw].max : vw) if vw
42
+ end
43
+
44
+ def row_indexes
45
+ used_indexes(@rows)
46
+ end
47
+
48
+ def column_indexes
49
+ @rows.reduce([]) { |accum, r| accum += used_indexes(r) }.uniq
50
+ end
51
+
52
+ def get_next_cell(current_row, current_col, going_down)
53
+ if going_down
54
+ while get(current_row, current_col) do current_row += 1 end
55
+ else
56
+ while get(current_row, current_col) do current_col += 1 end
57
+ end
58
+ [current_row, current_col]
59
+ end
60
+
61
+ def to_s
62
+ @rows.to_s
63
+ end
64
+
65
+ private
66
+
67
+ def used_indexes(array)
68
+ return [] if array.nil?
69
+ array.map.with_index { |o, i| o.present? ? i : nil }.compact
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,140 @@
1
+ module TkComponent
2
+ module Builder
3
+
4
+ TK_CMDS = %w(label entry button canvas text scale group).to_set.freeze
5
+ LAYOUT_CMDS = %w(frame hframe vframe row cell).to_set.freeze
6
+ EVENT_CMDS = %w(on_change on_mouse_down on_mouse_up on_mouse_drag on_mouse_wheel on_click on_event).to_set.freeze
7
+ TOKENS = (TK_CMDS + LAYOUT_CMDS + EVENT_CMDS).freeze
8
+
9
+ class Node
10
+ attr_accessor :name
11
+ attr_accessor :options
12
+ attr_accessor :sub_nodes
13
+ attr_accessor :grid
14
+ attr_accessor :grid_map
15
+ attr_accessor :event_handlers
16
+ attr_accessor :tk_item
17
+
18
+ delegate :value, to: :tk_item
19
+ delegate :"value=", to: :tk_item
20
+ delegate :update_value, to: :tk_item
21
+ delegate :i_value, to: :tk_item
22
+ delegate :f_value, to: :tk_item
23
+ delegate :s_value, to: :tk_item
24
+ delegate :from, to: :tk_item
25
+ delegate :to, to: :tk_item
26
+ delegate :native_item, to: :tk_item
27
+
28
+ def initialize(name, options = {})
29
+ @name = name
30
+ @options = options.with_indifferent_access
31
+ @sub_nodes = []
32
+ @grid = {}
33
+ @grid_map = GridMap.new
34
+ @event_handlers = []
35
+ @tk_item = nil
36
+ end
37
+
38
+ def short(level = 0)
39
+ puts(" " * level + " #{@name}")
40
+ @sub_nodes.each do |n|
41
+ n.short(level + 4)
42
+ end
43
+ nil
44
+ end
45
+
46
+ def insert_component(component_class, parent_component, options = {}, &block)
47
+ c_node = node_from_command(:frame, &block)
48
+ comp = component_class.new(options.merge(parent: parent_component, parent_node: c_node))
49
+ comp.generate(parent_component, options)
50
+ parent_component.add_child(comp)
51
+ end
52
+
53
+ def add_event_handler(name, lambda, options = {})
54
+ event_handlers << EventHandler.new(name, lambda, options)
55
+ end
56
+
57
+ def build(parent_node, parent_component)
58
+ parent_item = parent_node.present? ? parent_node.tk_item : parent_component.tk_item
59
+ self.tk_item = TkItem.create(parent_item, name, options, grid, event_handlers)
60
+ parent_component.tk_item = self.tk_item if parent_component.tk_item.nil?
61
+ sub_nodes.each do |n|
62
+ n.build(self, parent_component)
63
+ end
64
+ self.tk_item.apply_internal_grid(grid_map)
65
+ end
66
+
67
+ def prepare_option_events(component)
68
+ option_events = options.extract!(*EVENT_CMDS)
69
+ option_events.each do |k, v|
70
+ event_proc = v.is_a?(Proc) ? v : proc { |e| component.public_send(v, e) }
71
+ node_from_command(k, event_proc)
72
+ end
73
+ sub_nodes.each { |n| n.prepare_option_events(component) }
74
+ end
75
+
76
+ def prepare_grid
77
+ return unless self.sub_nodes.any?
78
+ current_row = -1
79
+ current_col = -1
80
+ final_sub_nodes = []
81
+ going_down = going_down?
82
+ while (n = sub_nodes.shift) do
83
+ if n.row?
84
+ current_row += 1
85
+ current_col = 0
86
+ sub_nodes.unshift(*n.sub_nodes)
87
+ else
88
+ # Set the initial row and cols if no row was specified
89
+ current_row = 0 if current_row < 0
90
+ current_col = 0 if current_col < 0
91
+ current_row, current_col = grid_map.get_next_cell(current_row, current_col, going_down)
92
+ binding.pry if n.options.nil?
93
+ grid = n.options.extract!(:column, :row, :rowspan, :columnspan, :sticky)
94
+ n.grid = grid.merge(column: current_col, row: current_row)
95
+ rowspan = grid[:rowspan] || 1
96
+ columnspan = grid[:columnspan] || 1
97
+ grid_map.fill(current_row, current_col, rowspan, columnspan, true)
98
+ weights = n.options.extract!(:h_weight, :v_weight)
99
+ grid_map.set_weights(current_row, current_col, weights)
100
+ n.prepare_grid
101
+ final_sub_nodes << n
102
+ end
103
+ end
104
+ self.sub_nodes = final_sub_nodes
105
+ end
106
+
107
+ def method_missing(method_name, *args, &block)
108
+ if method_name.to_s.match(/(.*)\?/)
109
+ return name.to_s == $1
110
+ end
111
+ if TOKENS.include?(method_name.to_s)
112
+ return node_from_command(method_name, *args, &block)
113
+ end
114
+ super
115
+ end
116
+
117
+ def node_from_command(method_name, *args, &block)
118
+ if method_name.to_s == 'on_event'
119
+ args[0] = "<#{args[0]}>"
120
+ add_event_handler(*args)
121
+ elsif method_name.to_s.match(/^on_(.*)/)
122
+ add_event_handler($1, *args)
123
+ else
124
+ builder = self.class.new(method_name, *args)
125
+ yield(builder) if block_given?
126
+ add_node(builder)
127
+ return builder
128
+ end
129
+ end
130
+
131
+ def going_down?
132
+ vframe?
133
+ end
134
+
135
+ def add_node(node)
136
+ sub_nodes << node
137
+ end
138
+ end
139
+ end
140
+ end
@@ -0,0 +1,197 @@
1
+ module TkComponent
2
+ module Builder
3
+ class TkItem
4
+
5
+ attr_accessor :native_item
6
+
7
+ def self.create(parent_item, name, options = {}, grid = {}, event_handlers = [])
8
+ item_class = ITEM_CLASSES[name.to_sym]
9
+ raise "Don't know how to create #{name}" unless item_class
10
+ item_class.new(parent_item, name, options, grid, event_handlers)
11
+ end
12
+
13
+ def initialize(parent_item, name, options = {}, grid = {}, event_handlers = [])
14
+ tk_class = TK_CLASSES[name.to_sym]
15
+ raise "Don't know how to create #{name}" unless tk_class
16
+ @native_item = tk_class.new(parent_item.native_item)
17
+ apply_options(options)
18
+ set_grid(grid)
19
+ set_event_handlers(event_handlers)
20
+ end
21
+
22
+ def apply_options(options)
23
+ options.each do |k,v|
24
+ apply_option(k, v)
25
+ end
26
+ end
27
+
28
+ def apply_option(option, value)
29
+ self.native_item.public_send(option, value)
30
+ end
31
+
32
+ def set_grid(grid)
33
+ self.native_item.grid(grid)
34
+ end
35
+
36
+ def apply_internal_grid(grid_map)
37
+ puts(grid_map)
38
+ grid_map.column_indexes.each { |c| TkGrid.columnconfigure(self.native_item, c, weight: grid_map.column_weight(c)) }
39
+ grid_map.row_indexes.each { |r| TkGrid.rowconfigure(self.native_item, r, weight: grid_map.row_weight(r)) }
40
+ # grid_map.column_indexes.each { |c| TkGrid.columnconfigure(self.native_item, c, weight: 1) }
41
+ # grid_map.row_indexes.each { |r| TkGrid.rowconfigure(self.native_item, r, weight: 1) }
42
+ end
43
+
44
+ def set_event_handlers(event_handlers)
45
+ event_handlers.each { |eh| set_event_handler(eh) }
46
+ end
47
+
48
+ def set_event_handler(event_handler)
49
+ case event_handler.name
50
+ when :click
51
+ Event.bind_command(event_handler.name, self, event_handler.options, event_handler.lambda)
52
+ when :change
53
+ Event.bind_variable(event_handler.name, self, event_handler.options, event_handler.lambda)
54
+ else
55
+ Event.bind_event(event_handler.name, self, event_handler.options, event_handler.lambda)
56
+ end
57
+ end
58
+ end
59
+
60
+ module ValueTyping
61
+ def apply_option(option, v)
62
+ case option.to_sym
63
+ when :value
64
+ self.value = v
65
+ else
66
+ super
67
+ end
68
+ end
69
+
70
+ def update_value(v)
71
+ self.value = v if value.to_s != v.to_s
72
+ end
73
+
74
+ def i_value
75
+ value.to_i
76
+ end
77
+
78
+ def f_value
79
+ value.to_f
80
+ end
81
+
82
+ def s_value
83
+ value.to_s
84
+ end
85
+ end
86
+
87
+ class TkItemWithVariable < TkItem
88
+ include ValueTyping
89
+
90
+ attr_accessor :tk_variable
91
+
92
+ def initialize(parent_item, name, options = {}, grid = {}, event_handlers = [])
93
+ @tk_variable = TkVariable.new
94
+ super
95
+ self.native_item.public_send(variable_name, @tk_variable)
96
+ end
97
+
98
+ def variable_name
99
+ :variable
100
+ end
101
+
102
+ delegate :value, to: :tk_variable
103
+ delegate :"value=", to: :tk_variable
104
+ end
105
+
106
+ class TkEntry < TkItemWithVariable
107
+ def variable_name
108
+ :textvariable
109
+ end
110
+ end
111
+
112
+ class TkScale < TkItemWithVariable
113
+ def variable_name
114
+ :variable
115
+ end
116
+
117
+ delegate :from, to: :native_item
118
+ delegate :to, to: :native_item
119
+
120
+ def set_event_handler(event_handler)
121
+ case event_handler.name
122
+ when :change
123
+ Event.bind_command(event_handler.name, self, event_handler.options, event_handler.lambda)
124
+ else
125
+ super
126
+ end
127
+ end
128
+ end
129
+
130
+ class TkText < TkItem
131
+ include ValueTyping
132
+
133
+ def value
134
+ native_item.get('1.0', 'end')
135
+ end
136
+
137
+ def value=(text)
138
+ native_item.replace('1.0', 'end', text)
139
+ end
140
+
141
+ def set_event_handler(event_handler)
142
+ case event_handler.name
143
+ when :change
144
+ pre_lambda = ->(e) do
145
+ # Prevent the event if the text wasn't really modified
146
+ # This is because setting "modified = false" triggers
147
+ # the modification event itself, which makes not much sense.
148
+ e.sender.is_a?(self.class) && !e.sender.native_item.modified?
149
+ end
150
+ post_lambda = ->(e) do
151
+ if e.sender.is_a?(self.class)
152
+ e.sender.native_item.modified = false
153
+ end
154
+ end
155
+ Event.bind_event('<Modified>', self, event_handler.options, event_handler.lambda, pre_lambda, post_lambda)
156
+ else
157
+ super
158
+ end
159
+ end
160
+ end
161
+
162
+ class TkWindow < TkItem
163
+ def initialize(parent_item, name, options = {}, grid = {}, event_handlers = [])
164
+ @native_item = TkRoot.new { title options[:title] }
165
+ apply_options(options)
166
+ end
167
+ end
168
+
169
+ TK_CLASSES = {
170
+ root: TkRoot,
171
+ frame: Tk::Tile::Frame,
172
+ hframe: Tk::Tile::Frame,
173
+ vframe: Tk::Tile::Frame,
174
+ label: Tk::Tile::Label,
175
+ entry: Tk::Tile::Entry,
176
+ button: Tk::Tile::Button,
177
+ canvas: Tk::Canvas,
178
+ text: ::TkText,
179
+ scale: Tk::Tile::Scale,
180
+ group: Tk::Tile::LabelFrame
181
+ }
182
+
183
+ ITEM_CLASSES = {
184
+ root: TkComponent::Builder::TkWindow,
185
+ frame: TkComponent::Builder::TkItem,
186
+ hframe: TkComponent::Builder::TkItem,
187
+ vframe: TkComponent::Builder::TkItem,
188
+ label: TkComponent::Builder::TkItem,
189
+ entry: TkComponent::Builder::TkEntry,
190
+ button: TkComponent::Builder::TkItem,
191
+ canvas: TkComponent::Builder::TkItem,
192
+ text: TkComponent::Builder::TkText,
193
+ scale: TkComponent::Builder::TkScale,
194
+ group: TkComponent::Builder::TkItem
195
+ }
196
+ end
197
+ end
@@ -0,0 +1,3 @@
1
+ module TkComponent
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,23 @@
1
+ module TkComponent
2
+ class Window < Base
3
+ def initialize(options = {})
4
+ super
5
+ @tk_item = Builder::TkItem.create(nil, :root, options)
6
+ end
7
+
8
+ def name
9
+ "Window"
10
+ end
11
+
12
+ def place_root_component(component, options = {})
13
+ component.parent = self
14
+ component.generate(self)
15
+ component.build(self)
16
+ h_weight = options[:h_weight] || 1
17
+ v_weight = options[:v_weight] || 1
18
+ TkGrid.columnconfigure tk_item.native_item, 0, weight: h_weight
19
+ TkGrid.rowconfigure tk_item.native_item, 0, weight: v_weight
20
+ add_child(component)
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,45 @@
1
+
2
+ lib = File.expand_path("../lib", __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require "tk_component/version"
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "tk_component"
8
+ spec.version = TkComponent::VERSION
9
+ spec.authors = ["Josep Egea"]
10
+ spec.email = ["jes@josepegea.com"]
11
+
12
+ spec.summary = %q{Use TK from Ruby in a component oriented fashion }
13
+ spec.description = %q{TkComponent allows you to create desktop UIs using a component structure taking advantadge of TK, the UI toolkit created for TCL and used by Python in TkInter }
14
+ spec.homepage = "https://github.com/josepegea/tk_component"
15
+ spec.license = "MIT"
16
+
17
+ # Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
18
+ # to allow pushing to a single host or delete this section to allow pushing to any host.
19
+ if spec.respond_to?(:metadata)
20
+ spec.metadata["allowed_push_host"] = "https://rubygems.org"
21
+
22
+ spec.metadata["homepage_uri"] = spec.homepage
23
+ spec.metadata["source_code_uri"] = "https://github.com/josepegea/tk_component"
24
+ spec.metadata["changelog_uri"] = "https://github.com/josepegea/tk_component/releases"
25
+ else
26
+ raise "RubyGems 2.0 or newer is required to protect against " \
27
+ "public gem pushes."
28
+ end
29
+
30
+ # Specify which files should be added to the gem when it is released.
31
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
32
+ spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
33
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
34
+ end
35
+ spec.bindir = "exe"
36
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
37
+ spec.require_paths = ["lib"]
38
+
39
+ spec.add_dependency "tk", "~> 0.3.0"
40
+ spec.add_dependency "activesupport", "~> 6.0.3"
41
+
42
+ spec.add_development_dependency "bundler", "~> 1.16"
43
+ spec.add_development_dependency "rake", "~> 10.0"
44
+ spec.add_development_dependency "rspec", "~> 3.0"
45
+ end
metadata ADDED
@@ -0,0 +1,141 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: tk_component
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Josep Egea
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2020-11-29 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: tk
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 0.3.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 0.3.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: activesupport
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 6.0.3
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 6.0.3
41
+ - !ruby/object:Gem::Dependency
42
+ name: bundler
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.16'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.16'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '10.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '10.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rspec
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '3.0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '3.0'
83
+ description: 'TkComponent allows you to create desktop UIs using a component structure
84
+ taking advantadge of TK, the UI toolkit created for TCL and used by Python in TkInter '
85
+ email:
86
+ - jes@josepegea.com
87
+ executables: []
88
+ extensions: []
89
+ extra_rdoc_files: []
90
+ files:
91
+ - ".gitignore"
92
+ - ".rspec"
93
+ - ".travis.yml"
94
+ - CODE_OF_CONDUCT.md
95
+ - Gemfile
96
+ - Gemfile.lock
97
+ - LICENSE.txt
98
+ - README.md
99
+ - Rakefile
100
+ - bin/console
101
+ - bin/setup
102
+ - lib/tk_component.rb
103
+ - lib/tk_component/base.rb
104
+ - lib/tk_component/builder.rb
105
+ - lib/tk_component/builder/event.rb
106
+ - lib/tk_component/builder/event_handler.rb
107
+ - lib/tk_component/builder/grid_map.rb
108
+ - lib/tk_component/builder/node.rb
109
+ - lib/tk_component/builder/tk_item.rb
110
+ - lib/tk_component/version.rb
111
+ - lib/tk_component/window.rb
112
+ - tk_component.gemspec
113
+ homepage: https://github.com/josepegea/tk_component
114
+ licenses:
115
+ - MIT
116
+ metadata:
117
+ allowed_push_host: https://rubygems.org
118
+ homepage_uri: https://github.com/josepegea/tk_component
119
+ source_code_uri: https://github.com/josepegea/tk_component
120
+ changelog_uri: https://github.com/josepegea/tk_component/releases
121
+ post_install_message:
122
+ rdoc_options: []
123
+ require_paths:
124
+ - lib
125
+ required_ruby_version: !ruby/object:Gem::Requirement
126
+ requirements:
127
+ - - ">="
128
+ - !ruby/object:Gem::Version
129
+ version: '0'
130
+ required_rubygems_version: !ruby/object:Gem::Requirement
131
+ requirements:
132
+ - - ">="
133
+ - !ruby/object:Gem::Version
134
+ version: '0'
135
+ requirements: []
136
+ rubyforge_project:
137
+ rubygems_version: 2.7.8
138
+ signing_key:
139
+ specification_version: 4
140
+ summary: Use TK from Ruby in a component oriented fashion
141
+ test_files: []