qt_connect 1.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,257 @@
1
+ qt\_connect
2
+ ==========
3
+
4
+ This project provides a Qt programming API for JRuby with a high degree of compatibility
5
+ with the existing Qt interface for Ruby. The API is implemented using Qt Jambi, the Java version
6
+ of the Qt framework. The interface consists of two software components: the actual Ruby
7
+ bindings (qt\_jambi) and a compatibility layer (qt\_compat) which makes it easier to run
8
+ existing Qt Ruby code using JRuby.
9
+ A third component (qt\_sugar) introduces the Qt Jambi signals interface to QtRuby. With this interface
10
+ there is no need to use C++ signatures or slots on the application level.
11
+ The three components are packaged in a single, pure-Ruby gem package 'qt\_connect'. The same gem
12
+ can be installed on Ruby and JRuby installations.
13
+
14
+ ### prerequisites and usage
15
+ **Ruby**
16
+ >*prerequisite:* qtbindings Ruby gem package
17
+ >>download from: <http://github.com/ryanmelt/qtbindings>
18
+ >>installation: follow qtbindings instructions
19
+
20
+ >`require 'qt_connect'` to use Qt Jambi Signals interface
21
+
22
+ **JRuby**
23
+ >*prerequisite:* qtjambi Java library
24
+ >>download from: <http://qt-jambi.org>
25
+ >>installation: install two jar-files on CLASSPATH (e.g. C:/jruby-1.6.7/lib or /usr/lib/jruby/lib)
26
+ >>qtjambi-`<version>`.jar (e.g. qtjambi-4.7.1.jar)
27
+ >>qtjambi-`<platform>`-`<version>`.jar (e.g. qtjambi-win32-msvc2008-4.7.1.jar)
28
+
29
+ >`require 'qt_jambi'` for basic Qt Jambi bindings only
30
+ >`require 'qt_connect'` for Qt Jambi bindings + QtRuby compatibility layer
31
+
32
+
33
+ ### tested environments
34
+
35
+ **[Windows 7 Home Premium]**
36
+ >ruby 1.9.1p429 [i386-mingw32]
37
+ > qtbindings-4.6.3.2-x86-mingw32
38
+ > qt_connect-1.5.1
39
+ >
40
+ >jruby 1.6.7 [Windows 7-x86-java]
41
+ >qtjambi-4.7.1.jar
42
+ >qtjambi-win32-msvc2008-4.7.1.jar
43
+ >qt\_connect-1.5.1
44
+
45
+ **[Mac OS X : NOT TESTED]** ; *please report your experiences*
46
+
47
+ **[Linux : ubuntu 11:10]**
48
+ >ruby 1.8.7p352 [x86_64-linux]
49
+ >qtbindings-4.6.3.4
50
+ >qt\_connect-1.5.1
51
+ >
52
+ >jruby 1.5.1p249 [amd64-java]
53
+ >qtjambi-4.7.0.jar
54
+ >qtjambi-linux64-gcc-4.7.0.jar
55
+ >qt\_connect-1.5.1
56
+
57
+ >>Note: Under Ubuntu I could only run the programs using administrator privilege
58
+ >>e.g. sudo jruby ..... My Linux expertise is limited but it seems that
59
+ >>the operating system itself uses the Qt library and that maybe jruby loads (part of)
60
+ >>the qtjambi library not from the jar but from somewere else on the loadpath. I have
61
+ >>the same problem with the QtJambi demo programs if I just install QtJambi (nothing
62
+ >>to do with JRuby). Any help would be appreciated.
63
+
64
+
65
+ qt\_jambi
66
+ --------
67
+ Provides the basic Ruby bindings for the Qt Jambi Java classes and an interface to the
68
+ Signals and Slots mechanism. Appropriate classes are extended with overloaded operators
69
+ such as +, - , | and &. Both Ruby-ish underscore and Java-ish camel case naming for
70
+ methods can be used. Like with Qt Ruby, constructors can be called in a conventional
71
+ style or with an initializer block. If you want an API which follows closely the QtJambi
72
+ documentation and programming examples all you need to do is `'require qt_jambi'`. A
73
+ minimalist example to display just a button which connects to a website when clicked:
74
+
75
+ require 'qt_jambi'
76
+
77
+ app=Qt::Application.new(ARGV)
78
+ button=Qt::PushButton.new( "Hello World")
79
+ button.clicked.connect{ Qt::DesktopServices.openUrl(Qt::Url.new('www.hello-world.com'))}
80
+ button.show
81
+ app.exec`
82
+
83
+
84
+ The QtJambi documentation contains a large number of examples written in Java. Even with
85
+ a moderate knowledge of Java it is fairly easy to translate these to Ruby or
86
+ use them as examples for new Ruby programs.
87
+
88
+ qt\_compat
89
+ ---------
90
+ A compatibility layer which can be installed on top of the 'qt\_jambi' bindings.
91
+ Including this layer makes it possible to run existing Qt Ruby code with no or only minor
92
+ modifications using JRuby.
93
+ Since the QtJambi API is build on the same underlying C++ library as the Qt Ruby API, the
94
+ Qt(C++) classes are mostly mapped to equivalent Qt Jambi(Java/Ruby) and Qt Ruby(Ruby)
95
+ classes. The majority of Ruby classes exposed through the Qt Jambi API are therefore
96
+ identical to the classes exposed through the Qt Ruby API. There are however differences
97
+ due to different language capabilities and limitations in converting C++ types to Java and
98
+ Ruby 'types'. The two most notable differences are the handling of Enums and the abstract
99
+ value type QVariant
100
+
101
+ ### Enums
102
+ Enums are used widely throughout the Qt framework, mostly as integer constants and
103
+ bit flags. Using Qt Jambi, Java Enums are accessible through JRuby as constants. Individual
104
+ values are accessed combining namespace with enum type name and enum value name:
105
+
106
+ Qt::Alignment::AlignCenter
107
+ Qt::Alignment::AlignLeft
108
+ Qt::Font::StyleHint::Times
109
+ Qt::Font::StyleStrategy::ForceOutline
110
+
111
+ QtRuby accesses the same values using a shorthand notation omitting the type name:
112
+
113
+ Qt::AlignCenter
114
+ Qt::AlignLeft
115
+ Qt::Font::Times
116
+ Qt::Font::ForceOutline
117
+
118
+ Using the compatibility layer the same shorthand notation constants are defined in addition
119
+ to the existing ones. If the shorthand notation clashes with other entries or existing class
120
+ names, the default mapping can be overruled.
121
+
122
+ ### QVariant
123
+ The Qt API provides an abstract value type, QVariant, which can hold values of many
124
+ C++ and Qt types. In Java - like Ruby - there is no real need for such a type because
125
+ the language already provides Object as an abstract type. This works because all Java
126
+ classes inherit Object. For that reason, the Qt Jambi API uses Object where Qt uses
127
+ QVariant.
128
+ To maintain code compatibility with QtRuby a dummy constructor method
129
+ is defined which basically returns the original object unchanged:
130
+
131
+ size=Qt::Size.new(100,200)
132
+ variant=Qt::Variant.new(size) #=> size
133
+
134
+ This works in many cases, but not always. QtRuby code using the Qt::Variant class may
135
+ need to be adjusted for use with the QtJambi bindings.
136
+
137
+
138
+ ### Slots and Signals
139
+ Although QtJambi supports a simpler mechanism to deal with signals which does not need
140
+ the C++ signatures and uses methods or code blocks in stead of slots, a full set of compatible
141
+ routines to handle Slots and Signals is included for compatibility with QtRuby. This includes
142
+ slots and signals functions, SLOT and SIGNAL 'macros', connect, disconnect, emit and sender
143
+ methods:
144
+
145
+ slots 'documentWasModified()'
146
+ signals 'contentsChanged()'
147
+
148
+ connect(@textEdit.document(), SIGNAL('contentsChanged()'),
149
+ self, SLOT('documentWasModified()'))
150
+
151
+ emit contentsChanged
152
+
153
+ ### Custom adjustments
154
+ A number of individual class definitions are 'tweaked' or extended to make the QtRuby API
155
+ fit on the QtJambi API. This is still very much work in progress
156
+
157
+ qt\_sugar
158
+ --------
159
+
160
+ Provides added functionality to the current signals/slots API in QtRuby
161
+ It avoids the need to use C++ signatures and signal/slots functions in applications.
162
+ The syntax is similar to the one used in QtJambi (the Java version of the Qt framework):
163
+ Each Signal-emitting Qt class is extended with a method for each Signal it may emit.
164
+ The default name of the method is the same as the signature name. In case of ambiguity
165
+ when the signal has arguments or overloads, the default name for a given signature can be
166
+ overruled through an entry in the **Signature2Signal** table. The initial entries in the table
167
+ are the same as used in the QtJambi interface.
168
+ Example:
169
+
170
+
171
+ require 'qt_connect'
172
+
173
+ button=Qt::PushButton.new
174
+
175
+ #connecting signal to a Ruby Block:
176
+ button.clicked.connect{ puts "Hello World"}
177
+
178
+ #connecting to receiver/method
179
+ button.clicked.connect(self,:methodname)
180
+
181
+ #disconnect all connections originating from this button
182
+ button.clicked.disconnect
183
+
184
+ #emitting the signal (normally done internally by Qt)
185
+ button.clicked.emit
186
+
187
+ #example passing String as argument
188
+ label=Qt::Label.new
189
+ label.linkActivated.connect{ |s| Qt::DesktopServices.openUrl(Qt::Url.new(s))}
190
+
191
+ Custom components can define their own signals and emit them like this:
192
+
193
+ @signal=Qt::Signal.new
194
+ @signal.connect{ |a1,a2| puts "someone send me #{a1} and #{a2}"}
195
+ .
196
+ .
197
+ @signal.emit(100,200)
198
+
199
+ background
200
+ -----------
201
+ For several years I have been an enthusiastic user of the excellent Ruby bindings to the Qt APIs as provided
202
+ by Richard Dale and others as part of the Korundum/QtRuby project. With the becoming of age of JRuby
203
+ I became interested in running my applications using JRuby, mainly for reasons of packaging and deployment.
204
+ The [qtjruby-core gem](https://github.com/nmerouze/qtjruby-core) provided by Nicolas Merouze
205
+ was a good start for a JRuby interface to QtJambi, but to get a reasonable compatibility with an existing
206
+ code base in QtRuby a lot more was required. I decided therefore to develop a new gem package which
207
+ would make it easy to port existing applications to JRuby or better use the same code for deployment
208
+ under Ruby and JRuby.
209
+ Because of the inherent differences between the two interfaces it will never be possible to have complete inter
210
+ operability, but it is certainly possible to write code in such a way that it will work with both Ruby and JRuby.
211
+ As a proof of principle I have a large Qt application (>10000 lines Ruby Code) which runs with the 'qt\_connect'
212
+ gem both under Ruby and JRuby. This allows me to do development under Ruby and package and deploy under JRuby.
213
+
214
+ Through the years I have seen several suggestions for changes to the slots and signals interface used by QtRuby.
215
+ The existing API is based on C++ signatures and not very appropriate for Ruby programming. The solution
216
+ provided by the QtJambi interface is quite elegant and in my opinion also suitable for Ruby. I have therefore included a
217
+ QtJambi based signals interface for Ruby programs in the gem package.
218
+
219
+ ### examples and testing
220
+ The example programs should run both in a Ruby (qtbindings + qt\_connect gem packages) and a JRuby
221
+ (qt\_connect gem + Qt Jambi jars) setting. The Class Explorer is for JRuby users only.
222
+ This browser extends the Javadoc documentation for the com.trolltech.qt Javapackages that
223
+ form the basis for the JRuby Qt Jambi API.
224
+ If you install the Ruby qtbindings gem package, the example directory includes more than 75 example programs.
225
+ Of these programs about a third run unchanged with JRuby and the qt\_connect gem. Of the remaining programs
226
+ quite a few can not run for the same reason in that they make use of the Qt's resources system which is different
227
+ from the Qt Jambi resource management (these are all the examples using files with the .qrc extension). Some programs
228
+ run after minor modifications and more may run with appropriate extensions of the qt\_compat module, I have not had
229
+ much time to test this out.
230
+
231
+ ### packaging and deployment
232
+ When it comes to packaging your Qt JRuby application, there is a great gem package to help you called
233
+ [Rawr](http://rawr.rubyforge.org). With Rawr, a simple, pre-generated configuration file turns your code into
234
+ an executable jar, a .exe for Windows, and a .app for OS X.
235
+
236
+ ### documentation
237
+ If you install the full Qt Jambi package, it comes with extensive documentation for the Java API and a large number
238
+ of Java demonstration programs. Use these together with the class explorer program (*part of this gem package*) to write
239
+ your Ruby programs.
240
+ The example programs (*deform.rb*, *qtmapzoom.rb* and *classexplorer.rb*) also show how to program the signal interface
241
+ in Ruby.
242
+ For Qt Ruby programming in general have a look at the demonstration programs included with the qtbindings gem package.
243
+ There is also an ebook
244
+ ([Rapid GUI Development with QtRuby](http://pragprog.com/book/ctrubyqt/rapid-gui-development-with-qtruby)
245
+ by Caleb Tennis) published by The Pragmatic Bookshelf. Although the book itself is somewhat out of date, it is still a good introduction
246
+ to Qt Ruby programming.
247
+ In addition to the [Qt Jambi documentation](http://doc.trolltech.com/qtjambi-4.4/html) there is also a lot of Qt documentation
248
+ online at the [Qt Developer Network](http://qt-project.org).
249
+ For information about JRuby look at <http://jruby.org> , read the [JRuby Cookbook](http://shop.oreilly.com/product/9780596519650.do)
250
+ published by O'Reilly or [Using JRuby](http://pragprog.com/book/jruby/using-jruby) from The Pragmatic Bookshelf.
251
+
252
+ ## license
253
+
254
+ Copyright &copy; 2010-2012 Cees Zeelenberg (<c.zeelenberg@computer.org>)
255
+ This software is released under the LGPL Version 2.1.
256
+ See COPYING.LIB.txt
257
+
@@ -0,0 +1,72 @@
1
+ #
2
+ # Created by Cees Zeelenberg
3
+ # Copyright (c) 2012. All rights reserved.
4
+ #
5
+ require 'rake/clean'
6
+ require 'rake/gempackagetask'
7
+
8
+ $:.unshift(File.join(Dir.pwd,"lib"))
9
+ require 'qt_connect/version'
10
+
11
+ task :default => [:jar]
12
+ CLEAN.include '*.class','Manifest'
13
+ CLOBBER.include '*.jar'
14
+
15
+ JAVA_FILES=FileList['java/*.java']
16
+ CLASS_FILES=JAVA_FILES.pathmap("%n").ext('class')
17
+
18
+ #directory 'package_dir'
19
+
20
+ rule '.class' => [proc {|s| x=s.sub(/\.[^.]+$/, '.java') ; 'java/'+x}] do |t|
21
+ sh "javac -target 1.6 #{t.source}"
22
+ puts f=t.source.ext('class')
23
+ verbose(true) { mv "#{f}", "."}
24
+ end
25
+
26
+
27
+ desc 'Build the .jar file for the Gem'
28
+ task :jar => [CLASS_FILES, 'Manifest'] do
29
+ sh 'jar -cfm lib/signalactions.jar Manifest SignalActions.class'
30
+ rm 'Manifest'
31
+ rm 'Signalactions.class'
32
+ end
33
+
34
+ file 'Manifest' do
35
+ File.open('Manifest','w'){ |f| f.puts "Main-Class: SignalActions"}
36
+ end
37
+
38
+
39
+ #~ desc 'Build the Gem package'
40
+ #~ task :build_package => [:jar,'package_dir'] do
41
+ gem = Gem::Specification.new do |s|
42
+ s.name ='qt_connect'
43
+ s.version = QtJambi::Version
44
+ s.date ='2012-05-16'
45
+ s.summary ='Uniform Qt bindings for JRuby and Ruby '
46
+ s.authors =['Cees Zeelenberg']
47
+ s.homepage = "https://github.com/CeesZ/qt_connect"
48
+ s.email ='c.zeelenberg@computer.org'
49
+ s.licenses = ["LGPLv2.1"]
50
+ s.files =Dir['lib/**/*.rb']
51
+ s.files +=Dir['java/**/*.java']
52
+ s.files +=Dir['lib/**/*.jar']
53
+ s.files +=Dir['examples/**/*.*']
54
+ s.files +=Dir['[A-Z]*']
55
+ s.require_paths= ["lib"]
56
+ s.description =
57
+ %Q[Qt bindings for JRuby with (optional) extension to provide backward
58
+ compatibility with Qt Ruby programs. The API is implemented using Qt Jambi, the
59
+ Java version of the Qt framework. For Qt Ruby programs it provides a QtJambi
60
+ inspired interface to the Signals/Slots system without the need to use
61
+ C++ signatures.
62
+ ]
63
+ end
64
+
65
+ Rake::GemPackageTask.new(gem) do |pkg|
66
+ #pkg.need_zip = true
67
+ #pkg.need_tar = true
68
+ #pkg.gem_spec = spec
69
+ #pkg.package_files << 'y'
70
+ end
71
+ #~ end
72
+
@@ -0,0 +1,828 @@
1
+ # encoding: utf-8
2
+ $KCODE = 'UTF8' if RUBY_VERSION =~ /1\.8/
3
+ =begin
4
+
5
+ Author: Cees Zeelenberg (c.zeelenberg@computer.org)
6
+ Copyright: (c) 2011-2012 by Cees Zeelenberg
7
+ License: This program is free software; you can redistribute it and/or modify
8
+ it under the terms of the GNU Lesser General Public License as published
9
+ by the Free Software Foundation; either version 2 of the License, or
10
+ (at your option) any later version. *
11
+ =end
12
+
13
+ unless RUBY_PLATFORM =~ /java/
14
+ puts "Sorry!"
15
+ puts "Class Explorer is an application"
16
+ puts "for browsing Qt Jambi class definitions"
17
+ puts "it can only be run using JRuby"
18
+ exit
19
+ end
20
+
21
+ require 'qt_connect'
22
+ require 'qt_extensions'
23
+ require 'menus'
24
+ require 'packagetree'
25
+
26
+ class ClassExplorer < Qt::MainWindow
27
+
28
+ MARKERS=[
29
+ [:"final?", 'Final Java method', '#81F7F3'],
30
+ [:"abstract?", 'Abstract Java method', '#F5D0A9'],
31
+ [:"ruby?", 'Ruby method', 'yellow']]
32
+
33
+ BGCOLOR="#ccccff"
34
+
35
+ def initialize
36
+
37
+ super
38
+ setup_UI
39
+ setup_panels
40
+ setup_menu_bar
41
+ create_dock_window
42
+ read_settings
43
+ @java_root_edit.text=@root
44
+ @java_root_edit.textChanged.connect{ |s| @root=s }
45
+ @java_root_edit.editing_finished.connect{ show_class(@lastpath) if @lastpath}
46
+ @packagetree=PackageTree.new('com.trolltech.qt')
47
+ @package_list.model=PackageModel.new(@packagetree)
48
+ @package_list.clicked.connect( self,:on_package_clicked)
49
+ @package_list.selectionModel=Qt::ItemSelectionModel.new(@package_list.model)
50
+ #use stylesheet to set selection-background-color
51
+ #default background color is wrong if item selected but not in focus
52
+ @package_list.styleSheet=%Q[selection-background-color: #3399ff; selection-color: white;]
53
+
54
+ @class_list.model=FilteredClassModel.new(@filter_line_edit)
55
+ @class_list.clicked.connect(self,:on_class_clicked)
56
+ @class_list.selectionModel=Qt::ItemSelectionModel.new(@class_list.model)
57
+ @class_list.styleSheet=%Q[selection-background-color: #3399ff; selection-color: white;]
58
+
59
+ MethodAttributes.namespace{ |method| rubyname(method) }
60
+
61
+ Qt::Internal::RUBY_extensions.each{ |method|
62
+ mas=MethodAttributes.new(method)
63
+ mas.lang=:ruby
64
+ @newmethods[mas.receiver] ||=[]
65
+ @newmethods[mas.receiver] << mas
66
+ }
67
+ #initial selection: all packages
68
+ @package_list.select_all
69
+ update_class_panel
70
+
71
+ end
72
+
73
+ def setup_UI
74
+ resize(1000,768)
75
+ # the widgets composing the UI
76
+ @central_widget=Qt::Widget.new
77
+ @caption=Qt::Label.new('')
78
+ @package_list=Qt::ListView.new
79
+ @class_list=Qt::ListView.new
80
+ @detail_panel=Qt::TextBrowser.new
81
+ @nav_left=Qt::ToolButton.new{ |tb| tb.icon=style.standardIcon(Qt::Style::SP_ArrowLeft)}
82
+ @nav_right=Qt::ToolButton.new{ |tb| tb.icon=style.standardIcon(Qt::Style::SP_ArrowRight)}
83
+ @inherit_box=Qt::CheckBox.new('include inherited Qt members')
84
+ @linkto_box=Qt::CheckBox.new('link to Qt Jambi Reference Documentation')
85
+ @filter_line_edit=QtExtensions::LineEdit.new
86
+
87
+ #the layout for the widgets
88
+ options_layout=Qt::VBoxLayout.new{ |v|
89
+ v.add_widget(@inherit_box,0)
90
+ v.add_widget(@linkto_box,0)
91
+ v.add_stretch(100)
92
+ }
93
+
94
+ navigate_layout=Qt::HBoxLayout.new{ |h|
95
+ h.add_widget(@nav_left,0)
96
+ h.add_widget(@nav_right,0)
97
+ h.add_stretch(100)
98
+ }
99
+
100
+ header_layout=Qt::VBoxLayout.new{ |v|
101
+ v.add_layout(navigate_layout,0)
102
+ v.add_widget(@caption,99)
103
+ }
104
+
105
+
106
+ footer=Qt::Label.new(html_footer)
107
+
108
+ headerframe_layout=Qt::HBoxLayout.new{ |h|
109
+ h.add_layout(header_layout,99)
110
+ h.add_layout(options_layout,0)
111
+
112
+ }
113
+
114
+ leftframe=Qt::Widget.new { |f|
115
+ #f.frame_style=Qt::Frame::Panel | Qt::Frame::Plain
116
+ f.layout=Qt::VBoxLayout.new { |v|
117
+ v.add_widget(@package_list,30)
118
+ v.add_widget(@class_list,70)
119
+ v.add_widget(@filter_line_edit,0)
120
+ }
121
+ }
122
+
123
+ rightframe=Qt::Widget.new { |f|
124
+ #f.frame_style=Qt::Frame::Panel | Qt::Frame::Plain
125
+ f.layout=Qt::VBoxLayout.new { |v|
126
+ v.add_layout(headerframe_layout,0)
127
+ v.add_widget(@detail_panel,99)
128
+ v.add_widget(footer,0)
129
+ }
130
+ }
131
+ #use a splitter as a flexible divider between left and right frames
132
+ @splitter=splitter=Qt::Splitter.new(Qt::Horizontal){ |s|
133
+ s.add_widget(leftframe)
134
+ s.add_widget(rightframe)
135
+ }
136
+ @splitter.sizes=[200,800]
137
+
138
+ @central_widget.layout=Qt::VBoxLayout.new { |v|
139
+ v.add_widget(@splitter,100)
140
+ }
141
+ self.central_widget=@central_widget
142
+ end
143
+ def setup_panels
144
+ @package_list.view_mode=Qt::ListView::ListMode
145
+ @package_list.selection_mode=Qt::ListView::SelectionMode::ExtendedSelection
146
+ @package_list.selectionRectVisible=true
147
+ @class_list.itemDelegate=QtExtensions::HtmlDelegate.new
148
+ @inherit_box.state_changed.connect(self,:html_class_description)
149
+ @linkto_box.toggled.connect{ |tf|
150
+ next unless @dock
151
+ @dock.visible=tf unless @dock.visible==tf
152
+ show_class(@lastpath) if @lastpath && @dock.visible?
153
+ }
154
+ @newmethods={}
155
+ @detail_panel.openLinks=false
156
+ @detail_panel.anchorClicked.connect(self,:on_class_navigate)
157
+ @filter_line_edit.placeholderText="Filter"
158
+ @filter_line_edit.setClearButton
159
+ @navigate=Navigator.new
160
+ @navigate.browse{ |path| show_class(path)}
161
+ @navigate.buttons(@nav_left,@nav_right)
162
+ end
163
+
164
+ def setup_menu_bar
165
+ @menubar=Qt::MenuBar.new(self) { |menubar|
166
+ setMenuBar(menubar)
167
+ @helpmenu=Menus::HelpMenu.new(self,menubar)
168
+ }
169
+ end
170
+
171
+ def create_dock_window
172
+ java_label=Qt::Label.new('Javadoc root:')
173
+ @java_root_edit=Qt::LineEdit.new
174
+ @java_url_edit=Qt::LineEdit.new
175
+
176
+ @jambibrowser=Qt::WebView.new
177
+ @jambibrowser.html=''
178
+ javadoc_header=Qt::Widget.new{ |w|
179
+ w.layout=Qt::HBoxLayout.new { |h|
180
+ h.add_widget(java_label,0)
181
+ h.add_widget(@java_root_edit,99)
182
+ }
183
+ }
184
+ @javadoc=Qt::Widget.new{ |w|
185
+ w.layout=Qt::VBoxLayout.new{ |v|
186
+ v.add_widget(javadoc_header,0)
187
+ v.add_widget(@java_url_edit,0)
188
+ v.add_widget(@jambibrowser,99)
189
+ }
190
+ }
191
+ @dock = Qt::DockWidget.new("Qt Jambi Reference Documentation", self)
192
+ @dock.objectName="Record_Browser"
193
+ @dock.minimum_width=500
194
+ @dock.allowedAreas = Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea
195
+
196
+ @dock.widget = @javadoc
197
+ addDockWidget(Qt::RightDockWidgetArea, @dock)
198
+ @dock.visibilityChanged.connect{ |tf| @linkto_box.checked=tf unless @linkto_box.checked==tf}
199
+ end
200
+
201
+ def read_settings
202
+ settings = Qt::Settings.new("Qt_connect", "Class Explorer")
203
+ pos = settings.value("pos", Qt::Point.new(200, 200))
204
+ size = settings.value("size", Qt::Size.new(1000,768))
205
+ @linkto_box.checked=Qt::Variant.toBoolean(settings.value('linkto_box')) || false
206
+ @inherit_box.checked=Qt::Variant.toBoolean(settings.value('inherit_box')) || false
207
+ @splitter.sizes=Qt::Variant.toList(settings.value("splitter")) || [200,800]
208
+ @root=Qt::Variant.toString(settings.value("root")) || "http://doc.trolltech.com/qtjambi-4.4/html"
209
+ state=settings.value("state")
210
+ resize(size)
211
+ move(pos)
212
+ restoreState(state) if state
213
+ end
214
+
215
+ #overrule (N.B. must use camelcase here!)
216
+ def closeEvent(event)
217
+ settings = Qt::Settings.new("Qt_connect", "Class Explorer")
218
+ settings.setValue("pos",pos)
219
+ settings.setValue("size",size)
220
+ settings.setValue("state",saveState)
221
+ settings.setValue("linkto_box",@linkto_box.checked?)
222
+ settings.setValue("inherit_box",@inherit_box.checked?)
223
+ settings.setValue("splitter",@splitter.sizes)
224
+ settings.setValue("root",@root)
225
+ super(event)
226
+ end
227
+
228
+ def on_package_clicked(index)
229
+ update_class_panel
230
+ ix0=@class_list.model.index(0,0)
231
+ @class_list.selectionModel.select(ix0,Qt::ItemSelectionModel::SelectionFlag::ClearAndSelect)
232
+ on_class_clicked(ix0)
233
+ end
234
+ def update_class_panel
235
+ classes=@package_list.selectedIndexes.
236
+ inject([]){ |memo,ix| memo += @package_list.model.classes(ix).
237
+ map{ |c| rubyname(@package_list.model.data(ix) + '.' +c)}}.
238
+ sort
239
+ @class_list.clear_selection
240
+ @class_list.model.layout_about_to_be_changed.emit
241
+ @class_list.model.classes=classes
242
+ @class_list.model.layout_changed.emit
243
+ @filter_line_edit.text=''
244
+ end
245
+
246
+ def on_class_clicked(index)
247
+ begin
248
+ @rubyname=@class_list.model.data(index,Qt::UserRole)
249
+ @klass=eval(@rubyname)
250
+ @klass=qt_hierarchy(@klass)[-1]
251
+ @path=@klass.java_class.name
252
+ rescue => e
253
+ puts "error on_class_clicked: #{e}"
254
+ puts caller
255
+ return
256
+ end
257
+ path=@path.gsub('.','/').gsub('$','.')
258
+ navigate_to_class(path)
259
+ end
260
+
261
+ #url: (QUrl) com/trolltech/qt/core/Qt.AlignmentFlag => path: "com/trolltech/qt/gui/Qt.AlignmentFlag"
262
+ #url: (QUrl) com/trolltech/qt/gui/QAccessibleInterface => path: "com/trolltech/qt/gui/QAccessibleInterface"
263
+ #url: (QUrl) com/trolltech/qt/gui/QAccessible.Method => path: "com/trolltech/qt/gui/QAccessible.Method"
264
+ def on_class_navigate(url)
265
+ path=url.path
266
+ @klass=eval(path.gsub('.','::').gsub('/','.')) rescue return
267
+ @rubyname=rubyname(@klass)
268
+ @path=@klass.java_class.name
269
+
270
+ model=@package_list.model
271
+ elems=@path.split('.')
272
+ klass_name=elems.pop
273
+ package_name=elems.join('.')
274
+ return unless ix=(0...model.rowCount(0)).map{ |r| model.index(r,0)}.detect{ |m| model.data(m)==package_name}
275
+ #if package present, but NOT selected in package_panel; select it now and update class_panel
276
+ unless @package_list.selectedIndexes.detect{ |m| m==ix}
277
+ @package_list.selectionModel.select(ix,Qt::ItemSelectionModel::SelectionFlag::Select)
278
+ update_class_panel
279
+ end
280
+
281
+ @filter_line_edit.text=''
282
+ model=@class_list.model
283
+ return unless ix=(0...model.rowCount(0)).map{ |r| model.index(r,0)}.detect{ |m| model.data(m)==@rubyname}
284
+ @class_list.selectionModel.select(ix,Qt::ItemSelectionModel::SelectionFlag::ClearAndSelect)
285
+ @class_list.scrollTo(ix)
286
+ on_class_clicked(ix)
287
+ #navigate_to_class(path)
288
+ end
289
+
290
+ def navigate_to_class(path)
291
+ @navigate.push(path)
292
+ show_class(path)
293
+ end
294
+
295
+ def show_class(path)
296
+ @klass=eval(path.gsub('.','::').gsub('/','.')) rescue return
297
+ @rubyname=rubyname(@klass)
298
+ @path=@klass.java_class.name
299
+ @lastpath=path
300
+ @caption.text=html_header
301
+ html_class_description
302
+ url=Qt::Url.new(%Q[#{@root}/]+path+".html")
303
+ return unless @linkto_box.checked?
304
+ begin
305
+ @jambibrowser.load(url)
306
+ @java_url_edit.text=url.toString
307
+ rescue => e
308
+ puts "error on_textbrowser: #{e}"
309
+ print e.backtrace.join("\n")
310
+ end
311
+ end
312
+
313
+ #
314
+ #klass.class:
315
+ #Module, Class: return name in Ruby namespace notation (e.g Qt::Widget, Qt::AbstractItemView::ConcreteWrapper)
316
+ #String: convert all class references in string to Ruby namespace notation:
317
+ # com.trolltech.qt.gui.QAbstractItemView$ConcreteWrapper => Qt::AbstractItemView::ConcreteWrapper
318
+ def rubyname(klass)
319
+ if klass.respond_to?(:java_class)
320
+ qtname=klass.java_class.name
321
+ elsif klass.kind_of? String
322
+ qtname="#{klass}"
323
+ else #Ruby defined class
324
+ return klass.name
325
+ end
326
+ #remove longest matchingpackage (e.g. 'com.trolltech.qt.gui.QAbstractButton' => 'Qt:AbstractButton')
327
+ @packagetree.keys.sort{ |a,b| b.size <=> a.size}.
328
+ each{ |package_name| qtname.gsub!(package_name + '.','Qt::')}
329
+
330
+ qtname=qtname.gsub('$','::').
331
+ gsub('Qt::Qt','Qt::').
332
+ gsub('Qt::Q','Qt::').
333
+ gsub('Qt::::','Qt::')
334
+ qtname=qtname[0..-3] if qtname.end_with?('::')
335
+ return qtname
336
+ end
337
+
338
+ def html_header
339
+ %Q[<h2>
340
+ <font size="-1">#{@packagetree.jars}
341
+ <br>
342
+ #{@path}</font>
343
+ <br>
344
+ #{@klass.class} #{@rubyname}</h2>]
345
+ end
346
+
347
+ def html_footer
348
+ html='<table border="1" width="100%" cellpadding="3" cellspacing="0"><tr>'
349
+ MARKERS.each{ |test,name,color| html+="<th bgcolor='#{color}'>#{name}</th>"}
350
+ html+='</tr></table>'
351
+ return html
352
+ end
353
+
354
+ #delete 'constants' from class_methods (static fields which implement constants)
355
+ def html_class_description
356
+ return unless @klass
357
+ sort_methods
358
+ @detail_panel.html= %Q[
359
+ #{html_ancestors}
360
+ <table border="1" width="100%" cellpadding="3" cellspacing="0" summary="">
361
+ #{html_enums}
362
+ #{html_constants}
363
+ #{html_nested_classes}
364
+ #{html_methods(@class_methods.delete_if{ |mas| @constants.include?(mas.methodname)},"#{@klass.class} Methods")}
365
+ #{html_signals}
366
+ #{html_methods(@instance_methods,"Instance Methods")}
367
+ </table>
368
+ ]
369
+ end
370
+
371
+ #Escape XML special characters <, & and >
372
+ def escape(str)
373
+ str.gsub('&', '&amp;').gsub('<', '&lt;').gsub('>', '&gt;')
374
+ end
375
+
376
+ def qt_ancestors(klass)
377
+ #klass.ancestors.delete_if{ |x| (!x.name.start_with? "Java::ComTrolltechQt")}
378
+ return [] unless klass.respond_to? :ancestors
379
+ klass.ancestors.select{ |x| x.name.start_with? "Java::ComTrolltechQt"}
380
+ end
381
+
382
+ def qt_hierarchy(klass)
383
+ qt_ancestors(klass).
384
+ map{ |ancestor| [ancestor,qt_ancestors(ancestor).size]}.
385
+ sort{|a,b| a[1]<=>b[1]}.
386
+ map{ |a,s| a}
387
+ end
388
+
389
+ def html_ancestors
390
+ html=%Q[<table border="0" cellpadding="4" cellspacing="0"><tr>]
391
+ modules,classes=qt_hierarchy(@klass).
392
+ partition{ |a| a.class==Module}.
393
+ map{ |klasses|
394
+ spaces=''
395
+ inherit=' '
396
+ klasses.map{ |x| leader=spaces+inherit ; spaces+=' '; inherit='┖' ; leader + html_qt_marker(rubyname(x))}.
397
+ join("\n")
398
+ }
399
+
400
+ if modules.size==0
401
+ return html + "<th>Class Hierarchy</th><td><pre>#{classes}</pre></td></tr></table>"
402
+ elsif classes.size==0
403
+ return html + "<th>Module Hierarchy</th><td><pre>#{modules}</pre></td></tr></table>"
404
+ else
405
+ return html + %Q[<th>Class Hierarchy:</th><td><pre>#{classes}</pre></td>
406
+ <th>Mixes in:</th>
407
+ <td><pre>#{modules}</pre></td>
408
+ </tr>
409
+ </table>]
410
+ end
411
+ end
412
+
413
+ def html_constants
414
+
415
+ html=@constants.
416
+ map{ |c| v=@klass.const_get(c)
417
+ val="#{v}"
418
+ name=(v.respond_to?(:java_class)) ? v.java_class.name : v.class.name
419
+ if v.respond_to?(:value)
420
+ val=" 0x%08x" % v.value
421
+ elsif v.kind_of?(String)
422
+ val='"' + v + '"'
423
+ end
424
+ [c.to_s,val,name]}.
425
+ sort{ |a,b| (a[2]+a[0]) <=> (b[2]+b[0])}.
426
+ uniq.
427
+ delete_if{|xx| xx[0].start_with?('_')}.
428
+ map{ |xx| "<tr><td>#{rubyname(xx[2])}</td><td>#{xx[0]}</td><td>#{xx[1]}</td></tr>"}
429
+ return (html.size>0) ? %Q[
430
+ <!-- =========================== Class Constants =========================== -->
431
+ <tr bgcolor="#{BGCOLOR}">
432
+ <th align="left" colspan="3"><font size="+2">
433
+ <b>Constants</b></font></th>
434
+ </tr>
435
+ <font size='-1'><tr><th>class</th><th>object</th><th>value</th></tr></font>
436
+ #{html}
437
+ ] : ''
438
+ end
439
+
440
+ def html_enums
441
+ html=''
442
+ @constants=[]
443
+ @enums=[]
444
+ @ignore=[]
445
+ @nested_classes=[]
446
+ konstants=@klass.constants
447
+ konstants.each{ |c|
448
+ ancestors=(eval("#{@klass.name}::#{c}.respond_to? :ancestors")) ? eval("#{@klass.name}::#{c}.ancestors"): []
449
+ if ancestors.include? java.lang.Enum
450
+ @enums << c
451
+ elsif (v=@klass.const_get(c)) && (v.class==Class)
452
+ @nested_classes << c
453
+ else
454
+ @constants << c
455
+ end
456
+ }
457
+
458
+ @enums.sort.each{ |enum|
459
+ values=eval(%Q[#{@klass.name}::#{enum}.values]).to_ary
460
+ enumname=html_qt_marker("#{@rubyname}::#{enum}")
461
+ html += "<tr><td rowspan='#{values.size}'>#{enumname}</td>"
462
+ tr=''
463
+ values.each{ |v|
464
+ s="#{v}"
465
+ fullname="#{@rubyname}::#{enum}::#{v}"
466
+ shortname="#{@rubyname}::#{v}"
467
+ name = if (Qt::Internal::DONT_ABBREVIATE[fullname]) || (s=~/\A[^A-Z].*\Z/)
468
+ #!const_defined?(shortname.to_sym)
469
+ "<font color='red'>#{fullname}</font>"
470
+ else
471
+ shortname
472
+ end
473
+ html+="#{tr}<td>#{v}</td><td>#{name}</td></tr>"
474
+ tr="<tr>"
475
+ @ignore << "#{v}"
476
+ }
477
+ }
478
+ @constants -= @ignore
479
+
480
+ return html unless html.size > 0
481
+ return %Q[
482
+ <!-- =========================== Class Enums =========================== -->
483
+ <tr bgcolor="#{BGCOLOR}">
484
+ <th align="left" colspan="3"><font size="+2">
485
+ <b>Enums</b></font></th>
486
+ </tr>
487
+ <font size='-1'><tr><th>enumerator</th><th>values</th><th>shorthand</th></tr>#{html}</font>
488
+ ]
489
+ end
490
+
491
+ def html_nested_classes
492
+ html=@nested_classes.
493
+ map{ |c| rubyname(@klass.const_get(c))}.
494
+ delete_if{|name| name=='Qt::'}.
495
+ delete_if{|name| !@inherit_box.is_checked? && !name.start_with?(@rubyname)}.
496
+ sort.compact.
497
+ map{ |name| html_qt_marker(name)}.
498
+ join("\n")
499
+ return (html.size>0) ? %Q[
500
+ <!-- =========================== Nested Classes =========================== -->
501
+ <tr bgcolor="#{BGCOLOR}">
502
+ <th align="left" colspan="3"><font size="+2">
503
+ <b>Nested classes</b></font></th>
504
+ </tr>
505
+ <tr></td><td colspan="3">#{html}</td></tr>
506
+ ] : ''
507
+ end
508
+
509
+ def sort_methods
510
+ @class_methods=[]
511
+ @instance_methods=[]
512
+ @fields=[]
513
+ @signals={}
514
+ rubynew=false
515
+ if @klass.respond_to?(:java_class) && javaclass=@klass.java_class
516
+ javamethods=org.jruby.javasupport.JavaClass.getMethods(javaclass).to_a
517
+ qt_ancestors(@klass).each{ |klass|
518
+ rname=rubyname(klass)
519
+ javamethods+=(@newmethods[rname] || [])
520
+ }
521
+ javamethods.each{ |javamethod|
522
+ mas=(javamethod.kind_of? MethodAttributes) ? javamethod : MethodAttributes.new(javamethod)
523
+ next if mas.native? #skip fromNativePointer etc.
524
+ next if mas.methodname.start_with? "__qt" #in case not labeled 'native'
525
+ next if !@inherit_box.is_checked? && (mas.receiver!=@rubyname)
526
+
527
+ if mas.private?
528
+ n=(mas.argtypes.size>0) ? mas.argtypes.count(',')+1 : 0
529
+ @signals[mas.methodname+n.to_s]=(mas.argtypes.size==0) ? '()' : mas.argtypes
530
+ elsif mas.static?
531
+ @class_methods << mas
532
+ rubynew=true if ((mas.methodname=='new') && (mas.lang==:ruby))
533
+ else
534
+ @instance_methods << mas
535
+ end
536
+ }
537
+ org.jruby.javasupport.JavaClass.getFields(javaclass).each{ |javamethod|
538
+ mas=MethodAttributes.new(javamethod)
539
+ next if mas.methodname.start_with? "_"
540
+ next if !@inherit_box.is_checked? && (mas.receiver!=@rubyname)
541
+ if (mas.returntype=~ /SignalEmitter/)
542
+ @fields << mas
543
+ elsif mas.static?
544
+ @class_methods << mas
545
+ else
546
+ @instance_methods << mas
547
+ end
548
+ }
549
+ #ignore java constructors if 'new' class method was redefined (e.g. rubynew==true)
550
+ if ((@klass.class==Class) && !rubynew)
551
+ javaclass.constructors.map{ |c|
552
+ mas=MethodAttributes.new(c)
553
+ mas.returntype=mas.methodname
554
+ mas.methodname='new'
555
+ mas.lang=:ruby
556
+ @class_methods<<mas
557
+ }
558
+ end
559
+
560
+ end
561
+ end
562
+
563
+ def html_methods(methods,caption)
564
+ html=''
565
+ methods.sort{ |a,b| a.methodname <=> b.methodname}.each{ |method|
566
+ next if (receiver=method.receiver) && !receiver.start_with?('Qt::') #only show inheritance from Qt members
567
+ bgcolor=''
568
+ MARKERS.each{ |test,name,color| bgcolor=color if method.send(test)}
569
+ html+=%Q[<tr>]
570
+ html+="<td>" + html_qt_marker(method.returntype) + "</td>"
571
+ html+="<td bgcolor='#{bgcolor}'><b>" + escape(method.methodname) + "</b>"
572
+
573
+ if (method.argtypes.size>0)
574
+ html += '(' + method.argtypes[1..-2].split(',').map{ |arg| html_qt_marker(arg)}.join(', ') +')'
575
+ end
576
+ html+="</td>"
577
+ #html+= method.annotations ? escape(" {#{method.annotations}}") : ''
578
+ html+=(method.receiver==@rubyname) ? '<td></td>' : "<td>#{html_qt_marker(method.receiver||'')}</td>" if @inherit_box.is_checked?
579
+ html+="</tr>"
580
+ }
581
+ return (html.size>0) ? %Q[
582
+ <!-- =========================== Instance Methods =========================== -->
583
+ <tr bgcolor="#{BGCOLOR}">
584
+ <th align="left" colspan='#{@inherit_box.is_checked? ? '3' : '3'}'><font size="+2">
585
+ <b>#{caption}</b></font></th>
586
+ #{"<tr bgcolor='#ccccff'><th colspan='2'></th><th>Inherited from</th>" if @inherit_box.is_checked?}
587
+ </tr>
588
+ #{html}
589
+ ] : ''
590
+ end
591
+
592
+ def html_qt_marker(s)
593
+ html=escape(s)
594
+ begin
595
+ if s.start_with?('Qt::') && s!=@rubyname && s!=(@rubyname+'[]') &&
596
+ (klass=eval(s.gsub('[]',''))) && (klass.respond_to? :java_class)
597
+ url=klass.java_class.name.gsub('.','/').gsub('$','.')
598
+ html="<a href='#{url}'>" + escape(s) + "</a>"
599
+ end
600
+ rescue
601
+ end
602
+
603
+ return html
604
+ end
605
+
606
+ def html_signals
607
+ html=''
608
+ @fields.sort{ |a,b| a.methodname <=> b.methodname}.each{ |method|
609
+ n=method.returntype[-1,1]
610
+ #emitargs=escape(@signals[method.methodname+n] || '')
611
+ emitargs= if (x=@signals[method.methodname+n]) && (x.size>0)
612
+ '(' + x[1..-2].
613
+ split(',').
614
+ map{ |arg| html_qt_marker(arg)}.
615
+ join(', ') +
616
+ ')'
617
+ else
618
+ '()'
619
+ end
620
+ html+=%Q[<tr><td>#{html_qt_marker(method.returntype)}</td>
621
+ <td>#{escape(method.methodname)}</td
622
+ ><td bgcolor="yellow"><i>slot</i>#{emitargs}</td></tr>]
623
+ }
624
+ return (html.size>0) ? %Q[
625
+ <!-- =========================== Signals =========================== -->
626
+ <tr bgcolor="#{BGCOLOR}">
627
+ <th align="left" colspan="3"><font size="+2">
628
+ <b>Signals</b></font></th>
629
+ </tr>
630
+ <font size='-1'><tr><th colspan="2">signalemitter</th><th>connects to</th>
631
+ #{html}
632
+ ] : ''
633
+ end
634
+
635
+ end
636
+
637
+
638
+ class MethodAttributes
639
+ attr_accessor :returntype,:methodname,:receiver,:argtypes,:lang,:prefixes,:annotations
640
+ #call with either signature string OR
641
+ #Java::JavaLangReflect::Method OR
642
+ #Java::JavaLangReflect::Field OR
643
+ #Java::JavaConstructor
644
+ def self.namespace(&block)
645
+ @@block=block
646
+ end
647
+
648
+ def initialize(javamethod)
649
+ #public final com.trolltech.qt.core.QPoint com.trolltech.qt.gui.QWidget.mapFrom(com.trolltech.qt.gui.QWidget,com.trolltech.qt.core.QPoint)
650
+ #public final com.trolltech.qt.core.QSize com.trolltech.qt.gui.QAbstractButton.iconSize()
651
+ #public final com.trolltech.qt.core.Qt$LayoutDirection com.trolltech.qt.gui.QWidget.layoutDirection()
652
+ #public final void com.trolltech.qt.gui.QWidget.grabMouse(com.trolltech.qt.gui.QCursor)
653
+ #protected void com.trolltech.qt.gui.QWidget.dragLeaveEvent(com.trolltech.qt.gui.QDragLeaveEvent)
654
+ @annotations=nil
655
+ if javamethod.respond_to?(:annotations)
656
+ @annotations=javamethod.annotations.
657
+ map{ |ann| ann.to_java(java.lang.annotation.Annotation).toString}.
658
+ join(',').
659
+ gsub!('@com.trolltech.qt.','')
660
+ end
661
+ method=@@block.call("#{javamethod}")
662
+ elems=method.split('(')
663
+ @prefixes=elems[0].split(' ')
664
+ @returntype=@prefixes[-2]
665
+ #@returntype=@prefixes[0..-2]
666
+ @methodname=@prefixes[-1].split('.')[-1]
667
+ @receiver=@prefixes[-1].split('.')[-2]
668
+ @argtypes=(elems.size>1) ? (elems[1].split(')')[0] || '') : ''
669
+ @argtypes='('+@argtypes+')' if @argtypes.size>0
670
+ @lang=:java
671
+ end
672
+ def ruby?
673
+ @lang==:ruby
674
+ end
675
+ #
676
+ # .final, public, private, protected, abstract
677
+ # .public?
678
+ def method_missing(symbol,*args)
679
+ m=symbol.to_s[0...-1]
680
+ return @prefixes.include?(m)
681
+ end
682
+ end
683
+
684
+ class PackageModel < Qt::AbstractListModel
685
+
686
+ def initialize(packagetree)
687
+ @package_tree=packagetree
688
+ @packages=packagetree.keys & Qt::Internal::Packages
689
+ super()
690
+ end
691
+
692
+ def classes(index)
693
+ package_name=@packages[index.row]
694
+ @package_tree[package_name].classes
695
+ end
696
+
697
+
698
+ def data(index, role=Qt::DisplayRole)
699
+ return @packages[index.row] if role == Qt::DisplayRole
700
+ return Qt::Variant.new
701
+ end
702
+
703
+ def rowCount(parent)
704
+ return @packages.size
705
+ end
706
+
707
+ end
708
+
709
+ #Qt::AbstractListModel specialized for acess to Classes
710
+ #supports active filter (respons while you type)
711
+ #filter_edit is Qt::LineEdit monitored for text changes, otherwise managed outside this class
712
+ #data supports Qt::UserRole (returns Class name as String)
713
+ # and Qt::DisplayRole (returns Class name as HTML with tagged filter matches)
714
+ class FilteredClassModel < Qt::AbstractListModel
715
+
716
+ def initialize(filter_edit,max_cache=20)
717
+ super()
718
+ @filter_edit=filter_edit
719
+ @max_cache=max_cache
720
+ @classes=[]
721
+ @row2rec=[]
722
+ reset_filter
723
+ @filter_edit.textChanged.connect{ |s| filter_changed(s)}
724
+ end
725
+
726
+ def classes=(cl)
727
+ @classes=cl
728
+ @row2rec=(0...@classes.size).to_a
729
+ reset_filter
730
+ end
731
+
732
+ def data(index, role=Qt::DisplayRole)
733
+ return Qt::Variant.new if index.row >= @row2rec.size
734
+ return @classes[@row2rec[index.row]] if role == Qt::UserRole
735
+ return Qt::Variant.new unless role == Qt::DisplayRole
736
+ s=@classes[@row2rec[index.row]].gsub('&', '&amp;').gsub('<', '&lt;').gsub('>', '&gt;')
737
+ s=s.gsub(@rx){ |m| %Q[<span class="hilite">#{m}</span>]} if @filter.size>0
738
+ return s
739
+
740
+ end
741
+
742
+ def rowCount(parent)
743
+ return @row2rec.size
744
+ end
745
+
746
+ private
747
+
748
+ def reset_filter
749
+ @filter=''
750
+ @history={}
751
+ @cache={}
752
+ @filter_edit.text=''
753
+ end
754
+
755
+ def filter_changed(str)
756
+ s=str.downcase
757
+ @rx=Regexp.new(Regexp.escape(s),Regexp::IGNORECASE)
758
+ if s==''
759
+ row2rec=(0...@classes.size).to_a
760
+ elsif row2rec=@cache[s]
761
+ elsif (s.size>1) && (s[0...-1]==@filter)
762
+ row2rec=@row2rec.select{ |i| @classes[i] =~ @rx}
763
+ else
764
+ row2rec=(0...@classes.size).select{ |i| @classes[i] =~ @rx}
765
+ end
766
+ @filter=s
767
+ layout_about_to_be_changed.emit
768
+ @row2rec=row2rec
769
+ layout_changed.emit
770
+ @cache[s]=row2rec
771
+ @history[s]=Time.now
772
+ if @history.size > @max_cache
773
+ s=@history.sort{ |a,b| a[1] <=> b[1]}.first.first
774
+ @cache.delete(s)
775
+ @history.delete(s)
776
+ end
777
+
778
+ end
779
+
780
+ end
781
+ # simple class to assist fwd/bwd navigation
782
+ # left/right are Qt widgets than can be enabled/disabled and clicked
783
+ #push: add a page to the end of the queue
784
+ #browse: passes a call-back block to display a page
785
+ class Navigator
786
+ def initialize
787
+ @browse=nil
788
+ @left=nil
789
+ @right=nil
790
+ @index=0
791
+ @history=[]
792
+ end
793
+ def buttons(left,right)
794
+ @left=left
795
+ @left.enabled=false
796
+ @left.clicked.connect{move(-1)}
797
+ @right=right
798
+ @right.enabled=false
799
+ @right.clicked.connect{move(1)}
800
+ end
801
+
802
+ def push(page)
803
+ @history << page
804
+ @index=-1
805
+ @left.enabled=(@history.size>1)
806
+ @right.enabled=false
807
+ end
808
+
809
+ def browse(&block)
810
+ @browse=block
811
+ end
812
+
813
+ private
814
+
815
+ def move(delta)
816
+ return if (@index+delta >=0) || (@history.size+@index+delta<0)
817
+ @index+=delta
818
+ @browse.call(@history[@index]) if @browse
819
+ @left.enabled=(@history.size+@index>0)
820
+ @right.enabled=(@index<-1)
821
+ end
822
+ end
823
+
824
+ app=Qt::Application.new(ARGV)
825
+ classexplorer=ClassExplorer.new
826
+ classexplorer.show
827
+ app.exec
828
+