#!/usr/bin/env ruby require 'Korundum' about = KDE::AboutData.new("one", "two", "three") KDE::CmdLineArgs.init(ARGV, about) app = KDE::Application.new() # Qt.debug_level = TQt::DebugLevel::High # Qt.debug_level = TQt::DebugLevel::Extensive # TODO # improve appearence of sidebar massively # cut off after certain number of results? # seperate title from proof of hit? # when pressing return adjust the current node # major speed ups for ctrl-n/p # ... DEBUG = false DEBUG_IDX = false DEBUG_FAST = true DEBUG_SEARCH = true DEBUG_GOTO = false # crashes? def time_me str t1 = Time.now yield t2 = Time.now log "#{str}: #{"%.02f" % (t2 - t1).to_f}s" end module DOMUtils def DOMUtils.each_child node indent = 0 until node.isNull yield node if not node.firstChild.isNull node = node.firstChild indent += 1 elsif not node.nextSibling.isNull node = node.nextSibling else while indent > 0 and !node.isNull and node.nextSibling.isNull node = node.parentNode indent -= 1 end if not node.isNull node = node.nextSibling end end break if indent == 0 end end def DOMUtils.find_node doc, path_a n = doc path_a.reverse.each { |index| top = n.childNodes.length n = n.childNodes.item (top - index) } n end def DOMUtils.each_parent node n = node until n.isNull yield n n = n.parentNode end end def DOMUtils.list_parent_node_types node types_a = [] each_parent(node) { |n| types_a << { :nodeType => n.nodeType, :elementId => n.elementId } } types_a end def DOMUtils.get_node_path node n = node path_a = [] until n.isNull top = n.parentNode.childNodes.length idx = n.index path_a << (top-idx) if (n.elementId != 0) n = n.parentNode end path_a end end class String def trigrams list = [] 0.upto(self.length-3) { |pos| list << self.slice(pos, 3) } list end end class GenericTriGramIndex attr_accessor :trigrams def initialize clear end def clear @trigrams = {} end def insert_with_key string, key string.downcase.trigrams.each { |trigram| @trigrams[trigram] = [] unless @trigrams.has_key? trigram @trigrams[trigram] << key } end # returns a list of matching keys def search search_string warn "searching for a nil???" if search_string.nil? return [] if search_string.nil? return [] if search_string.length < 3 trigs = search_string.downcase.trigrams key_subset = @trigrams[trigs.delete_at(0)] return [] if key_subset.nil? trigs.each { |trigram| trigram_subset = @trigrams[trigram] return [] if trigram_subset.nil? key_subset &= trigram_subset } key_subset end end module LoggedDebug def init_logger parent @logger = TQt::TextEdit.new parent @logger.setTextFormat TQt::LogText end def log s @logger.append s puts "LOG: #{s}" scrolldown_logger end def scrolldown_logger @logger.scrollToBottom end end module MyGui def init_gui buttons = TQt::HBox.new self @panes = TQt::Splitter.new self @panes.setOrientation TQt::Splitter::Horizontal setStretchFactor @panes, 10 @results_pane = TQt::VBox.new @panes @rightpane = TQt::Splitter.new @panes @rightpane.setOrientation TQt::Splitter::Vertical @viewed = KDE::HTMLPart.new @rightpane init_logger @rightpane @listbox = TQt::ListBox.new @results_pane @label = TQt::Label.new self TQt::Object.connect @listbox, SIGNAL("clicked(TQListBoxItem*)"), self, SLOT("clicked_result(TQListBoxItem*)") TQt::Object.connect @viewed, SIGNAL("completed()"), self, SLOT("khtml_part_init_complete()") TQt::Object::connect @viewed, SIGNAL("setWindowCaption(const TQString&)"), @viewed.widget.topLevelWidget, SLOT("setCaption(const TQString&)") TQt::Object::connect @viewed.browserExtension, SIGNAL("openURLRequest(const KURL&, const KParts::URLArgs&)"), self, SLOT("open_url(const KURL&)") KDE::Action.new "&Quit", "quit", KDE::Shortcut.new(), self, SLOT("quit()"), @main.actionCollection, "file_quit" KDE::Action.new "&Index-All", KDE::Shortcut.new(), self, SLOT("index_all()"), @main.actionCollection, "index_all" @back = \ KDE::Action.new "&Back", "back", KDE::Shortcut.new(TQt::ALT + TQt::Key_Left), self, SLOT("go_back()"), @main.actionCollection, "back" @forward = \ KDE::Action.new "&Forward", "forward", KDE::Shortcut.new(TQt::ALT + TQt::Key_Right), self, SLOT("go_forward()"), @main.actionCollection, "forward" KDE::Action.new "&Home", "gohome", KDE::Shortcut.new(TQt::Key_Home), self, SLOT("go_home()"), @main.actionCollection, "home" KDE::Action.new "&Prev Match", "previous",KDE::Shortcut.new(TQt::CTRL + TQt::Key_P), self, SLOT("goto_prev_match()"), @main.actionCollection, "prev_match" KDE::Action.new "&Next Match", "next", KDE::Shortcut.new(TQt::CTRL + TQt::Key_N), self, SLOT("goto_next_match()"), @main.actionCollection, "next_match" KDE::Action.new "&Follow Match","down", KDE::Shortcut.new(TQt::Key_Return), self, SLOT("goto_current_match_link()"), @main.actionCollection, "open_match" KDE::Action.new "Search", "find", KDE::Shortcut.new(TQt::Key_F6), self, SLOT("focus_search()"), @main.actionCollection, "focus_search" KDE::Action.new "New Search", "find", KDE::Shortcut.new(TQt::CTRL + TQt::Key_Slash), self, SLOT("focus_and_clear_search()"), @main.actionCollection, "focus_and_clear_search" KDE::Action.new "&Create", "new", KDE::Shortcut.new(), self, SLOT("project_create()"), @main.actionCollection, "project_create" KDE::Action.new "&Choose...", "select", KDE::Shortcut.new(), self, SLOT("project_goto()"), @main.actionCollection, "project_goto" clearLocation = KDE::Action.new "Clear Location Bar", "locationbar_erase", KDE::Shortcut.new(), self, SLOT("clear_location()"), @main.actionCollection, "clear_location" clearLocation.setWhatsThis "Clear Location bar
Clears the content of the location bar." @searchlabel = TQt::Label.new @main @searchlabel.setText "Search: " @searchcombo = KDE::HistoryCombo.new @main focus_search TQt::Object.connect @searchcombo, SIGNAL("returnPressed()"), self, SLOT("goto_search()") TQt::Object.connect @searchcombo, SIGNAL("textChanged(const TQString&)"), self, SLOT("search(const TQString&)") KDE::WidgetAction.new @searchlabel, "Search: ", KDE::Shortcut.new(TQt::Key_F6), nil, nil, @main.actionCollection, "location_label" @searchlabel.setBuddy @searchcombo ca = KDE::WidgetAction.new @searchcombo, "Search", KDE::Shortcut.new, nil, nil, @main.actionCollection, "toolbar_url_combo" ca.setAutoSized true TQt::WhatsThis::add @searchcombo, "Search
Enter a search term." end def focus_search @searchcombo.setFocus end def focus_and_clear_search clear_location focus_search end def clear_location @searchcombo.clearEdit end def uri_anchor_split url url =~ /(.*?)(#(.*))?$/ return $1, $3 end def open_url kurl url, anchor = uri_anchor_split kurl.url goto_url url, false unless id == @shown_doc_id @viewed.gotoAnchor anchor unless anchor.nil? end def gui_init_proportions # todo - save these settings desktop = TQt::Application::desktop sx = (desktop.width * (2.0/3.0)).to_i sy = (desktop.height * (2.0/3.0)).to_i @main.resize sx, sy logsize = 0 resultssize = (sx / 5.0).to_i @rightpane.setSizes [sy-logsize, logsize] @panes.setSizes [resultssize, sx-resultssize] @rightpane.setResizeMode @logger, TQt::Splitter::KeepSize @panes.setResizeMode @results_pane, TQt::Splitter::KeepSize @panes.setResizeMode @rightpane, TQt::Splitter::KeepSize end end module IndexStorage INDEX_VERSION = 3 IndexStore = Struct.new :index, :nodeindex, :textcache, :id2title, :id2uri, :id2depth, :version def index_fname basedir = ENV["HOME"] + "/.rubberdocs" prefix = basedir + "/." + @pref.gsub(/\//,",") + ".idx" Dir.mkdir basedir unless File.exists? basedir "#{prefix}.doc" end def depth_debug puts "depth_debug : begin" @id2depth.each_key { |id| puts "indexed to depth #{@id2depth[id]} : #{@id2uri[id]}" } puts "end :" end def load_indexes return false unless File.exists? index_fname TQt::Application::setOverrideCursor(TQt::Cursor.new TQt::WaitCursor) File.open(index_fname, "r") { |file| w = Marshal.load file rescue nil return false if w.nil? || w.version < INDEX_VERSION @index = w.index @nodeindex = w.nodeindex @textcache = w.textcache @id2title = w.id2title @id2uri = w.id2uri @id2depth = w.id2depth @indexed_more = false true } TQt::Application::restoreOverrideCursor end def save_indexes return unless @indexed_more File.open(index_fname, "w") { |file| w = IndexStore.new w.index = @index w.nodeindex = @nodeindex w.textcache = @textcache w.id2title = @id2title w.id2uri = @id2uri w.id2depth = @id2depth w.version = INDEX_VERSION Marshal.dump w, file } end end module HTMLIndexer DocNodeRef = Struct.new :doc_idx, :node_path module IndexDepths # TitleIndexed implies "LinkTitlesIndexed" Allocated, TitleIndexed, LinksFollowed, Partial, Node = 0, 1, 2, 3, 4 end def index_documents # fix this to use kde's actual dir @t1 = Time.now @url = first_url already_indexed = load_indexes @top_doc_id = already_indexed ? @id2uri.keys.max + 1 : 0 return if already_indexed t1 = Time.now @viewed.hide @done = [] @todo_links = [] progress = KDE::ProgressDialog.new(self, "blah", "Indexing files...", "Abort Indexing", true) total_num_files = Dir.glob("#{@pref}/**/*.html").length progress.progressBar.setTotalSteps total_num_files @todo_links = [ DOM::DOMString.new first_url.url ] until @todo_links.empty? @todo_next = [] while more_to_do progress.progressBar.setProgress @id2title.keys.length end @todo_links = @todo_next fail "errr, you really didn't want to do that dave" if progress.wasCancelled end progress.progressBar.setProgress total_num_files save_indexes t2 = Time.now log "all documents indexed in #{(t2 - t1).to_i}s" end def should_follow? lhref case lhref when /source/, /members/ ret = false when /^file:#{@pref}/ ret = true else ret = false end ret end def gather_for_current_page index_current_title return [] if @id2depth[@shown_doc_id] >= IndexDepths::LinksFollowed todo_links = [] title_map = {} anchors = @viewed.htmlDocument.links f = anchors.firstItem count = anchors.length until (count -= 1) < 0 text = "" DOMUtils.each_child(f) { |node| text << node.nodeValue.string if node.nodeType == DOM::Node::TEXT_NODE } link = TQt::Internal::cast_object_to f, DOM::HTMLLinkElement if should_follow? link.href.string title_map[link.href.string] = text urlonly, = uri_anchor_split link.href.string add_link_to_index urlonly, text todo_links << link.href unless DEBUG_FAST end f = anchors.nextItem end @id2depth[@shown_doc_id] = IndexDepths::LinksFollowed return todo_links end def find_allocated_uri uri id = @id2uri.invert[uri] return id end # sets @shown_doc_id def index_current_title id = find_allocated_uri(@viewed.htmlDocument.URL.string) return if !id.nil? and @id2depth[id] >= IndexDepths::TitleIndexed log "making space for url #{@viewed.htmlDocument.URL.string.sub(@pref,"")}" id = alloc_index_space @viewed.htmlDocument.URL.string if id.nil? @indexed_more = true @id2title[id] = @viewed.htmlDocument.title.string @id2depth[id] = IndexDepths::TitleIndexed @shown_doc_id = id end def alloc_index_space uri @indexed_more = true id = @top_doc_id @id2uri[@top_doc_id] = uri @id2title[@top_doc_id] = nil @id2depth[@top_doc_id] = IndexDepths::Allocated @top_doc_id += 1 id end def add_link_to_index uri, title return unless find_allocated_uri(uri).nil? @indexed_more = true new_id = alloc_index_space uri @id2title[new_id] = title @index.insert_with_key title, new_id end def index_current_document return if @id2depth[@shown_doc_id] >= IndexDepths::Partial TQt::Application::setOverrideCursor(TQt::Cursor.new TQt::WaitCursor) @indexed_more = true @label.setText "Scanning : #{@url.prettyURL}" log "indexing url #{@viewed.htmlDocument.URL.string.sub(@pref,"")}" DOMUtils.each_child(@viewed.document) { |node| next unless node.nodeType == DOM::Node::TEXT_NODE @index.insert_with_key node.nodeValue.string, @shown_doc_id } @id2depth[@shown_doc_id] = IndexDepths::Partial @label.setText "Ready" TQt::Application::restoreOverrideCursor end def preload_text return if @id2depth[@shown_doc_id] >= IndexDepths::Node TQt::Application::setOverrideCursor(TQt::Cursor.new TQt::WaitCursor) @indexed_more = true index_current_document log "deep indexing url #{@viewed.htmlDocument.URL.string.sub(@pref,"")}" @label.setText "Indexing : #{@url.prettyURL}" doc_text = "" t1 = Time.now DOMUtils.each_child(@viewed.document) { |node| next unless node.nodeType == DOM::Node::TEXT_NODE ref = DocNodeRef.new @shown_doc_id, DOMUtils.get_node_path(node) @nodeindex.insert_with_key node.nodeValue.string, ref @textcache[ref] = node.nodeValue.string doc_text << node.nodeValue.string } @id2depth[@shown_doc_id] = IndexDepths::Node @label.setText "Ready" TQt::Application::restoreOverrideCursor end end # TODO - this sucks, use khtml to get the values module IDS A = 1 META = 62 STYLE = 85 TITLE = 95 end module TermHighlighter include IDS FORBIDDEN_TAGS = [IDS::TITLE, IDS::META, IDS::STYLE] def update_highlight return if @search_text.nil? || @search_text.empty? return if @in_update_highlight @in_update_highlight = true preload_text highlighted_nodes = [] @nodeindex.search(@search_text).each { |ref| next unless ref.doc_idx == @shown_doc_id highlighted_nodes << ref.node_path } highlight_node_list highlighted_nodes @in_update_highlight = false end def mark_screwup @screwups = 0 if @screwups.nil? warn "if you see this, then alex screwed up!.... #{@screwups} times!" @screwups += 1 end def highlight_node_list highlighted_nodes doc = @viewed.document no_undo_buffer = @to_undo.nil? current_doc_already_highlighted = (@shown_doc_id == @last_highlighted_doc_id) undo_highlight @to_undo unless no_undo_buffer or !current_doc_already_highlighted @last_highlighted_doc_id = @shown_doc_id @to_undo = [] return if highlighted_nodes.empty? TQt::Application::setOverrideCursor(TQt::Cursor.new TQt::WaitCursor) cursor_override = true @current_matching_node_index = 0 if @current_matching_node_index.nil? @current_matching_node_index = @current_matching_node_index.modulo highlighted_nodes.length caretnode = DOMUtils.find_node doc, highlighted_nodes[@current_matching_node_index] @viewed.setCaretVisible false @viewed.setCaretPosition caretnode, 0 caret_path = DOMUtils.get_node_path(caretnode) count = 0 @skipped_highlight_requests = false @current_matched_href = nil highlighted_nodes.sort.each { |path| node = DOMUtils.find_node doc, path next mark_screwup if node.nodeValue.string.nil? match_idx = node.nodeValue.string.downcase.index @search_text.downcase next mark_screwup if match_idx.nil? parent_info = DOMUtils.list_parent_node_types node has_title_parent = !(parent_info.detect { |a| FORBIDDEN_TAGS.include? a[:elementId] }.nil?) next if has_title_parent if path == caret_path DOMUtils.each_parent(node) { |n| next unless n.elementId == IDS::A # link = DOM::HTMLLinkElement.new n # WTF? why doesn't this work??? link = TQt::Internal::cast_object_to n, "DOM::HTMLLinkElement" @current_matched_href = link.href.string } end before = doc.createTextNode node.nodeValue.split(0) matched = doc.createTextNode before.nodeValue.split(match_idx) after = doc.createTextNode matched.nodeValue.split(@search_text.length) DOM::CharacterData.new(DOM::Node.new after).setData DOM::DOMString.new("") \ if after.nodeValue.string.nil? span = doc.createElement DOM::DOMString.new("span") spanelt = DOM::HTMLElement.new span classname = (path == caret_path) ? "foundword" : "searchword" spanelt.setClassName DOM::DOMString.new(classname) span.appendChild matched node.parentNode.insertBefore before, node node.parentNode.insertBefore span, node node.parentNode.insertBefore after, node @to_undo << [node.parentNode, before] node.parentNode.removeChild node rate = (count > 50) ? 50 : 10 allow_user_input = ((count+=1) % rate == 0) if allow_user_input TQt::Application::restoreOverrideCursor if cursor_override cursor_override = false @in_node_highlight = true TQt::Application::eventLoop.processEvents TQt::EventLoop::AllEvents, 10 @in_node_highlight = false if @skipped_highlight_requests @timer.start 50, true return false end @viewed.view.layout end } if @skipped_highlight_requests @timer.start 50, true end TQt::Application::restoreOverrideCursor if cursor_override end def undo_highlight to_undo to_undo.reverse.each { |pnn| pn, before = *pnn mid = before.nextSibling after = mid.nextSibling beforetext = before.nodeValue aftertext = after.nodeValue pn.removeChild after midtxtnode = mid.childNodes.item(0) midtext = midtxtnode.nodeValue str = DOM::DOMString.new "" str.insert aftertext, 0 str.insert midtext, 0 str.insert beforetext, 0 chardata = DOM::CharacterData.new(DOM::Node.new before) chardata.setData str pn.removeChild mid } end end class SmallIconSet def SmallIconSet.[] name loader = KDE::Global::instance.iconLoader return loader.loadIconSet name, KDE::Icon::Small, 0 end end class ProjectEditDialog < TQt::Object slots "select_file()", "slot_ok()" def initialize project_name, parent=nil,name=nil,caption=nil super(parent, name) @parent = parent @dialog = KDE::DialogBase.new(parent,name, true, caption, KDE::DialogBase::Ok|KDE::DialogBase::Cancel, KDE::DialogBase::Ok, false) vbox = TQt::VBox.new @dialog grid = TQt::Grid.new 2, TQt::Horizontal, vbox titlelabel = TQt::Label.new "Name:", grid @title = KDE::LineEdit.new grid titlelabel.setBuddy @title urllabel = TQt::Label.new "Location:", grid lochbox = TQt::HBox.new grid @url = KDE::LineEdit.new lochbox urllabel.setBuddy @url locselc = TQt::PushButton.new lochbox locselc.setIconSet SmallIconSet["up"] blub = TQt::HBox.new vbox TQt::Label.new "Is main one?:", blub @cb = TQt::CheckBox.new blub enabled = @parent.projects_data.project_list.empty? unless project_name.nil? project_url = @parent.projects_data.project_list[project_name] @title.setText project_name @url.setText project_url enabled = true if (project_name == @parent.projects_data.enabled_name) end @cb.setChecked true if enabled TQt::Object.connect @dialog, SIGNAL("okClicked()"), self, SLOT("slot_ok()") TQt::Object.connect locselc, SIGNAL("clicked()"), self, SLOT("select_file()") @title.setFocus @dialog.setMainWidget vbox @modified = false end def select_file s = TQt::FileDialog::getOpenFileName ENV["HOME"], "HTML Files (*.html)", @parent, "open file dialog", "Choose a file" @url.setText s unless s.nil? end def edit @dialog.exec return @modified end def new_name @title.text end def new_url @url.text end def new_enabled @cb.isChecked end def slot_ok @parent.projects_data.project_list[new_name] = new_url @parent.projects_data.enabled_name = new_name if new_enabled @modified = true end end class ProjectSelectDialog < TQt::Object slots "edit_selected_project()", "delete_selected_project()", "project_create_button()", "project_selected()" def initialize parent=nil,name=nil,caption=nil super(parent, name) @parent = parent @dialog = KDE::DialogBase.new parent,name, true, caption, KDE::DialogBase::Ok|KDE::DialogBase::Cancel, KDE::DialogBase::Ok, false vbox = TQt::VBox.new @dialog @listbox = TQt::ListBox.new vbox fill_listbox hbox = TQt::HBox.new vbox button_new = TQt::PushButton.new "New...", hbox button_del = TQt::PushButton.new "Delete", hbox button_edit = TQt::PushButton.new "Edit...", hbox TQt::Object.connect button_new, SIGNAL("clicked()"), self, SLOT("project_create_button()") TQt::Object.connect button_del, SIGNAL("clicked()"), self, SLOT("delete_selected_project()") TQt::Object.connect button_edit, SIGNAL("clicked()"), self, SLOT("edit_selected_project()") TQt::Object.connect @listbox, SIGNAL("doubleClicked(TQListBoxItem *)"), self, SLOT("project_selected()") @dialog.setMainWidget vbox end def project_selected return if @listbox.selectedItem.nil? @parent.current_project_name = @listbox.selectedItem.text @parent.blah_blah @dialog.reject end def fill_listbox @listbox.clear @parent.projects_data.project_list.keys.each { |name| enabled = (name == @parent.projects_data.enabled_name) icon = enabled ? "forward" : "down" pm = SmallIconSet[icon].pixmap(TQt::IconSet::Automatic, TQt::IconSet::Normal) it = TQt::ListBoxPixmap.new pm, name @listbox.insertItem it } end def edit_selected_project return if @listbox.selectedItem.nil? oldname = @listbox.selectedItem.text dialog = ProjectEditDialog.new oldname, @parent mod = dialog.edit if mod and oldname != dialog.new_name @parent.projects_data.project_list.delete oldname end fill_listbox if mod end def project_create_button mod = @parent.project_create fill_listbox if mod end def delete_selected_project return if @listbox.selectedItem.nil? # TODO - confirmation dialog @parent.projects_data.project_list.delete @listbox.selectedItem.text fill_listbox end def select @dialog.exec end end module ProjectManager def project_create dialog = ProjectEditDialog.new nil, self dialog.edit while @projects_data.project_list.empty? dialog.edit end end def project_goto dialog = ProjectSelectDialog.new self dialog.select if @projects_data.project_list.empty? project_create end end require 'yaml' def yamlfname ENV["HOME"] + "/.rubberdocs/projects.yaml" end PROJECT_STORE_VERSION = 0 Projects = Struct.new :project_list, :enabled_name, :version def load_projects okay = false if File.exists? yamlfname @projects_data = YAML::load File.open(yamlfname) if (@projects_data.version rescue -1) >= PROJECT_STORE_VERSION okay = true end end if not okay or @projects_data.project_list.empty? @projects_data = Projects.new({}, nil, PROJECT_STORE_VERSION) project_create end if @projects_data.enabled_name.nil? @projects_data.enabled_name = @projects_data.project_list.keys.first end end def save_projects File.open(yamlfname, "w+") { |file| file.puts @projects_data.to_yaml } end end class RubberDoc < TQt::VBox slots "khtml_part_init_complete()", "go_back()", "go_forward()", "go_home()", "goto_url()", "goto_search()", "clicked_result(TQListBoxItem*)", "search(const TQString&)", "update_highlight()", "quit()", "open_url(const KURL&)", "index_all()", "goto_prev_match()", "goto_next_match()", "clear_location()", "activated()", "goto_current_match_link()", "focus_search()", "focus_and_clear_search()", "project_create()", "project_goto()" attr_accessor :back, :forward, :url, :projects_data include LoggedDebug include MyGui include IndexStorage include HTMLIndexer include TermHighlighter include ProjectManager def init_blah @index = GenericTriGramIndex.new @nodeindex = GenericTriGramIndex.new @textcache = {} @id2uri, @id2title, @id2depth = {}, {}, {} @history, @popped_history = [], [] @shown_doc_id = 0 @freq_sorted_idxs = nil @last_highlighted_doc_id, @to_undo = nil, nil, nil @search_text = nil @current_matched_href = nil @in_update_highlight = false @in_node_highlight = false @lvis = nil end def initialize parent super parent @main = parent load_projects @current_project_name = @projects_data.enabled_name init_blah init_gui gui_init_proportions @timer = TQt::Timer.new self TQt::Object.connect @timer, SIGNAL("timeout()"), self, SLOT("update_highlight()") @viewed.openURL KDE::URL.new("about:blank") @init_connected = true end def blah_blah save_indexes init_blah khtml_part_init_complete end def quit @main.close end def khtml_part_init_complete TQt::Object.disconnect @viewed, SIGNAL("completed()"), self, SLOT("khtml_part_init_complete()") if @init_connected @pref = File.dirname first_url.url.gsub("file:","") init_khtml_part_settings @viewed if @init_connected index_documents # maybe make a better choice as to the start page??? @shown_doc_id = 0 goto_url @id2uri[@shown_doc_id], false @viewed.show search "qlistview" if DEBUG_SEARCH || DEBUG_GOTO goto_search if DEBUG_GOTO @init_connected = false end def finish save_projects save_indexes end def init_khtml_part_settings khtmlpart khtmlpart.setJScriptEnabled true khtmlpart.setJavaEnabled false khtmlpart.setPluginsEnabled false khtmlpart.setAutoloadImages false end def load_page @viewed.setCaretMode true @viewed.setCaretVisible false @viewed.document.setAsync false @viewed.document.load DOM::DOMString.new @url.url @viewed.setUserStyleSheet "span.searchword { background-color: yellow } span.foundword { background-color: green }" TQt::Application::eventLoop.processEvents TQt::EventLoop::ExcludeUserInput end attr_accessor :current_project_name def first_url return KDE::URL.new @projects_data.project_list[@current_project_name] end def search s if @in_node_highlight @skipped_highlight_requests = true return end puts "search request: #{s}" @search_text = s results = @index.search(s) results += @nodeindex.search(s).collect { |docref| docref.doc_idx } idx_hash = Hash.new { |h,k| h[k] = 0 } results.each { |idx| idx_hash[idx] += 1 } @freq_sorted_idxs = idx_hash.to_a.sort_by { |val| val[1] }.reverse update_lv hl_timeout = 150 # continuation search should be slower? @timer.start hl_timeout, true unless @freq_sorted_idxs.empty? end def look_for_prefixes prefixes = [] # TODO - fix this crappy hack @id2title.values.compact.sort.each { |title| title.gsub! "\n", "" pos = title.index ":" next if pos.nil? prefix = title[0..pos-1] prefixes << prefix new_title = title[pos+1..title.length] new_title.gsub! /(\s\s+|^\s+|\s+$)/, "" title.replace new_title } end class ResultItem < TQt::ListBoxItem def initialize header, text super() @text, @header = text, header @font = TQt::Font.new("Helvetica", 8) @flags = TQt::AlignLeft | TQt::WordBreak end def paint painter w, h = width(listBox), height(listBox) header_height = (text_height @font, @header) + 5 painter.setFont @font painter.fillRect 5, 5, w - 10, header_height, TQt::Brush.new(TQt::Color.new 150,100,150) painter.drawText 5, 5, w - 10, header_height, @flags, @header painter.fillRect 5, header_height, w - 10, h - 10, TQt::Brush.new(TQt::Color.new 100,150,150) painter.setFont @font painter.drawText 5, header_height + 2, w - 10, h - 10, @flags, @text end def text_height font, text fm = TQt::FontMetrics.new font br = fm.boundingRect 0, 0, width(listBox) - 20, 8192, @flags, text br.height end def height listbox h = 0 h += text_height @font, @text h += text_height @font, @header return h + 10 end def width listbox listBox.width - 5 end end CUTOFF = 100 def update_lv @listbox.clear @lvis = {} look_for_prefixes return if @freq_sorted_idxs.nil? @freq_sorted_idxs.each { |a| idx, count = *a title = @id2title[idx] # we must re-search until we have a doc -> nodes list matches_text = "" @nodeindex.search(@search_text).each { |ref| break if matches_text.length > CUTOFF next unless ref.doc_idx == idx matches_text << @textcache[ref] << "\n" } matches_text = matches_text.slice 0..CUTOFF lvi = ResultItem.new "(#{count}) #{title}", matches_text @listbox.insertItem lvi @lvis[lvi] = idx } end def goto_search idx, count = *(@freq_sorted_idxs.first) goto_id_and_hl idx end def clicked_result i return if i.nil? idx = @lvis[i] goto_id_and_hl idx end def goto_id_and_hl idx @current_matching_node_index = 0 goto_url @id2uri[idx], true @shown_doc_id = idx update_highlight end def goto_current_match_link open_url KDE::URL.new(@current_matched_href) unless @current_matched_href.nil? end def skip_matches n @current_matching_node_index += n # autowraps if @in_node_highlight @skipped_highlight_requests = true return end update_highlight end def goto_prev_match skip_matches -1 end def goto_next_match skip_matches +1 end def more_to_do return false if @todo_links.empty? lhref = @todo_links.pop do_for_link lhref true end def do_for_link lhref idx = (lhref.string =~ /#/) unless idx.nil? lhref = lhref.copy lhref.truncate idx end skip = @done.include? lhref.string return [] if skip time_me("loading") { @viewed.document.setAsync false @viewed.document.load lhref } @done << lhref.string newlinks = gather_for_current_page @todo_next += newlinks end def update_ui_elements @forward.setEnabled !@popped_history.empty? @back.setEnabled !@history.empty? end def go_back @popped_history << @url fail "ummm... already at the start, gui bug" if @history.empty? goto_url @history.pop, false, false update_loc update_ui_elements end def go_forward fail "history bug" if @popped_history.empty? goto_url @popped_history.pop, false, false @history << @url update_loc update_ui_elements end def update_loc # @location.setText @url.prettyURL end def go_home goto_url first_url end def index_all @viewed.hide @id2uri.keys.each { |id| goto_id_and_hl id } @viewed.show end def goto_url url = nil, history_store = true, clear_forward = true @popped_history = [] if clear_forward if history_store @history << @url end @url = KDE::URL.new(url.nil? ? @location.text : url) @label.setText "Loading : #{@url.prettyURL}" urlonly, = uri_anchor_split @url.url id = @id2uri.invert[@url.url] if id.nil? and !(should_follow? urlonly) warn "link points outside indexed space!" return end load_page if id.nil? gather_for_current_page id = @shown_doc_id index_current_document else @shown_doc_id = id end @label.setText "Ready" update_loc unless url.nil? update_ui_elements end end m = KDE::MainWindow.new browser = RubberDoc.new m browser.update_ui_elements guixmlfname = Dir.pwd + "/RubberDoc.rc" guixmlfname = File.dirname(File.readlink $0) + "/RubberDoc.rc" unless File.exists? guixmlfname m.createGUI guixmlfname m.setCentralWidget browser app.setMainWidget(m) m.show app.exec() browser.finish __END__ TESTCASE - w = KDE::HTMLPart # notice the missing .new w.begin => crashes badly (RECHECK) ./kde.rb:29:in `method_missing': Cannot handle 'const TQIconSet&' as argument to TQTabWidget::changeTab (ArgumentError) from ./kde.rb:29:in `initialize' from ./kde.rb:92:in `new' from ./kde.rb:92 for param nil given to param const TQIconSet & occurs frequently dum di dum can't get tabwidget working. umm... wonder what i'm messing up... (RECHECK) tabwidget = KDE::TabWidget.new browser tabwidget.setTabPosition TQt::TabWidget::Top @viewed = KDE::HTMLPart.new tabwidget w2 = KDE::HTMLPart.new tabwidget tabwidget.changeTab @viewed, TQt::IconSet.new, "blah blah" tabwidget.showPage @viewed tabwidget.show @viewed.show # possible BUG DOM::Text.new(node).data.string # strange that this one doesn't work... (RECHECK) wierd khtml bug @rightpane.setResizeMode @viewed, TQt::Splitter::KeepSize in order to use KURL's as constants one must place this KApplication init at the top of the file otherwise KInstance isn't init'ed before KURL usage class ProjectSelectDialog < KDE::DialogBase def initialize parent=nil,name=nil,caption=nil super(parent,name, true, caption, KDE::DialogBase::Ok|KDE::DialogBase::Cancel, KDE::DialogBase::Ok, false, KDE::GuiItem.new) blah blah end end # painter.fillRect 5, 5, width(listBox) - 10, height(listBox) - 10, TQt::Color.new(255,0,0)