├── Rakefile ├── lib ├── canis │ ├── version.rb │ └── core │ │ ├── util │ │ ├── extras │ │ │ └── README │ │ ├── focusmanager.rb │ │ ├── textutils.rb │ │ ├── defaultcolorparser.rb │ │ └── ansiparser.rb │ │ ├── widgets │ │ ├── extras │ │ │ └── README.md │ │ ├── rlink.rb │ │ ├── rmenulink.rb │ │ ├── box.rb │ │ ├── rtabbedwindow.rb │ │ ├── listfooter.rb │ │ ├── rprogress.rb │ │ ├── statusline.rb │ │ ├── applicationheader.rb │ │ └── tree │ │ │ └── treecellrenderer.rb │ │ ├── docs │ │ ├── tabbedpane.txt │ │ ├── style_help.yml │ │ ├── list.txt │ │ ├── tree.txt │ │ ├── table.txt │ │ ├── textpad.txt │ │ └── index.txt │ │ ├── include │ │ ├── rchangeevent.rb │ │ ├── actionmanager.rb │ │ ├── rinputdataevent.rb │ │ ├── listoperations.rb │ │ ├── bordertitle.rb │ │ ├── defaultfilerenderer.rb │ │ ├── widgetmenu.rb │ │ ├── ractionevent.rb │ │ ├── action.rb │ │ ├── rhistory.rb │ │ ├── canisparser.rb │ │ ├── layouts │ │ │ ├── flowlayout.rb │ │ │ ├── stacklayout.rb │ │ │ └── SplitLayout.rb │ │ ├── textdocument.rb │ │ ├── deprecated │ │ │ └── listcellrenderer.rb │ │ └── multibuffer.rb │ │ └── system │ │ ├── keydefs.rb │ │ ├── panel.rb │ │ └── deprecated │ │ └── keyboard.rb └── canis.rb ├── Gemfile ├── examples ├── data │ ├── unix2.txt │ ├── README.markdown │ ├── brew.txt │ ├── todo.txt │ ├── tasks.txt │ ├── gemlist.txt │ ├── unix1.txt │ ├── table.txt │ ├── todocsv.csv │ ├── lotr.txt │ ├── color.2 │ └── tasks.csv ├── app.sample ├── alpmenu.rb ├── testflowlayout.rb ├── testsplitlayout1.rb ├── teststacklayout.rb ├── testsplitlayout.rb ├── testwsshortcuts.rb ├── newtesttabp.rb ├── common │ └── file.rb ├── term2.rb ├── testree.rb ├── testkeypress.rb ├── testcombo.rb ├── testprogress.rb ├── atree.rb ├── newtabbedwindow.rb ├── testwsshortcuts2.rb ├── dirtree.rb ├── testdb.rb └── testmessagebox.rb ├── .gitignore ├── LICENSE.txt ├── canis.gemspec └── CHANGES /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/gem_tasks" 2 | 3 | -------------------------------------------------------------------------------- /lib/canis/version.rb: -------------------------------------------------------------------------------- 1 | module Canis 2 | VERSION = "0.0.13" 3 | end 4 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Specify your gem's dependencies in canis.gemspec 4 | gemspec 5 | -------------------------------------------------------------------------------- /lib/canis/core/util/extras/README: -------------------------------------------------------------------------------- 1 | I have discontinued using bottomline. Use at your own risk. Copy into your project 2 | if you wish to use it. 3 | 4 | Padreader is not to be used, use viewer.rb instead. Padreader does not display unless 5 | you press a key first. 6 | -------------------------------------------------------------------------------- /examples/data/unix2.txt: -------------------------------------------------------------------------------- 1 | 1. Small is beautiful. 2 | 2. Make each program do one thing well. 3 | 3. Build a prototype as soon as possible. 4 | 4. Choose portability over efficiency. 5 | 5. Store data in flat text files. 6 | 6. Use software leverage to your advantage. 7 | 7. Use shell scripts to increase leverage and portability. 8 | 8. Avoid captive user interfaces. 9 | 9. Make every program a filter. 10 | 11 | 12 | -------------------------------------------------------------------------------- /examples/data/README.markdown: -------------------------------------------------------------------------------- 1 | I've put some data here to use in programs as text for textareas, textviews or lists. 2 | 3 | brew.txt - single list, use in listboxes 4 | color.2 - ANSI formatted text, use in formatted textview 5 | gemlist.txt - single column list 6 | lotr.txt - running text for text views 7 | ports.txt - 3 column list, use in tables, tabular 8 | unix1.txt - running text 9 | unix2.txt - running text 10 | -------------------------------------------------------------------------------- /examples/data/brew.txt: -------------------------------------------------------------------------------- 1 | ack 2 | aqua-less 3 | atool 4 | bash-completion 5 | calcurse 6 | cmake 7 | coreutils 8 | curl 9 | elinks 10 | findutils 11 | gawk 12 | gettext 13 | git 14 | gnu-sed 15 | grep 16 | htop 17 | jed 18 | libevent 19 | moreutils 20 | most 21 | msmtp 22 | multitail 23 | mysql 24 | oniguruma 25 | pcre 26 | pidof 27 | pigz 28 | pkg-config 29 | pv 30 | readline 31 | rlwrap 32 | s-lang 33 | sqlite 34 | tal 35 | task 36 | tig 37 | tmux 38 | wget 39 | -------------------------------------------------------------------------------- /lib/canis/core/widgets/extras/README.md: -------------------------------------------------------------------------------- 1 | README 2 | ====== 3 | 4 | Pls use the objects in this dir with care. 5 | Pls test them thoroughly, these are largely minimally tested 6 | items or utilities that may be of help but are not critical to 7 | a minimal toolkit. 8 | 9 | These will only be updated if a bug is pointed out, or a pull request / patch is sent. 10 | 11 | 12 | It may be preferable to copy these into your application and make whatever changes you want. 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | *.rbc 3 | .bundle 4 | .config 5 | .yardoc 6 | Gemfile.lock 7 | InstalledFiles 8 | _yardoc 9 | coverage 10 | doc/ 11 | lib/bundler/man 12 | pkg 13 | rdoc 14 | spec/reports 15 | test/tmp 16 | test/version_tmp 17 | tmp 18 | *.bundle 19 | *.so 20 | *.o 21 | *.a 22 | mkmf.log 23 | *.log 24 | *.sqlite 25 | *.bak 26 | todo 27 | issues 28 | deprecated 29 | .srclist 30 | NOTES 31 | bug 32 | examples/dump.yml 33 | issues.txt 34 | tags 35 | test.yml 36 | todo.0.0.1 37 | -------------------------------------------------------------------------------- /examples/app.sample: -------------------------------------------------------------------------------- 1 | require 'canis/core/util/app' 2 | def help_text 3 | <<-eos 4 | Enter as much help text 5 | here as you want 6 | eos 7 | end 8 | 9 | App.new do 10 | ## application code comes here 11 | @form.help_manager.help_text = help_text() 12 | 13 | @header = app_header "My App #{App::VERSION}", :text_center => "Yet Another Email Client that sucks", :text_right =>"Some text", :color => :black, :bgcolor => :white 14 | 15 | @status_line = status_line 16 | @status_line.command { 17 | 18 | } 19 | end # app 20 | -------------------------------------------------------------------------------- /lib/canis/core/docs/tabbedpane.txt: -------------------------------------------------------------------------------- 1 | # Help for TabbedPanes 2 | 3 | TabbedPanes allow multiple forms to occupy one area on the screen. 4 | Following are some keys useful on a tabbedpane: 5 | < 6 | 7 | - navigate between tabs or components 8 | - navigate to the components of a tab 9 | - navigate between components on a tab 10 | or back to the the tabs 11 | - activate the tab under cursor 12 | Earlier, would have worked, but now 13 | only fires the default button an a page. 14 | > 15 | (This page needs to be completed) 16 | -------------------------------------------------------------------------------- /lib/canis.rb: -------------------------------------------------------------------------------- 1 | require "canis/version" 2 | require 'ffi-ncurses' 3 | require 'canis/core/system/ncurses' 4 | require 'canis/core/system/window' 5 | require 'canis/core/widgets/rwidget' 6 | # added textpad here since it is now the basis of so many others 2014-04-09 - 12:57 7 | require 'canis/core/widgets/textpad' 8 | require 'canis/core/util/rdialogs' 9 | 10 | module Canis 11 | # key used to select a row in multiline widgets (earlier was SPACE but conflicted with paging) 12 | $row_selector = 'v'.ord 13 | # key used to range-select rows in multiline widgets (earlier was CTRL_SPACE ) 14 | $range_selector = 'V'.ord 15 | # Your code goes here... 16 | end 17 | -------------------------------------------------------------------------------- /lib/canis/core/docs/style_help.yml: -------------------------------------------------------------------------------- 1 | --- 2 | link: 3 | :color: :yellow 4 | :bgcolor: :black 5 | :attr: :underline 6 | bold: 7 | :attr: :bold 8 | em: 9 | :color: :blue 10 | :attr: :bold 11 | strong: 12 | :color: :yellow 13 | :attr: :bold 14 | code: 15 | :color: :cyan 16 | :bgcolor: 236 17 | h1: 18 | :attr: :reverse 19 | h2: 20 | :color: :green 21 | :attr: :bold 22 | h3: 23 | :color: :yellow 24 | key: 25 | :color: :red 26 | :attr: :bold 27 | ul: 28 | :color: :blue 29 | :bgcolor: :black 30 | :attr: :underline 31 | wb: 32 | :color: :white 33 | :_bgcolor: :black 34 | :attr: :bold 35 | -------------------------------------------------------------------------------- /lib/canis/core/include/rchangeevent.rb: -------------------------------------------------------------------------------- 1 | =begin 2 | * Name: ChangeEvent 3 | * Description: Event used to notify interested parties that state of component has changed 4 | * Author: jkepler (ABCD) 5 | 6 | -------- 7 | * Date: 2010-02-26 11:32 8 | * License: 9 | Same as Ruby's License (http://www.ruby-lang.org/LICENSE.txt) 10 | 11 | =end 12 | 13 | # Event created when state changed (as in ViewPort) 14 | module Canis 15 | class ChangeEvent 16 | attr_accessor :source 17 | def initialize source 18 | @source = source 19 | end 20 | def to_s 21 | inspect 22 | end 23 | def inspect 24 | "ChangeEvent #{@source}" 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /lib/canis/core/docs/list.txt: -------------------------------------------------------------------------------- 1 | # Help for Lists 2 | 3 | Lists support selection, either single or multiple. 4 | 5 | Single selecion is done using "v" which is the visual mark key in vim. 6 | In multiple selection, the "v" may be used to select multiple rows. 7 | The "V" character may be used for range selection. 8 | 9 | Lists also provide for inversion of selection (*), select all (a) and clear selection (u). 10 | These bindings may have been modified by your individual application. 11 | 12 | *Ask_select* and *Ask_Unselect* may be mapped for certain applications 13 | allowing a user to give a pattern to select or unselect. 14 | 15 | Lists support all other motion commands of [[Textpad]]. 16 | 17 | -------------------------------------------------------------------------------- /lib/canis/core/docs/tree.txt: -------------------------------------------------------------------------------- 1 | # Help for Trees 2 | 3 | Trees are multirow components that support tree-like information, with 4 | leafs and branches (nodes) that contain more leafs or branches. 5 | 6 | These are often used for directory-like structures. 7 | 8 | Trees support all or most motion commands of [[Textpad]] in addition to 9 | defining some additional ones such as: 10 | > 11 | - O Expand all children 12 | - X Collapse all children 13 | - o toggle expanded state 14 | - p go to parent 15 | - x collapse parent 16 | < 17 | 18 | Pressing expands or collapses a node (branch). 19 | The `$row_selector` key by default is "v" which is used to trigger a 20 | method, such as displaying details on some other list or view. 21 | 22 | (This page needs to be completed) 23 | 24 | -------------------------------------------------------------------------------- /lib/canis/core/widgets/rlink.rb: -------------------------------------------------------------------------------- 1 | require 'canis' 2 | ## 3 | module Canis 4 | class Link < Button 5 | dsl_property :description 6 | 7 | 8 | def initialize form, config={}, &block 9 | super 10 | @text_offset = 0 11 | # haha we've never done this, pin the cursor up on 0,0 12 | @col_offset = -1 13 | # this won't be triggered since the shortcut does not set menmo 14 | # unless form is there. 15 | # Sometimes the mnemonic is not in text, such as '?' 16 | if @mnemonic 17 | form.bind_key(@mnemonic.downcase, self){ self.fire } 18 | end 19 | @width = config[:width] 20 | end 21 | def fire 22 | super 23 | self.focus 24 | end 25 | def getvalue_for_paint 26 | getvalue() 27 | end 28 | ## 29 | end # class 30 | end # module 31 | -------------------------------------------------------------------------------- /examples/data/todo.txt: -------------------------------------------------------------------------------- 1 | 1. clear_row in %textpad can overlap next row if data long 2 | 2. how to override +- * a and others for listbox - need to call before super() 3 | 2. simplify %Field. datatype and mask is overlapping 4 | 2. simplify %field, two ways of creating a label 5 | 4. %menu - remove complex rare functionality from main code 6 | 4. app to contain window close and confirm close 7 | 4. redo %menu bar -- current code is old and messy 8 | 4. test out vieditable and listeditable with core 9 | 6. when sorting cursor on old row but curr changed 10 | .3. Make keylabels more rubyesque - later 11 | x1. check native_text again to see how much used 12 | x2. Show key mappings to user 13 | x3. row_selector to be v 14 | x5. convert testlistbox to core 15 | x5. messagebox default button - done but current button should show default char 16 | x5. messagebox to catch YN keys also 17 | -------------------------------------------------------------------------------- /lib/canis/core/widgets/rmenulink.rb: -------------------------------------------------------------------------------- 1 | require 'canis/core/widgets/rlink' 2 | ## 3 | module Canis 4 | class MenuLink < Link 5 | dsl_property :description 6 | 7 | def initialize form, config={}, &block 8 | config[:hotkey] = true 9 | super 10 | @col_offset = -1 * (@col || 1) 11 | @row_offset = -1 * (@row || 1) 12 | # in this case, we wish to use ENTER for firing 13 | bind_key( KEY_ENTER, "fire" ) { fire } 14 | # next did not work 15 | #bind_key( KEY_ENTER, "fire" ) { get_action( 32 ) } 16 | # next 2 work 17 | #bind_key( KEY_ENTER, "fire" ) { @form.window.ungetch(32) } 18 | #@_key_map[KEY_ENTER] = @_key_map[32] 19 | #get_action_map()[KEY_ENTER] = get_action(32) 20 | end 21 | # added for some standardization 2010-09-07 20:28 22 | # alias :text :getvalue # NEXT VERSION 23 | # change existing text to label 24 | 25 | def getvalue_for_paint 26 | "%s %-12s - %-s" % [ @mnemonic , getvalue(), @description ] 27 | end 28 | ## 29 | end # class 30 | end # module 31 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 kepler 2 | 3 | MIT License 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /canis.gemspec: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | lib = File.expand_path('../lib', __FILE__) 3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 4 | require 'canis/version' 5 | 6 | Gem::Specification.new do |spec| 7 | spec.name = "canis" 8 | spec.version = Canis::VERSION 9 | spec.authors = ["kepler"] 10 | spec.email = ["githubkepler.50s@gishpuppy.com"] 11 | spec.summary = %q{ruby ncurses library for easy application development} 12 | spec.description = %q{ruby ncurses library for easy application development providing most controls, minimal source} 13 | spec.homepage = "https://github.com/mare-imbrium/canis" 14 | spec.license = "MIT" 15 | 16 | spec.files = `git ls-files -z`.split("\x0") 17 | spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) } 18 | spec.test_files = spec.files.grep(%r{^(test|spec|features)/}) 19 | spec.require_paths = ["lib"] 20 | 21 | spec.add_development_dependency "bundler", "~> 1.6" 22 | spec.add_development_dependency "rake", ">= 0.9.6" 23 | #spec.add_development_dependency "ffi-ncurses", ">= 0.4.0" 24 | spec.add_runtime_dependency "ffi-ncurses", ">= 0.4.0", ">= 0.4.0" 25 | end 26 | -------------------------------------------------------------------------------- /lib/canis/core/docs/table.txt: -------------------------------------------------------------------------------- 1 | # Help for Tables 2 | 3 | Tables are multirow components that support columnar information. 4 | 5 | Columns may be resized, or hidden. Keys for this will have to be defined 6 | by the application, if appropriate. 7 | 8 | Data may be sorted on column by pressing on the header, if the 9 | default table sorter has been enabled, or a custom sorter defined. 10 | 11 | Tables support all or most motion commands of [[Textpad]]. 12 | 13 | Tables may be configured to support selection capabilities of [[List]] 14 | if required. 15 | 16 | Some operations on tables are as follows: 17 | < 18 | w - move to next column 19 | b - move to previous column 20 | - to reduce column width 21 | - to increase column width 22 | <=> - press to size width of column to width of data in current column 23 | 24 | > 25 | 26 | Some basic row addition and editing facilities are also provided, which 27 | must be bound to a key, if the application allows editing. It is 28 | expected that an application will customize the basic row editing 29 | feature for its needs. See |tabular.rb|. 30 | 31 | (This page needs to be completed) 32 | -------------------------------------------------------------------------------- /examples/data/tasks.txt: -------------------------------------------------------------------------------- 1 | Field to have history which pops up 2 | App to have layout objects @1.6 3 | F2 menu to have context sensitive items 4 | SL and Dock events Hide. Move ... 5 | Stack and Flow to be objects @1.5 6 | ability to share directory options and functions across dir apps 7 | add event to form, key_resize 8 | app: add popup @1.5 9 | app: widgets can register with dock @1.5 10 | can use textpad in resultsetdb view to make things simpler 11 | colored module that allows row-wise coloring for all multirow wids 12 | find_file like microemacs @1.5 13 | motion options in textview and lists gg G cYcEcDcB zz zt zb etc 14 | registering command with Alt-X SL and Dock 15 | schemes (color) @1.5 16 | textview etc can have source. also module to recog file type,fmt 17 | use textpad for lists etc 18 | window.close can have a event so cleanup of any widget can be done 19 | Backward char search using F 20 | add progress bar to StatusWindow 21 | bottomline options global @1.5 22 | catch_alt_digits maybe at form level not global 23 | widgets needs to expose mapped keys in some easy way 24 | color_pair as :red_on_black 25 | confirm quit option, and call a proc before quitting 26 | elusive error if row not given in button 27 | rpopupmenu and rmenu share same class names 28 | -------------------------------------------------------------------------------- /examples/data/gemlist.txt: -------------------------------------------------------------------------------- 1 | activesupport (3.1.0, 3.0.10) 2 | arrayfields (4.7.4) 3 | bond (0.4.1) 4 | bugzyrb (0.3.8) 5 | bundler (1.0.18) 6 | cheat (1.3.0) 7 | chronic (0.6.4, 0.3.0) 8 | colored (1.2) 9 | dooby (0.3.1) 10 | fattr (2.2.0) 11 | ffi (1.0.9) 12 | ffi-locale (1.0.1) 13 | ffi-ncurses (0.4.0) 14 | gem-man (0.3.0) 15 | gemcutter (0.7.0) 16 | gemedit (1.0.1) 17 | git (1.2.5) 18 | gmail (0.4.0) 19 | gmail_xoauth (0.3.0) 20 | growl (1.0.3) 21 | highline (1.6.2, 1.6.1) 22 | hoe (2.12.3) 23 | i18n (0.6.0) 24 | interactive_editor (0.0.10) 25 | jeweler (1.6.4) 26 | json_pure (1.6.1) 27 | lightning (0.4.0) 28 | live_console (0.2.1) 29 | mail (2.3.0) 30 | main (4.7.7, 4.2.0) 31 | map (4.3.0) 32 | maruku (0.6.0) 33 | mime (0.1) 34 | mime-types (1.16) 35 | multi_json (1.0.3) 36 | ncurses (1.2.4) 37 | nokogiri (1.5.0) 38 | oauth (0.4.5) 39 | optiflag (0.7) 40 | polyglot (0.3.2) 41 | qwandry (0.1.4) 42 | rake (0.9.2, 0.8.7) 43 | random_data (1.5.2) 44 | rubyforge (2.0.4) 45 | rubygems-update (1.8.10) 46 | sequel (3.27.0) 47 | snipplr (0.0.9) 48 | spoon (0.0.1) 49 | sqlite3 (1.3.4) 50 | sqlite3-ruby (1.3.3) 51 | subcommand (1.0.6) 52 | syntax (1.0.0) 53 | terminal-table (1.4.2) 54 | thor (0.14.6) 55 | todorb (1.2.3) 56 | treetop (1.4.10) 57 | tzinfo (0.3.29) 58 | vmail (2.3.2) 59 | yard (0.7.2) 60 | -------------------------------------------------------------------------------- /lib/canis/core/util/focusmanager.rb: -------------------------------------------------------------------------------- 1 | # Allow some objects to take focus when a certain key is pressed. 2 | # This is for objects like scrollbars and grabbars. We don't want these always 3 | # getting focus, only sometimes when we want to resize panes. 4 | # This will not only be included by Form but by containers such as Vimsplit 5 | # or MasterDetail. 6 | # Usage: the idea is that when you create grabbars, you would add them to the FocusManager 7 | # Thus they would remain non-focusable on creation. When hte user presses (say F3) then 8 | # make_focusable is called, or toggle_focusable. Now user can press TAB and access 9 | # these bars. When he is done he can toggle again. 10 | # TODO: we might add a Circular class here so user can traverse only these objects 11 | module Canis 12 | module FocusManager 13 | extend self 14 | attr_reader :focusables 15 | # add a component to this list so it can be made focusable later 16 | def add component 17 | @focusables ||= [] 18 | @focusables << component 19 | self 20 | end 21 | def make_focusable bool=true 22 | @focusing = bool 23 | @focusables.each { |e| e.focusable(bool) } 24 | end 25 | def toggle_focusable 26 | return unless @focusables 27 | alert "FocusManager Making #{@focusables.length} objects #{!@focusing} " 28 | make_focusable !@focusing 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /lib/canis/core/system/keydefs.rb: -------------------------------------------------------------------------------- 1 | # ----------------------------------------------------------------------------- # 2 | # File: keydefs.rb 3 | # Description: Some common keys used in app. Earlier part of rwidget.rb 4 | # Author: jkepler http://github.com/mare-imbrium/canis/ 5 | # Date: 08.11.11 - 14:57 6 | # License: Same as Ruby's License (http://www.ruby-lang.org/LICENSE.txt) 7 | # Last update: 2014-04-20 17:28 8 | # ----------------------------------------------------------------------------- # 9 | # 10 | 11 | # some common definition that we use throughout app. Do not add more, only what is common. 12 | # I should not have added Sh-F9 and C-left since they are rare, but only to show they exist. 13 | # 14 | # THESE are now obsolete since we are moving to string based return values 15 | # else they should be updated. 16 | KEY_TAB = 9 17 | KEY_F1 = FFI::NCurses::KEY_F1 18 | KEY_F10 = FFI::NCurses::KEY_F10 19 | KEY_ENTER = 13 # FFI::NCurses::KEY_ENTER gives 343 20 | KEY_RETURN = 10 # FFI gives 10 too 21 | KEY_BTAB = 353 # nc gives same 22 | KEY_DELETE = 330 23 | KEY_BACKSPACE = KEY_BSPACE = 127 # Nc gives 263 for BACKSPACE 24 | KEY_CC = 3 # C-c 25 | KEY_LEFT = FFI::NCurses::KEY_LEFT 26 | KEY_RIGHT = FFI::NCurses::KEY_RIGHT 27 | KEY_UP = FFI::NCurses::KEY_UP 28 | KEY_DOWN = FFI::NCurses::KEY_DOWN 29 | C_LEFT = 18168 30 | C_RIGHT = 18167 31 | S_F9 = 17949126 32 | META_KEY = 128 33 | -------------------------------------------------------------------------------- /lib/canis/core/include/actionmanager.rb: -------------------------------------------------------------------------------- 1 | # ----------------------------------------------------------------------------- # 2 | # File: actionmanager.rb 3 | # Description: a class that manages actions for a widget 4 | # 5 | # Author: jkepler http://github.com/mare-imbrium/canis/ 6 | # Date: 2012-01-4 7 | # License: Same as Ruby's License (http://www.ruby-lang.org/LICENSE.txt) 8 | # Last update: ,,L 9 | # ----------------------------------------------------------------------------- # 10 | # 11 | # Maintains actions for a widget 12 | module Canis 13 | class ActionManager 14 | include Io 15 | attr_reader :actions 16 | 17 | def initialize #form, config={}, &block 18 | @actions = [] 19 | #instance_eval &block if block_given? 20 | end 21 | def add_action act 22 | @actions << act 23 | end 24 | def remove_action act 25 | @actions.remove act 26 | end 27 | # 28 | # insert an item at given position (index) 29 | def insert_action pos, *val 30 | @actions[pos] = val 31 | end 32 | #def create_menuitem *args 33 | #PromptMenu.create_menuitem *args 34 | #end 35 | 36 | # popup the hist 37 | # 38 | def show_actions 39 | return if @actions.empty? 40 | list = @actions 41 | menu = PromptMenu.new self do |m| 42 | list.each { |e| 43 | m.add *e 44 | } 45 | end 46 | menu.display_new :title => 'Widget Menu (Press letter)' 47 | end 48 | end # class 49 | end # mod RubyC 50 | -------------------------------------------------------------------------------- /examples/data/unix1.txt: -------------------------------------------------------------------------------- 1 | Eric S. Raymond, in his book The Art of Unix Programming,[2] summarizes the Unix philosophy as the widely-used KISS Principle of "Keep it Simple, Stupid."[3] He also provides a series of design rules: 2 | 3 | * Rule of Modularity: Write simple parts connected by clean interfaces. 4 | * Rule of Clarity: Clarity is better than cleverness. 5 | * Rule of Composition: Design programs to be connected to other programs. 6 | * Rule of Separation: Separate policy from mechanism; separate interfaces from engines. 7 | * Rule of Simplicity: Design for simplicity; add complexity only where you must. 8 | * Rule of Parsimony: Write a big program only when it is clear by demonstration that nothing else will do. 9 | * Rule of Transparency: Design for visibility to make inspection and debugging easier. 10 | * Rule of Robustness: Robustness is the child of transparency and simplicity. 11 | * Rule of Representation: Fold knowledge into data so program logic can be stupid and robust.[4] 12 | * Rule of Least Surprise: In interface design, always do the least surprising thing. 13 | * Rule of Silence: When a program has nothing surprising to say, it should say nothing. 14 | * Rule of Repair: When you must fail, fail noisily and as soon as possible. 15 | * Rule of Economy: Programmer time is expensive; conserve it in preference to machine time. 16 | * Rule of Generation: Avoid hand-hacking; write programs to write programs when you can. 17 | * Rule of Optimization: Prototype before polishing. Get it working before you optimize it. 18 | * Rule of Diversity: Distrust all claims for "one true way". 19 | * Rule of Extensibility: Design for the future, because it will be here sooner than you think. 20 | 21 | 22 | -------------------------------------------------------------------------------- /lib/canis/core/widgets/box.rb: -------------------------------------------------------------------------------- 1 | # ------------------------------------------------------------ # 2 | # File: box.rb 3 | # Description: draws a box around some group of items 4 | # Author: jkepler http://github.com/mare-imbrium/canis/ 5 | # Date: 06.11.11 - 18:22 6 | # Last update: 06.11.11 - 19:53 7 | # ------------------------------------------------------------ # 8 | # 9 | require 'canis' 10 | require 'canis/core/include/bordertitle' 11 | include Canis 12 | #include Canis::BorderTitle 13 | 14 | # @example 15 | # 16 | # At a later stage, we will integrate this with lists and tables, so it will happen automatically. 17 | # 18 | # @since 1.4.1 UNTESTED 19 | module Canis 20 | class Box < Widget 21 | 22 | include BorderTitle 23 | 24 | # margin for placing widgets inside 25 | # This is not used inside here, but is used by stacks. 26 | # @see widgetshortcuts.rb 27 | dsl_accessor :margin_left, :margin_top 28 | 29 | def initialize form, config={}, &block 30 | 31 | bordertitle_init 32 | super 33 | @window = form.window if @form 34 | @editable = false 35 | @focusable = false 36 | #@height += 1 # for that silly -1 that happens 37 | @repaint_required = true 38 | end 39 | 40 | ## 41 | # repaint the scrollbar 42 | def repaint 43 | return unless @repaint_required 44 | bc = $datacolor 45 | bordercolor = @border_color || bc 46 | borderatt = @border_attrib || Ncurses::A_NORMAL 47 | @window.print_border row, col, height, width, bordercolor, borderatt 48 | #print_borders 49 | print_title 50 | @repaint_required = false 51 | end 52 | ## 53 | ## 54 | # ADD HERE 55 | end 56 | end 57 | if __FILE__ == $PROGRAM_NAME 58 | end 59 | -------------------------------------------------------------------------------- /lib/canis/core/include/rinputdataevent.rb: -------------------------------------------------------------------------------- 1 | =begin 2 | * Name: InputDataEvent 3 | * Description: Event created when data modified in Field or TextEdit 4 | * Author: jkepler (ABCD) 5 | 6 | -------- 7 | * Date: 2008-12-24 17:27 8 | * License: 9 | Same as Ruby's License (http://www.ruby-lang.org/LICENSE.txt) 10 | 11 | NOTE: this is how we used to write code in the Java days. Anyone reading this source, 12 | this is NOT how to code in rubyland. Please see this link, for how to code such as class: 13 | http://blog.grayproductions.net/articles/all_about_struct 14 | 15 | =end 16 | 17 | # Event created when data modified in Field or TextEdit 18 | # 2008-12-24 17:54 19 | module Canis 20 | class InputDataEvent 21 | attr_accessor :index0, :index1, :source, :type, :row, :text 22 | def initialize index0, index1, source, type, row, text 23 | @index0 = index0 24 | @index1 = index1 25 | @source = source 26 | @type = type 27 | @row = row 28 | @text = text 29 | end 30 | # until now to_s was returning inspect, but to make it easy for users let us return the value 31 | # they most expect which is the text that was changed 32 | def to_s 33 | inspect 34 | end 35 | def inspect 36 | ## now that textarea.to_s prints content we shouldn pass it here. 37 | #"#{@type.to_s}, #{@source}, ind0:#{@index0}, ind1:#{@index1}, row:#{@row}, text:#{@text}" 38 | "#{@type.to_s}, ind0:#{@index0}, ind1:#{@index1}, row:#{@row}, text:#{@text}" 39 | end 40 | # this is so that earlier applications were getting source in the block, not an event. they 41 | # were doing a fld.getvalue, so we must keep those apps running 42 | # @since 1.2.0 added 2010-09-11 12:25 43 | def getvalue 44 | @source.getvalue 45 | end 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /examples/alpmenu.rb: -------------------------------------------------------------------------------- 1 | require 'canis/core/util/app' 2 | 3 | def load_relative(path) 4 | load File.expand_path("../#{path}", __FILE__) 5 | end 6 | 7 | App.new do 8 | #title "Demo of Menu - canis" 9 | #subtitle "Hit F1 to quit, F2 for menubar toggle" 10 | header = app_header "canis #{Canis::VERSION}", :text_center => "Alpine Menu Demo", :text_right =>"" 11 | 12 | stack :margin_top => 10, :margin_left => 15 do 13 | #w = "Messages".length + 1 14 | w = 60 15 | menulink :text => "&View Dirs", :width => w, :description => "View Dirs in tree" do |s, *stuff| 16 | message "Pressed #{s.text} " 17 | load_relative './dirtree.rb' 18 | #require './viewtodo'; todo = ViewTodo::TodoApp.new; todo.run 19 | end 20 | blank 21 | menulink :text => "&Tabular", :width => w, :description => "Tabula Rasa" do |s, *stuff| 22 | message "Pressed #{s.text} " 23 | load_relative './tabular.rb' 24 | #require './testtodo'; todo = TestTodo::TodoApp.new; todo.run 25 | end 26 | blank 27 | menulink :text => "&Messages", :width => w, :description => "View Tasks" do |s, *stuff| 28 | message "Pressed #{s.text} " 29 | load_relative './tasks.rb' 30 | end 31 | blank 32 | menulink :text => "&Database", :width => w, :description => "Database Demo" do |s, *stuff| 33 | message "Pressed #{s.getvalue} " 34 | load_relative './dbdemo.rb' 35 | end 36 | blank 37 | # somehow ? in mnemonic won't work 38 | menulink :text => "&Setup", :width => w, :description => "Configure Alpine options" do |s, *stuff| 39 | #message "Pressed #{s.text} " 40 | alert "Not done!" 41 | end 42 | blank 43 | menulink :text => "&Quit", :width => w, :description => "Quit this application" do |s, *stuff| 44 | quit 45 | end 46 | @form.bind(:ENTER) do |w| 47 | header.text_right = w.text 48 | end 49 | end # stack 50 | end # app 51 | -------------------------------------------------------------------------------- /examples/testflowlayout.rb: -------------------------------------------------------------------------------- 1 | # ----------------------------------------------------------------------------- # 2 | # File: testflowlayout.rb 3 | # Description: 4 | # Author: j kepler http://github.com/mare-imbrium/canis/ 5 | # Date: 2014-05-09 - 20:24 6 | # License: MIT 7 | # Last update: 2014-05-09 20:39 8 | # ----------------------------------------------------------------------------- # 9 | # testflowlayout.rb Copyright (C) 2012-2014 j kepler 10 | 11 | if __FILE__ == $PROGRAM_NAME 12 | require 'canis/core/util/app' 13 | require 'canis/core/widgets/listbox' 14 | require 'canis/core/include/layouts/flowlayout' 15 | App.new do 16 | 17 | # if i add form here, it will repaint them first, and have no dimensions 18 | lb = Listbox.new @form, :list => ["borodin","berlioz","bernstein","balakirev", "elgar"] , :name => "mylist" 19 | lb1 = Listbox.new @form, :list => ["bach","beethoven","mozart","gorecki", "chopin","wagner","grieg","holst"] , :name => "mylist1" 20 | 21 | lb2 = Listbox.new @form, :list => `gem list --local`.split("\n") , :name => "mylist2" 22 | =begin 23 | 24 | alist = %w[ ruby perl python java jruby macruby rubinius rails rack sinatra pylons django cakephp grails] 25 | str = "Hello, people of Earth.\nI am HAL, a textbox.\nUse arrow keys, j/k/h/l/gg/G/C-a/C-e/C-n/C-p\n" 26 | str << alist.join("\n") 27 | tv = TextPad.new @form, :name => "text", :text => str.split("\n") 28 | =end 29 | 30 | w = Ncurses.COLS-1 31 | h = Ncurses.LINES-3 32 | #layout = StackLayout.new :height => -1, :top_margin => 1, :bottom_margin => 1, :left_margin => 1 33 | layout = FlowLayout.new :height => -1, :top_margin => 1, :bottom_margin => 1, :left_margin => 1 34 | layout.form = @form 35 | @form.layout_manager = layout 36 | layout.cset lb, :weight, 15 37 | layout.cset lb1, :weight, 25 38 | #$status_message.value =" Flow: #{@r.components[0].orientation} | Stack #{@r.components[1].orientation}. Use Ctrl-Space to change " 39 | 40 | 41 | st = status_line :row => -1 42 | end # app 43 | end # if 44 | -------------------------------------------------------------------------------- /lib/canis/core/include/listoperations.rb: -------------------------------------------------------------------------------- 1 | # Some methods for traversing list like widgets such as tree, listbox and maybe table 2 | # Different components may bind different keys to these 3 | # 4 | module Canis 5 | module ListOperations 6 | 7 | # get a char ensure it is a char or number 8 | # In this state, it could accept control and other chars. 9 | private 10 | def _ask_a_char 11 | ch = @graphic.getch 12 | #message "achar is #{ch}" 13 | if ch < 26 || ch > 255 14 | @graphic.ungetch ch 15 | return :UNHANDLED 16 | end 17 | return ch.chr 18 | end 19 | public 20 | # sets the selection to the next row starting with char 21 | # Trying to return unhandled is having no effect right now. if only we could pop it into a 22 | # stack or unget it. 23 | def set_selection_for_char char=nil 24 | char = _ask_a_char unless char 25 | #alert "got #{char} " 26 | return :UNHANDLED if char == :UNHANDLED 27 | @oldrow = @current_index 28 | @last_regex = /^#{char}/ 29 | ix = next_regex @last_regex 30 | #alert "next returned #{ix}" 31 | return unless ix 32 | @current_index = ix[0] 33 | @search_found_ix = @current_index 34 | @curpos = ix[1] 35 | ensure_visible 36 | return @current_index 37 | end 38 | # Find the next row that contains given string 39 | # @return row and col offset of match, or nil 40 | # @param String to find 41 | def next_regex str 42 | first = nil 43 | ## content can be string or Chunkline, so we had to write index for this. 44 | ## =~ does not give an error, but it does not work. 45 | @list.each_with_index do |line, ix| 46 | #col = line.index str 47 | # for treemodel which will give us user_object.to_s 48 | col = line.to_s.index str 49 | if col 50 | first ||= [ ix, col ] 51 | if ix > @current_index 52 | return [ix, col] 53 | end 54 | end 55 | end 56 | return first 57 | end 58 | 59 | 60 | end # end module 61 | end # end module 62 | -------------------------------------------------------------------------------- /lib/canis/core/include/bordertitle.rb: -------------------------------------------------------------------------------- 1 | # I am moving the common title and border printing stuff into 2 | # a separate module. 3 | module Canis 4 | module BorderTitle 5 | dsl_accessor :suppress_borders #to_print_borders 6 | dsl_accessor :border_attrib, :border_color 7 | dsl_accessor :title #set this on top 8 | dsl_accessor :title_attrib #bold, reverse, normal 9 | 10 | def bordertitle_init 11 | @_bordertitle_init_called = true 12 | @row_offset = @col_offset = 0 if @suppress_borders 13 | @internal_width = 1 if @suppress_borders # the other programs have zero not 1 NOTE 14 | end 15 | # why the dash does it reduce height by one. 16 | def print_borders 17 | bordertitle_init unless @_bordertitle_init_called 18 | raise ArgumentError, "Graphic not set" unless @graphic 19 | raise "#{self} needs width" unless @width 20 | raise "#{self} needs height" unless @height 21 | width = @width 22 | height = @height-1 23 | window = @graphic 24 | startcol = @col 25 | startrow = @row 26 | @color_pair = get_color($datacolor) 27 | bordercolor = @border_color || @color_pair 28 | borderatt = @border_attrib || Ncurses::A_NORMAL 29 | window.print_border startrow, startcol, height, width, bordercolor, borderatt 30 | print_title 31 | end 32 | def print_title 33 | bordertitle_init unless @_bordertitle_init_called 34 | return unless @title 35 | raise "#{self} needs width" unless @width 36 | # removed || since this can change after first invocation and should be recalculated. 37 | @color_pair = get_color($datacolor) 38 | #$log.debug " print_title #{@row}, #{@col}, #{@width} " 39 | # check title.length and truncate if exceeds width 40 | _title = @title 41 | if @title.length > @width - 2 42 | _title = @title[0..@width-2] 43 | end 44 | @graphic.printstring( @row, @col+(@width-_title.length)/2, _title, @color_pair, @title_attrib) unless @title.nil? 45 | end 46 | 47 | end 48 | end 49 | include BorderTitle 50 | -------------------------------------------------------------------------------- /examples/data/table.txt: -------------------------------------------------------------------------------- 1 | 69|C-u not available for textpad and view. how to|P2|open 2 | 85|combo symbol when label, see newmessagebox|P2|open 3 | 86|combo let caller suggest width and use if longer than longest item|P2|open 4 | 88|keep working on wsshortcuts as in testws..2.rb|P2|open 5 | 89|messagebox, see about background for zterm-256 as in header|P2|open 6 | 97|binding to KEY_ENTER doesn't work, have to bid to 13|P2|open 7 | 98|if list not binding ENTER then dont consume it|P2|open 8 | 22|widget hide (visible false) does not hide|P3|open 9 | 65|clean up window.rb prv_printstring etc|P3|open 10 | 74|list and others should just calculate longest in list|P3|open 11 | 79|cleanup button getpaint etc|P3|open 12 | 80|use @focusable in form to simplify|P3|open 13 | 87|praps global setting lists etc use SPC for scroll or selection|P3|open 14 | 92|messagebox: if text longer than display then can we split|P3|open 15 | 95|window.refresh required after alert of messagebox closes|P3|open 16 | 99|button option to set mnemo for keys without alt|P3|open 17 | 10|combo keys|P4|open 18 | 17|selected_item of list broken|P4|open 19 | 20|cannot bind_key using Alt key and another. |P4|open 20 | 27|#fix testvimsplit not sizing STACK correctly|P4|open 21 | 32| #tree many empty methods in #treemodel|P4|open 22 | 37|simplify #vimsplit calculation|P4|open 23 | 52|%label set_label may have to calculate at repaint esp in app|P4|open 24 | 55|Have a module Expandable for those that are multiline|P4|open 25 | 56|DRY up titles and borders|P4|open 26 | 60|fields width, display_len is for input area, not label plus input|P4|open 27 | 61|test2.rb color change not affecting text objects|P4|open 28 | 70|confusion between renderer and color_parser|P4|open 29 | 75|textpad to allow append << at some stage|P4|open 30 | 93|messagebox conform and choice whith single key getch|P4|open 31 | 8|container keep repainting all|P5|open 32 | 26|App to have a layout abject |P5|open 33 | 39|tabularwidget truncate needed left_margin|P5|open 34 | 42|append_to_kill, yank not working in %listbox|P5|open 35 | 49|resultsetview needs way to specify key fields|P5|open 36 | 50|sort on tabularwidget with resultset error sometimes|P5|open 37 | 96|configure confirm quit etc through a file|P5|open 38 | -------------------------------------------------------------------------------- /examples/testsplitlayout1.rb: -------------------------------------------------------------------------------- 1 | # ----------------------------------------------------------------------------- # 2 | # File: testsplitlayout.rb 3 | # Description: 4 | # Author: j kepler http://github.com/mare-imbrium/canis/ 5 | # Date: 2014-05-09 - 20:24 6 | # License: MIT 7 | # Last update: 2014-05-10 20:16 8 | # ----------------------------------------------------------------------------- # 9 | # testsplitlayout.rb Copyright (C) 2012-2014 j kepler 10 | 11 | if __FILE__ == $PROGRAM_NAME 12 | require 'canis/core/util/app' 13 | require 'canis/core/widgets/listbox' 14 | require 'canis/core/include/layouts/splitlayout' 15 | App.new do 16 | 17 | # if i add form here, it will repaint them first, and have no dimensions 18 | lb = Listbox.new @form, :list => ["borodin","berlioz","bernstein","balakirev", "elgar"] , :name => "mylist" 19 | lb1 = Listbox.new @form, :list => ["bach","beethoven","mozart","gorecki", "chopin","wagner","grieg","holst"] , :name => "mylist1" 20 | 21 | 22 | alist = %w[ ruby perl python java jruby macruby rubinius rails rack sinatra pylons django cakephp grails] 23 | str = "Hello, people of Earth.\nI am HAL, a textbox.\nUse arrow keys, j/k/h/l/gg/G/C-a/C-e/C-n/C-p\n" 24 | str << alist.join("\n") 25 | tv = TextPad.new @form, :name => "text", :text => str.split("\n") 26 | lb3 = Listbox.new @form, :list => alist , :name => "mylist3" 27 | lb2 = Listbox.new @form, :list => `gem list --local`.split("\n") , :name => "mylist2" 28 | 29 | w = Ncurses.COLS-1 30 | h = Ncurses.LINES-3 31 | #layout = StackLayout.new :height => -1, :top_margin => 1, :bottom_margin => 1, :left_margin => 1 32 | 33 | layout = SplitLayout.new :height => -1, :top_margin => 1, :bottom_margin => 1, :left_margin => 1 34 | @form.layout_manager = layout 35 | layout.vsplit(0.2, 0.8) do |x,y| 36 | x.component = lb 37 | y.split(0.4, 0.6) do |a,b| 38 | b.component = lb2 39 | a.vsplit( 0.3, 0.3, 0.4) do | p,q,r | 40 | p << lb1 41 | q << tv 42 | r << lb3 43 | end 44 | end 45 | end 46 | 47 | st = status_line :row => -1 48 | end # app 49 | end # if 50 | -------------------------------------------------------------------------------- /examples/teststacklayout.rb: -------------------------------------------------------------------------------- 1 | # #!/usr/bin/env ruby -w 2 | # ----------------------------------------------------------------------------- # 3 | # File: teststacklayout.rb 4 | # Description: 5 | # Author: j kepler http://github.com/mare-imbrium/canis/ 6 | # Date: 2014-05-08 - 23:34 7 | # License: MIT 8 | # Last update: 2014-05-28 22:09 9 | # ----------------------------------------------------------------------------- # 10 | 11 | if __FILE__ == $PROGRAM_NAME 12 | require 'canis/core/util/app' 13 | require 'canis/core/widgets/listbox' 14 | require 'canis/core/include/layouts/stacklayout' 15 | App.new do 16 | 17 | 18 | @form.bind_key(FFI::NCurses::KEY_F3,'view log') { 19 | require 'canis/core/util/viewer' 20 | Canis::Viewer.view("canis14.log", :close_key => KEY_ENTER, :title => " to close") 21 | } 22 | # if i add form here, it will repaint them first, and have no dimensions 23 | lb = Listbox.new @form, :list => ["borodin","berlioz","bernstein","balakirev", "elgar"] , :name => "mylist" 24 | lb1 = Listbox.new @form, :list => ["bach","beethoven","mozart","gorecki", "chopin","wagner","grieg","holst"] , :name => "mylist1" 25 | 26 | lb2 = Listbox.new @form, :list => `gem list --local`.split("\n") , :name => "mylist2" 27 | =begin 28 | 29 | alist = %w[ ruby perl python java jruby macruby rubinius rails rack sinatra pylons django cakephp grails] 30 | str = "Hello, people of Earth.\nI am HAL, a textbox.\nUse arrow keys, j/k/h/l/gg/G/C-a/C-e/C-n/C-p\n" 31 | str << alist.join("\n") 32 | tv = TextPad.new @form, :name => "text", :text => str.split("\n") 33 | =end 34 | 35 | w = Ncurses.COLS-1 36 | h = Ncurses.LINES-3 37 | #layout = StackLayout.new :height => -1, :top_margin => 1, :bottom_margin => 1, :left_margin => 1 38 | layout = StackLayout.new :height_pc => 1.0, :top_margin => 1, :bottom_margin => 1, :left_margin => 1 39 | layout.form = @form 40 | @form.layout_manager = layout 41 | layout.weightage lb, 7 42 | #layout.weightage lb1, 9 43 | #$status_message.value =" Flow: #{@r.components[0].orientation} | Stack #{@r.components[1].orientation}. Use Ctrl-Space to change " 44 | 45 | 46 | st = status_line :row => -1 47 | end # app 48 | end # if 49 | -------------------------------------------------------------------------------- /lib/canis/core/docs/textpad.txt: -------------------------------------------------------------------------------- 1 | # Help for Textpads 2 | 3 | (These were earlier called _TextViews_ ) 4 | 5 | Textpads allow display of multiline textual information and provide for 6 | basic search, and vim-like navigation keys. 7 | 8 | Some textpads are extended to display multiple buffers such as the help 9 | screens. Textpads may display text that has ANSI escape codes such as 10 | unix `man` pages, or the output of commands such as `ri` or `dooby` or 11 | any other unix command that outputs colored output. 12 | 13 | There is a second format that provides a lot more control than ANSI, 14 | which is a `tmux` like format. This is used in the `status_line`. 15 | 16 | Finally, there is a (new) help format, much like markdown (very 17 | restricted, though). This page is formatted using the help format. 18 | 19 | Following are some styles it provides: 20 | 21 | - strong using __ as in __strong__. end 22 | - strong using double asterisks that is ** : **strong text with spaces**. end 23 | - emphasis using single asterisk * as in *emphasis*. end. 24 | Emphasis *taking spaces in* between with single asterisk. 25 | - emphasis using single _ as in _thisisemphsized_, will not take _ 26 | inside. 27 | - code using tilde as in `do_process_now` or `@bgcolor` and `$fgcolor`. 28 | - underline using bar as in |underlined|, this depends on TERM setting. 29 | xterm shows underlines, most others do not. 30 | - Link as in [[list]] (double brackets in source, but single in display). 31 | - key using angular brackets as in . 32 | 33 | A block follows in white and bold (start with a single > on previous 34 | line, and end with a single < char after block: 35 | > 36 | I think most of these need to be fixed so they don't start capturing 37 | if surrounded by spaces. A lookahead and lookbehind is required other 38 | wise they can wreak havoc. 39 | < 40 | 41 | 42 | Textpads do not provide row selection, but do allow mapping of the 43 | key and provide the `word_under_cursor` to the calling block as part of 44 | the action event. For instance, on this (or other) help pages, pressing 45 | the key will take the cursor to the next link (if there is one). 46 | Pressing on the link will open the linked help file. This is the 47 | link to the [[index]] or main help page. 48 | 49 | -------------------------------------------------------------------------------- /lib/canis/core/include/defaultfilerenderer.rb: -------------------------------------------------------------------------------- 1 | # ----------------------------------------------------------------------------- # 2 | # File: defaultfilerenderer.rb 3 | # Description: Simple file renderer, colors an entire line based on some keyword. 4 | # Author: j kepler http://github.com/mare-imbrium/canis/ 5 | # Date: 2014-06-25 - 12:57 6 | # License: MIT 7 | # Last update: 2014-06-25 12:58 8 | # ----------------------------------------------------------------------------- # 9 | # defaultfilerenderer.rb Copyright (C) 2012-2014 j kepler 10 | 11 | 12 | # a simple file renderer that allows setting of colors per line based on 13 | # regexps passed to +insert_mapping+. See +tasks.rb+ for example usage. 14 | # 15 | class DefaultFileRenderer < AbstractTextPadRenderer 16 | attr_accessor :default_colors 17 | attr_reader :hash 18 | 19 | def initialize source=nil 20 | @default_colors = [:white, :black, NORMAL] 21 | @pair = get_color($datacolor, @default_colors.first, @default_colors[1]) 22 | end 23 | 24 | def color_mappings hash 25 | @hash = hash 26 | end 27 | # takes a regexp, and an array of color, bgcolor and attr 28 | def insert_mapping regex, dim 29 | @hash ||= {} 30 | @hash[regex] = dim 31 | end 32 | # matches given line with each regexp to determine color use 33 | # Internally used by render. 34 | def match_line line 35 | @hash.each_pair {| k , p| 36 | if line =~ k 37 | return p 38 | end 39 | } 40 | return @default_colors 41 | end 42 | # render given line in color configured using +insert_mapping+ 43 | def render pad, lineno, text 44 | if @hash 45 | dim = match_line text 46 | fg = dim.first 47 | bg = dim[1] || @default_colors[1] 48 | if dim.size == 3 49 | att = dim.last 50 | else 51 | att = @default_colors.last 52 | end 53 | cp = get_color($datacolor, fg, bg) 54 | else 55 | cp = @pair 56 | att = @default_colors[2] 57 | end 58 | 59 | FFI::NCurses.wattron(pad,FFI::NCurses.COLOR_PAIR(cp) | att) 60 | FFI::NCurses.mvwaddstr(pad, lineno, 0, text) 61 | FFI::NCurses.wattroff(pad,FFI::NCurses.COLOR_PAIR(cp) | att) 62 | end 63 | # 64 | end 65 | -------------------------------------------------------------------------------- /lib/canis/core/include/widgetmenu.rb: -------------------------------------------------------------------------------- 1 | # ----------------------------------------------------------------------------- # 2 | # File: widgetmenu.rb 3 | # Description: a module that displays a menu for customization of a field 4 | # e.g., 5 | # field.extend(WidgetMenu) 6 | # 7 | # Author: jkepler http://github.com/mare-imbrium/canis/ 8 | # Date: 2011-12-2x 9 | # License: Same as Ruby's License (http://www.ruby-lang.org/LICENSE.txt) 10 | # Last update: 2011-12-26 - 20:25 11 | # ----------------------------------------------------------------------------- # 12 | # 13 | # Provide a system for us to define a menu for customizing a widget, such that 14 | # applicatin can also add more menuitems 15 | module Canis 16 | extend self 17 | module WidgetMenu 18 | include Io # added 2011-12-26 19 | # add a menu item which can any one of 20 | # @param key, label, desc, action | symbol 21 | # key, symbol 22 | # Action 23 | # Action[] (maybe) 24 | def self.extended(obj) 25 | # don't want this executed each time 26 | @objects ||= [] 27 | return if @objects.include? obj 28 | @objects << obj 29 | 30 | obj.instance_exec { 31 | @_menuitems ||= [] 32 | # callign this method means that no other programs can use those actions else 33 | # that method will be called more than once, so it must either be called in the constructor 34 | # or else have a check that it is only called once. 35 | obj.init_menu if obj.respond_to? :init_menu 36 | } 37 | 38 | end 39 | def add_menu_item *val 40 | #@_menuitems ||= [] 41 | @_menuitems << val 42 | end 43 | # 44 | # insert an item at given position (index) 45 | def insert_menu_item pos, *val 46 | #@_menuitems ||= [] 47 | @_menuitems[pos] = val 48 | end 49 | def create_menuitem *args 50 | PromptMenu.create_menuitem *args 51 | end 52 | 53 | # popup the hist 54 | # 55 | def _show_menu 56 | return if @_menuitems.nil? || @_menuitems.empty? 57 | list = @_menuitems 58 | menu = PromptMenu.new self do |m| 59 | list.each { |e| 60 | m.add *e 61 | } 62 | end 63 | menu.display_new :title => 'Widget Menu (Press letter)' 64 | end 65 | end # mod History 66 | end # mod RubyC 67 | -------------------------------------------------------------------------------- /examples/data/todocsv.csv: -------------------------------------------------------------------------------- 1 | FIXME,MSGBOX,5,Confirm dialog: box vertical line overwritten in 2 spots,TODO 2 | FIXME,MSGBOX,5,Confirm dialog: use normal key as hotkey also,TODO,Tue Jan 20 11:44:49 +0530 2009 3 | FIXME,MSGBOX,5,Confirm dialog: arrow keys not navigating anylonger,TODO,Tue Jan 20 11:45:27 +0530 2009 4 | FIXME,GEN,9,Message Box sizing,TODO,Thu Jan 22 20:39:21 +0530 2009 5 | DONE,LIST,5,case insensitive char search in list and combo,TESTED,Sat Feb 21 20:43:05 +0530 2009 6 | DONE,TABLE,5,increase the maxlen of this field please. Let us see how it goes.,TESTED 7 | DONE,TABLE,5,Can we disable down arrow in Chkbox in table?,TESTED,Mon Jan 19 00:00:00 +0530 2009 8 | DONE,TABLE,0,editing on enter,TESTED,Mon Jan 19 01:37:00 +0530 2009 9 | DONE,TABLE,5,cell editors pcol is not being reset each time,TESTED,Mon Jan 19 17:47:00 +0530 2009 10 | DONE,TABLE,5,Use TAB for intercell navig. use M-TAB for next f,TESTED,Tue Jan 20 00:38:19 +0530 2009 11 | DONE,TABLE,5,Searching,TESTED,Sat Feb 21 20:42:10 +0530 2009 12 | DONE,TABLE,3,Columns editable or not,TESTED,Sat Feb 21 20:43:10 +0530 2009 13 | DONE,TABLE,1,Any way to start a table with no data and pop late,TODO,Sat Feb 21 20:43:33 +0530 2009 14 | DONE,GEN,5,Make widget of Keylabelprinter,TESTED,Tue Jan 20 00:38:43 +0530 2009 15 | DONE,GEN,5,Added Action class shared by Button Menuitem ,TESTED,Thu Jan 22 18:08:28 +0530 2009 16 | DONE,GEN,5,Added PopupMenu 2009-01-22 18:09 ,TESTED,Thu Jan 22 18:09:34 +0530 2009 17 | DONE,LIST,0,call on_enter and on_leave of component,TOTEST,Sun Feb 22 12:19:38 +0530 2009 18 | DONE,FIELD,5,Field: OVERWRITE Mode,TESTED,2010-09-13 11:24:35 +0530 19 | DONE,GEN,5,"Modified should check if value changed, not UP etc",TOTEST,2010-09-13 11:25:18 +0530 20 | TODO,TABLE,1,table.set_data should check if models already created.,TODO 21 | TODO,TABLE,5,"Set column_class in TableColumn, to avoid hassles",TODO 22 | TODO,TABLE,2,Table sorting and filtering is required - using VIEW,TODO 23 | TODO,TABLE,5,Table height and col widths auto sizing or FILLING extra space.,TODO 24 | TODO,TEXTAREA,9,"Textarea: wrap options NONE, COLUMN",TODO,Tue Jan 20 01:04:15 +0530 2009 25 | TODO,GEN,5,Give a decent FileChooser and FileSaver,TODO 26 | TODO,GEN,5,Focus Traversable vs focusable,TODO 27 | TODO,GEN,5,Action class: fire event for listeners,TODO,Thu Jan 22 20:09:50 +0530 2009 28 | TODO,FIELD,5,Field: Auto-skip when reaching end of maxlen,TODO 29 | -------------------------------------------------------------------------------- /examples/testsplitlayout.rb: -------------------------------------------------------------------------------- 1 | # ----------------------------------------------------------------------------- # 2 | # File: testsplitlayout.rb 3 | # Description: 4 | # Author: j kepler http://github.com/mare-imbrium/canis/ 5 | # Date: 2014-05-09 - 20:24 6 | # License: MIT 7 | # Last update: 2014-05-10 19:15 8 | # ----------------------------------------------------------------------------- # 9 | # testsplitlayout.rb Copyright (C) 2012-2014 j kepler 10 | 11 | if __FILE__ == $PROGRAM_NAME 12 | require 'canis/core/util/app' 13 | require 'canis/core/widgets/listbox' 14 | require 'canis/core/include/layouts/splitlayout' 15 | App.new do 16 | 17 | # if i add form here, it will repaint them first, and have no dimensions 18 | lb = Listbox.new @form, :list => ["borodin","berlioz","bernstein","balakirev", "elgar"] , :name => "mylist" 19 | lb1 = Listbox.new @form, :list => ["bach","beethoven","mozart","gorecki", "chopin","wagner","grieg","holst"] , :name => "mylist1" 20 | 21 | 22 | alist = %w[ ruby perl python java jruby macruby rubinius rails rack sinatra pylons django cakephp grails] 23 | str = "Hello, people of Earth.\nI am HAL, a textbox.\nUse arrow keys, j/k/h/l/gg/G/C-a/C-e/C-n/C-p\n" 24 | str << alist.join("\n") 25 | tv = TextPad.new @form, :name => "text", :text => str.split("\n") 26 | lb3 = Listbox.new @form, :list => alist , :name => "mylist3" 27 | lb2 = Listbox.new @form, :list => `gem list --local`.split("\n") , :name => "mylist2" 28 | 29 | w = Ncurses.COLS-1 30 | h = Ncurses.LINES-3 31 | #layout = StackLayout.new :height => -1, :top_margin => 1, :bottom_margin => 1, :left_margin => 1 32 | 33 | layout = SplitLayout.new :height => -1, :top_margin => 1, :bottom_margin => 1, :left_margin => 1 34 | @form.layout_manager = layout 35 | x,y = layout.vsplit( 0.30, 0.70) 36 | raise "X" unless x 37 | raise "Y" unless y 38 | ##x = arr.first 39 | #y = arr[1] 40 | $log.debug "XY: #{x} , #{y} " 41 | x.component = lb 42 | y1, y2 = y.split( 0.40, 0.60 ) 43 | #y1.component = lb1 44 | y2.component = lb2 45 | y11,y12,y13 = y1.vsplit( 0.3, 0.3, 0.4) 46 | #y11,y12 = y1.vsplit( 0.4, 0.6) 47 | y11.component = lb1 48 | y12.component = tv 49 | y13.component = lb3 50 | 51 | st = status_line :row => -1 52 | end # app 53 | end # if 54 | -------------------------------------------------------------------------------- /examples/testwsshortcuts.rb: -------------------------------------------------------------------------------- 1 | # this is a test program, tests out tabbed panes. type F1 to exit 2 | # 3 | require 'canis' 4 | require 'canis/core/util/widgetshortcuts' 5 | 6 | include Canis 7 | 8 | class SetupMessagebox 9 | include Canis::WidgetShortcuts 10 | def initialize config={}, &block 11 | @window = Canis::Window.root_window 12 | @form = Form.new @window 13 | end 14 | def run 15 | _create_form 16 | @form.repaint 17 | @window.wrefresh 18 | while ((ch = @window.getchar()) != 999) 19 | break if ch == ?\C-q.getbyte(0) 20 | @form.handle_key ch 21 | @window.wrefresh 22 | end 23 | end 24 | def _create_form 25 | widget_shortcuts_init 26 | stack :margin_top => 2, :margin_left => 3, :width => 50 , :color => :cyan, :bgcolor => :black do 27 | label :text => " Details ", :color => :blue, :attr => :reverse, :width => :expand, :justify => :center 28 | blank 29 | field :text => "john", :attr => :reverse, :label => "%15s" % ["Name: "] 30 | field :label => "%15s" % ["Address: "], :width => 15, :attr => :reverse 31 | check :text => "Using version control", :value => true, :onvalue => "yes", :offvalue => "no" 32 | check :text => "Upgraded to Lion", :value => false, :onvalue => "yes", :offvalue => "no" 33 | blank 34 | radio :text => "Linux", :value => "LIN", :group => :os 35 | radio :text => "OSX", :value => "OSX", :group => :os 36 | radio :text => "Window", :value => "Win", :group => :os 37 | flow :margin_top => 2, :margin_left => 4, :item_width => 15 do 38 | button :text => "Ok", :default_button => true do throw :close; end 39 | button :text => "Cancel" 40 | button :text => "Apply" 41 | end 42 | end 43 | end 44 | 45 | end 46 | if $0 == __FILE__ 47 | # Initialize curses 48 | begin 49 | # XXX update with new color and kb 50 | Canis::start_ncurses # this is initializing colors via ColorMap.setup 51 | path = File.join(ENV["LOGDIR"] || "./" ,"canis14.log") 52 | file = File.open(path, File::WRONLY|File::TRUNC|File::CREAT) 53 | $log = Logger.new(path) 54 | $log.level = Logger::DEBUG 55 | catch(:close) do 56 | tp = SetupMessagebox.new() 57 | buttonindex = tp.run 58 | $log.debug "XXX: MESSAGEBOX retirned #{buttonindex} " 59 | end 60 | rescue => ex 61 | ensure 62 | Canis::stop_ncurses 63 | p ex if ex 64 | p(ex.backtrace.join("\n")) if ex 65 | $log.debug( ex) if ex 66 | $log.debug(ex.backtrace.join("\n")) if ex 67 | end 68 | end 69 | -------------------------------------------------------------------------------- /examples/data/lotr.txt: -------------------------------------------------------------------------------- 1 | I amar prestar aen. The world is changed. Han matho ne nen. I feel it in the water. Han mathon ned cae. I feel it in the earth. A han noston ned gwilith. I smell it in the air. Much that once was is lost, for none now live who remember it. 2 | 3 | It began with the forging of the Great Rings. Three were given to the Elves, immortal, wisest and fairest of all beings. Seven to the Dwarf-Lords, great miners and craftsmen of the mountain halls. And nine, nine rings were gifted to the race of Men, who above all else desire power. For within these rings was bound the strength and the will to govern each race. But they were all of them deceived, for another ring was made. Deep in the land of Mordor, in the Fires of Mount Doom, the Dark Lord Sauron forged in secret a master ring to control all others, and into this ring he poured his cruelty, his malice and his will to dominate all life. One ring to rule them all. 4 | 5 | One by one, the free lands of Middle-Earth fell to the power of the Ring, but there were some who resisted. A last alliance of Men and Elves marched against the armies of Mordor, and on the very slopes of Mount Doom, they fought for the freedom of Middle-Earth. Victory was near, but the power of the ring could not be undone. 6 | It was in this moment, when all hope had faded, that Isildur, son of the king, took up his father's sword. 7 | 8 | Sauron, the enemy of the free peoples of Middle-Earth, was defeated. The Ring passed to Isildur, who had this once chance to destroy evil forever. But the hearts of Men are easily corrupted. And the ring of power has a will of its own. It betrayed Isildur, to his death. And some things that should not have been forgotten were lost. History became legend, legend became myth. And for two and a half thousand years, the ring passed out of all knowledge. Until, when chance came, it ensnared a new bearer. 9 | 10 | The Ring came to the creature Gollum, who took it deep into the tunnels of the Misty Mountains. And there it consumed him. The ring brought to Gollum unnatural long life. For five hundred years it poisoned his mind, and in the gloom of Gollum's cave, it waited. Darkness crept back into the forests of the world. Rumor grew of a shadow in the East, whispers of a nameless fear, and the Ring of Power percieved its time had now come. It abandoned Gollum. But something happened then that the Ring did not intend. It was picked up by the most unlikely creature imaginable: a Hobbit, Bilbo Baggins, of the Shire. 11 | 12 | For the time will soon come when hobbits will shape the fortunes of all. 13 | -------------------------------------------------------------------------------- /lib/canis/core/widgets/rtabbedwindow.rb: -------------------------------------------------------------------------------- 1 | =begin 2 | * Name: newtabbedwindow.rb 3 | * Description : This is a window that contains a tabbedpane (NewTabbedPane). This for situation 4 | when you want to pop up a setup/configuration type of tabbed pane. 5 | See examples/newtabbedwindow.rb for an example of usage, and test2.rb 6 | which calls it from the menu (Options2 item). 7 | In a short while, I will deprecate the existing complex TabbedPane and use this 8 | in the lib/canis dir. 9 | * Author: jkepler (http://github.com/mare-imbrium/canis/) 10 | * Date: 22.10.11 - 20:35 11 | * License: Same as Ruby's License (http://www.ruby-lang.org/LICENSE.txt) 12 | * Last update: 2013-04-01 13:42 13 | 14 | == CHANGES 15 | == TODO 16 | =end 17 | require 'canis' 18 | require 'canis/core/widgets/rtabbedpane' 19 | require 'canis/core/widgets/rcontainer' 20 | 21 | include Canis 22 | module Canis 23 | class TabbedWindow 24 | attr_reader :tabbed_pane 25 | # The given block is passed to the TabbedPane 26 | # The given dimensions are used to create the window. 27 | # The TabbedPane is placed at 0,0 and fills the window. 28 | def initialize config={}, &block 29 | 30 | h = config.fetch(:height, 0) 31 | w = config.fetch(:width, 0) 32 | t = config.fetch(:row, 0) 33 | l = config.fetch(:col, 0) 34 | @window = Canis::Window.new :height => h, :width => w, :top => t, :left => l 35 | @form = Form.new @window 36 | config[:row] = config[:col] = 0 37 | @tabbed_pane = TabbedPane.new @form, config , &block 38 | end 39 | # returns button index 40 | # Call this after instantiating the window 41 | def run 42 | @form.repaint 43 | @window.wrefresh 44 | return handle_keys 45 | end 46 | # returns button index 47 | private 48 | def handle_keys 49 | buttonindex = catch(:close) do 50 | while((ch = @window.getchar()) != FFI::NCurses::KEY_F10 ) 51 | break if ch == ?\C-q.getbyte(0) 52 | begin 53 | @form.handle_key(ch) 54 | @window.wrefresh 55 | rescue => err 56 | $log.debug( err) if err 57 | $log.debug(err.backtrace.join("\n")) if err 58 | textdialog ["Error in TabbedWindow: #{err} ", *err.backtrace], :title => "Exception" 59 | $error_message.value = "" 60 | ensure 61 | end 62 | 63 | end # while loop 64 | end # close 65 | $log.debug "XXX: CALLER GOT #{buttonindex} " 66 | @window.destroy 67 | return buttonindex 68 | end 69 | end 70 | end 71 | -------------------------------------------------------------------------------- /examples/data/color.2: -------------------------------------------------------------------------------- 1 | Showing all items... 2 | (8b0dbf) #enh view with table, text, list, tree, fields hash 3 | (3ef256) #fix clearing/hiding widget 4 | (df584b) %menu list place cursor firstchar match 5 | (749828) %menu RT_ARROW if none go left 6 | (ed1515) %vimsplit when adding, put grabbar cmd in system_commands 7 | (b3102b) %listbox reduce repaint, only if change, or selection 8 | (344ad2) %form dn_arrow to lower field, rt_arrow to right field 9 | (35bed1) %tabularwidget ask_select and ask_unselect 10 | (7c24e7) check other widgets for color changes 11 | (f864fe) check for #UTF-8 errors where match method used #fix 12 | (4bef44) testdir returns path wrong if i use ../ #fix 13 | (2171c9) %TabularWidget example scrollbar showing white space at end #fix 14 | (ed7b0a) search in tabular does not take offset of header into account #fix 15 | (fc95e4) #enh widgets can have %keylabels, so mode gets taken care of auto 16 | (c538e9) app_header assumes root_window, shd take window width into acount 17 | (e22c8e) menu in dbdemo one column short (ony after F4 closes) #fix 18 | (83dfff) additional footer text in text view area #hold 19 | (891157) maybe selected and focussed should be shown same so no confusion 20 | (dd5f96) calculate color_pair once, and not so many times #fix 21 | (2406f8) listcellrenderer is horribly complicated - simplify 22 | (f42383) reduce #repaint in #textarea and #tree 23 | (543b53) dock through div by zero if row Ncurses.Lines-1 #fix 24 | (c48a48) The view in #dbdemo should allow sorting on header. and maybe select one row 25 | (85461c) common routine tabular(headings,content) 26 | (9b3501) sort on tabularwidget with resultset sometimes gives error ofcomparison 27 | (31f476) put comopnent traversal into traversal module 28 | (5be1c4) popuplist: space in single selection should select? 29 | (330928) system shell etc menu items should be available somehow ifwe want 30 | (3663dc) install mysql and gem and test resultset with that too 31 | (745c3f) remove multiform 32 |  33 | ==================== TODAY ==================== 34 | (d56b4d) alert dialog overflow on data, shd have increased in size #fix #today 35 |  36 | ==================== URGENT ==================== 37 | (140d16) #fix %testvimsplit not sizing STACK correctly #urgent 38 | -------------------------------------------------------------------------------- /lib/canis/core/include/ractionevent.rb: -------------------------------------------------------------------------------- 1 | =begin 2 | * Name: ActionEvent 3 | * Description: Event used to notify interested parties that an action has happened on component 4 | Usually a button press. Nothing more. 5 | * Author: jkepler (ABCD) 6 | 7 | -------- 8 | * Date: 2010-09-12 18:53 9 | * License: 10 | Same as Ruby's License (http://www.ruby-lang.org/LICENSE.txt) 11 | 12 | =end 13 | 14 | # Event created when state changed (as in ViewPort) 15 | module Canis 16 | # source - as always is the object whose event has been fired 17 | # id - event identifier (seems redundant since we bind events often separately. 18 | # event - is :PRESS 19 | # action_command - command string associated with event (such as title of button that changed 20 | ActionEvent = Struct.new(:source, :event, :action_command) do 21 | # This should always return the most relevant text associated with this object 22 | # so the user does not have to go through the source object's documentation. 23 | # It should be a user-friendly string 24 | # @return text associated with source (label of button) 25 | def text 26 | source.text 27 | end 28 | 29 | # This is similar to text and can often be just an alias. 30 | # However, i am putting this for backward compatibility with programs 31 | # that received the object and called it's getvalue. It is better to use text. 32 | # @return text associated with source (label of button) 33 | def getvalue 34 | source.getvalue 35 | end 36 | end 37 | # a derivative of Action Event for textviews 38 | # We allow a user to press ENTER on a row and use that for processing. 39 | # We are basically using TextView as a list in which user can scroll around 40 | # and move cursor at will. 41 | class TextActionEvent < ActionEvent 42 | # current_index or line number starting 0 43 | attr_accessor :current_index 44 | # cursor position on the line 45 | attr_accessor :curpos 46 | def initialize source, event, action_command, current_index, curpos 47 | super source, event, action_command 48 | @current_index = current_index 49 | @curpos = curpos 50 | end 51 | # the text of the line on which the user is 52 | def text 53 | source.current_value.to_s 54 | end 55 | # the word under the cursor TODO 56 | # if its a text with pipe delim, then ?? 57 | def word_under_cursor line=text(), pos=@curpos, delim=" " 58 | line ||= text() 59 | pos ||= @curpos 60 | # if pressed on a space, try to go to next word to make easier 2013-03-24 61 | if line[pos,1] == delim 62 | while line[pos,1] == delim 63 | pos += 1 64 | end 65 | end 66 | finish = line.index(delim, pos) 67 | start = line.rindex(delim,pos) 68 | finish = -1 if finish.nil? 69 | start = 0 if start.nil? 70 | return line[start..finish] 71 | end 72 | end 73 | end 74 | -------------------------------------------------------------------------------- /lib/canis/core/include/action.rb: -------------------------------------------------------------------------------- 1 | # ----------------------------------------------------------------------------- # 2 | # File: action.rb 3 | # Description: A common action class which can be used with buttons, popupmenu 4 | # and anythign else that takes an action or command 5 | # Author: jkepler http://github.com/mare-imbrium/canis/ 6 | # Date: been around since the beginning 7 | # License: Same as Ruby's License (http://www.ruby-lang.org/LICENSE.txt) 8 | # Last update: 2014-04-28 14:53 9 | # NOTE: I don't like the dependence on rwidget and EventHandler. Seems it needs 10 | # that only for fire_handler and not sure if that's used. I've not bound :FIRE 11 | # ever. 12 | # 13 | # Darn, do i really need to have dsl_accessors and property This is not a 14 | # widget and there's no repaint. Do button's and popups really repaint 15 | # themselves when a dsl_property is modified ? 16 | # ----------------------------------------------------------------------------- # 17 | # 18 | #require 'canis/core/widgets/rwidget' 19 | include Canis 20 | module Canis 21 | ## encapsulates behaviour allowing centralization 22 | # == Example 23 | # a = Action.new("&New Row") { commands } 24 | # a.accelerator "Alt N" 25 | # menu.add(a) 26 | # b = Button.new form do 27 | # action a 28 | # ... 29 | # end 30 | class Action < Proc 31 | # once again commented on 2014-04-28 - 14:37 to keep simple 32 | #include EventHandler # removed 2012-01-3 maybe you can bind FIRE 33 | #include ConfigSetup # removed 2012-01-3 34 | # name used on button or menu 35 | attr_accessor :name 36 | attr_accessor :enabled 37 | attr_accessor :tooltip_text 38 | attr_accessor :help_text 39 | attr_accessor :mnemonic 40 | attr_accessor :accelerator 41 | 42 | def initialize name, config={}, &block 43 | super &block 44 | @name = name 45 | @name.freeze 46 | @enabled = true 47 | # removing dependency from config 48 | #config_setup config # @config.each_pair { |k,v| variable_set(k,v) } 49 | @config = config 50 | keys = @config.keys 51 | keys.each do |e| 52 | variable_set(e, @config[e]) 53 | end 54 | #@_events = [:FIRE] 55 | end 56 | def call *args 57 | return unless @enabled 58 | # seems to be here, if you've bound :FIRE no this, not on any widget 59 | #fire_handler :FIRE, self 60 | super 61 | end 62 | 63 | 64 | # the next 3 are to adapt this to CMenuitems 65 | def hotkey 66 | return @mnemonic if @mnemonic 67 | ix = @name.index('&') 68 | if ix 69 | return @name[ix+1, 1].downcase 70 | end 71 | end 72 | # to adapt this to CMenuitems 73 | def label 74 | @name.sub('&','') 75 | end 76 | # to adapt this to CMenuitems 77 | def action 78 | self 79 | end 80 | 81 | end # class 82 | end # module 83 | 84 | -------------------------------------------------------------------------------- /lib/canis/core/widgets/listfooter.rb: -------------------------------------------------------------------------------- 1 | require 'canis' 2 | 3 | module Canis 4 | 5 | # 6 | # A vim-like application status bar that can display time and various other statuses 7 | # at the bottom, typically above the dock (3rd line from last). 8 | # 9 | class ListFooter 10 | attr_accessor :config 11 | attr_accessor :color_pair 12 | attr_accessor :attrib 13 | 14 | def initialize config={}, &block 15 | @config = config 16 | @color_pair = get_color($datacolor, config[:color], config[:bgcolor]) 17 | @attrib = config[:attrib] 18 | instance_eval &block if block_given? 19 | end 20 | # 21 | # command that returns a string that populates the status line (left aligned) 22 | # @see :right 23 | # e.g. 24 | # @l.command { "%-20s [DB: %-s | %-s ]" % [ Time.now, $current_db || "None", $current_table || "----"] } 25 | # 26 | def command *args, &blk 27 | @command_text = blk 28 | @command_args = args 29 | end 30 | alias :left :command 31 | 32 | # 33 | # Procudure for text to be right aligned in statusline 34 | def command_right *args, &blk 35 | @right_text = blk 36 | @right_args = args 37 | end 38 | def text(comp) 39 | @command_text.call(comp, @command_args) 40 | end 41 | def right_text(comp) 42 | @right_text.call(comp, @right_args) 43 | end 44 | 45 | # supply a default print function. The user program need not call this. It may be overridden 46 | # The idea of passing a component at this stage is that one footer can be created for several 47 | # tables or text components, each will pass +self+ when calling print. 48 | # @param comp : the calling textpad component (usually passed as +self+) 49 | def print comp 50 | config = @config 51 | row = comp.row + comp.height - 1 52 | col = comp.col + 2 53 | len = comp.width - col 54 | g = comp.form.window 55 | # we check just in case user nullifies it deliberately, since he may have changed config values 56 | @color_pair ||= get_color($datacolor, config[:color], config[:bgcolor]) 57 | @attrib ||= config[:attrib] || Ncurses::A_REVERSE 58 | 59 | 60 | # first print dashes through 61 | #g.printstring row, col, "%s" % "-" * len, @color_pair, Ncurses::A_REVERSE 62 | 63 | # now call the block to get current values for footer text on left 64 | ftext = nil 65 | if @command_text 66 | ftext = text(comp) 67 | else 68 | if !@right_text 69 | # user has not specified right or left, so we use a default on left 70 | ftext = "#{comp.current_index} of #{comp.size} " 71 | end 72 | end 73 | g.printstring(row, col, ftext, @color_pair, @attrib) if ftext 74 | 75 | # user has specified text for right, print it 76 | if @right_text 77 | len = comp.width 78 | ftext = right_text(comp) 79 | c = len - ftext.length - 2 80 | g.printstring row, c, ftext, @color_pair, @attrib 81 | end 82 | end 83 | 84 | 85 | end # class 86 | end # module 87 | -------------------------------------------------------------------------------- /examples/newtesttabp.rb: -------------------------------------------------------------------------------- 1 | # this is a test program, tests out tabbed panes. type F1 to exit 2 | # 3 | require 'logger' 4 | require 'canis' 5 | #require 'canis/core/widgets/newtabbedpane' 6 | require 'canis/core/widgets/rtabbedpane' 7 | require 'canis/core/widgets/rcontainer' # tempo FIXME remove this since we arent using afterfixing rtabbedp 8 | 9 | class TestTabbedPane 10 | def initialize 11 | acolor = $reversecolor 12 | #$config_hash ||= {} 13 | end 14 | def run 15 | $config_hash ||= Variable.new Hash.new 16 | #configvar.update_command(){ |v| $config_hash[v.source()] = v.value } 17 | @window = Canis::Window.root_window 18 | @form = Form.new @window 19 | r = 1; c = 30; 20 | tp = Canis::TabbedPane.new @form, :height => 12, :width => 50, 21 | :row => 13, :col => 10 do 22 | button_type :ok 23 | end 24 | tp.add_tab "&Language" do 25 | _r = 2 26 | colors = [:red, :green, :cyan] 27 | %w[ ruby jruby macruby].each_with_index { |e, i| 28 | item RadioButton.new nil, 29 | :variable => $config_hash, 30 | :name => "radio1", 31 | :text => e, 32 | :value => e, 33 | :color => colors[i], 34 | :row => _r+i, 35 | :col => 5 36 | } 37 | end 38 | tp.add_tab "&Settings" do 39 | r = 2 40 | butts = [ "Use &HTTP/1.0", "Use &frames", "&Use SSL" ] 41 | bcodes = %w[ HTTP, FRAMES, SSL ] 42 | butts.each_with_index do |t, i| 43 | item Canis::CheckBox.new nil, 44 | :text => butts[i], 45 | :variable => $config_hash, 46 | :name => bcodes[i], 47 | :row => r+i, 48 | :col => 5 49 | end 50 | end 51 | tp.add_tab "&Editors" do 52 | butts = %w[ &Vim E&macs &Jed &Other ] 53 | bcodes = %w[ VIM EMACS JED OTHER] 54 | row = 2 55 | butts.each_with_index do |name, i| 56 | item Canis::CheckBox.new nil , 57 | :text => name, 58 | :variable => $config_hash, 59 | :name => bcodes[i], 60 | :row => row+i, 61 | :col => 5 62 | end 63 | end 64 | help = "q to quit. through tabs, Space or Enter to select Tab." 65 | Canis::Label.new @form, {:text => help, :row => 1, :col => 2, :color => :yellow} 66 | @form.repaint 67 | @window.wrefresh 68 | Ncurses::Panel.update_panels 69 | while((ch = @window.getchar()) != ?q.getbyte(0) ) 70 | @form.handle_key(ch) 71 | @window.wrefresh 72 | end 73 | end 74 | end 75 | if $0 == __FILE__ 76 | # Initialize curses 77 | begin 78 | # XXX update with new color and kb 79 | Canis::start_ncurses # this is initializing colors via ColorMap.setup 80 | $log = Logger.new("canis14.log") 81 | $log.level = Logger::DEBUG 82 | n = TestTabbedPane.new 83 | n.run 84 | rescue => ex 85 | ensure 86 | Canis::stop_ncurses 87 | p ex if ex 88 | p(ex.backtrace.join("\n")) if ex 89 | $log.debug( ex) if ex 90 | $log.debug(ex.backtrace.join("\n")) if ex 91 | end 92 | end 93 | -------------------------------------------------------------------------------- /CHANGES: -------------------------------------------------------------------------------- 1 | 2019-03-12 - 15:19 2 | Fix prefresh in table.rb. Table header was not printing if row was greater than 3 | 1. 4 | 5 | 2017-03-09 - 23:29 6 | Replaced Fixnum with Integer since Fixnum deprecated in Ruby 2.4. 7 | 8 | 2014-04-22 - 00:13 9 | 10 | Made some changes in getchar to return STRINGS but not implemented. Evaluating benefits 11 | in moving to chars, and how to go about. 12 | 13 | 2014-04-23 - 00:23 14 | 15 | removed many dead methods from window and some from widget 16 | slight changes in button and label : when_form replaced by FORM_ATTACHED 17 | 18 | 2014-05-05 - 20:12 19 | 20 | - moved to new cleaner method of reading keys from keyboard 21 | - Added ButtonGroup for RadioButtons. 22 | - removed Bottomline and enhanced rcommandline instead 23 | 24 | 2014-05-09 - 22:23 25 | 26 | - introduced layouts. Now a user can hardcode locations of widgets or use a layout 27 | to do the same. A layout is able to handle a resize of the screen. 28 | 29 | 2014-05-25 - 16:03 30 | 31 | - Major rewrite of colorparser. now colors are not hardcoded while parsing. We now store link 32 | to parent chunk, so that a change in textpads color can affect the pad without a re-parse. 33 | Parent's color's are resolved at render time. 34 | 35 | 2014-05-31 - 18:20 36 | - minor changes that could result in some color related issues with buttons or other widgets 37 | - repaint in various widgets no longer sets @bgcolor and @color, it uses the method 38 | Same with @attr. messagebox also does not set the color variables but uses the method 39 | - clear_pad clear_row uses content_cols i/o width so complete pad is cleared. 40 | Also i have put a return in the middle, i think the second part of clear is redundant 41 | now, if the first half works properly. 42 | 43 | 2014-06-06 - 00:05 44 | - Released 0.0.1 45 | 46 | 2014-06-19 - 17:16 47 | - Still trying to simplify and refactor TextPad so there are no changes for a fairly long time. 48 | - Removed content_type complexity into TextDocument so Textpad can be dumb. 49 | 50 | Built 0.0.2 but i did not release it. 51 | 52 | 2014-07-09 - 22:48 53 | - for 0.0.3 54 | - removed label string from Field and moved to LabeledField 55 | This change has an impact in many examples, and combo 56 | - some changes in creation of $log so that if none specified then it can go to /dev/null 57 | This way an app can have its own logger and not bother about canis' logging. 58 | Or it can merge the two. 59 | - Moved helpmanager from rwidgets to its own file. Needs to be refactored and cleaned up. 60 | 61 | 2014-08-18 - 17:11 62 | - for 0.0.5 63 | - fixed a bug in handling of multiple key assignments due to which extra keys pushed onto stack 64 | - App keyblock yields string of key without changing or converting to symbol 65 | 66 | 2014-09-01 67 | - for 0.0.6 68 | - Statusline location defaults to -1 from bottom, earlier -3 69 | - Statusline attrib was A_REVERSE earlier, now defaults to A_NORMAL. 70 | - bugs and omissions in parse_format, and in DefaultColorParser fixed. 71 | 72 | 2014-09-02 73 | - for 0.0.7 74 | rdialog.rb introduced a syntax error while adding names to windows, had to yank 0.0.6 75 | 76 | 2014-09-11 77 | - for 0.0.8 78 | fix in colorparser.rb : wrong default was picked, now sending in textpads color and attr 79 | -------------------------------------------------------------------------------- /examples/common/file.rb: -------------------------------------------------------------------------------- 1 | # ----------------------------------------------------------------------------- # 2 | # File: file.rb 3 | # Description: some common file related methods which can be used across 4 | # file manager demos, since we seems to have a lot of them :) 5 | # Author: jkepler http://github.com/mare-imbrium/canis/ 6 | # Date: 2011-11-15 - 19:54 7 | # License: Same as Ruby's License (http://www.ruby-lang.org/LICENSE.txt) 8 | # Last update: 2014-07-10 14:49 9 | # ----------------------------------------------------------------------------- # 10 | # NOTE after calling shell_out you now need to call Window.refresh_all if you have 11 | # pads on the screen which are getting left black 12 | 13 | require 'canis/core/include/appmethods' 14 | module Canis 15 | def file_edit fp #=@current_list.filepath 16 | #$log.debug " edit #{fp}" 17 | editor = ENV['EDITOR'] || 'vi' 18 | vimp = %x[which #{editor}].chomp 19 | shell_out "#{vimp} #{fp}" 20 | Window.refresh_all 21 | end 22 | 23 | # TODO we need to move these to some common file so differnt programs and demos 24 | # can use them on pressing space or enter. 25 | def file_page fp #=@current_list.filepath 26 | unless File.exists? fp 27 | pwd = %x[pwd] 28 | alert "No such file. My pwd is #{pwd} " 29 | return 30 | end 31 | ft=%x[file #{fp}] 32 | if ft.index("text") 33 | pager = ENV['PAGER'] || 'less' 34 | vimp = %x[which #{pager}].chomp 35 | shell_out "#{vimp} #{fp}" 36 | Window.refresh_all 37 | elsif ft.index(/zip/i) 38 | shell_out "tar tvf #{fp} | less" 39 | Window.refresh_all 40 | elsif ft.index(/directory/i) 41 | shell_out "ls -lh #{fp} | less" 42 | Window.refresh_all 43 | else 44 | alert "#{fp} is not text, not paging " 45 | #use_on_file "als", fp # only zip or archive 46 | end 47 | end 48 | def file_listing path, config={} 49 | listing = config[:mode] || :SHORT 50 | ret = [] 51 | if File.exists? path 52 | files = Dir.new(path).entries 53 | files.delete(".") 54 | return files if listing == :SHORT 55 | 56 | files.each do |f| 57 | if listing == :LONG 58 | pf = File.join(path, f) 59 | if File.exists? pf 60 | $log.info " File (#{f}) found " 61 | else 62 | $log.info " File (#{f}) NOT found " 63 | end 64 | stat = File.stat(pf) 65 | ff = "%10s %s %s" % [readable_file_size(stat.size,1), date_format(stat.mtime), f] 66 | ret << ff 67 | end 68 | end 69 | end 70 | return ret 71 | end 72 | ## code related to long listing of files 73 | GIGA_SIZE = 1073741824.0 74 | MEGA_SIZE = 1048576.0 75 | KILO_SIZE = 1024.0 76 | 77 | # Return the file size with a readable style. 78 | def readable_file_size(size, precision) 79 | case 80 | #when size == 1 : "1 B" 81 | when size < KILO_SIZE then "%d B" % size 82 | when size < MEGA_SIZE then "%.#{precision}f K" % (size / KILO_SIZE) 83 | when size < GIGA_SIZE then "%.#{precision}f M" % (size / MEGA_SIZE) 84 | else "%.#{precision}f G" % (size / GIGA_SIZE) 85 | end 86 | end 87 | ## format date for file given stat 88 | def date_format t 89 | t.strftime "%Y/%m/%d" 90 | end 91 | 92 | end # module 93 | include Canis 94 | -------------------------------------------------------------------------------- /examples/term2.rb: -------------------------------------------------------------------------------- 1 | require 'canis/core/util/app' 2 | require 'canis/core/widgets/tabular' 3 | require 'canis/core/widgets/scrollbar' 4 | 5 | def my_help_text 6 | <<-eos 7 | term2.rb 8 | ========================================================================= 9 | Basic Usage 10 | 11 | This example shows different ways of putting data in tabular format. 12 | 13 | The 2 tables on the right differ in behaviour. The first puts tabular data 14 | into a listbox so you get single/multiple selection. The second puts tabular 15 | data into a textview, so there's no selection. scrolls instead of 16 | selects allows us to use the word under cursor for further actions. 17 | 18 | To see an example of placing tabular data in a tabular widget, see tabular.rb. 19 | The advantage of tabular_widget is column resizing, hiding, aligning and sorting. 20 | 21 | ========================================================================= 22 | :n or Alt-n for next buffer. 'q' to quit. 23 | 24 | eos 25 | end 26 | App.new do 27 | header = app_header "canis #{Canis::VERSION}", :text_center => "Tabular Demo", :text_right =>"New Improved!", :color => :black, :bgcolor => :white, :attr => :bold 28 | message "F10 quit, F1 Help, ? Bindings" 29 | #install_help_text my_help_text 30 | @form.help_manager.help_text = my_help_text 31 | 32 | flow :width => FFI::NCurses.COLS , :height => FFI::NCurses.LINES-2 do 33 | stack :margin_top => 1, :width_pc => 20 do 34 | t = Tabular.new(['a', 'b'], [1, 2], [3, 4], [5,6]) 35 | listbox :list => t.render 36 | 37 | t = Tabular.new ['a', 'b'] 38 | t << [1, 2] 39 | t << [3, 4] 40 | t << [4, 6] 41 | t << [8, 6] 42 | t << [2, 6] 43 | #list_box :list => t.to_s.split("\n") 44 | listbox :list => t.render 45 | end # stack 46 | 47 | 48 | file = File.expand_path("../data/tasks.csv", __FILE__) 49 | lines = File.open(file,'r').readlines 50 | heads = %w[ id sta type prio title ] 51 | t = Tabular.new do |t| 52 | t.headings = heads 53 | lines.each { |e| t.add_row e.chomp.split '|' } 54 | end 55 | 56 | t = t.render 57 | wid = t[0].length + 2 58 | wid = 30 59 | stack :margin_top => 1, :width_pc => 80 , :height_pc => 100 do 60 | listbox :list => t, :title => '[ tasks ]', :height_pc => 60 61 | 62 | r = `ls -l` 63 | res = r.split("\n") 64 | 65 | t = Tabular.new do 66 | # self.headings = 'Perm', 'Gr', 'User', 'U', 'Size', 'Mon', 'Date', 'Time', 'File' # changed 2011 dts 67 | self.headings = 'User', 'Size', 'Mon', 'Date', 'Time', 'File' 68 | res.each { |e| 69 | cols = e.split 70 | next if cols.count < 6 71 | cols = cols[3..-1] 72 | cols = cols[0..5] if cols.count > 6 73 | add_row cols 74 | } 75 | column_width 1, 6 76 | align_column 1, :right 77 | end 78 | #lb = list_box :list => t.render2 79 | lb = textview :set_content => t.render, :title => '[ls -l]', :height_pc => 40 80 | lb.bind(:PRESS){|tae| 81 | alert "Pressed list on line #{tae.current_index} #{tae.word_under_cursor(nil, nil, "|")} " 82 | } 83 | Scrollbar.new @form, :parent => lb 84 | #make a textview that is vienabled by default. 85 | end 86 | end # end 87 | 88 | end # app 89 | -------------------------------------------------------------------------------- /examples/testree.rb: -------------------------------------------------------------------------------- 1 | require 'canis' 2 | require 'canis/core/widgets/tree' 3 | 4 | if $0 == __FILE__ 5 | $choice = ARGV[0].to_i || 1 6 | class Tester 7 | def initialize 8 | acolor = $reversecolor 9 | end 10 | def run 11 | @window = Canis::Window.root_window 12 | @form = Form.new @window 13 | 14 | h = 20; w = 75; t = 3; l = 4 15 | #$choice = 1 16 | case $choice 17 | when 1 18 | root = TreeNode.new "ROOT" 19 | subroot = TreeNode.new "subroot" 20 | leaf1 = TreeNode.new "leaf 1" 21 | leaf2 = TreeNode.new "leaf 2" 22 | 23 | model = DefaultTreeModel.new root 24 | #model.insert_node_into(subroot, root, 0) 25 | #model.insert_node_into(leaf1, subroot, 0) 26 | #model.insert_node_into(leaf2, subroot, 1) 27 | root << subroot 28 | subroot << leaf1 << leaf2 29 | leaf1 << "leaf11" 30 | leaf1 << "leaf12" 31 | 32 | root.add "blocky", true do 33 | add "block2" 34 | add "block3" do 35 | add "block31" 36 | end 37 | end 38 | Tree.new @form, :data => model, :row =>2, :col=>2, :height => 20, :width => 30 39 | 40 | when 2 41 | 42 | # use an array to populate 43 | # we need to do root_visible = false so you get just a list 44 | model = %W[ ruby lua jruby smalltalk haskell scheme perl lisp ] 45 | Tree.new @form, :data => model, :row =>2, :col=>2, :height => 20, :width => 30 46 | 47 | when 3 48 | 49 | # use an Has to populate 50 | #model = { :ruby => %W[ "jruby", "mri", "yarv", "rubinius", "macruby" ], :python => %W[ cpython jython laden-swallow ] } 51 | model = { :ruby => [ "jruby", {:mri => %W[ 1.8.6 1.8.7]}, {:yarv => %W[1.9.1 1.9.2]}, "rubinius", "macruby" ], :python => %W[ cpython jython pypy ] } 52 | 53 | Tree.new @form, :data => model, :row =>2, :col=>2, :height => 20, :width => 30 54 | #when 4 55 | else 56 | Tree.new @form, :row =>2, :col=>2, :height => 20, :width => 30 do 57 | root "root" do 58 | branch "vim" do 59 | leaf "vimrc" 60 | end 61 | branch "ack" do 62 | leaf "ackrc" 63 | leaf "agrc" 64 | end 65 | end 66 | end 67 | 68 | end 69 | 70 | # 71 | help = "C-q to quit. to expand nodes. j/k to navigate. Pass command-line argument 1,2,3,4 #{$0} " 72 | Canis::Label.new @form, {:text => help, :row => 1, :col => 2, :color => :cyan} 73 | @form.repaint 74 | @window.wrefresh 75 | Ncurses::Panel.update_panels 76 | while((ch = @window.getchar()) != ?\C-q.getbyte(0)) 77 | ret = @form.handle_key(ch) 78 | @window.wrefresh 79 | if ret == :UNHANDLED 80 | str = keycode_tos ch 81 | $log.debug " UNHANDLED #{str} by Vim #{ret} " 82 | end 83 | end 84 | 85 | @window.destroy 86 | 87 | end 88 | end 89 | include Canis 90 | include Canis::Utils 91 | # Initialize curses 92 | begin 93 | # XXX update with new color and kb 94 | Canis::start_ncurses # this is initializing colors via ColorMap.setup 95 | $log = Logger.new("canis14.log") 96 | $log.level = Logger::DEBUG 97 | n = Tester.new 98 | n.run 99 | rescue => ex 100 | ensure 101 | Canis::stop_ncurses 102 | p ex if ex 103 | puts(ex.backtrace.join("\n")) if ex 104 | $log.debug( ex) if ex 105 | $log.debug(ex.backtrace.join("\n")) if ex 106 | end 107 | end 108 | -------------------------------------------------------------------------------- /lib/canis/core/util/textutils.rb: -------------------------------------------------------------------------------- 1 | # ----------------------------------------------------------------------------- # 2 | # File: textutils.rb 3 | # Description: contains some common string or Array utilities 4 | # that may be required by various parts of application. 5 | # Author: j kepler http://github.com/mare-imbrium/canis/ 6 | # Date: 2014-05-22 - 11:11 7 | # License: MIT 8 | # Last update: 2014-05-26 19:40 9 | # ----------------------------------------------------------------------------- # 10 | # textutils.rb Copyright (C) 2012-2014 j kepler 11 | 12 | module Canis 13 | module TextUtils 14 | # Convert an array of Strings that has help markup into tmux style 15 | # which can then by parsed into native format by the tmux parser 16 | # 'help' markup is very much like markdown, but a very restricted 17 | # subset. 18 | # Currently called only by help_manager in rwidgets.rb 19 | # Some of these need to be fixed since they may not allow some 20 | # characters, maybe too restrictive, or may match within a word. FIXME 21 | def self.help2tmux arr 22 | arr.each do |e| 23 | # double sq brackets are like wiki links, to internal documents in same location 24 | e.gsub! /\[\[(\S+)\]\]/, '#[style=link][\1]#[/end]' 25 | # double asterisk needs to be more permissive and take a space FIXME 26 | e.gsub! /\*\*(\S.*?\S)\*\*/, '#[style=strong]\1#[/end]' 27 | # the next is wrong and could match two asteriks also 28 | #e.gsub! /\*(\S[^\*]+\S)\*/, '#[style=em]\1#[/end]' 29 | e.gsub! /\*(?!\s)([^\*]+)(?" surrounding 45 | e.gsub! /(\<\S+\>)/, '#[style=key]\1#[/end]' 46 | # headers start with "#" 47 | e.sub! /^###\s*(.*)$/, '#[style=h3]\1#[/end]' 48 | e.sub! /^## (.*)$/, '#[style=h2]\1#[/end]' 49 | e.sub! /^# (.*)$/, '#[style=h1]\1#[/end]' 50 | # line starting with "">" starts a white bold block as in vim's help. "<" ends block. 51 | e.sub! /^\>$/, '#[style=wb]' 52 | e.sub! /^\<$/, '#[/end]' 53 | end 54 | return arr 55 | end 56 | ## 57 | # wraps text given max length, puts newlines in it. 58 | # it does not take into account existing newlines 59 | # Some classes have @maxlen or display_length which may be passed as the second parameter 60 | def self.wrap_text(txt, max ) 61 | txt.gsub(/(.{1,#{max}})( +|$\n?)|(.{1,#{max}})/, 62 | "\\1\\3\n") 63 | end 64 | 65 | # remove tabs, newlines and non-print chars from a string since these 66 | # can mess display 67 | def self.clean_string! content 68 | content.chomp! # don't display newline 69 | content.gsub!(/[\t\n]/, ' ') # don't display tab 70 | content.gsub!(/[^[:print:]]/, '') # don't display non print characters 71 | content 72 | end 73 | end 74 | end 75 | -------------------------------------------------------------------------------- /lib/canis/core/util/defaultcolorparser.rb: -------------------------------------------------------------------------------- 1 | # ----------------------------------------------------------------------------- # 2 | # File: colorparser.rb 3 | # Description: Default parse for our tmux format 4 | # The aim is to be able to specify parsers so different kinds 5 | # of formatting or documents can be used, such as ANSI formatted 6 | # manpages. 7 | # Author: jkepler http://github.com/mare-imbrium/canis/ 8 | # Date: 07.11.11 - 13:17 9 | # License: Same as Ruby's License (http://www.ruby-lang.org/LICENSE.txt) 10 | # Last update: 2014-09-01 11:47 11 | # ----------------------------------------------------------------------------- # 12 | # == TODO 13 | # - perhaps we can compile the regexp once and reuse 14 | # == CHANGES 15 | # - adding style 2014-05-19 - 12:52 16 | # 17 | 18 | module Canis 19 | class DefaultColorParser 20 | 21 | # NOTE: Experimental and minimal 22 | # parses the formatted string and yields either an array of color, bgcolor and attrib 23 | # or the text. This will be called by convert_to_chunk. 24 | # 25 | # Currently, assumes colors and attributes are correct. No error checking or fancy stuff. 26 | # s="#[fg=green]hello there#[fg=yellow, bg=black, dim]" 27 | # @since 1.4.1 2011-11-3 experimental, can change 28 | # @return [nil] knows nothign about output format. 29 | 30 | # 187compat 2013-03-20 - 19:33 not working in 187 so added ,1 in some cases for string 31 | def parse_format s # yields attribs or text 32 | ## set default colors 33 | # 2014-09-01 - 11:46 setting default fg and bg is wrong, it should either set the 34 | # objects default (which we do not know in case of statusline, or maybe remain nil) 35 | color = $def_fg_color 36 | bgcolor = $def_bg_color 37 | color = nil 38 | bgcolor = nil 39 | attrib = FFI::NCurses::A_NORMAL 40 | text = "" 41 | 42 | ## split #[...] 43 | a = s.split /(#\[[^\]]*\])/ 44 | a.each { |e| 45 | ## process color or attrib portion 46 | if e[0,2] == "#[" && e[-1,1] == "]" 47 | # now resetting 1:20 PM November 3, 2011 , earlier we were carrying over 48 | color, bgcolor, attrib = nil, nil, nil 49 | style = nil 50 | catch(:done) do 51 | e = e[2..-2] 52 | # TODO we could atthis point check against a hash to see if this string exists, and take 53 | # the array from there and pass back so we don't keep splitting and parsing. 54 | ## first split on commas to separate fg, bg and attr 55 | atts = e.split /\s*,\s*/ 56 | atts.each { |att| 57 | ## next split on = 58 | part = att.split /\s*=\s*/ 59 | case part[0] 60 | when "fg" 61 | color = part[1] 62 | when "bg" 63 | bgcolor = part[1] 64 | when "style" 65 | style = part[1] 66 | when "/end", "end" 67 | yield :endcolor if block_given? 68 | #next 69 | throw :done 70 | else 71 | # attrib 72 | attrib = part[0] 73 | end 74 | } 75 | # 2013-03-25 - 13:31 if numeric color specified 76 | color = color.to_i if color =~ /^[0-9]+$/ 77 | bgcolor = bgcolor.to_i if bgcolor =~ /^[0-9]+$/ 78 | yield [color,bgcolor,attrib,style] if block_given? 79 | end # catch 80 | else 81 | text = e 82 | yield text if block_given? 83 | end 84 | } 85 | end 86 | 87 | end 88 | end 89 | -------------------------------------------------------------------------------- /lib/canis/core/include/rhistory.rb: -------------------------------------------------------------------------------- 1 | # ----------------------------------------------------------------------------- # 2 | # File: rhistory.rb 3 | # Description: a module that pops up history, and then updates selected value 4 | # This goes with Field. 5 | # e.g., 6 | # field.extend(FieldHistory) 7 | # 8 | # The module name History was throwing up errors 9 | # Author: jkepler http://github.com/mare-imbrium/canis/ 10 | # Date: 2011-11-27 - 18:10 11 | # License: Same as Ruby's License (http://www.ruby-lang.org/LICENSE.txt) 12 | # Last update: 2014-08-19 00:02 13 | # ----------------------------------------------------------------------------- # 14 | # 15 | # supply history for this object, at least give an empty array 16 | # widget would typically be Field, 17 | # otherwise it should implement *text()* for getting and setting value 18 | # and a *CHANGED* event for when user has modified a value and moved out 19 | # You can externally set $history_key to any unused key, otherwise it is M-h 20 | module Canis 21 | extend self 22 | module FieldHistory 23 | def self.extended(obj) 24 | 25 | obj.instance_exec { 26 | @history ||= [] 27 | $history_key ||= ?\M-h 28 | # ensure that the field is not overriding this in handle_key 29 | bind_key($history_key) { _show_history } 30 | # widget should have CHANGED event, or this will either give error, or just not work 31 | # else please update history whenever you want a value to be retrieved 32 | bind(:CHANGED) { @history << @text if @text && (!@history.include? @text) } 33 | } 34 | end 35 | 36 | # pass the array of history values 37 | # Trying out a change where an item can also be sent in. 38 | # I am lost, i want the initialization to happen once. 39 | def history arr 40 | return @history unless arr 41 | if arr.is_a? Array 42 | @history = arr 43 | else 44 | @history << arr unless @history.include? arr 45 | end 46 | end 47 | def history=(x); history(x); end 48 | 49 | # pass in some configuration for histroy such as row and column to show popup on 50 | def history_config config={} 51 | @_history_config = config 52 | end 53 | 54 | # popup the hist 55 | # 56 | private 57 | def _show_history 58 | return unless @history 59 | return if @history.empty? 60 | list = @history 61 | @_history_config ||= {} 62 | #list = ["No history"] if @history.empty? 63 | raise ArgumentError, "show_history got nil list" unless list 64 | # calculate r and c 65 | # col if fine, except for when there's a label. 66 | wcol = 0 # taking care of when dialog uses history 2012-01-4 67 | wcol = self.form.window.left if self.form 68 | c = wcol + ( @field_col || @col) # this is also dependent on window coords, as in a status_window or messagebox 69 | sz = @history.size 70 | wrow = 0 71 | wrow = self.form.window.top if self.form 72 | crow = wrow + @row 73 | # if list can be displayed above, then fit it just above 74 | if crow > sz + 2 75 | r = crow - sz - 2 76 | else 77 | # else fit it in next row 78 | r = crow + 1 79 | end 80 | #r = @row - 10 81 | #if @row < 10 82 | #r = @row + 1 83 | #end 84 | r = @_history_config[:row] || r 85 | c = @_history_config[:col] || c 86 | ret = popuplist(list, :row => r, :col => c, :title => " History ", :color => :white, :bgcolor => :cyan) 87 | if ret 88 | self.text = list[ret] 89 | self.set_form_col 90 | end 91 | @form.repaint if @form 92 | @window.wrefresh if @window 93 | end 94 | end # mod History 95 | end # mod RubyC 96 | -------------------------------------------------------------------------------- /examples/testkeypress.rb: -------------------------------------------------------------------------------- 1 | # demo to test keypresses 2 | # press any key and see the value that ncurses or our routines catch. 3 | # Press alt and control combinations and Function keys 4 | # Ideally each key should return only one value. Sometimes, some TERM setting 5 | # or terminal emulator may not give correct values or may give different values 6 | # from what we are expecting. 7 | # Exit using 'q'. 8 | # # see window.rb for keycodes related to shift+F C_LEFT C_RIGHT etc. 9 | require 'logger' 10 | require 'canis' 11 | #require 'canis/core/widgets/rtextview' 12 | if $0 == __FILE__ 13 | include Canis 14 | include Canis::Utils 15 | 16 | dumpfile = "dump.yml" 17 | 18 | begin 19 | # Initialize curses 20 | Canis::start_ncurses # this is initializing colors via ColorMap.setup 21 | $log = Logger.new((File.join(ENV["LOGDIR"] || "./" ,"canis14.log"))) 22 | 23 | $log.level = Logger::DEBUG 24 | 25 | @window = Canis::Window.root_window 26 | 27 | catch(:close) do 28 | colors = Ncurses.COLORS 29 | $log.debug "START #{colors} colors ---------" 30 | @form = Form.new @window 31 | r = 1; c = 1; 32 | w = Ncurses.COLS - c 33 | h = Ncurses.LINES - 4 34 | FFI::NCurses.set_escdelay(500) 35 | nd = Ncurses.ESCDELAY 36 | 37 | # please use a hash to pass these values, avoid this old style 38 | # i want to move away from it as it comlpicates code 39 | texta = TextPad.new @form do 40 | name "mytext" 41 | row r 42 | col c 43 | width w 44 | height h 45 | #editable false 46 | focusable false 47 | title "[ Keypresses ]" 48 | #title_attrib (Ncurses::A_REVERSE | Ncurses::A_BOLD) 49 | title_attrib (Ncurses::A_BOLD) 50 | end 51 | help = "q to quit. escdelay is #{nd}. Check keys. F1..10, C-a..z, Alt a-zA-Z0-9, C-left,rt, Sh-F5..10 .: #{$0}" 52 | help1 = "Press in quick succession: 1) M-[, w and (2) M-[, M-w. (3) M-Sh-O, w." 53 | Canis::Label.new @form, {'text' => help, "row" => r+h+1, "col" => 2, "color" => "yellow"} 54 | Canis::Label.new @form, {'text' => help1, "row" => r+h+2, "col" => 2, "color" => "green"} 55 | texta.text = ["Press any key, Function, control, alt etc to see ","if it works.", 56 | "See window.rb for keycodes if something is not being trapped properly"] 57 | 58 | @form.repaint 59 | @window.wrefresh 60 | Ncurses::Panel.update_panels 61 | while((ch = @window.getchar()) != 999 ) 62 | str = keycode_tos ch if ch.is_a? Integer 63 | #str1 = @window.key_tos ch if ch.is_a? Integer 64 | str1 = $key_chr 65 | $log.debug "#{ch} got (#{str} $key_chr:#{str1})" 66 | texta << "#{ch} got (#{str}) #{str1}" 67 | if ch == 99999 68 | name = get_string "Add a name for #{$key_chr}?" 69 | if name 70 | texta << "setting #{name} for #{str1} " 71 | $kh[str1] = name 72 | _name = $kh[str1] 73 | texta << "set #{_name} for #{str1} " 74 | $kh.each_pair { |name, val| $log.debug " MMMAP #{name} : #{val}" } 75 | File.open(dumpfile, 'w' ) do |f| 76 | f << YAML::dump($kh) 77 | end 78 | texta << "Written new keys to #{dumpfile}. Pls copy over ~/ncurses-keys.yml" 79 | end 80 | end 81 | texta.goto_end 82 | texta.repaint 83 | @form.repaint 84 | @window.wrefresh 85 | break if ch == ?\q.getbyte(0) 86 | #break if ch == "q" 87 | end 88 | end 89 | rescue => ex 90 | ensure 91 | @window.destroy if !@window.nil? 92 | Canis::stop_ncurses 93 | p ex if ex 94 | p(ex.backtrace.join("\n")) if ex 95 | $log.debug( ex) if ex 96 | $log.debug(ex.backtrace.join("\n")) if ex 97 | end 98 | end 99 | -------------------------------------------------------------------------------- /examples/testcombo.rb: -------------------------------------------------------------------------------- 1 | require 'logger' 2 | require 'canis' 3 | require 'canis/core/widgets/rcombo' 4 | require 'canis/core/include/appmethods.rb' 5 | def help_text 6 | <<-eos 7 | # COMBO HELP 8 | 9 | This is some help text for |Combos| 10 | 11 | You may press to invoke the popup and then use arrow keys to traverse. (j and k may also work) 12 | 'v' on the popup to select (or whatever has been set as $row_selector (#{keycode_tos($row_selector)}) ) 13 | 14 | You may press the first character of the desired item to see all those items starting with that char. 15 | e.g. pressing "v" will cycle through vt100 and vt102 16 | 17 | 18 | If the `arrow_key_policy` is set to `:popup` then a down arrow will also invoke popup. However, if you 19 | prefer the down arrow to go to the next field, use `:ignore`. In that case, only can trigger POPUP. 20 | 21 | ----------------------------------------------------------------------- 22 | eos 23 | end 24 | if $0 == __FILE__ 25 | 26 | include Canis 27 | include Canis::Utils 28 | 29 | begin 30 | # Initialize curses 31 | Canis::start_ncurses # this is initializing colors via ColorMap.setup 32 | path = File.join(ENV["LOGDIR"] || "./" ,"canis14.log") 33 | file = File.open(path, File::WRONLY|File::TRUNC|File::CREAT) 34 | $log = Logger.new(path) 35 | $log.level = Logger::DEBUG 36 | 37 | @window = Canis::Window.root_window 38 | # Initialize few color pairs 39 | # Create the window to be associated with the form 40 | # Un post form and free the memory 41 | 42 | catch(:close) do 43 | colors = Ncurses.COLORS 44 | $log.debug "START #{colors} colors testcombo.rb --------- #{@window} " 45 | @form = Form.new @window 46 | title = (" "*30) + "Demo of Combo (F10 quits, F1 help, SPACE to popup combo) " + Canis::VERSION 47 | Label.new @form, {:text => title, :row => 1, :col => 0, :color => :green, :bgcolor => :black} 48 | r = 3; fc = 12; 49 | 50 | 51 | 52 | cb = ComboBox.new @form, :row => 7, :col => 2, :width => 20, 53 | :list => %w[xterm xterm-color xterm-256color screen vt100 vt102], 54 | :arrow_key_policy => :popup, 55 | :label => "Declare terminal as: " 56 | 57 | # arrow_key_policy can be popup or ignore 58 | # width is used to place combo symbol and popup and should be calculated 59 | # from label.text 60 | 61 | @form.help_manager.help_text = help_text 62 | #@form.bind_key(FFI::NCurses::KEY_F1, 'help') { display_app_help help_text() } 63 | #@form.bind_key(FFI::NCurses::KEY_F1, 'help') { display_app_help } 64 | @form.repaint 65 | @window.wrefresh 66 | Ncurses::Panel.update_panels 67 | 68 | # the main loop 69 | 70 | while((ch = @window.getchar()) != FFI::NCurses::KEY_F10 ) 71 | break if ch == ?\C-q.getbyte(0) 72 | begin 73 | @form.handle_key(ch) 74 | 75 | rescue => err 76 | $log.error( err) if err 77 | $log.error(err.backtrace.join("\n")) if err 78 | textdialog err 79 | $error_message.value = "" 80 | end 81 | 82 | # this should be avoided, we should not muffle the exception and set a variable 83 | # However, we have been doing that 84 | if $error_message.get_value != "" 85 | alert($error_message, {:bgcolor => :red, 'color' => 'yellow'}) if $error_message.get_value != "" 86 | $error_message.value = "" 87 | end 88 | 89 | @window.wrefresh 90 | end # while loop 91 | end # catch 92 | rescue => ex 93 | ensure 94 | $log.debug " -==== EXCEPTION =====-" 95 | $log.debug( ex) if ex 96 | $log.debug(ex.backtrace.join("\n")) if ex 97 | @window.destroy if !@window.nil? 98 | Canis::stop_ncurses 99 | puts ex if ex 100 | puts(ex.backtrace.join("\n")) if ex 101 | end 102 | end 103 | -------------------------------------------------------------------------------- /lib/canis/core/docs/index.txt: -------------------------------------------------------------------------------- 1 | # G E N E R A L H E L P 2 | ======================= 3 | to quit this window. to scroll. 4 | goes to next link in help file. Press to open link. 5 | 6 | _____________________________________________________________________ 7 | ## 1. General Help 8 | 9 | F10 - exit application. Also, 10 | F1 - Help 11 | 12 | In some applications, the following may have been provided: 13 | 14 | ? - Should display all the key bindings available 15 | for that widget. Alternatively, 16 | 17 | Alt-x - additional commands 18 | : - additional commands 19 | :! - system command 20 | c - system command 21 | 22 | 23 | _____________________________________________________________________ 24 | ## 2. Form 25 | 26 | and navigate between widgets. 27 | 28 | _____________________________________________________________________ 29 | ## 3. Multiline widgets 30 | 31 | In most widgets such as [[list]], [[textpad]], [[table]], and [[tree]] the 32 | following vim-bindings are applicable. 33 | 34 | j,k,l,h, gg, G, C-d, C-b, C-e, C-y, w, b, / 35 | 36 | scrolls 37 | scroll up (aka C-@) 38 | 39 | In multiline widgets such as _list_ and _tree_ to move to first item 40 | starting with a character, press "f" followed by that character. 41 | 42 | 43 | _____________________________________________________________________ 44 | ## 4. Editable Widgets 45 | 46 | In _field_ and editable _textarea_, some emacs/Pine keys such as C-e, 47 | C-a, C-k (delete till end) are available. 48 | 49 | - start of line 50 | - end of line 51 | - delete till end of line 52 | - toggle overwrite mode 53 | 54 | _____________________________________________________________________ 55 | ## 5. Buttons 56 | 57 | _Button_ can be fired using . The default button, if 58 | declared, is shown with as > Ok < as against other buttons, shown as [ 59 | Cancel ]. This can be fired by hitting anywhere on the form 60 | (unless the current widget traps ). 61 | 62 | Pressing will trigger the default button, even if you are on 63 | another button. 64 | 65 | Hotkeys are available using Alt- and will be displayed with an 66 | underline if the TERM displays underlines. 67 | 68 | _____________________________________________________________________ 69 | ## 6. Others 70 | 71 | ### 6.1 Selection in lists 72 | 73 | Please note that in earlier versions, was used for selection. 74 | However, since this conflicts with the scrolling behavior in most 75 | multiline widgtes, and are now exclusively used for 76 | scrolling down and up. The characters "v" and "V" are used for 77 | selection. This may be altered by setting "`$row_selector`". 78 | 79 | v - toggle selection 80 | V - range select 81 | a - select all 82 | u - unselect all (should be changed, as conflicts with 83 | vim undo) 84 | * - invert selection 85 | 86 | - - `ask_unselect` 87 | + - `ask_select` 88 | 89 | ### 6.2 Terminal (TERM) settings 90 | 91 | Some terminals (such as xterm) show underlines , however the 256color ones 92 | do not. 93 | 94 | Some terminals mess the screen output such as `xterm-256color`. At least 95 | while working under `tmux` in `iTerm` (with solarized), this terminal was 96 | messing the output. 97 | Things are fine with `screen-256color`. 98 | 99 | ### 6.3 Other components 100 | 101 | - [[tabbedpane]] 102 | _____________________________________________________________________ 103 | q to quit, gg to goto top. goes to next link in help file 104 | Press to open link. 105 | -------------------------------------------------------------------------------- /examples/testprogress.rb: -------------------------------------------------------------------------------- 1 | # ----------------------------------------------------------------------------- # 2 | # File: testprogress.rb 3 | # Description: a test program for progress bar widget 4 | # creates two progress bars, one old style and one new 5 | # Keeps running till 'q' pressed. 6 | # Author: jkepler http://github.com/mare-imbrium/canis/ 7 | # Date: 2014-03-27 - 23:31 8 | # License: Same as Ruby's License (http://www.ruby-lang.org/LICENSE.txt) 9 | # Last update: 2014-04-05 12:39 10 | # ----------------------------------------------------------------------------- # 11 | # testprogress.rb Copyright (C) 2012-2014 kepler kumar 12 | require 'logger' 13 | require 'canis' 14 | require 'canis/core/widgets/rprogress' 15 | if $0 == __FILE__ 16 | 17 | #include Canis 18 | 19 | begin 20 | # Initialize curses 21 | Canis::start_ncurses # this is initializing colors via ColorMap.setup 22 | path = File.join(ENV["LOGDIR"] || "./" ,"canis14.log") 23 | file = File.open(path, File::WRONLY|File::TRUNC|File::CREAT) 24 | $log = Logger.new(path) 25 | $log.level = Logger::DEBUG 26 | 27 | @window = Canis::Window.root_window 28 | 29 | catch(:close) do 30 | @form = Form.new @window 31 | title = (" "*30) + "Demo of Progress Bar (q quits, s - slow down) " + Canis::VERSION 32 | Label.new @form, {:text => title, :row => 1, :col => 0, :color => :green, :bgcolor => :black} 33 | Label.new @form, {:text => "Press q to quit, s/f make slow or fast", :row => 10, :col => 10} 34 | Label.new @form, {:text => "Old style and modern style progress bars", :row => 12, :col => 10} 35 | r = 14; fc = 12; 36 | 37 | pbar1 = Progress.new @form, {:width => 20, :row => r, :col => fc, 38 | :name => "pbar1", :style => :old} 39 | #:bgcolor => :white, :color => 'red', :name => "pbar1", :style => :old} 40 | pbar = Progress.new @form, {:width => 20, :row => r+2, :col => fc, 41 | :bgcolor => :white, :color => :red, :name => "pbar"} 42 | 43 | pbar.visible false 44 | pb = @form.by_name["pbar"] 45 | pb.visible true 46 | len = 1 47 | ct = (100) * 1.00 48 | pb.fraction(len/ct) 49 | pbar1.fraction(len/ct) 50 | i = ((len/ct)*100).to_i 51 | i = 100 if i > 100 52 | pb.text = "completed:#{i}" 53 | 54 | 55 | @form.repaint 56 | @window.wrefresh 57 | Ncurses::Panel.update_panels 58 | 59 | # the main loop 60 | 61 | # this is so there's no wait for a key, i want demo to proceed without key press, but 62 | # respond if there is one 63 | Ncurses::nodelay(@window.get_window, bf = true) 64 | 65 | # sleep seconds between refresh of progress 66 | slp = 0.1 67 | ##while((ch = @window.getchar()) != FFI::NCurses::KEY_F10 ) 68 | #break if ch == ?\C-q.getbyte(0) 69 | while((ch = @window.getch()) != 27) 70 | break if ch == "q".ord 71 | ## slow down 72 | if ch == "s".ord 73 | slp *= 2 74 | elsif ch == "f".ord 75 | ## make faster 76 | slp /= 2.0 77 | end 78 | begin 79 | 80 | sleep(slp) 81 | len += 1 82 | if len > 100 83 | len = 1 84 | sleep(1.0) 85 | end 86 | ct = (100) * 1.00 87 | pb.fraction(len/ct) 88 | pbar1.fraction(len/ct) 89 | i = ((len/ct)*100).to_i 90 | i = 100 if i > 100 91 | pb.text = "completed:#{i}" 92 | 93 | #@form.handle_key(ch) 94 | @form.repaint 95 | @window.wrefresh 96 | Ncurses::Panel.update_panels 97 | #end 98 | 99 | rescue => err 100 | break 101 | end 102 | 103 | @window.wrefresh 104 | end # while loop 105 | end # catch 106 | rescue => ex 107 | ensure 108 | $log.debug " -==== EXCEPTION =====-" 109 | $log.debug( ex) if ex 110 | $log.debug(ex.backtrace.join("\n")) if ex 111 | @window.destroy if !@window.nil? 112 | Canis::stop_ncurses 113 | puts ex if ex 114 | puts(ex.backtrace.join("\n")) if ex 115 | end 116 | end 117 | -------------------------------------------------------------------------------- /lib/canis/core/include/canisparser.rb: -------------------------------------------------------------------------------- 1 | # ----------------------------------------------------------------------------- # 2 | # File: canisparser.rb 3 | # Description: creates an returns instances of parser objects 4 | # Author: j kepler http://github.com/mare-imbrium/canis/ 5 | # Date: 2014-06-11 - 12:23 6 | # License: MIT 7 | # Last update: 2014-06-16 16:56 8 | # ----------------------------------------------------------------------------- # 9 | # canisparser.rb Copyright (C) 2012-2014 j kepler 10 | module Canis 11 | 12 | # Uses multiton pattern from http://blog.rubybestpractices.com/posts/gregory/059-issue-25-creational-design-patterns.html 13 | # to create and returns cached instances of a text parser. 14 | # Users will call the +[]+ method rather than the +new+ method. 15 | # If users wish to declare their own custom parser, then the +map+ method is to be used. 16 | # 17 | # @example 18 | # 19 | # CanisParser[:tmux] 20 | # 21 | # To define your own parser: 22 | # 23 | # CanisParser.map( :custom => [ 'canis/core/include/customparser', 'Canis::CustomParser' ] 24 | # 25 | # and later at some point, 26 | # 27 | # CanisParser[:custom] 28 | # 29 | class CanisParser 30 | class << self 31 | # hash storing a filename and classname per content_type 32 | def content_types 33 | #@content_types ||= {} 34 | unless @content_types 35 | @content_types = {} 36 | #map(:tmux => [ 'canis/core/util/defaultcolorparser', 'DefaultColorParser']) 37 | #map(:ansi => [ 'canis/core/util/ansiparser', 'AnsiParser'] ) 38 | @content_types[:tmux] = [ 'canis/core/util/defaultcolorparser', 'DefaultColorParser'] 39 | @content_types[:ansi] = [ 'canis/core/util/ansiparser', 'AnsiParser'] 40 | end 41 | return @content_types 42 | end 43 | # hash storing a parser instance per content_type 44 | def instances 45 | @instances ||= {} 46 | end 47 | # Used by user to define a new parser 48 | # map( :tmux => ['filename', 'klassname'] ) 49 | def map(params) 50 | content_types.update params 51 | end 52 | 53 | # Used by user to retrieve a parser instance, creating one if not present 54 | # CanisParser[:tmux] 55 | def [](name) 56 | $log.debug " [] got #{name} " 57 | raise "nil received by [] " unless name 58 | instances[name] ||= new(content_types[name]) 59 | #instances[name] ||= create(content_types[name]) 60 | end 61 | def create args 62 | filename = args.first 63 | klassname = args[1] 64 | $log.debug " canisparser create got #{args} " 65 | require filename 66 | clazz = Object.const_get(klassname).new 67 | $log.debug " created #{clazz.class} " 68 | # clazz = 'Foo::Bar'.split('::').inject(Object) {|o,c| o.const_get c} 69 | return clazz 70 | end 71 | end 72 | ## WARNING - this creates a CanisParser class which we really can't use. 73 | # So we need to delegate to the color parse we created. 74 | # create and return a parser instance 75 | # Canisparser.new filename, klassname 76 | # Usually, *not* called by user, since this instance is not cached. Use +map+ 77 | # and then +[]+ instead for creating and cacheing. 78 | def initialize *args 79 | args = args.flatten 80 | filename = args.first 81 | klassname = args[1] 82 | $log.debug " canisparser init got #{args} " 83 | raise "Canisparser init got nil" unless filename 84 | require filename 85 | clazz = Object.const_get(klassname).new 86 | # clazz = 'Foo::Bar'.split('::').inject(Object) {|o,c| o.const_get c} 87 | #return clazz 88 | @clazz = clazz 89 | end 90 | # delegate call to color parser 91 | def parse_format s, *args, &block 92 | @clazz.parse_format(s, *args, &block) 93 | end 94 | # delegate all call to color parser 95 | def method_missing meth, *args, &block 96 | #$log.debug " canisparser got method_missing for #{meth}, sending to #{@clazz.class} " 97 | @clazz.send( meth, *args, &block) 98 | end 99 | end 100 | end 101 | -------------------------------------------------------------------------------- /lib/canis/core/include/layouts/flowlayout.rb: -------------------------------------------------------------------------------- 1 | # ----------------------------------------------------------------------------- # 2 | # File: stacklayout.rb 3 | # Description: 4 | # Author: j kepler http://github.com/mare-imbrium/canis/ 5 | # Date: 2014-05-08 - 18:33 6 | # License: MIT 7 | # Last update: 2017-03-09 23:13 8 | # ----------------------------------------------------------------------------- # 9 | # stacklayout.rb Copyright (C) 2012-2014 j kepler 10 | require 'canis/core/include/layouts/abstractlayout' 11 | # ---- 12 | # This does a simple left to right stacking of objects. 13 | # if no objects are passed to it, it will take all widgets from the form. 14 | # 15 | # Individual objects may be configured by setting :weight using +cset+. 16 | # layout = FlowLayout.new :height => -1, :top_margin => 1, :bottom_margin => 1, :left_margin => 1 17 | # layout.cset(obj, :weight, 15) # fixed width of 15 18 | # layout.cset(obj1, :weight, 0.50) # takes 50% of balance area (area not fixed) 19 | # 20 | class FlowLayout < AbstractLayout 21 | 22 | # @param [Form] optional give a form 23 | # @param [Hash] optional give settings/attributes which will be set into variables 24 | def initialize arg, config={}, &block 25 | super 26 | end 27 | 28 | 29 | # This program lays out the widgets deciding their row and columm and height and weight. 30 | # This program is called once at start of application, and again whenever a RESIZE event happens. 31 | def do_layout 32 | _init_layout 33 | r = @top_margin 34 | c = @left_margin 35 | # 36 | # determine fixed widths and how much is left to share with others, 37 | # and how many variable width components there are. 38 | ht = 0 # accumulate fixed height 39 | fixed_ctr = 0 # how many items have a fixed wt 40 | var_ctr = 0 41 | var_wt = 0.0 42 | @components.each do |e| 43 | $log.debug " looping 1 #{e.name} " 44 | _tmpwt = cget(e, :weight) || 0 45 | # what of field and button placed side by side 46 | if e.is_a? Field or e.is_a? Button or e.is_a? Label 47 | # what to do here ? 48 | @wts[e] ||= 1 49 | ht += @wts[e] || 1 50 | fixed_ctr += 1 51 | elsif _tmpwt >= 1 52 | ht += _tmpwt || 0 53 | fixed_ctr += 1 54 | elsif _tmpwt > 0 and _tmpwt <= 1 55 | # FIXME how to specify 100 % ??? 56 | var_ctr += 1 57 | var_wt += _tmpwt 58 | end 59 | end 60 | unaccounted = @components.count - (fixed_ctr + var_ctr) 61 | $log.debug " unacc #{unaccounted} , fixed #{fixed_ctr} , var : #{var_ctr} , ht #{ht} height #{@height} " 62 | balance_ht = @width - ht # use this for those who have specified a % 63 | balance_ht1 = balance_ht * (1 - var_wt ) 64 | average_ht = (balance_ht1 / unaccounted).floor # give this to those who have not specified ht 65 | average_ht = (balance_ht1 / unaccounted) # give this to those who have not specified ht 66 | $log.debug " #{balance_ht} , #{balance_ht1} , #{average_ht} " 67 | # not accounted for gap in heights 68 | rem = 0 # remainder to be carried over 69 | @components.each do |e| 70 | $log.debug " looping 2 #{e.name} #{e.class.to_s.downcase} " 71 | next if @ignore_list.include? e.class.to_s.downcase 72 | $log.debug " looping 3 #{e.name} " 73 | e.row = r 74 | e.col = c 75 | wt = cget(e, :weight) 76 | if wt 77 | if wt.is_a? Integer 78 | e.width = wt 79 | elsif wt.is_a? Float 80 | e.width = (wt * balance_ht).floor 81 | end 82 | else 83 | # no wt specified, give average of balance wt 84 | e.width = average_ht 85 | hround = e.width.floor 86 | 87 | rem += e.width - hround 88 | e.width = hround 89 | # see comment in prev block regarding remaininder 90 | if rem >= 1 91 | e.width += 1 92 | rem = 0 93 | end 94 | end 95 | $log.debug " layout #{e.name} , w: #{e.width} r: #{e.row} , c = #{e.col} " 96 | 97 | e.height = @height 98 | c += e.width.floor 99 | c += @gap 100 | end 101 | $log.debug " layout finished " 102 | end 103 | 104 | end 105 | -------------------------------------------------------------------------------- /lib/canis/core/include/layouts/stacklayout.rb: -------------------------------------------------------------------------------- 1 | # ----------------------------------------------------------------------------- # 2 | # File: stacklayout.rb 3 | # Description: 4 | # Author: j kepler http://github.com/mare-imbrium/canis/ 5 | # Date: 2014-05-08 - 18:33 6 | # License: MIT 7 | # Last update: 2017-03-09 23:14 8 | # ----------------------------------------------------------------------------- # 9 | # stacklayout.rb Copyright (C) 2012-2014 j kepler 10 | require 'canis/core/include/layouts/abstractlayout' 11 | # ---- 12 | # This does a simple stacking of objects. Or all objects. 13 | # Some simple layout managers may not require objects to be passed to 14 | # it, others that are complex may require the same. 15 | class StackLayout < AbstractLayout 16 | 17 | # @param [Form] optional give a form 18 | # @param [Hash] optional give settings/attributes which will be set into variables 19 | def initialize arg, config={}, &block 20 | @wts = {} 21 | super 22 | end 23 | 24 | 25 | # in case user does not wish to add objects, but wishes to specify the weightage on one, 26 | # send in the widget and its weightage. 27 | # 28 | # @param [Widget] widget whose weightage is to be specified 29 | # @param [Float, Integer] weightage for the given widget (@see add_with_weight) 30 | def weightage item, wt 31 | @wts[item] = wt 32 | end 33 | 34 | 35 | # This program lays out the widgets deciding their row and columm and height and weight. 36 | # This program is called once at start of application, and again whenever a RESIZE event happens. 37 | def do_layout 38 | _init_layout 39 | r = @top_margin 40 | c = @left_margin 41 | # 42 | # determine fixed widths and how much is left to share with others, 43 | # and how many variable width components there are. 44 | ht = 0 # accumulate fixed height 45 | fixed_ctr = 0 # how many items have a fixed wt 46 | var_ctr = 0 47 | var_wt = 0.0 48 | @components.each do |e| 49 | $log.debug " looping 1 #{e.name} " 50 | _tmpwt = @wts[e] || 0 51 | # what of field and button placed side by side 52 | if e.is_a? Field or e.is_a? Button or e.is_a? Label 53 | @wts[e] ||= 1 54 | ht += @wts[e] || 1 55 | fixed_ctr += 1 56 | elsif _tmpwt >= 1 57 | ht += @wts[e] || 0 58 | fixed_ctr += 1 59 | elsif _tmpwt > 0 and _tmpwt <= 1 60 | # FIXME how to specify 100 % ??? 61 | var_ctr += 1 62 | var_wt += @wts[e] 63 | end 64 | end 65 | unaccounted = @components.count - (fixed_ctr + var_ctr) 66 | $log.debug " unacc #{unaccounted} , fixed #{fixed_ctr} , var : #{var_ctr} , ht #{ht} height #{@height} " 67 | balance_ht = @height - ht # use this for those who have specified a % 68 | balance_ht1 = balance_ht * (1 - var_wt ) 69 | average_ht = (balance_ht1 / unaccounted).floor # give this to those who have not specified ht 70 | average_ht = (balance_ht1 / unaccounted) # give this to those who have not specified ht 71 | $log.debug " #{balance_ht} , #{balance_ht1} , #{average_ht} " 72 | # not accounted for gap in heights 73 | rem = 0 # remainder to be carried over 74 | @components.each do |e| 75 | $log.debug " looping 2 #{e.name} #{e.class.to_s.downcase} " 76 | next if @ignore_list.include? e.class.to_s.downcase 77 | $log.debug " looping 3 #{e.name} " 78 | e.row = r 79 | e.col = c 80 | wt = @wts[e] 81 | if wt 82 | if wt.is_a? Integer 83 | e.height = wt 84 | elsif wt.is_a? Float 85 | e.height = (wt * balance_ht).floor 86 | end 87 | else 88 | # no wt specified, give average of balance wt 89 | e.height = average_ht 90 | hround = e.height.floor 91 | 92 | rem += e.height - hround 93 | e.height = hround 94 | # see comment in prev block regarding remaininder 95 | if rem >= 1 96 | e.height += 1 97 | rem = 0 98 | end 99 | end 100 | $log.debug " layout #{e.name} , h: #{e.height} r: #{e.row} , c = #{e.col} " 101 | 102 | e.width = @width 103 | r += e.height.floor 104 | r += @gap 105 | end 106 | $log.debug " layout finished " 107 | end 108 | 109 | end 110 | -------------------------------------------------------------------------------- /examples/atree.rb: -------------------------------------------------------------------------------- 1 | require 'canis/core/util/app' 2 | 3 | App.new do 4 | var = Variable.new 5 | header = app_header "canis #{Canis::VERSION}", :text_center => "Tree Demo", :text_right =>"New Improved!", :color => :black, :bgcolor => :white, :attr => :bold 6 | message "Press Enter to expand/collapse" 7 | 8 | @form.bind_key(FFI::NCurses::KEY_F3) { 9 | require 'canis/core/util/viewer' 10 | Canis::Viewer.view("canis14.log", :close_key => KEY_ENTER, :title => " to close") 11 | } 12 | 13 | ww = FFI::NCurses.COLS-0 14 | flow :width => ww , :margin_top => 1, :height => FFI::NCurses.LINES-2 do 15 | stack :margin_top => 0, :width_pc => "30" do 16 | tm = nil 17 | atree = tree :height => 10, :title => '[ ruby way ]' do 18 | root "root" do 19 | branch "hello" do 20 | leaf "ruby" 21 | end 22 | branch "goodbye" do 23 | leaf "java" 24 | leaf "verbosity" 25 | end 26 | end 27 | end 28 | found=atree.get_node_for_path "goodbye" 29 | atree.set_expanded_state(atree.root, true) 30 | atree.set_expanded_state(found,true) 31 | 32 | # using a Hash 33 | model = { :ruby => [ "jruby", {:mri => %W[ 1.8.6 1.8.7]}, {:yarv => %W[1.9.1 1.9.2]}, "rubinius", "macruby" ], :python => %W[ cpython jython laden-swallow ] } 34 | tree :data => model, :title => "[ Hash ]" 35 | 36 | end # stack 37 | stack :margin_top => 0, :width_pc => "30" do 38 | 39 | # using an Array, these would be expanded on selection, using an event 40 | tree :data => Dir.glob("*"), :title=> "[ Array ]" do 41 | command do |node| 42 | # insert dir entries unless done so already 43 | if node.children && !node.children.empty? 44 | else 45 | f = node.user_object 46 | if File.directory? f 47 | l = Dir.glob(f + "/*") 48 | node.add(l) if l 49 | end 50 | end 51 | end 52 | bind :ENTER_ROW do |t| 53 | # now ENTER_ROW comes from TEXTpad and gives an event 54 | var.value = t.text 55 | end 56 | end 57 | 58 | # long way ISO 9001 certifed, SEI CMM 5 compliant 59 | # 60 | root = TreeNode.new "ROOT" 61 | subroot = TreeNode.new "subroot" 62 | leaf1 = TreeNode.new "leaf 1" 63 | leaf2 = TreeNode.new "leaf 2" 64 | model = DefaultTreeModel.new root 65 | #model.insert_node_into(subroot, root, 0) # BLEAH JAVA !! 66 | 67 | # slightly better, since we return self in ruby 68 | root << subroot 69 | subroot << leaf1 << leaf2 70 | leaf1 << "leaf11" 71 | leaf1 << "leaf12" 72 | 73 | # more rubyish way 74 | root.add "blocky", true do 75 | add "block2" 76 | add "block3" do 77 | add "block31" 78 | end 79 | end 80 | 81 | tree :data => model, :title => "[ legacy way ]" 82 | 83 | end 84 | #stack :margin_top => 2 do 85 | stack :margin_top => 0, :width_pc => "40", :height => :expand do 86 | # using height_pc as 100 was causing prefresh to fail if file lines went beyond 31 87 | # tput lines gives 32 so only when file length exceeded was it actually writing beyond screen 88 | t = textview :suppress_borders => true, :height_pc => 90, :color => :green, :bgcolor => :black 89 | var.command do |filename| 90 | filename = filename.value 91 | if File.directory? filename 92 | lines = Dir.entries(filename ) 93 | t.set_content lines 94 | elsif File.exist? filename 95 | # next line bombs on "invalid byte sequence on UTF-8" on split. 96 | lines = File.open(filename,'r').read.split("\n") 97 | # next line bombs on binary files. normally we would check file type using +file+ command 98 | t.set_content lines 99 | else 100 | alert " #{filename} does not appear to be a file " 101 | end 102 | end 103 | end 104 | end # flow 105 | end # app 106 | -------------------------------------------------------------------------------- /lib/canis/core/util/ansiparser.rb: -------------------------------------------------------------------------------- 1 | # ----------------------------------------------------------------------------- # 2 | # File: colorparser.rb 3 | # Description: Default parse for our tmux format 4 | # The aim is to be able to specify parsers so different kinds 5 | # of formatting or documents can be used, such as ANSI formatted 6 | # manpages. 7 | # Author: jkepler http://github.com/mare-imbrium/canis/ 8 | # Date: 07.11.11 - 13:17 9 | # License: Same as Ruby's License (http://www.ruby-lang.org/LICENSE.txt) 10 | # Last update: 2013-04-01 13:43 11 | # ----------------------------------------------------------------------------- # 12 | # == TODO 13 | # - perhaps we can compile the regexp once and reuse 14 | # 15 | 16 | module Canis 17 | class AnsiParser 18 | 19 | # NOTE: Experimental and minimal 20 | # parses the formatted string and yields either an array of color, bgcolor and attrib 21 | # or the text. This will be called by convert_to_chunk. 22 | # 23 | # Currently, assumes colors and attributes are correct. No error checking or fancy stuff. 24 | # s="#[fg=green]hello there#[fg=yellow, bg=black, dim]" 25 | # @since 1.4.1 2011-11-3 experimental, can change 26 | # @return [nil] knows nothign about output format. 27 | 28 | def parse_format s # yields attribs or text 29 | ## set default colors 30 | color = :white 31 | bgcolor = :black 32 | attrib = FFI::NCurses::A_NORMAL 33 | text = "" 34 | 35 | ## split #[...] 36 | #a = s.split /(#\[[^\]]*\])/ 37 | a = s.split /(\x1b\[\d*(?:;\d+)*?[a-zA-Z])/ 38 | a.each { |e| 39 | ## process color or attrib portion 40 | #[ "", "\e[1m", "", "\e[34m", "", "\e[47m", "Showing all items...", "\e[0m", "", "\e[0m", "\n"] 41 | if e[0,1] == "\x1b" && e[-1,1] == "m" 42 | 43 | #e.each { |f| x=/^.\[(.*).$/.match(f) 44 | $log.debug "XXX: ANSI e #{e} " 45 | x=/^.\[(.*).$/.match(e) 46 | color, bgcolor, attrib = nil, nil, nil 47 | $log.debug "XXX: ANSI #{x} ..... #{x[1]} " 48 | args = x[1].split ';' 49 | ## first split on commas to separate fg, bg and attr 50 | # http://ascii-table.com/ansi-escape-sequences.php 51 | args.each { |att| 52 | $log.debug "XXX: ANSI att: #{att} " 53 | case att.to_i 54 | when 0 55 | color, bgcolor, attrib = nil, nil, nil 56 | yield :reset # actually this resets all so we need an endall or clearall reset 57 | 58 | when 1 59 | attrib = 'bold' 60 | when 2 61 | attrib = 'dim' 62 | when 4 63 | attrib = 'underline' 64 | when 5 65 | attrib = 'blink' 66 | when 7 67 | attrib = 'reverse' 68 | when 8 69 | attrib = 'hidden' # XXX 70 | when 30 71 | color = 'black' 72 | when 31 73 | color = 'red' 74 | when 32 75 | color = 'green' 76 | when 33 77 | color = 'yellow' 78 | when 34 79 | color = 'blue' 80 | when 35 81 | color = 'magenta' 82 | when 36 83 | color = 'cyan' 84 | when 37 85 | color = 'white' 86 | 87 | #Background colors 88 | when 40 89 | bgcolor = 'black' 90 | when 41 91 | bgcolor = 'red' 92 | when 42 93 | bgcolor = 'green' 94 | when 43 95 | bgcolor = 'yellow' 96 | when 44 97 | bgcolor = 'blue' 98 | when 45 99 | bgcolor = 'magenta' 100 | when 46 101 | bgcolor = 'cyan' 102 | when 47 103 | bgcolor = 'white' 104 | else 105 | $log.warn "XXX: WARN ANSI not used #{att} " 106 | end 107 | } # args.ea 108 | #} # e.each 109 | $log.debug "XXX: ANSI YIELDING #{color} , #{bgcolor} , #{attrib} " 110 | yield [color,bgcolor,attrib] if block_given? 111 | else 112 | text = e 113 | yield text if block_given? 114 | end 115 | } # a.each 116 | end 117 | 118 | end 119 | end 120 | -------------------------------------------------------------------------------- /lib/canis/core/widgets/rprogress.rb: -------------------------------------------------------------------------------- 1 | #require 'ncurses' 2 | require 'logger' 3 | require 'canis' 4 | 5 | #include Ncurses # FFI 2011-09-8 6 | include Canis 7 | module Canis 8 | extend self 9 | ## 10 | # TODO user may want to print a label on progress: like not started or complete. 11 | class Progress < Widget 12 | dsl_property :width # please give this to ensure the we only print this much 13 | dsl_property :fraction # how much to cover 14 | dsl_property :char # what char to use for filling, default space 15 | dsl_property :text # text to put over bar 16 | dsl_accessor :style # :old or nil/anything else 17 | dsl_accessor :surround_chars # "[]" 18 | 19 | def initialize form, config={}, &block 20 | 21 | @row = config.fetch("row",-1) 22 | @col = config.fetch("col",-1) 23 | @bgcolor = config.fetch("bgcolor", $def_bg_color) 24 | @color = config.fetch("color", $def_fg_color) 25 | @name = config.fetch("name", "pbar") 26 | @editable = false 27 | @focusable = false 28 | super 29 | @surround_chars ||= "[]" # for :old style 30 | @repaint_required = true 31 | end 32 | def getvalue 33 | @fraction || 0.0 34 | end 35 | 36 | ## 37 | # 38 | def repaint 39 | return unless @repaint_required 40 | $log.debug " XXXX PBAR inside repaint #{@color} , #{@fraction} " 41 | r,c = rowcol 42 | #value = getvalue_for_paint 43 | acolor = get_color @bgcolor 44 | bcolor = get_color @color 45 | @graphic = @form.window if @graphic.nil? ## HACK messagebox givig this in repaint, 423 not working ?? 46 | len = 0 47 | w2 = @width - 6 #2 account for brackets and printing of percentage 48 | if @fraction 49 | @fraction = 1.0 if @fraction > 1.0 50 | @fraction = 0 if @fraction < 0 51 | if @fraction > 0 52 | len = @fraction * @width 53 | end 54 | end 55 | if @style == :old 56 | ftext="" 57 | char = @char || "=" 58 | if @fraction && @fraction >= 0 59 | len = @fraction * (w2) 60 | ftext << sprintf("%3d%s",(@fraction * 100).to_i, "%") 61 | end 62 | incomplete = w2 - len 63 | complete = len 64 | # I am printing 2 times since sometimes the ending bracket gets printed one position less 65 | str = @surround_chars[0] + " "*w2 + @surround_chars[1] + ftext 66 | @graphic.printstring r, c, str , acolor,@attr 67 | str = char*complete 68 | str[-1] = ">" if char == "=" && complete > 2 69 | @graphic.printstring r, c+1, str , acolor,@attr 70 | else 71 | 72 | char = @char || " " 73 | # first print the background horizonal bar 74 | @graphic.printstring r, c, " " * @width , acolor,@attr 75 | 76 | # if the user has passed a percentage we need to print that in @color 77 | if @fraction 78 | #bcolor = get_color @color 79 | #@fraction = 1.0 if @fraction > 1.0 80 | #@fraction = 0 if @fraction < 0 81 | #if @fraction > 0 82 | #len = @fraction * @width 83 | #char = @char || " " 84 | 85 | # if text is to printed over the bar 86 | if @text 87 | textcolor = get_color $datacolor, 'black' 88 | txt = @text 89 | txt = @text[0..@width] if @text.length > @width 90 | textattr = 'bold' 91 | # write the text in a color that contrasts with the background 92 | # typically black 93 | @graphic.printstring r, c, txt , textcolor, textattr if @text 94 | 95 | # now write the text again, in a color that contrasts with the progress 96 | # bar color that is expanding. However, the text must be padded to len and truncated 97 | # to len as well. it must be exactly len in size. 98 | txt = sprintf("%-*s", len, txt) 99 | if len > 0 100 | if len < txt.length 101 | txt = txt[0..len] 102 | end 103 | textcolor = get_color $datacolor, 'white', @color 104 | @graphic.printstring r, c, txt , textcolor, textattr if @text 105 | end 106 | else 107 | # no text was given just print a horizontal bar 108 | @graphic.printstring r, c, char * len , bcolor, 'reverse' 109 | end 110 | end # frac > 0 111 | end # fraction 112 | end # style 113 | @repaint_required = false 114 | end 115 | def repaint_old 116 | end 117 | # ADD HERE progress 118 | end 119 | -------------------------------------------------------------------------------- /lib/canis/core/system/panel.rb: -------------------------------------------------------------------------------- 1 | require "ffi-ncurses" 2 | module Ncurses # changed on 2011-09-8 3 | # making minimal changes as per ffi-ncurses 0.4.0 which implements panels 4 | #module Canis # too many places call Ncurses::Panel 5 | class Panel #< Struct.new(:pointer) 6 | 7 | def initialize(window) 8 | if window.respond_to?(:pointer) 9 | @pointer = FFI::NCurses.new_panel(window.pointer) 10 | else 11 | @pointer = FFI::NCurses.new_panel(window) 12 | end 13 | end 14 | def pointer 15 | @pointer 16 | end 17 | 18 | # Puts panel below all other panels. 19 | def bottom_panel 20 | FFI::NCurses.bottom_panel(@pointer) 21 | end 22 | alias bottom bottom_panel 23 | 24 | # Put the visible panel on top of all other panels in the stack. 25 | # 26 | # To ensure compatibility across platforms, use this method instead of 27 | # {show_panel} when the panel is shown. 28 | def top_panel 29 | FFI::NCurses.top_panel(@pointer) 30 | end 31 | alias top top_panel 32 | 33 | # Makes hidden panel visible by placing it on the top of the stack. 34 | # 35 | # To ensure compatibility across platforms, use this method instead of 36 | # {top_panel} when the panel is hidden. 37 | def show_panel 38 | FFI::NCurses.show_panel(@pointer) 39 | end 40 | alias show show_panel 41 | 42 | # Removes the given panel from the panel stack and thus hides it from 43 | # view. 44 | # The PANEL structure is not lost, merely removed from the stack. 45 | def hide_panel 46 | FFI::NCurses.hide_panel(@pointer) 47 | end 48 | alias hide hide_panel 49 | 50 | # Returns a pointer to the window of the given panel. 51 | def panel_window 52 | FFI::NCurses.panel_window(@pointer) 53 | end 54 | alias window panel_window 55 | 56 | # Replace the window of the panel with the given window. 57 | # Useful, for example, if you want to resize a panel. 58 | # You can call {replace_panel} on the output of {wresize}. 59 | # It does not change the position of the panel in the stack. 60 | def replace_panel(window) 61 | FFI::NCurses.replace_panel(@pointer, window) 62 | end 63 | alias replace replace_panel 64 | 65 | # Move the panel window so that its upper-left corner is at 66 | # (+starty+,+startx+). 67 | # It does not change the position of the panel in the stack. 68 | # Be sure to use this method instead of {mvwin}, to move a panel window. 69 | def move_panel(starty = 0, startx = 0) 70 | FFI::NCurses.move_panel(@pointer, starty, startx) 71 | end 72 | alias move move_panel 73 | 74 | # Returns true if the panel is in the panel stack, false if not. 75 | # Returns ERR if the panel pointer is a null pointer. 76 | def panel_hidden 77 | FFI::NCurses.panel_hidden(@pointer) == 0 78 | end 79 | alias hidden? panel_hidden 80 | 81 | # Returns pointer to the panel above. 82 | def panel_above 83 | FFI::NCurses.panel_above(@pointer) 84 | end 85 | alias above panel_above 86 | 87 | # Return a pointer to the panel just below panel. 88 | # If the panel argument is a pointer to 0, it returns a pointer to the 89 | # top panel in the stack. 90 | def panel_below 91 | FFI::NCurses.panel_below(@pointer) 92 | end 93 | alias below panel_below 94 | 95 | # Returns the user pointer for a given panel. 96 | def panel_userptr 97 | FFI::NCurses.panel_userptr(@pointer) 98 | end 99 | alias userptr panel_userptr 100 | 101 | # sets the panel's user pointer. 102 | def set_panel_userptr(user_pointer) 103 | FFI::NCurses.set_panel_userptr(@pointer, user_pointer) 104 | end 105 | alias userptr= set_panel_userptr 106 | 107 | # Remove the panel from the stack and deallocate the PANEL structure. 108 | # Doesn't remove the associated window. 109 | def del_panel 110 | FFI::NCurses.del_panel(@pointer) 111 | end 112 | alias del del_panel 113 | alias delete del_panel 114 | 115 | class << self 116 | # these will be used when you say Ncurses::Panel.del_panel(@panel.pointer) 117 | # You could directly say FFI:NCurses or even @panel.del_panel. 118 | def update_panels 119 | FFI::NCurses.update_panels 120 | end 121 | def method_missing(name, *args) 122 | if (FFI::NCurses.respond_to?(name)) 123 | return FFI::NCurses.send(name, *args) 124 | end 125 | raise "Panel did not respond_to #{name} " 126 | end 127 | end 128 | end 129 | end 130 | -------------------------------------------------------------------------------- /examples/newtabbedwindow.rb: -------------------------------------------------------------------------------- 1 | # this is a test program, tests out tabbed panes. type F1 to exit 2 | # 3 | require 'logger' 4 | require 'canis' 5 | require 'canis/core/widgets/rtabbedpane' 6 | require 'canis/core/widgets/rcontainer' 7 | require 'canis/core/widgets/rcombo' 8 | require 'canis/core/widgets/rtabbedwindow' 9 | 10 | include Canis 11 | class SetupTabbedPane 12 | def run 13 | $config_hash ||= Variable.new Hash.new 14 | #configvar.update_command(){ |v| $config_hash[v.source()] = v.value } 15 | 16 | r = Container.new nil, :suppress_borders => true 17 | l1 = Label.new nil, :name => "profile", :attr => 'bold', :text => "Profile" 18 | f1 = LabeledField.new nil, :name => "name", :maxlen => 20, :width => 20, :bgcolor => :white, 19 | :color => :black, :text => "abc", :label => ' Name: ' 20 | f2 = LabeledField.new nil, :name => "email", :width => 20, :bgcolor => :white, 21 | :color => :blue, :text => "me@google.com", :label => 'Email: ' 22 | f3 = RadioButton.new nil, :variable => $config_hash, :text => "red", :value => "RED", :color => :red 23 | f4 = RadioButton.new nil, :variable => $config_hash, :text => "blue", :value => "BLUE", :color => :blue 24 | f5 = RadioButton.new nil, :variable => $config_hash, :text => "green", :value => "GREEN", :color => :green 25 | r.add(l1,f1) 26 | r.add(f2) 27 | r.add(f3,f4,f5) 28 | 29 | tp = TabbedWindow.new :row => 3, :col => 7, :width => 60, :height => 20 do 30 | title "User Setup" 31 | button_type :ok_apply_cancel 32 | tab "&Profile" do 33 | item LabeledField.new nil, :row => 2, :col => 2, :text => "enter your name", :label => ' Name: ' 34 | item LabeledField.new nil, :row => 3, :col => 2, :text => "enter your email", :label => 'Email: ' 35 | end 36 | tab "&Settings" do 37 | item Label.new nil, :text => "Text", :row => 1, :col => 2, :attr => 'bold' 38 | item CheckBox.new nil, :row => 2, :col => 2, :text => "Antialias text" 39 | item CheckBox.new nil, :row => 3, :col => 2, :text => "Use bold fonts" 40 | item CheckBox.new nil, :row => 4, :col => 2, :text => "Allow blinking text" 41 | item CheckBox.new nil, :row => 5, :col => 2, :text => "Display ANSI Colors" 42 | item Label.new nil, :text => "Cursor", :row => 7, :col => 2, :attr => 'bold' 43 | $config_hash.set_value Variable.new, :cursor 44 | item RadioButton.new nil, :row => 8, :col => 2, :text => "Block", :value => "block", :variable => $config_hash[:cursor] 45 | item RadioButton.new nil, :row => 9, :col => 2, :text => "Blink", :value => "blink", :variable => $config_hash[:cursor] 46 | item RadioButton.new nil, :row => 10, :col => 2, :text => "Underline", :value => "underline", :variable => $config_hash[:cursor] 47 | end 48 | tab "&Term" do 49 | 50 | item Label.new nil, :text => "Arrow Key in Combos", :row => 2, :col => 2, :attr => 'bold' 51 | x = Variable.new 52 | $config_hash.set_value x, :term 53 | item RadioButton.new nil, :row => 3, :col => 2, :text => "ignore", :value => "ignore", :variable => $config_hash[:term] 54 | item RadioButton.new nil, :row => 4, :col => 2, :text => "popup", :value => "popup", :variable => $config_hash[:term] 55 | item RadioButton.new nil, :row => 5, :col => 2, :text => "next", :value => "next", :variable => $config_hash[:term] 56 | cb = ComboBox.new nil, :row => 7, :col => 2, :width => 20, 57 | :list => %w[xterm xterm-color xterm-256color screen vt100 vt102], 58 | :label => "Declare terminal as: " 59 | #radio.update_command() {|rb| ENV['TERM']=rb.value } 60 | item cb 61 | x.update_command do |rb| 62 | cb.arrow_key_policy=rb.value.to_sym 63 | end 64 | 65 | end 66 | tab "Conta&iner" do 67 | item r 68 | end 69 | # tell tabbedpane what to do if a button is pressed (ok/apply/cancel) 70 | command do |eve| 71 | alert "user pressed button index:#{eve.event} , Name: #{eve.action_command}, Tab: #{eve.source.current_tab} " 72 | case eve.event 73 | when 0,2 # ok cancel 74 | throw :close, eve.event 75 | when 1 # apply 76 | end 77 | end 78 | end 79 | tp.run 80 | end 81 | 82 | end 83 | if $0 == __FILE__ 84 | # Initialize curses 85 | begin 86 | # XXX update with new color and kb 87 | Canis::start_ncurses # this is initializing colors via ColorMap.setup 88 | $log = Logger.new((File.join(ENV["LOGDIR"] || "./" ,"canis14.log"))) 89 | $log.level = Logger::DEBUG 90 | tp = SetupTabbedPane.new() 91 | buttonindex = tp.run 92 | rescue => ex 93 | ensure 94 | Canis::stop_ncurses 95 | p ex if ex 96 | p(ex.backtrace.join("\n")) if ex 97 | $log.debug( ex) if ex 98 | $log.debug(ex.backtrace.join("\n")) if ex 99 | end 100 | end 101 | -------------------------------------------------------------------------------- /lib/canis/core/include/layouts/SplitLayout.rb: -------------------------------------------------------------------------------- 1 | # ----------------------------------------------------------------------------- # 2 | # File: SplitLayout.rb 3 | # Description: 4 | # Author: j kepler http://github.com/mare-imbrium/canis/ 5 | # Date: 2014-05-10 - 13:48 6 | # License: MIT 7 | # Last update: 2014-05-10 20:19 8 | # ----------------------------------------------------------------------------- # 9 | # SplitLayout.rb Copyright (C) 2012-2014 j kepler 10 | # ---- 11 | # This layout allows for complex arrangements of stacks and flows. One can divide the layout 12 | # into splits (vertical or horizontal) and keep dividing a split, or placing a component in it. 13 | # However, to keep it simple and reduce the testing, I am insisting that a weightage be specified 14 | # with a split. 15 | # 16 | # layout = SplitLayout.new :height => -1, :top_margin => 1, :bottom_margin => 1, :left_margin => 1 17 | # x, y = layout.vsplit( 0.30, 0.70) 18 | # x.component = mylist 19 | # y1, y2 = y.split( 0.40, 0.60 ) 20 | # y2.component = mytable 21 | # y11,y12,y13 = y1.vsplit( 0.3, 0.3, 0.4) 22 | # y11 = list1 23 | # y12 = list2 24 | # y13 = list3 25 | # 26 | # Or hopefully: 27 | # 28 | # layout.split(0.3, 0.7) do |x,y| 29 | # x.component = mylist 30 | # y.split(0.4, 0.6) do |a,b| 31 | # b.component = mytable 32 | # a.vsplit( 0.3, 0.3, 0.4) do | p,q,r | 33 | # p.component = list1 34 | # end 35 | # end 36 | # end 37 | # 38 | # 39 | class Split 40 | 41 | attr_reader :component 42 | attr_accessor :name 43 | attr_reader :splits 44 | attr_accessor :height, :width, :top, :left 45 | # link to parent 46 | # own weight 47 | attr_accessor :parent, :weight 48 | # weights of child splits, given in cons 49 | attr_accessor :split_wts 50 | attr_reader :type 51 | 52 | def initialize type, weight, parent 53 | @type = type 54 | @weight = weight 55 | @parent = parent 56 | end 57 | def _split type, args, &block 58 | @split_wts = args 59 | @splits = [] 60 | args.each do |e| 61 | @splits << Split.new(type, e, self) 62 | end 63 | if block_given? 64 | yield @splits.flatten 65 | else 66 | return @splits.flatten 67 | end 68 | end 69 | def split *args, &block 70 | _split :h, args, &block 71 | end 72 | def vsplit *args, &block 73 | _split :v, args, &block 74 | end 75 | 76 | # Set a component into a split. 77 | # set name of component as name of split, more for debugging 78 | def component=(c) 79 | @component = c 80 | @name = c.name || c.class.to_s 81 | end 82 | 83 | # shorthand to place a component in a split. 84 | alias :<< :component= 85 | end 86 | 87 | 88 | 89 | require 'canis/core/include/layouts/abstractlayout' 90 | class SplitLayout < AbstractLayout 91 | 92 | # @param [Form] optional give a form 93 | # @param [Hash] optional give settings/attributes which will be set into variables 94 | def initialize arg, config={}, &block 95 | super 96 | @splits = nil 97 | end 98 | def _split type, args, &block 99 | @splits = [] 100 | @split_wts = args 101 | args.each do |e| 102 | @splits << Split.new(type, e, self) 103 | end 104 | if block_given? 105 | yield @splits.flatten 106 | else 107 | return @splits.flatten 108 | end 109 | end 110 | 111 | def split *args, &block 112 | raise "already split " if @splits 113 | _split :h, args, &block 114 | end 115 | def vsplit *args, &block 116 | raise "already split " if @splits 117 | $log.debug " SPLIT GOT #{args} " 118 | _split :v, args, &block 119 | end 120 | alias :top :top_margin 121 | alias :left :left_margin 122 | 123 | 124 | # This program lays out the widgets deciding their row and columm and height and weight. 125 | # This program is called once at start of application, and again whenever a RESIZE event happens. 126 | def do_layout 127 | _init_layout 128 | recalc @splits, @top_margin, @left_margin if @splits 129 | # 130 | $log.debug " layout finished " 131 | end 132 | def recalc splits, r, c 133 | splits.each_with_index do |s,i| 134 | $log.debug " recalc #{i}, #{s} " 135 | p = s.parent 136 | s.top = r 137 | s.left = c 138 | case s.type 139 | when :v 140 | s.width = (s.weight * p.width ).floor 141 | s.height = p.height 142 | c += s.width 143 | when :h 144 | s.height = (s.weight * p.height ).floor 145 | s.width = p.width 146 | r += s.height 147 | end 148 | if s.component 149 | s.component.height = s.height 150 | s.component.row = s.top 151 | s.component.col = s.left 152 | s.component.width = s.width 153 | elsif s.splits 154 | recalc s.splits, s.top, s.left if s.splits 155 | else 156 | raise "Neither splits nor a component placed in #{s} #{s.type}, #{s.weight} #{s.name}" 157 | end 158 | end 159 | 160 | end 161 | end 162 | -------------------------------------------------------------------------------- /lib/canis/core/system/deprecated/keyboard.rb: -------------------------------------------------------------------------------- 1 | module Canis 2 | module Keyboard # avoid initialize 3 | ESC = 27 # keycode 4 | @polling = false 5 | 6 | module_function 7 | 8 | def focus=(receiver) 9 | @stack = [] 10 | @focus = receiver 11 | poll unless @polling 12 | end 13 | 14 | def poll 15 | @polling = true 16 | 17 | while char = @focus.window.getch 18 | break if @focus.stopping? # XXX 19 | #break if VER.stopping? 20 | $log.debug("char: #{char} stakc: #{@stack.inspect}") if char != Ncurses::ERR 21 | if char == Ncurses::ERR # timeout or signal 22 | @focus.press('esc') if @stack == [ESC] 23 | @stack.clear 24 | elsif ready = resolve(char) 25 | $log.debug("char: #{char} ready: #{ready}") 26 | @stack.clear 27 | @focus.press(ready) 28 | end 29 | end 30 | 31 | ensure 32 | @polling = false 33 | end 34 | 35 | def resolve(char) 36 | @stack << char 37 | 38 | if @stack.first == ESC 39 | MOD_KEYS[@stack] || SPECIAL_KEYS[@stack] 40 | else 41 | NCURSES_KEYS[char] || CONTROL_KEYS[char] || PRINTABLE_KEYS[char] 42 | end 43 | end 44 | 45 | # TODO: make this section sane 46 | 47 | ASCII = (0..255).map{|c| c.chr } 48 | CONTROL = ASCII.grep(/[[:cntrl:]]/) 49 | PRINTABLE = ASCII.grep(/[[:print:]]/) 50 | 51 | SPECIAL_KEYS = { 52 | [27, 79, 50, 81] => 'F14', 53 | [27, 79, 50, 82] => 'F15', 54 | [27, 79, 70] => 'end', 55 | [27, 79, 70] => 'end', 56 | [27, 79, 72] => 'home', 57 | [27, 79, 80] => 'F1', 58 | [27, 79, 81] => 'F2', 59 | [27, 79, 82] => 'F3', 60 | [27, 79, 83] => 'F4', 61 | [27, 91, 49, 126] => 'end', 62 | [27, 91, 49, 126] => 'home', 63 | [27, 91, 49, 49, 126] => 'F1', 64 | [27, 91, 49, 50, 126] => 'F2', 65 | [27, 91, 49, 51, 126] => 'F3', 66 | [27, 91, 49, 52, 126] => 'F4', 67 | [27, 91, 49, 52, 126] => 'F4', 68 | [27, 91, 49, 53, 126] => 'F5', 69 | [27, 91, 49, 55, 126] => 'F6', 70 | [27, 91, 49, 56, 59, 50, 126] => 'F19', 71 | [27, 91, 49, 56, 59, 51, 126] => 'F7', 72 | [27, 91, 49, 59, 51, 65] => 'ppage', 73 | [27, 91, 49, 59, 51, 66] => 'npage', 74 | [27, 91, 49, 59, 53, 65] => 'ppage', 75 | [27, 91, 49, 59, 53, 66] => 'npage', 76 | [27, 91, 49, 59, 53, 70] => 'M-<', 77 | [27, 91, 49, 59, 53, 72] => 'M->', 78 | [27, 91, 50, 54, 126] => 'F14', 79 | [27, 91, 50, 56, 126] => 'F15', 80 | [27, 91, 51, 59, 51, 126] => 'del', 81 | [27, 91, 52, 126] => 'end', 82 | [27, 91, 55, 126] => 'home', 83 | [27, 91, 55, 126] => 'home', 84 | [27, 91, 56, 126] => 'end', 85 | [27, 91, 56, 126] => 'end', 86 | [27, 91, 65] => 'up', 87 | [27, 91, 66] => 'down', 88 | [27, 91, 67] => 'right', 89 | [27, 91, 68] => 'left', 90 | [27, 91, 70] => 'end', 91 | [27, 91, 72] => 'end', 92 | [27, 91, 72] => 'home', 93 | [27, 91, 91, 65] => 'F1', 94 | [27, 91, 91, 66] => 'F2', 95 | [27, 91, 91, 67] => 'F3', 96 | [27, 91, 91, 68] => 'F4', 97 | [27, 91, 91, 69] => 'F5', 98 | } 99 | 100 | CONTROL_KEYS = { 101 | 0 => 'C-space', 102 | 1 => 'C-a', 103 | 2 => 'C-b', 104 | 3 => 'C-c', 105 | 4 => 'C-d', 106 | 5 => 'C-e', 107 | 6 => 'C-f', 108 | 7 => 'C-g', 109 | 8 => 'C-h', 110 | 9 => 'tab', 111 | 10 => 'return', # C-j 112 | 11 => 'C-k', 113 | 12 => 'C-l', 114 | 13 => 'return', # C-m 115 | 14 => 'C-n', 116 | 15 => 'C-o', 117 | 16 => 'C-p', 118 | 17 => 'C-q', 119 | 18 => 'C-r', 120 | 19 => 'C-s', 121 | 20 => 'C-t', 122 | 21 => 'C-u', 123 | 22 => 'C-v', 124 | 23 => 'C-w', 125 | 24 => 'C-x', 126 | 25 => 'C-y', 127 | 26 => 'C-z', # FIXME: is usually suspend in shell job control 128 | # 27 => 'esc', 129 | 32 => 'space', 130 | 127 => 'backspace', 131 | } 132 | 133 | PRINTABLE_KEYS = {} 134 | MOD_KEYS = {} 135 | 136 | PRINTABLE.each do |key| 137 | code = key.unpack('c')[0] # using unpack to be compatible with 1.9 138 | PRINTABLE_KEYS[code] = key 139 | MOD_KEYS[[ESC, code]] = "M-#{key}" unless key == '[' # don't map esc 140 | end 141 | 142 | NCURSES_KEYS = {} 143 | Ncurses.constants.grep(/^KEY_/).each do |const| 144 | value = Ncurses.const_get(const) 145 | key = const[/^KEY_(.*)/, 1] 146 | key = key =~ /^F/ ? key : key.downcase # function keys 147 | NCURSES_KEYS[value] = key 148 | end 149 | end 150 | end 151 | -------------------------------------------------------------------------------- /lib/canis/core/widgets/statusline.rb: -------------------------------------------------------------------------------- 1 | require 'canis' 2 | 3 | module Canis 4 | 5 | # 6 | # A vim-like application status bar that can display time and various other statuses 7 | # at the bottom, typically above the dock (3rd line from last), or else the last line. 8 | # 9 | # == Example 10 | # 11 | # require 'canis/core/widgets/statusline' 12 | # @status_line = Canis::StatusLine.new @form, :row => Ncurses.LINES-2 13 | # @status_line.command { 14 | # "F1 Help | F2 Menu | F3 View | F4 Shell | F5 Sh | %20s" % [message_label.text] 15 | # } 16 | # 17 | # == Changes 18 | # Earlier, the color of teh status line was REVERSED while printing which can be confusing 19 | # and surprising. We should use normal, or use whatever attribute the user gives. 20 | # Also, using the row as -3 is assuming that a dock is used, which may not be the case, 21 | # so -1 should be used. 22 | # 23 | class StatusLine < Widget 24 | @@negative_offset = -1 # 2014-08-31 - 12:18 earlier -3 25 | #attr_accessor :row_relative # lets only advertise this when we've tested it out 26 | 27 | def initialize form, config={}, &block 28 | @row_relative = @@negative_offset 29 | if form.window.height == 0 30 | @row = Ncurses.LINES + @@negative_offset 31 | else 32 | @row = form.window.height + @@negative_offset 33 | end 34 | # in root windows FIXME 35 | @col = 0 36 | @name = "sl" 37 | super 38 | # if negativ row passed we store as relative to bottom, so we can maintain that. 39 | if @row < 0 40 | @row_relative = @row 41 | @row = Ncurses.LINES - @row 42 | else 43 | @row_relative = (Ncurses.LINES - @row) * -1 44 | end 45 | @focusable = false 46 | @editable = false 47 | @command = nil 48 | @repaint_required = true 49 | bind(:PROPERTY_CHANGE) { |e| @color_pair = nil ; } 50 | end 51 | # 52 | # command that returns a string that populates the status line (left aligned) 53 | # @see :right 54 | # @see dbdemo.rb 55 | # == Example 56 | # 57 | # @l.command { "%-20s [DB: %-s | %-s ]" % [ Time.now, $current_db || "None", $current_table || "----"] } 58 | # 59 | def command *args, &blk 60 | @command = blk 61 | @args = args 62 | end 63 | alias :left :command 64 | 65 | # 66 | # Procedure for text to be right aligned in statusline 67 | def right *args, &blk 68 | @right_text = blk 69 | @right_args = args 70 | end 71 | 72 | # NOTE: I have not put a check of repaint_required, so this will print on each key-stroke OR 73 | # rather whenever form.repaint is called. 74 | def repaint 75 | @color_pair ||= get_color($datacolor, @color, @bgcolor) 76 | # earlier attrib defaulted to REVERSE which was surprising. 77 | _attr = @attr || Ncurses::A_NORMAL 78 | len = @form.window.getmaxx # width does not change upon resizing so useless, fix or do something 79 | len = Ncurses.COLS if len == 0 || len > Ncurses.COLS 80 | # this should only happen if there's a change in window 81 | if @row_relative 82 | @row = Ncurses.LINES+@row_relative 83 | end 84 | 85 | # first print dashes through 86 | @form.window.printstring @row, @col, "%s" % "-" * len, @color_pair, _attr 87 | 88 | # now call the block to get current values 89 | if @command 90 | ftext = @command.call(self, @args) 91 | else 92 | status = $status_message ? $status_message.value : "" 93 | #ftext = " %-20s | %s" % [Time.now, status] # should we print a default value just in case user doesn't 94 | ftext = status # should we print a default value just in case user doesn't 95 | end 96 | # 2013-03-25 - 11:52 replaced $datacolor with @color_pair - how could this have been ? 97 | # what if user wants to change attrib ? 98 | if ftext =~ /#\[/ 99 | # hopefully color_pair does not clash with formatting 100 | @form.window.printstring_formatted @row, @col, ftext, @color_pair, _attr 101 | else 102 | @form.window.printstring @row, @col, ftext, @color_pair, _attr 103 | end 104 | 105 | if @right_text 106 | ftext = @right_text.call(self, @right_args) 107 | if ftext =~ /#\[/ 108 | # hopefully color_pair does not clash with formatting 109 | @form.window.printstring_formatted_right @row, nil, ftext, @color_pair, _attr 110 | else 111 | c = len - ftext.length 112 | @form.window.printstring @row, c, ftext, @color_pair, _attr 113 | end 114 | else 115 | t = Time.now 116 | tt = t.strftime "%F %H:%M:%S" 117 | #r = Ncurses.LINES 118 | # somehow the bg defined here affects the bg in left text, if left does not define 119 | # a bg. The bgcolor defined of statusline is ignored in left or overriden by this 120 | #ftext = "#[fg=white,bg=blue] %-20s#[/end]" % [tt] # print a default 121 | @form.window.printstring_formatted_right @row, nil, tt, @color_pair, _attr 122 | end 123 | 124 | @repaint_required = false 125 | end 126 | # not used since not focusable 127 | def handle_keys ch 128 | return :UNHANDLED 129 | end 130 | 131 | end # class 132 | end # module 133 | -------------------------------------------------------------------------------- /lib/canis/core/include/textdocument.rb: -------------------------------------------------------------------------------- 1 | # ----------------------------------------------------------------------------- # 2 | # File: textdocument.rb 3 | # Description: Abstracts complex text preprocessing and rendering from TextPad 4 | # Author: j kepler http://github.com/mare-imbrium/canis/ 5 | # Date: 2014-06-25 - 12:52 6 | # License: MIT 7 | # Last update: 2014-09-11 19:48 8 | # ----------------------------------------------------------------------------- # 9 | # textdocument.rb Copyright (C) 2012-2014 j kepler 10 | 11 | module Canis 12 | # In an attempt to keep TextPad simple, and move complexity of complex content out of it, 13 | # I am trying to move specialized processing and rendering to a Document class which manages the same. 14 | # I would also like to keep content, and content_type etc together. This should percolate to multibuffers 15 | # to. 16 | # An application may create a TextDocument object and pass it to TextPad using the +text+ method. 17 | # Or an app may send in a hash, which +text+ uses to create this object. 18 | class TextDocument 19 | attr_accessor :content_type 20 | attr_accessor :stylesheet 21 | # +hash+ of options passed in constructor including content_type and stylesheet 22 | attr_accessor :options 23 | # +text+ is the original Array which contains markup of some sort 24 | # which source will retrieve. Changes happen to this (row added, deleted, changed) 25 | attr_accessor :text 26 | 27 | # returns the native or transformed format of original content. +text+ gets transformed into 28 | # native text. The renderer knows how to display native_text. 29 | # NOTE: native_text is currently Chunklines - chunks of text with information of color 30 | def native_text 31 | unless @native_text 32 | preprocess_text @text 33 | end 34 | return @native_text 35 | end 36 | # specify a renderer if you do not want the DefaultRenderer to be installed. 37 | attr_accessor :renderer 38 | # the source object using this document 39 | attr_reader :source 40 | 41 | def initialize hash 42 | @parse_required = true 43 | @options = hash 44 | @content_type = hash[:content_type] 45 | @stylesheet = hash[:stylesheet] 46 | @text = hash[:text] 47 | $log.debug " TEXTDOCUMENT created with #{@content_type} , #{@stylesheet} " 48 | raise "textdoc recieves nil content_type in constructor" unless @content_type 49 | end 50 | # declare that transformation of entire content is required. Currently called by fire_dimension_changed event 51 | # of textpad. NOTE: not called from event, now called in text() 52 | def parse_required 53 | @parse_required = true 54 | end 55 | # set the object that is using this textdocument (typically TextPad). 56 | # This allows us to bind to events such as adding or deleting a row, or modification of data. 57 | def source=(sou) 58 | @source = sou 59 | if @renderer 60 | @source.renderer = @renderer 61 | end 62 | @source.bind :ROW_CHANGED do | o, ix| parse_line ix ; end 63 | @source.bind :DIMENSION_CHANGED do | o, _meth| parse_required() ; end 64 | @source.title = self.title() if self.title() 65 | end 66 | # if there is a content_type specfied but nothing to handle the content 67 | # then we create a default handler. 68 | def create_default_content_type_handler 69 | raise "source is nil in textdocument" unless @source 70 | require 'canis/core/include/colorparser' 71 | # cp will take the content+type from self and select actual parser 72 | cp = Chunks::ColorParser.new @source 73 | @content_type_handler = cp 74 | end 75 | # called by textpad to do any parsing or conversion on data since a textdocument by default 76 | # does some transformation on the content 77 | def preprocess_text data 78 | parse_formatted_text data 79 | end 80 | # transform a given line number from original content to internal format. 81 | # Called by textpad when a line changes (update) 82 | def parse_line(lineno) 83 | @native_text[lineno] = @content_type_handler.parse_line( @list[lineno]) 84 | end 85 | # This is now to be called at start when text is set, 86 | # and whenever there is a data modification. 87 | # This updates @native_text 88 | # @param [Array] original content sent in by user 89 | # which may contain markup 90 | # @param [Hash] config containing 91 | # content_type 92 | # stylesheet 93 | # @return [Chunklines] content in array of chunks. 94 | def parse_formatted_text(formatted_text, config=nil) 95 | return unless @parse_required 96 | 97 | unless @content_type_handler 98 | create_default_content_type_handler 99 | end 100 | @parse_required = false 101 | # 2014-09-11 - 19:47 sending in color from here, otherwise the wrong default is picked. TEST 102 | @native_text = @content_type_handler.parse_text formatted_text, @source.color_pair, @source.attr 103 | end 104 | # returns title of document 105 | def title 106 | return @options[:title] 107 | end 108 | # set title of document (to be displayed by textpad) 109 | def title=(t) 110 | @options[:title] = t 111 | end 112 | end 113 | end # mod 114 | -------------------------------------------------------------------------------- /examples/data/tasks.csv: -------------------------------------------------------------------------------- 1 | 1|clo|bug|X3|messagebox label print overlaps left border 2 | 2|clo|enh|P4|Field: methods not chainable 3 | 3|clo|bug|P3|button crash if mnemo not in text 4 | 4|clo|enh|P3|link mnemo should not have Alt 5 | 8|ope|bug|P5|container keep repainting all 6 | 9|clo|enh|P3|menu bar keys 7 | 10|ope|enh|P4|combo keys 8 | 11|clo|bug|X3|hand written DSL defs dont fire prop handler 9 | 12|clo|bug|X4|textview on_enter 10 | 13|clo|enh|P3|traversal of listboxes 11 | 14|clo|enh|P3|check keys Alt-Sh-O, Alt-[. Add some missing 12 | 15|clo|bug|P3|2 keys simulaneously with Alt-O or Alt-[ 13 | 16|clo|enh|P3|put key combins in arrays and match 14 | 17|ope|bug|P4|selected_item of list broken 15 | 18|clo|enh|P3|valid_range for Field 16 | 19|clo|bug|X2|tree can print beyond right margin 17 | 20|ope|bug|P4|cannot bind_key using Alt key and another. 18 | 21|sta|enh|P2|make code from App common so reusable in other examples 19 | 22|ope|bug|P3|widget hide (visible false) does not hide 20 | 23|clo|bug|P3|listbox color changes not reflecting in cell_renderer 21 | 24|clo|bug|P3|Table: delete row should reflect focussed_index. 22 | 25|clo|enh|X3|#cleanup add_cols rows_panned #urgent 23 | 26|ope|fea|P5|App to have a layout abject 24 | 27|ope|bug|P4|#fix testvimsplit not sizing STACK correctly 25 | 28|clo|enh|P1|FieldValidationException should rollback change, 26 | 29|clo|enh|X1|return self from dsl_prop and dsl_accessor, 27 | 30|can|bug|P3|on_leave has validations that should be separated, 28 | 31|clo|enh|X1|#listbox data more accessible from LB class, 29 | 32|ope|enh|P4| #tree many empty methods in #treemodel 30 | 33|clo|enh|X3|display_menu shd furhter glob on tab 31 | 34|clo|enh|X3|display_menu large list need scrolling 32 | 35|clo|bug|X3|alert message getting truncated after 2 lines 33 | 36|clo|fea|X3|use j k h l for navigation if unused by a button of widget 34 | 37|ope|enh|P4|simplify #vimsplit calculation 35 | 38|clo|bug|X3|#tabbedpane when fire, state of old tab discrep 36 | 39|ope|bug|P5|tabularwidget truncate needed left_margin 37 | 40|clo|bug|X3|%textarea C-e goes to last char, not one after that 38 | 41|clo|bug|X3|M-2 did not work in textarea 39 | 42|ope|bug|P5|append_to_kill, yank not working in %listbox 40 | 43|clo|bug|X1|rt arrow and backspace issue ask(), dbdemo save #urgent 41 | 44|clo|bug|P1|need to hide window after ask(), dbdemo save 42 | 45|clo|bug|X3|directory list, after pressing Enter on ../ focus on header 43 | 46|clo|enh|X3|textview repaints all even when no scrolling 44 | 47|ope|enh|P4|vieditable and io.rb need to create statuswindow and take input 45 | 48|clo|bug|X3|say not working after ask hides window 46 | 49|ope|enh|P5|resultsetview needs way to specify key fields 47 | 50|ope|bug|P5|sort on tabularwidget with resultset error sometimes 48 | 51|clo|bug|X2|TabbedPane's TabbedButtons rely on window being 0,0 49 | 52|ope|bug|P4|%label set_label may have to calculate at repaint esp in app 50 | 53|clo|bug|X3|%App. if shortcut used outside stack or flow, dont attach form or set 51 | 54|clo|bug|X3|inside newtabbedpane button mnemonic and underline not happening #fix 52 | 55|ope|enh|P4|Have a module Expandable for those that are multiline 53 | 56|ope|enh|P4|DRY up titles and borders 54 | 57|clo|enh|X2|dont put default for color and bg in init of Wid or field 55 | 58|clo|enh|X2|Fields display_length should be width or made alias 56 | 60|ope|enh|P4|fields width, display_len is for input area, not label plus input 57 | 61|ope|bug|P4|test2.rb color change not affecting text objects 58 | 62|clo|bug|X2|when using chunks, movement crashes C-e etc 59 | 63|clo|bug|X2|chunks in textview, when scrolling earlier lines shown 60 | 64|clo|enh|X2|abstract chunk and parse into Format class 61 | 65|ope|enh|P3|clean up window.rb prv_printstring etc 62 | 66|clo|bug|X2|colorparser needs to carryover, and :reset 63 | 67|clo|enh|X3|several places doing attrib conversion in window, use get_attrib 64 | 68|clo|bug|X4|move chunk parsing from window.rb to chunk as in textview 65 | 69|ope|bug|P2|C-u not available for textpad and view. how to 66 | 70|ope|enh|P4|confusion between renderer and color_parser 67 | 71|clo|enh|X1|redo combo with basiclist or new popup 68 | 72|clo|enh|X2|redo label simplify, one line only 69 | 73|clo|bug|X2|rbasiclist needs to reduce selected symbol 70 | 74|ope|enh|P3|list and others should just calculate longest in list 71 | 75|ope|enh|P4|textpad to allow append << at some stage 72 | 76|clo|enh|X3|appmethods.rb may need to go into lib/common or somewhere else 73 | 77|clo|enh|X2|switch messagebox old and new and change rdialog 74 | 78|clo|enh|X1|switch tabbedpane classes and update examples 75 | 79|ope|enh|P3|cleanup button getpaint etc 76 | 80|ope|enh|P3|use @focusable in form to simplify 77 | 81|clo|bug|X2|field label mnemonic not being set 78 | 82|ope|bug|P2|new messagebox need default_button option 79 | 83|clo|enh|X2|check statusline usage, should use formatted only 80 | 84|clo|bug|X2|dbdemo M-z not doing anything 81 | 85|ope|bug|P2|combo symbol when label, see newmessagebox 82 | 86|ope|bug|P2|combo let caller suggest width and use if longer than longest item 83 | 87|ope|enh|P3|praps global setting lists etc use SPC for scroll or selection 84 | 88|ope|enh|P2|keep working on wsshortcuts as in testws..2.rb 85 | 89|ope|bug|P2|messagebox, see about background for zterm-256 as in header 86 | 90|clo|bug|X2|messagebox: message sets width, even if user has specified 87 | 91|clo|enh|X2|messagebox: default row col cannot be 0,0 88 | 92|ope|enh|P3|messagebox: if text longer than display then can we split 89 | -------------------------------------------------------------------------------- /lib/canis/core/widgets/applicationheader.rb: -------------------------------------------------------------------------------- 1 | # ----------------------------------------------------------------------------- # 2 | # File: applicationheader.rb 3 | # Description: Prints a header on first row, with right, left and centered text 4 | # NOTE: on some terminal such as xterm-256color spaces do not print 5 | # so you will see black or empty spaces between text. 6 | # This does not happen on screen and xterm-color. 7 | # I've done some roundabout stuff to circumvent that. 8 | # Author: jkepler http://github.com/mare-imbrium/canis-core/ 9 | # Date: 10 | # License: Same as Ruby's License (http://www.ruby-lang.org/LICENSE.txt) 11 | # Last update: 2016-01-13 18:42 12 | # 13 | # CHANGES: 14 | # For some terminals, like xterm-256color which were not printing spaces 15 | # I've changed to code so only text is printed where it has to with no 16 | # padding. These terminals remove the padding color. 17 | # ----------------------------------------------------------------------------- # 18 | # 19 | require 'canis/core/widgets/rwidget' 20 | include Canis 21 | module Canis 22 | # Maintain an application header on the top of an application. 23 | # Application related text may be placed in the left, center or right slots. 24 | # 25 | # == Example 26 | # a = ApplicationHeader.new "MyApp v1.0", :text_center => "Application Name", :text_right => "module", 27 | # :color => :white, :bgcolor => :blue 28 | # 29 | # # Later as user traverses a list or table, update row number on app header 30 | # a.text_right "Row #{n}" 31 | # 32 | class ApplicationHeader < Widget 33 | # text on left of header 34 | dsl_property :text1 35 | # text on left of header, after text1 36 | dsl_property :text2 37 | # text in center of header 38 | dsl_property :text_center 39 | # text on right side of header 40 | dsl_property :text_right 41 | 42 | # @param text1 String text on left of header 43 | def initialize form, text1, config={}, &block 44 | 45 | @name = "header" 46 | @text1 = text1 47 | # setting default first or else Widget will place its BW default 48 | @color, @bgcolor = ColorMap.get_colors_for_pair $bottomcolor 49 | super form, config, &block 50 | @color_pair = get_color $bottomcolor, @color, @bgcolor 51 | @window = form.window 52 | @editable = false 53 | @focusable = false 54 | @cols ||= Ncurses.COLS-1 55 | @row ||= 0 56 | @col ||= 0 57 | @repaint_required = true 58 | #@color_pair ||= $bottomcolor # XXX this was forcing the color 59 | #pair 60 | @text2 ||= "" 61 | @text_center ||= "" 62 | @text_right ||= "" 63 | # 2016-01-13 - added since "1" was giving problems in mvhline in some cases 64 | @space_char = " ".codepoints.first 65 | end 66 | # returns value of text1, i.e. text on left of header 67 | def getvalue 68 | @text1 69 | end 70 | 71 | ## 72 | # XXX need to move wrapping etc up and done once. 73 | def repaint 74 | return unless @repaint_required 75 | 76 | # 2014-08-10 - 14:53 changing bgcolor or color resets color_pair, so this must be reset if nil 77 | @color_pair ||= get_color $bottomcolor, @color, @bgcolor 78 | #print_header(htext, posy = 0, posx = 0) 79 | att = get_attrib @attr 80 | len = @window.width 81 | len = Ncurses.COLS-0 if len == 0 82 | # print a bar across the screen 83 | @window.attron(Ncurses.COLOR_PAIR(@color_pair) | att) 84 | # 2016-01-13 - changed since "1" was giving problems in mvhline in some cases 85 | #@window.mvhline(@row, @col, 1, len) 86 | @window.mvhline(@row, @col, @space_char, len) 87 | @window.attroff(Ncurses.COLOR_PAIR(@color_pair) | att) 88 | #print_header(@text1 + " %15s " % @text2 + " %20s" % @text_center , posy=0, posx=0) 89 | 90 | # Now print the text in the correct positions with no padding, else some terminal 91 | # will blacken the text out. 92 | print_header("#{@text1} #{@text2}") # + " %20s" % @text_center , posy=0, posx=0) 93 | print_center("#{@text_center}") # + " %20s" % @text_center , posy=0, posx=0) 94 | print_top_right(@text_right) 95 | @repaint_required = false 96 | end 97 | # internal method, called by repain to print text1 and text2 on left side 98 | def print_header(htext, r = 0, c = 0) 99 | #win = @window 100 | #len = @window.width 101 | #len = Ncurses.COLS-0 if len == 0 102 | # 103 | @form.window.printstring r, c, htext, @color_pair, @attr 104 | end 105 | # internal method, called by repaint to print text_center in the center 106 | def print_center(htext, r = 0, c = 0) 107 | win = @window 108 | len = win.getmaxx 109 | len = Ncurses.COLS-0 if len == 0 || len > Ncurses.COLS 110 | # 111 | win.printstring r, ((len-htext.length)/2).floor, htext, @color_pair, @attr 112 | end 113 | # internal method to print text_right 114 | def print_top_right(htext) 115 | hlen = htext.length 116 | len = @window.getmaxx # width was not changing when resize happens 117 | len = Ncurses.COLS-0 if len == 0 || len > Ncurses.COLS 118 | #$log.debug " def print_top_right(#{htext}) #{len} #{Ncurses.COLS} " 119 | @form.window.printstring 0, len-hlen, htext, @color_pair, @attr 120 | end 121 | ## 122 | ## 123 | # ADD HERE 124 | end 125 | end 126 | -------------------------------------------------------------------------------- /examples/testwsshortcuts2.rb: -------------------------------------------------------------------------------- 1 | # this is a test program, tests out widget shortcuts. type C-q to exit 2 | # 3 | require 'canis' 4 | require 'canis/core/util/widgetshortcuts' 5 | 6 | include Canis 7 | include Canis::Utils 8 | 9 | class SetupMessagebox 10 | include Canis::WidgetShortcuts 11 | def initialize config={}, &block 12 | @window = Canis::Window.root_window 13 | @form = Form.new @window 14 | end 15 | def run 16 | _create_form 17 | @form.repaint 18 | @window.wrefresh 19 | catch(:close) do 20 | while ((ch = @window.getchar()) != 999) 21 | break if ch == ?\C-q.getbyte(0) 22 | @form.handle_key ch 23 | @window.wrefresh 24 | end 25 | end # catch 26 | end 27 | def _create_form 28 | widget_shortcuts_init 29 | stack :margin_top => 1, :width => :expand do 30 | label :text => " Details ", :color => :cyan, :attr => :reverse, :width => :expand, :justify => :center 31 | flow :margin_top => 1, :margin_left => 4, :item_width => 50 do 32 | stack :margin_top => 0, :margin_left => 3, :width => 50 , :color => :cyan, :bgcolor => :black do 33 | box do 34 | field :text => "steve", :attr => :reverse, :label => "%15s" % ["Name: "] 35 | field :label => "%15s" % ["Address: "], :width => 15, :attr => :reverse 36 | blank 37 | check :text => "Using version control", :value => true, :onvalue => "yes", :offvalue => "no" do |eve| 38 | unless eve.item.value 39 | alert "NO VC! We need to talk" 40 | end 41 | end 42 | check :text => "Upgraded to Mavericks", :value => false, :onvalue => "yes", :offvalue => "no" do |eve| 43 | unless eve.item.value 44 | alert "You goin back to Snow Leopard?" 45 | end 46 | end 47 | end # box 48 | end 49 | stack :margin_top => 0, :margin_left => 3, :width => 50 , :color => :cyan, :bgcolor => :black do 50 | box :title => "OS Maintenance", :margin_left => 2 do 51 | radio :text => "Linux", :value => "LIN", :group => :os 52 | radio :text => "OSX", :value => "OSX", :group => :os 53 | radio :text => "Window", :value => "Win", :group => :os 54 | blank 55 | flow :item_width => 15 do 56 | button :text => "Install" do 57 | # you can avoid this by giving the radio buttons your own Variable (see test2.rb) 58 | choice = @variables[:os].value 59 | case choice 60 | when "" 61 | alert "Select an OS" 62 | when "OSX", "LIN" 63 | alert "Good choice" 64 | else 65 | alert "Pfft !" 66 | end 67 | end 68 | button :text => "Uninstall" 69 | button :text => "Delete" 70 | end 71 | end # box 72 | end 73 | end # flow 74 | #button :text => "Execute" 75 | text = [" #[reverse]Unix Philosophy #[end] ", 76 | "#[fg=green, underline]Eric Raymond#[end] in his book, #[fg=green, underline]The Art of Unix Programming#[end] summarized the Unix philosophy.", 77 | " ", 78 | "Rule of #[fg=yellow]Modularity#[end]: Write simple parts connected by clean interfaces.", 79 | "Rule of #[fg=blue]Clarity#[end]: #[bold]Clarity#[end] is better than cleverness.", 80 | "Rule of #[fg=red]Separation#[end]: Separate #[bold]policy#[end] from mechanism; separate interfaces from engines.", 81 | "Rule of #[fg=green]Simplicity#[end]: Design for #[bold]simplicity;#[end] add complexity only where you must.", 82 | "Rule of #[fg=magenta]Parsimony#[end]: Write a big program only when it is clear by demonstration that nothing else will do.", 83 | "Rule of #[fg=cyan]Representation#[end]: Fold knowledge into #[bold]data#[end] so program logic can be stupid and robust"] 84 | text << "For more check: #[underline]http://en.wikipedia.org/wiki/Unix_philosophy#Eric_Raymond#[end]" 85 | formatted = [] 86 | #text.each { |line| formatted << @window.convert_to_chunk(line) } 87 | 88 | #textview :text => formatted 89 | #textview do |t| t.formatted_text(text, :tmux) end 90 | t = textview :title => 'tmux format' 91 | #t.formatted_text(text, :tmux) 92 | t.text(text, :content_type => :tmux) 93 | t1 = textview :title => 'ansi formatted document' 94 | text = File.open(File.expand_path("../data/color.2", __FILE__),"r").readlines 95 | #text = `ri -f bs String`.split("\n") 96 | #t1.formatted_text(text, :ansi) 97 | t1.text(text, :content_type => :ansi) 98 | 99 | flow do 100 | #box do 101 | button :text => " Ok " do alert "Pressed okay" end 102 | button :text => "Cancel" do throw :close if(confirm "Do you wish to Quit?") ; end 103 | button :text => "Apply " 104 | #end 105 | end 106 | 107 | end 108 | 109 | end 110 | end 111 | if $0 == __FILE__ 112 | # Initialize curses 113 | begin 114 | # XXX update with new color and kb 115 | Canis::start_ncurses # this is initializing colors via ColorMap.setup 116 | $log = Logger.new((File.join(ENV["LOGDIR"] || "./" ,"canis14.log"))) 117 | $log.level = Logger::DEBUG 118 | tp = SetupMessagebox.new() 119 | buttonindex = tp.run 120 | $log.debug "XXX: MESSAGEBOX retirned #{buttonindex} " 121 | rescue => ex 122 | ensure 123 | Canis::stop_ncurses 124 | p ex if ex 125 | p(ex.backtrace.join("\n")) if ex 126 | $log.debug( ex) if ex 127 | $log.debug(ex.backtrace.join("\n")) if ex 128 | end 129 | end 130 | -------------------------------------------------------------------------------- /lib/canis/core/include/deprecated/listcellrenderer.rb: -------------------------------------------------------------------------------- 1 | require 'canis/core/widgets/rwidget' 2 | module Canis 3 | 4 | ## 5 | # 2010-09-27 11:06 : i have modified this quite a bit, to calculate some stuff 6 | # once in the init, to reduce work in repaint 7 | # This is a basic list cell renderer that will render the to_s value of anything. 8 | # Using alignment one can use for numbers too. 9 | # However, for booleans it will print true and false. If editing, you may want checkboxes 10 | # NOTE: this class is being extended by many other classes. Careful while making 11 | # sweeping changes. 12 | class ListCellRenderer 13 | include Canis::ConfigSetup 14 | include Canis::Utils 15 | dsl_accessor :justify # :right, :left, :center # added 2008-12-22 19:02 16 | dsl_accessor :display_length # please give this to ensure the we only print this much 17 | dsl_accessor :height # if you want a multiline label. 18 | dsl_accessor :text # text of label 19 | dsl_accessor :color, :bgcolor 20 | dsl_accessor :row, :col 21 | dsl_accessor :parent #usuall the table to get colors and other default info 22 | 23 | def initialize text="", config={}, &block 24 | @text = text 25 | @editable = false 26 | @focusable = false 27 | config_setup config # @config.each_pair { |k,v| variable_set(k,v) } 28 | instance_eval &block if block_given? 29 | init_vars 30 | end 31 | # NOTE: please call super() if you override this 32 | def init_vars #:nodoc: 33 | # omg, some classes won't have justify !! 34 | #@justify ||= (@parent.justify || :left) 35 | unless @justify 36 | if @parent.respond_to? :justify 37 | @justify ||= (@parent.justify || :left) 38 | else 39 | @justify ||= :left 40 | end 41 | end 42 | @format = @justify.to_sym == :right ? "%*s" : "%-*s" 43 | @display_length ||= 10 44 | # create color pairs once for this 2010-09-26 20:53 45 | end 46 | # creates pairs of colors at start 47 | # since often classes are overriding init_vars, so not gettin created 48 | def create_color_pairs 49 | @color_pair = get_color $datacolor 50 | @pairs = Hash.new(@color_pair) 51 | @attrs = Hash.new(Ncurses::A_NORMAL) 52 | color_pair = get_color $selectedcolor, @parent.selected_color, @parent.selected_bgcolor 53 | @pairs[:normal] = @color_pair 54 | @pairs[:selected] = color_pair 55 | @pairs[:focussed] = @pairs[:normal] 56 | @attrs[:selected] = $row_selected_attr 57 | @attrs[:focussed] = $row_focussed_attr 58 | 59 | end 60 | def getvalue 61 | @text 62 | end 63 | ## 64 | # sets @color_pair and @attr 65 | def select_colors focussed, selected 66 | create_color_pairs unless @pairs 67 | raise ArgumentError, "pairs hash is null. Changes have happened in listcellrenderer" unless @pairs 68 | @color_pair = @pairs[:normal] 69 | #@attr = $row_attr 70 | @attr = @row_attr || $row_attr # changed 2011-10-15 since we seem to be ignoring row_attr changes 71 | # give precedence to a selected row 72 | if selected 73 | @color_pair = @pairs[:selected] 74 | @attr = @attrs[:selected] 75 | elsif focussed 76 | @color_pair = @pairs[:focussed] 77 | @attr = @attrs[:focussed] 78 | end 79 | end 80 | 81 | ## 82 | # paint a list box cell 83 | # 84 | # @param [Buffer] window or buffer object used for printing 85 | # @param [Fixnum] row 86 | # @param [Fixnum] column 87 | # @param [Fixnum] actual index into data, some lists may have actual data elsewhere and 88 | # display data separate. e.g. rfe_renderer (directory listing) 89 | # @param [String] text to print in cell 90 | # @param [Boolean, cell focussed, not focussed 91 | # @param [Boolean] cell selected or not 92 | def repaint graphic, r=@row,c=@col, row_index=-1,value=@text, focussed=false, selected=false 93 | 94 | select_colors focussed, selected 95 | # if listboxes width is reduced, display_len remains the same 96 | # XXX FIXME parent may not be the list but a container like rfe !! 97 | # maybe caller should update at start of repain loop. 98 | #@display_length = @parent.width - 2 - @parent.left_margin 99 | 100 | value=value.to_s 101 | if !@display_length.nil? 102 | if value.length > @display_length 103 | value = value[0..@display_length-1] 104 | end 105 | # added 2010-09-27 11:05 TO UNCOMMENT AND TEST IT OUT 106 | if @justify == :center 107 | value = value.center(@display_length) 108 | end 109 | end 110 | len = @display_length || value.length 111 | #$log.debug " XXX @display_length: #{@display_length}, #{value.length}, L:#{len}, pw:#{@parent.width} ::attr:: #{@attr} " 112 | graphic.printstring r, c, @format % [len, value], @color_pair, @attr 113 | end # repaint 114 | 115 | # @deprecated 116 | # only for older code that may have extended this. 117 | def prepare_default_colors focussed, selected 118 | @color_pair = get_color $datacolor 119 | @attr = @row_attr || Ncurses::A_NORMAL 120 | 121 | 122 | ## determine bg and fg and attr 123 | if selected 124 | @attr = Ncurses::A_BOLD if selected 125 | @color_pair =get_color $selectedcolor, @parent.selected_color, @parent.selected_bgcolor unless @parent.nil? 126 | end 127 | case focussed 128 | when :SOFT_FOCUS 129 | @attr |= Ncurses::A_BOLD 130 | when true 131 | @attr |= Ncurses::A_REVERSE 132 | when false 133 | end 134 | #if focussed 135 | #@attr |= Ncurses::A_REVERSE 136 | #end 137 | end 138 | end # class 139 | 140 | end # module 141 | -------------------------------------------------------------------------------- /examples/dirtree.rb: -------------------------------------------------------------------------------- 1 | require 'canis/core/util/app' 2 | require 'fileutils' 3 | require 'canis/core/widgets/tree/treemodel' 4 | require_relative './common/file' 5 | require_relative './common/devel' 6 | 7 | def _directories wd 8 | $log.debug " directories got :#{wd}: " 9 | wd ||= "" 10 | return [] if wd == "" 11 | d = Dir.new(wd) 12 | ent = d.entries.reject{|e| !File.directory? File.join(wd,e)} 13 | $log.debug " directories got XXX: #{ent} " 14 | ent.delete(".");ent.delete("..") 15 | return ent 16 | end 17 | $log = create_logger "canis14.log" 18 | App.new do 19 | def help_text 20 | <<-eos 21 | 22 | ========================================================================= 23 | ## Basic Usage 24 | 25 | ### Left Window 26 | 27 | expand/collapse directories 28 | v select and list a directory in other window 29 | 30 | See also [tree] 31 | 32 | ### Right Window 33 | 34 | enter a directory 35 | open a file in 'EDITOR' 36 | v page a file using 'PAGER' 37 | 38 | See also [list] 39 | 40 | [index] 41 | eos 42 | end 43 | 44 | # print the dir list on the right listbox upon pressing ENTER or row_selector (v) 45 | # separated here so it can be called from two places. 46 | def lister node 47 | path = File.join(*node.user_object_path) 48 | populate path 49 | end 50 | 51 | def populate path 52 | ll = @form.by_name["ll"] 53 | return unless ll 54 | if File.exists? path 55 | files = Dir.new(path).entries 56 | files.delete(".") 57 | #files = file_listing path, :mode => :LONG 58 | ll.clear_selection 59 | ll.list files 60 | ll.title path 61 | #TODO show all details in filelist 62 | @current_path = path 63 | return path 64 | end 65 | end 66 | 67 | header = app_header "canis #{Canis::VERSION}", 68 | :text_center => "Dorado", 69 | :text_right =>"Directory Lister" , 70 | :color => :white, :bgcolor => 242 #, :attr => Ncurses::A_BLINK 71 | message "Press Enter to expand/collapse, v to view in lister. Help" 72 | 73 | @form.help_manager.help_text = help_text() 74 | 75 | pwd = Dir.getwd 76 | entries = _directories pwd 77 | patharray = pwd.split("/") 78 | # we have an array of path, to add recursively, one below the other 79 | nodes = [] 80 | nodes << TreeNode.new(patharray.shift) 81 | patharray.each do |e| 82 | nodes << nodes.last.add(e) 83 | end 84 | last = nodes.last 85 | nodes.last.add entries 86 | model = DefaultTreeModel.new nodes.first 87 | model.root_visible = false 88 | 89 | ht = FFI::NCurses.LINES - 2 90 | borderattrib = :normal 91 | flow :margin_top => 1, :margin_left => 0, :width => :expand, :height => ht do 92 | @tree = tree :data => model, :width_pc => 30, :border_attrib => borderattrib 93 | rend = @tree.renderer # just test method out. 94 | rend.row_selected_attr = BOLD 95 | @tree.bind :TREE_WILL_EXPAND_EVENT do |node| 96 | path = File.join(*node.user_object_path) 97 | dirs = _directories path 98 | ch = node.children 99 | ch.each do |e| 100 | o = e.user_object 101 | if dirs.include? o 102 | dirs.delete o 103 | else 104 | # delete this child since its no longer present TODO 105 | end 106 | end 107 | #message " #{node} will expand: #{path}, #{dirs} " 108 | node.add dirs 109 | lister node 110 | end 111 | @tree.bind :TREE_WILL_COLLAPSE_EVENT do |node| 112 | # FIXME do if ony not already showing on other side 113 | lister node 114 | end 115 | @tree.bind :TREE_SELECTION_EVENT do |ev| 116 | if ev.state == :SELECTED 117 | node = ev.node 118 | lister node 119 | end 120 | end # select 121 | #$def_bg_color = :blue 122 | @form.bgcolor = :blue 123 | @tree.expand_node last # 124 | @tree.mark_parents_expanded last # make parents visible 125 | @l = listbox :width_pc => 70, :border_attrib => borderattrib, :selection_mode => :single, :name => 'll', 126 | :left_margin => 1 127 | @l.renderer directory_renderer(@l) 128 | @l.renderer().row_focussed_attr = REVERSE 129 | 130 | @l.bind :LIST_SELECTION_EVENT do |ev| 131 | message ev.source.current_value #selected_value 132 | # FIXME if ".." then remove basename 133 | e = ev.source.current_value 134 | if e == ".." 135 | _f = File.dirname(@current_path) 136 | else 137 | _f = File.join(@current_path, e) 138 | end 139 | file_page _f if ev.type == :INSERT 140 | #TODO when selects drill down 141 | #TODO when selecting, sync tree with this 142 | end 143 | # on pressing enter, we edit the file using vi or EDITOR 144 | @l.bind :PRESS do |ev| 145 | # FIXME if ".." then remove basename 146 | e = ev.source.current_value 147 | if e == ".." 148 | _f = File.dirname(@current_path) 149 | else 150 | _f = File.join(@current_path, e) 151 | end 152 | #_f = File.join(@current_path, ev.source.current_value) 153 | if File.directory? _f 154 | populate _f 155 | else 156 | file_edit _f if File.exists? _f 157 | end 158 | end 159 | @form.bind_key([?\\, ?l, ?1]){ 160 | ll = @form.by_name["ll"] 161 | ll.renderer.formatter = proc do | fname, stat, prefix| 162 | "%s%-40s | %10d | %s " % [prefix, fname, stat.size, stat.mtime.strftime("%Y-%m-%d")] 163 | end 164 | # why is repaint all not enough ? is repaint not being called ? 165 | ll.repaint_all 166 | #ll.render_all 167 | ll.repaint 168 | #ll.fire_dimension_changed 169 | } 170 | @form.bind_key([?\\, ?l, ?2]){ 171 | ll = @form.by_name["ll"] 172 | ll.renderer.formatter = nil 173 | #ll.fire_dimension_changed 174 | ll.repaint_all 175 | #ll.render_all 176 | ll.repaint 177 | } 178 | end 179 | status_line :row => FFI::NCurses.LINES - 1 180 | end # app 181 | -------------------------------------------------------------------------------- /examples/testdb.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | # Using tablewidget with sqlite3 resultset 4 | # TODO : make columns hidden on key - toggle, how to get back then 5 | # TODO : move column position 6 | # TODO : filter column 7 | # TODO : menu on C-x to delete a column, hide unhide expand etc, use pad menu 8 | require 'logger' 9 | require 'canis' 10 | require 'canis/core/widgets/table' 11 | require 'sqlite3' 12 | # 13 | def get_data 14 | dbname = "movie.sqlite" 15 | raise unless File.exists? dbname 16 | db = SQLite3::Database.new(dbname) 17 | sql = "select * from movie" 18 | $columns, *rows = db.execute2(sql) 19 | content = rows 20 | return nil if content.nil? or content[0].nil? 21 | $datatypes = content[0].types #if @datatypes.nil? 22 | return content 23 | end 24 | 25 | def edit_row tw 26 | row = tw.current_value 27 | h = tw.columns 28 | _edit h, row, " Edit " 29 | tw.fire_row_changed tw.current_index 30 | end 31 | def insert_row tw 32 | h = tw.columns 33 | row = [] 34 | h.each { |e| row << "" } 35 | ret = _edit h, row, "Insert" 36 | if ret 37 | tw.add row 38 | tw.fire_dimension_changed 39 | end 40 | end 41 | 42 | # making a generic edit messagebox - quick dirty 43 | def _edit h, row, title 44 | _l = longest_in_list h 45 | _w = _l.size 46 | # _w can be longer than 70, assuming that screen is 70 or more 47 | config = { :width => 70, :title => title } 48 | bw = get_color $datacolor, :black, :white 49 | mb = MessageBox.new config do 50 | txt = nil 51 | h.each_with_index { |f, i| 52 | txt = row[i] || "" 53 | add LabeledField.new :label => "%*s:" % [_w, f], :text => txt.chomp, :name => i.to_s, 54 | :bgcolor => :cyan, 55 | :width => 50, 56 | :label_color_pair => bw 57 | } 58 | button_type :ok_cancel 59 | end 60 | index = mb.run 61 | return nil if index != 0 62 | h.each_with_index { |e, i| 63 | f = mb.widget(i.to_s) 64 | row[i] = f.text 65 | } 66 | row 67 | end 68 | begin 69 | # Initialize curses 70 | Canis::start_ncurses # this is initializing colors via ColorMap.setup 71 | path = File.join(ENV["LOGDIR"] || "./" ,"canis14.log") 72 | logfilename = File.open(path, File::WRONLY|File::TRUNC|File::CREAT) 73 | $log = Logger.new(logfilename) 74 | $log.level = Logger::DEBUG 75 | 76 | 77 | colors = Ncurses.COLORS 78 | back = :black 79 | lineback = :blue 80 | back = 234 if colors >= 256 81 | lineback = 236 if colors >= 256 82 | 83 | catch(:close) do 84 | @window = Canis::Window.root_window 85 | @form = Form.new @window 86 | 87 | #header = app_header "0.0.1", :text_center => "Movie Database", :text_right =>"" , :name => "header" , :color => :white, :bgcolor => lineback , :attr => :bold 88 | 89 | 90 | 91 | _col = "#[fg=yellow]" 92 | $message = Variable.new 93 | $message.value = "" 94 | =begin 95 | @status_line = status_line :row => Ncurses.LINES-1 #, :bgcolor => :red, :color => :yellow 96 | @status_line.command { 97 | "#[bg=236, fg=black]#{_col}F1#[/end] Help | #{_col}?#[/end] Keys | #{_col}M-c#[/end] Ask | #{_col}M-d#[/end] History | #{_col}M-m#[/end] Methods | %20s" % [$message.value] 98 | } 99 | =end 100 | 101 | h = FFI::NCurses.LINES-4 102 | w = FFI::NCurses.COLS 103 | r = 1 104 | #header = %w[ Pos Last Title Director Year Country Mins BW] 105 | #file = "movies1000.txt" 106 | 107 | arr = get_data 108 | tv = Canis::Table.new @form, :row => 2, :col => 0, :height => h, :width => w, :name => "tv", :suppress_borders => false do |b| 109 | 110 | b.resultset $columns, arr 111 | 112 | b.model_row 1 113 | b.column_width 0, 5 114 | #b.get_column(2).color = :red 115 | #b.get_column(3).color = :yellow 116 | #b.get_column(2).bgcolor = :blue 117 | b.column_width 1, 5 118 | b.column_width 4, 5 119 | #b.column_width 2, 5 120 | b.column_align 6, :right 121 | #b.column_width 2, b.calculate_column_width(2) 122 | b.column_width 2, 50 123 | b.column_width 3, 25 124 | b.column_width 5, 10 125 | #b.column_width 3, 55 126 | #b.column_hidden 1, true 127 | end 128 | mcr = Canis::DefaultTableRenderer.new tv 129 | mcr.header_colors :white, :red 130 | tv.renderer mcr 131 | mcr.column_model ( tv.column_model ) 132 | tv.create_default_sorter 133 | tv.move_column 1,-1 134 | 135 | # pressing ENTER on a method name will popup details for that method 136 | tv.bind(:PRESS) { |ev| 137 | if @current_index > 0 138 | w = ev.word_under_cursor.strip 139 | w = ev.text 140 | # the curpos does not correspond to the formatted display, this is just the array 141 | # without the width info XXX 142 | alert "#{ev.current_index}, #{ev.curpos}: #{w}" 143 | end 144 | } 145 | tv.bind_key(?e) { edit_row(tv) } 146 | tv.bind_key(?i) { insert_row(tv) } 147 | tv.bind_key(?D) { tv.delete_at tv.current_index } 148 | @form.bind_key(?\M-c, "Filter") { 149 | tv = @form.by_name["tv"]; 150 | str = get_string "Enter name of director:" 151 | if str && str.length > 0 152 | m = tv.matching_indices do |ix, fields| 153 | fields[3] =~ /#{str}/i 154 | end 155 | else 156 | tv.clear_matches 157 | end 158 | } 159 | 160 | 161 | $message.value = "#{tv.current_index}: #{tv.lastrow}, #{tv.lastcol}" 162 | @form.repaint 163 | @window.wrefresh 164 | Ncurses::Panel.update_panels 165 | while((ch = @window.getchar()) != KEY_F10 ) 166 | break if ch == ?q.ord || ch == ?\C-q.getbyte(0) 167 | @form.handle_key(ch) 168 | $message.value = "#{tv.current_index}: #{tv.lastrow}, #{tv.lastcol}" 169 | @window.wrefresh 170 | end 171 | end 172 | rescue => ex 173 | textdialog ["Error in rib: #{ex} ", *ex.backtrace], :title => "Exception" 174 | $log.debug( ex) if ex 175 | $log.debug(ex.backtrace.join("\n")) if ex 176 | ensure 177 | @window.destroy if !@window.nil? 178 | Canis::stop_ncurses 179 | p ex if ex 180 | p(ex.backtrace.join("\n")) if ex 181 | end 182 | # a test renderer to see how things go 183 | -------------------------------------------------------------------------------- /examples/testmessagebox.rb: -------------------------------------------------------------------------------- 1 | # To test out the new messagebox 2 | # The old messagebox provided a lot of convenience methods that were complicated 3 | # and confusing. This one is simpler. 4 | # The examples here are based on the old test1.rb that will not work now 5 | # since the interface has been changed and simplified 6 | # 7 | require 'logger' 8 | require 'canis' 9 | require 'canis/core/widgets/rmessagebox' 10 | #require 'canis/deprecated/widgets/rmessagebox' 11 | 12 | if $0 == __FILE__ 13 | # Initialize curses 14 | begin 15 | # XXX update with new color and kb 16 | Canis::start_ncurses # this is initializing colors via ColorMap.setup 17 | $log = Logger.new((File.join(ENV['LOGDIR'] || "./" ,"canis14.log"))) 18 | $log.level = Logger::DEBUG 19 | 20 | # @window = Canis::Window.root_window 21 | 22 | 23 | catch(:close) do 24 | choice = ARGV[0] && ARGV[0].to_i || 3 25 | $log.debug "START MESSAGE BOX TEST #{ARGV[0]}. choice==#{choice} ---------" 26 | # need to pass a form, not window. 27 | case choice 28 | when 1 29 | require 'canis/core/widgets/listbox' 30 | nn = "mylist" 31 | l = Listbox.new nil, :row => 2, :col => 5, :list => %w[john tim lee wong kepler edward why chad andy], 32 | :selection_mode => :multiple, :height => 10, :width => 20 , :selected_color => :green, :selected_bgcolor => :white, :selected_indices => [2,6], :name => nn 33 | #default_values %w[ lee why ] 34 | 35 | l.unbind_key(KEY_ENTER) 36 | @mb = MessageBox.new :width => 30, :height => 18 do 37 | title "Select a name" 38 | button_type :ok_cancel 39 | item Label.new :row => 1, :col => 1, :text => "Enter your name:" 40 | item l 41 | end 42 | @mb.run 43 | $log.debug "XXX: #{l.selected_indices} " 44 | n = @mb.widget(nn) 45 | $log.debug "XXXX: #{n.selected_indices}, #{n.name} " 46 | when 2 47 | @mb = Canis::MessageBox.new do 48 | title "Color selector" 49 | message "Select a color" 50 | #item Label.new :text => "Select a color", :row => 1 , :col => 2 51 | 52 | r = 3 53 | c = 2 54 | %w[&red &green &blue &yellow].each_with_index { |b, i| 55 | bu = Button.new :name => b, :text => b, :row => r, :col => c 56 | bu.command { throw(:close, i) } 57 | item bu 58 | #bu.default_button(true) if i == 0 59 | #r += 1 60 | c += b.length + 5 61 | } 62 | end 63 | index = @mb.run 64 | $log.debug "XXX: messagebox 2 ret #{index} " 65 | when 3 66 | @mb = Canis::MessageBox.new do 67 | title "Enter your name" 68 | #message "Enter your first name. You are not permitted to enter x z or q and must enter a capital first" 69 | message "Enter your first name. Initcaps " 70 | add Field.new :chars_allowed => /[^0-9]/, :valid_regex => /^[A-Z][a-z]*/, :default => "Matz", :bgcolor => :cyan 71 | button_type :ok_cancel 72 | end 73 | @mb.run 74 | $log.debug "XXX: got #{@mb.widget(1).text} " 75 | when 4 76 | mb = MessageBox.new :title => "HTTP Configuration" , :width => 50 do 77 | add LabeledField.new :label => 'User', :name => "user", :width => 30, :bgcolor => :cyan 78 | add CheckBox.new :text => "No &frames", :onvalue => "Selected", :offvalue => "UNselected" 79 | add CheckBox.new :text => "Use &HTTP/1.0", :value => true 80 | add CheckBox.new :text => "Use &passive FTP" 81 | add Label.new :text => " Language ", :attr => REVERSE 82 | $radio = Canis::Variable.new 83 | add RadioButton.new :text => "py&thon", :value => "python", :color => :blue, :variable => $radio 84 | add RadioButton.new :text => "rub&y", :color => :red, :variable => $radio 85 | button_type :ok 86 | end 87 | field = mb.widget("user") 88 | field.bind(:ENTER) do |f| 89 | listconfig = {:bgcolor => :blue, :color => :white, 90 | :relative_to => field, :col => field.col + 6, :width => field.width} 91 | users= %w[john tim lee wong kepler edward _why chad andy] 92 | #index = popuplist(users, :relative_to => field, :col => field.col + 6, :width => field.width) 93 | index = popuplist(users, listconfig) 94 | field.text users[index] if index 95 | end 96 | mb.run 97 | 98 | when 5 99 | require 'canis/core/widgets/listbox' 100 | label = Label.new 'text' => 'File', 'mnemonic'=>'F', :row => 3, :col => 5 101 | field = Field.new :name => "file", :row => 3 , :col => 10, :width => 40, :set_label => label 102 | #flist = Dir.glob(File.join( File.expand_path("~/"), "*")) 103 | flist = Dir.glob("*") 104 | listb = Listbox.new :name => "mylist", :row => 4, :col => 3, :width => 50, :height => 10, 105 | :list => flist, :title => "File List", :selected_bgcolor => :white, :selected_color => :blue, 106 | :selection_mode => :single, :border_attrib => REVERSE 107 | #listb.bind(:ENTER_ROW) { field.text listb.selected_item } 108 | # if you find that cursor goes into listbox while typing, then 109 | # i've put set_form_row in listbox list_data_changed 110 | field.bind(:CHANGE) do |f| 111 | flist = Dir.glob("*"+f.getvalue+"*") 112 | #l.insert( 0, *flist) if flist 113 | listb.list flist 114 | end 115 | listb.unbind_key(KEY_ENTER) 116 | mb = Canis::MessageBox.new :height => 20, :width => 60 do 117 | title "Sample File Selector" 118 | add label 119 | add field 120 | add listb 121 | #height 20 122 | #width 60 123 | #top 5 124 | #left 20 125 | #default_button 0 126 | button_type :ok_cancel 127 | 128 | end 129 | mb.run 130 | $log.debug "MBOX :1selected #{listb}" 131 | $log.debug "MBOX :selected #{listb.selected_value}" 132 | end 133 | 134 | end 135 | rescue => ex 136 | ensure 137 | @window.destroy unless @window.nil? 138 | Canis::stop_ncurses 139 | p ex if ex 140 | p(ex.backtrace.join("\n")) if ex 141 | $log.debug( ex) if ex 142 | $log.debug(ex.backtrace.join("\n")) if ex 143 | end 144 | end 145 | -------------------------------------------------------------------------------- /lib/canis/core/include/multibuffer.rb: -------------------------------------------------------------------------------- 1 | require 'canis/core/util/promptmenu' 2 | module Canis 3 | # this module makes it possible for a textview to maintain multiple buffers 4 | # The first buffer has been placed using set_content(lines, config). 5 | # After this, additional buffers mst be supplied with add_content text, config. 6 | # Also, please note that after you call set_content the first time, you must call 7 | # add_content so the buffer can be accessed while cycling. will try to fix this. 8 | # (I don't want to touch textview, would prefer not to write a decorator). 9 | 10 | # TODO ?? allow setting of a limit, so in some cases where we keep adding 11 | # programatically, the 12 | # TODO: maintain cursor and line number so user can revert to same point. this will have to be 13 | # updated by buffer_next and others. 14 | # Done: need to be able to set multiple file names. which are read in only when 15 | # buffer is accessed. filename to be maintained and used as title. 16 | # == CHANGE: 17 | # allow filename to be sent, rather than array. Array was very limiting since it 18 | # did not have a name to list or goto a buffer with. Also, now we can add file names that 19 | # are read only if the buffer is selected. 20 | module MultiBuffers 21 | extend self 22 | 23 | # add content to buffers of a textview 24 | # @param [Array] text, or String (filename) 25 | # @param [Hash] options, typically :content_type => :ansi or :tmux, and :title 26 | def add_content text, config={} 27 | unless @_buffers 28 | bind_key(?\M-n, :buffer_next) 29 | bind_key(?\M-p, :buffer_prev) 30 | bind_key(KEY_BACKSPACE, :buffer_prev) # backspace, already hardcoded in textview ! 31 | bind_key(?:, :buffer_menu) 32 | end 33 | @_buffers ||= [] 34 | @_buffers_conf ||= [] 35 | @_buffers << text 36 | if text.is_a? String 37 | config[:filename] = text 38 | config[:title] ||= text 39 | end 40 | @_buffers_conf << config 41 | @_buffer_ctr ||= 0 42 | $log.debug "XXX: HELP adding text #{@_buffers.size} " 43 | end 44 | 45 | # supply an array of files to the multibuffer. These will be read 46 | # as the user presses next or last etc. 47 | def add_files filearray, config={} 48 | filearray.each do |e| add_content(e, config.dup); end 49 | end 50 | 51 | # display next buffer 52 | def buffer_next 53 | buffer_update_info 54 | @_buffer_ctr += 1 55 | x = @_buffer_ctr 56 | l = @_buffers[x] 57 | if l 58 | populate_buffer_from_filename x 59 | else 60 | @_buffer_ctr = 0 61 | end 62 | set_content @_buffers[@_buffer_ctr], @_buffers_conf[@_buffer_ctr] 63 | buffer_update_position 64 | end 65 | def populate_buffer_from_filename x 66 | l = @_buffers[x] 67 | if l 68 | if l.is_a? String 69 | if File.directory? l 70 | Dir.chdir(l) 71 | arr = Dir.entries(".") 72 | @_buffers[x] = arr 73 | else 74 | arr = File.open(l,"r").read.split("\n") 75 | @_buffers[x] = arr 76 | end 77 | end 78 | end 79 | end 80 | # 81 | # display previous buffer if any 82 | def buffer_prev 83 | buffer_update_info 84 | if @_buffer_ctr < 1 85 | buffer_last 86 | return 87 | end 88 | @_buffer_ctr -= 1 if @_buffer_ctr > 0 89 | x = @_buffer_ctr 90 | l = @_buffers[x] 91 | if l 92 | populate_buffer_from_filename x 93 | l = @_buffers[x] 94 | $log.debug "bp calling set_content with #{l.class} " 95 | set_content l, @_buffers_conf[x] 96 | buffer_update_position 97 | end 98 | end 99 | def buffer_last 100 | buffer_update_info 101 | @_buffer_ctr = @_buffers.count - 1 102 | x = @_buffer_ctr 103 | l = @_buffers.last 104 | if l 105 | populate_buffer_from_filename x 106 | l = @_buffers[x] 107 | $log.debug " calling set_content with #{l.class} " 108 | set_content l, @_buffers_conf.last 109 | buffer_update_position 110 | end 111 | end 112 | def buffer_at index 113 | buffer_update_info 114 | @_buffer_ctr = index 115 | l = @_buffers[index] 116 | if l 117 | populate_buffer_from_filename index 118 | l = @_buffers[index] 119 | set_content l, @_buffers_conf[index] 120 | buffer_update_position 121 | 122 | 123 | end 124 | end 125 | # close window, a bit clever, we really don't know what the CLOSE_KEY is 126 | def close 127 | @graphic.ungetch(?q.ord) 128 | end 129 | # display a menu so user can do buffer management 130 | # However, how can application add to these. Or disable, such as when we 131 | # add buffer delete or buffer insert or edit 132 | def buffer_menu 133 | menu = PromptMenu.new self do 134 | item :n, :buffer_next 135 | item :p, :buffer_prev 136 | item :b, :scroll_backward 137 | item :f, :scroll_forward 138 | item :l, :list_buffers 139 | item :q, :close 140 | submenu :m, "submenu..." do 141 | item :p, :goto_last_position 142 | item :r, :scroll_right 143 | item :l, :scroll_left 144 | end 145 | end 146 | menu.display_new :title => "Buffer Menu" 147 | end 148 | # pops up a list of buffers using titles allowing the user to select 149 | # Based on selection, that buffer is displayed. 150 | def list_buffers 151 | arr = [] 152 | @_buffers_conf.each_with_index do |e, i| 153 | t = e[:title] || "no title for #{i}" 154 | #$log.debug " TITLE is #{e.title} , t is #{t} " 155 | arr << t 156 | end 157 | ix = popuplist arr 158 | buffer_at ix 159 | end 160 | def buffer_update_info 161 | x = @_buffer_ctr || 0 162 | @_buffers_conf[x][:current_index] = @current_index || 0 163 | @_buffers_conf[x][:curpos] = @curpos || 0 164 | end 165 | def buffer_update_position 166 | x = @_buffer_ctr || 0 167 | ci = (@_buffers_conf[x][:current_index] || 0) 168 | goto_line ci 169 | @curpos = (@_buffers_conf[x][:curpos] || 0) 170 | end 171 | 172 | end 173 | end 174 | -------------------------------------------------------------------------------- /lib/canis/core/widgets/tree/treecellrenderer.rb: -------------------------------------------------------------------------------- 1 | # 2010-09-18 15:35 2 | require 'canis' 3 | require 'canis/core/widgets/rwidget' 4 | module Canis 5 | 6 | ## 7 | # This is a basic list cell renderer that will render the to_s value of anything. 8 | # 9 | # TODO upgrade as per new listcellrenderer 10 | class TreeCellRenderer 11 | PLUS_PLUS = "++" 12 | PLUS_MINUS = "+-" 13 | PLUS_Q = "+?" 14 | include Canis::ConfigSetup 15 | include Canis::Utils 16 | dsl_accessor :justify # :right, :left, :center # added 2008-12-22 19:02 17 | dsl_accessor :display_length # please give this to ensure the we only print this much 18 | dsl_accessor :height # if you want a multiline label. 19 | dsl_accessor :text # text of label 20 | dsl_accessor :color, :bgcolor 21 | dsl_accessor :row, :col 22 | dsl_accessor :parent #usuall the table to get colors and other default info 23 | attr_reader :actual_length 24 | attr_accessor :pcol 25 | 26 | def initialize text="", config={}, &block 27 | @text = text 28 | @editable = false 29 | @focusable = false 30 | @actual_length = 0 31 | config_setup config # @config.each_pair { |k,v| variable_set(k,v) } 32 | instance_eval &block if block_given? 33 | init_vars 34 | end 35 | def init_vars 36 | @justify ||= :left 37 | @display_length ||= 10 38 | end 39 | def getvalue 40 | @text 41 | end 42 | ## 43 | # sets @color_pair and @attr 44 | def prepare_default_colors focussed, selected 45 | @color_pair = get_color $datacolor 46 | @attr = @row_attr || Ncurses::A_NORMAL 47 | 48 | 49 | ## determine bg and fg and attr 50 | if selected 51 | #@attr = Ncurses::A_BOLD if selected 52 | ## 2010-09-18 18:32 making selected row reverse 53 | @attr |= Ncurses::A_REVERSE 54 | 55 | # 2010-09-18 18:33 maybe not required, just confuses the whole thing and uglifies it 56 | #@color_pair =get_color $selectedcolor, @parent.selected_color, @parent.selected_bgcolor unless @parent.nil? 57 | end 58 | case focussed 59 | when :SOFT_FOCUS 60 | @attr |= Ncurses::A_BOLD 61 | when true 62 | # earlier focussed row showed up in reverse, which was confusing since it looked selected 63 | # now focussed row has cursor on side, and can be bold. that's enough. 64 | @attr |= Ncurses::A_BOLD 65 | #@attr |= Ncurses::A_REVERSE 66 | when false 67 | end 68 | end 69 | 70 | ## 71 | # paint a list box cell 72 | # 2010-09-02 15:38 changed focussed to take true, false and :SOFT_FOCUS 73 | # SOFT_FOCUS means the form focus is no longer on this field, but this row 74 | # was focussed when use was last on this field. This row will take focus 75 | # when field is focussed again 76 | # 77 | # @param [Buffer] window or buffer object used for printing 78 | # @param [Integer] row 79 | # @param [Integer] column 80 | # @param [Integer] actual index into data, some lists may have actual data elsewhere and 81 | # display data separate. e.g. rfe_renderer (directory listing) 82 | # @param [String] text to print in cell 83 | # @param [Boolean, :SOFT_FOCUS] cell focussed, not focussed, cell focussed but field is not focussed 84 | # @param [Boolean] cell selected or not 85 | #renderer.repaint @graphic, r+hh, c+@left_margin, crow, object, content, focus_type, selected, expanded, leaf 86 | def repaint graphic, r=@row,c=@col, row_index=-1, treearraynode=nil, value=@text, leaf=nil, focussed=false, selected=false, expanded=false 87 | #$log.debug "label :#{@text}, #{value}, #{r}, #{c} col= #{@color}, #{@bgcolor} acolor= #{acolor} j:#{@justify} dlL: #{@display_length} " 88 | 89 | prepare_default_colors focussed, selected 90 | 91 | value=value.to_s # ?? 92 | #icon = object.is_leaf? ? "-" : "+" 93 | #icon = leaf ? "-" : "+" 94 | 95 | #level = treearraynode.level 96 | #node = treearraynode.node 97 | level = treearraynode.level 98 | node = treearraynode 99 | if parent.node_expanded? node 100 | icon = PLUS_MINUS # can collapse 101 | else 102 | icon = PLUS_PLUS # can expand 103 | end 104 | if node.children.size == 0 105 | icon = PLUS_Q # either no children or not visited yet 106 | if parent.has_been_expanded node 107 | icon = PLUS_MINUS # definitely no children, we've visited 108 | end 109 | end 110 | # adding 2 to level, that's the size of icon 111 | # XXX FIXME if we put the icon here, then when we scroll right, the icon will show, it shoud not 112 | # FIXME we ignore truncation etc on previous level and take the object as is !!! 113 | _value = "%*s %s" % [ level+2, icon, node.user_object ] 114 | @actual_length = _value.length 115 | pcol = @pcol 116 | if pcol > 0 117 | _len = @display_length || @parent.width-2 118 | _value = _value[@pcol..@pcol+_len-1] 119 | end 120 | _value ||= "" 121 | if @height && @height > 1 122 | else 123 | # ensure we do not exceed 124 | if !@display_length.nil? 125 | if _value.length > @display_length 126 | @actual_length = _value.length 127 | _value = _value[0..@display_length-1] 128 | end 129 | end 130 | #lablist << value 131 | end 132 | len = @display_length || _value.length 133 | graphic.printstring r, c, "%-*s" % [len, _value], @color_pair,@attr 134 | #_height = @height || 1 135 | #0.upto(_height-1) { |i| 136 | #graphic.printstring r+i, c, ( " " * len) , @color_pair,@attr 137 | #} 138 | #lablist.each_with_index do |_value, ix| 139 | #break if ix >= _height 140 | #if @justify.to_sym == :center 141 | #padding = (@display_length - _value.length)/2 142 | #_value = " "*padding + _value + " "*padding # so its cleared if we change it midway 143 | #end 144 | #graphic.printstring r, c, str % [len, _value], @color_pair,@attr 145 | #r += 1 146 | #end 147 | end 148 | # ADD HERE 149 | end 150 | end 151 | --------------------------------------------------------------------------------