summaryrefslogtreecommitdiff
path: root/cad/src/foundation/wiki_help.py
blob: 3c58a31987d216419c1be900bb6a541a2d78a5dc (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
# Copyright 2005-2008 Nanorex, Inc.  See LICENSE file for details.
"""
wiki_help.py -- associate webpages (typically in a wiki) with program features,
and provide access to them. Pages typically contain feature-specific help info,
FAQ, forum, etc.

@author: Will, Bruce
@version: $Id$
@copyright: 2005-2008 Nanorex, Inc.  See LICENSE file for details.


Code cleanup needed:

There are two parallel systems of code which do similar things
(e.g. prepending "Feature:" and wiki_prefix()) for different kinds
of UI access; see duplicated code warnings below.


Module classification: [bruce 080101]

Mostly ui code, some io code; could be a subsystem of a general help system,
if we have one outside of ne1_ui.

Definitely doesn't belong in ne1_ui:
* not specific to NE1's individual UI
* imports nothing from ne1_ui
* imported by various things which ought to be lower than ne1_ui.

So, if we have a help module outside ne1_ui, put it there;
if we don't, it probably belongs in something like foundation.


Terminology note:

We use "web help" rather than "wiki help" in menu command text, since more users
will know what it means, and since nothing in principle forces the web pages
accessed this way to be wiki pages.

But we use "wiki help" in history messages, since we want people to think of
the wiki (rather than a rarely-changing web page) as an integral part of the
idea.


History:

idea from Eric D

will 051010 added wiki help feature to Mode classes

bruce 051130 revised it

bruce 051201 made new source file for it, extended it to other kinds of objects
(so far, some Node subclasses)
"""

from PyQt4 import QtGui
from PyQt4.Qt import QDialog
from PyQt4.Qt import QTextBrowser
from widgets.NE1_QToolBar import NE1_QToolBar

from PyQt4.Qt import QWhatsThisClickedEvent
from PyQt4.Qt import QGridLayout
from PyQt4.Qt import QPushButton
from PyQt4.Qt import QSizePolicy
from PyQt4.Qt import QSpacerItem
from PyQt4.Qt import QSize
from PyQt4.Qt import QApplication
from PyQt4.Qt import SIGNAL
from PyQt4.Qt import SLOT

import os
import foundation.env as env
import webbrowser
from utilities.debug import print_compact_traceback
from utilities.Log import redmsg
##from qt4transition import qt4todo
from utilities.prefs_constants import wiki_help_prefix_prefs_key

def webbrowser_open(url):
    """
    """
    if len(webbrowser._tryorder) == 0:
        # Sometimes webbrowser.py does not find a web browser. Also, its list
        # of web browsers is somewhat antiquated. Give it some help.
        def register(pathname, key):
            webbrowser._tryorder += [ key ]
            webbrowser.register(key, None,
                                webbrowser.GenericBrowser("%s '%%s'" % pathname))
        # Candidates are in order of decreasing desirability. Browser
        # names for different platforms can be mixed in this list. Where a
        # browser is not normally found on the system path (like IE on
        # Windows), give its full pathname. There is a working default for
        # Windows and Mac, apparently the only problem is when Linux has
        # neither "mozilla" nor "netscape".
        for candidate in [
            'firefox',
            'opera',
            'netscape',
            'konqueror',
            # 'c:/Program Files/Internet Explorer/iexplore.exe'
            ]:
            if os.path.exists(candidate):
                # handle candidates with full pathnames
                register(candidate, candidate)
                continue
            for dir in os.environ['PATH'].split(':'):
                pathname = os.path.join(dir, candidate)
                if os.path.exists(pathname):
                    register(pathname, candidate)
                    continue
    if False:
        # testing purposes only - simulate not finding a browser
        webbrowser._tryorder = [ ]
    # We should now have at least one browser available
    if len(webbrowser._tryorder) == 0:
        env.history.message(redmsg("Wiki Help cannot find a web browser"))
    webbrowser.open(url)

def open_wiki_help_dialog( featurename, actually_open = True ):
    #e actually_open = False is presently disabled in the implem
    """
    Show a dialog containing a link which can
    open the wiki help page corresponding to the named nE-1 feature, in ways influenced by user preferences.
    Assume the featurename might contain blanks, but contains no other characters needing URL-encoding.
    [In the future, we might also accept options about the context or specific instance of the feature,
     which might turn into URL-anchors or the like.]
       If actually_open = False [not yet implemented, probably won't ever be],
    don't open a web browser, but instead
    print a history message so the user can open it manually.
    Intended use is for a user preference setting to pass this, either always
    or when the feature is invoked in certain ways.
    """
    url = wiki_help_url( featurename)
    if url:
        #bruce 051215 experimental: always use the dialog with a link.
        # When this works, figure out how prefs should influence what to do, how to clean up the code, etc.
        # Other known issues:
        # - UI to access this is unfinished
        #   (F1 key, revise "web help" to "context help" in menu commands, access from Help menu)
        # - text is a stub;
        # - maybe need checkbox "retain dialog" so it stays open after the click
        # - doesn't put new dialog fully in front -- at least, closing mmkit brings main window in front of dialog
        # - dialog might be nonmodal, but if we keep that, we'll need to autoupdate its contents i suppose
        html = """Click one of the following links to launch your web browser
                  to a NanoEngineer-1 wiki page containing help on the appropriate topic:<br>
                  - The current command/mode: %s<br>
                  - %s
               </p>""" % (HTML_link(url, featurename), \
                      HTML_link(wiki_prefix() + "Main_Page", "The NanoEngineer-1 Wiki main page"))
                    #e in real life it'll be various aspects of your current context
        def clicked_func(url):
            worked = open_wiki_help_URL(url)
            ## close_dialog = worked # not good to not close it on error, unless text in dialog is preserved or replaced with error msg
            close_dialog = True
            return close_dialog
        parent = env.mainwindow() # WikiHelpBrowser now in a Dialog, so this works. Fixes bug 1235. mark060322
        w = WikiHelpBrowser(html, parent, clicked_func = clicked_func, caption = "Web Help")
        w.show()
        return
        ## if not actually_open: ## not yet used (and untested) as of 051201
        ##    env.history.message("Help for %r is available at: %s" % (featurename, url))
    return

def open_wiki_help_URL(url, whosdoingthis = "Wiki help"): #bruce 051229 split this out of open_wiki_help_dialog
    """
    Try to open the given url in the user's browser (unless they've set preferences to prevent this (NIM)),
    first emitting a history message containing the url
    (which is described as coming from whosdoingthis, which should be a capitalized string).
    Return True if there's no evidence of an error; print error message to history and return False if it definitely failed.
    """
    url = str(url) # precaution in case of QString
    ###e should check prefs to see if we should really open browser; if not, print different hist message
    env.history.message("%s: opening " % whosdoingthis + url) # see module docstring re "wiki help" vs. "web help"
        # print this in case user wants to open it manually or debug the url prefix preference
    try:
        webbrowser_open( url)
        worked = True
    except:
        #bruce 051201 catch exception to mitigate bug 1167
        # (e.g. when Linux user doesn't have BROWSER env var set).
        # Probably need to make this more intelligent, perhaps by
        # catching the specific exception in the bug report, knowing
        # the OS, passing options to webbrowser.open, etc.
        print_compact_traceback("webbrowser exception: ")
        env.history.message( redmsg("Problem opening web browser.") +
            "Suggest opening above URL by hand. "\
            "On some platforms, setting BROWSER environment variable might help."
         )
        worked = False
    return worked

def wiki_prefix():
    """
    Return the prefix to which wiki page titles should be appended,
    to form their urls. By default, these reference the Nanorex
    public wiki, but the prefix can be overridden by a user preference.
    """
    # note: public, but only used in this file;
    # but has three callers, all of which do related things
    # for different kinds of UI access. [bruce 081209 comment]
    prefix = env.prefs[wiki_help_prefix_prefs_key]
    return prefix

def wiki_help_url( featurename):
    """
    Return a URL at which the wiki help page for the named feature (e.g. "Rotary Motor" or "Build Mode")
    might be found (or should be created), or '' if this is not a valid featurename for this purpose [NIM - validity not yet checked].
    Assume the featurename might contain blanks, but contains no other characters needing URL-encoding.
    [Note: in future, user prefs might include a series of wiki prefixes to try,
     so this API might need revision to return a series of URLs to try.]
    """
    # WARNING:
    # If this function's behavior is ever changed, lots of wiki pages might need to be renamed,
    # with both old and new names supported as long as the old code is in use.
    # (The same would be true for wiki pages about specific features whose featurenames are changed.)
    prefix = wiki_prefix()
    title = "Feature:" + featurename.replace(' ', '_') # e.g. Feature:Build_Mode
        # note: partly duplicates code in turn_featurenames_into_links in whatsthis_utilities.py
    return prefix + title # assume no URL-encoding needed in title, since featurenames so far are just letters and spaces

# ==

def featurename_for_object(object):
    """
    Return the standard "feature name" for the type of this object
    (usually for its class), or "" if none can be found.
    """
    # Note: this is presently [051201, still true 080101] only used for
    # wiki help (and only in this module), but it might someday be used
    # for other things requiring permanent feature names, like class-specific
    # preference settings. So it might belong in a different module.
    # [bruce 080101 comment]
    try:
        method = object.get_featurename
    except AttributeError:
        return ""
    return method()

def wiki_help_menutext( featurename):
    """
    Return the conventional menu text for offering wiki help for the feature with the given name.
    """
    return "Web help: " + featurename # see module docstring re "wiki help" vs. "web help"

def wiki_help_lambda( featurename):
    """
    Return a callable for use as a menuspec command, which provides wiki help for featurename.
    """
    def res(arg1 = None, arg2 = None, featurename = featurename):
        #k what args come in, if any? args of res might not be needed (though they would be if it was a lambda...)
        open_wiki_help_dialog( featurename)
    return res

def wiki_help_menuspec_for_object(object):
    fn = featurename_for_object(object)
    if fn:
        return wiki_help_menuspec_for_featurename( fn)
    return []

def wiki_help_menuspec_for_featurename( featurename):
    menutext = wiki_help_menutext( featurename)
    command = wiki_help_lambda( featurename)
    return [( menutext, command )]

# ==

class QToolBar_WikiHelp(NE1_QToolBar):
    """
    A subclass of NE1_QToolBar to be used when whatsthis text
    in the toolbar might contain "wiki help" links.

    It overrides the event method to interpret a QWhatsThisClickedEvent
    by prepending the event's href with wiki_prefix() and opening
    that url in a webbrowser.
    """
    # Any widget can be extended this way. Wherever we need to have wiki help
    # active (presumably in a container with buttons or some such) we should
    # feel free to extend other container widgets as needed.
    def event(self, evt):
        if isinstance(evt, QWhatsThisClickedEvent):
            url = wiki_prefix() + evt.href()
                # note: I'm guessing that in older code, this was done
                # by the class MyWhatsThis (still mentioned in some old
                # comments) [bruce 081209 comment]
            webbrowser_open(str(url)) # Must be string. mark 2007-05-10
                # REVIEW: should we call its caller open_wiki_help_URL instead?
                # This would add a history message and some exception catching.
                # Guess: yes, perhaps with a flag to turn off history
                # except when there are errors. [bruce 081209 question]
            return True
        else:
            return NE1_QToolBar.event(self, evt)

class WikiHelpBrowser(QDialog):
    """
    The WikiHelpBrowser Dialog.
    """
    def __init__(self, text, parent = None, clicked_func = None, caption = "(caption)", size = None):
        QDialog.__init__(self,parent)

        self.setWindowTitle(caption)
        self.setWindowIcon(QtGui.QIcon("ui/border/MainWindow"))
        self.setObjectName("WikiHelpBrowser")
        TextBrowserLayout = QGridLayout(self)
        TextBrowserLayout.setSpacing(5)
        TextBrowserLayout.setMargin(2)
        self.text_browser = QTextBrowser(self)
        self.text_browser.setOpenExternalLinks(True)
        self.text_browser.setObjectName("text_browser")
        TextBrowserLayout.addWidget(self.text_browser, 0, 0, 1, 0)

        self.text_browser.setMinimumSize(400, 200)
        # make it pale yellow like a post-it note
        self.text_browser.setHtml("<qt bgcolor=\"#FFFF80\">" + text)

        self.close_button = QPushButton(self)
        self.close_button.setObjectName("close_button")
        self.close_button.setText("Close")
        TextBrowserLayout.addWidget(self.close_button, 1, 1)

        spacer = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum)
        TextBrowserLayout.addItem(spacer, 1, 0)

        self.resize(QSize(300, 300).expandedTo(self.minimumSizeHint()))
        if size == 1:
            self.text_browser.setMinimumSize(200, 400)
            self.resize(QSize(300, 550).expandedTo(self.minimumSizeHint()))
        if size == 2:
            self.resize(QSize(650, 250).expandedTo(self.minimumSizeHint()))
        self.connect(self.close_button, SIGNAL("clicked()"), self.close)
        return

    pass

def HTML_link(url, text):
    #e might need to do some encoding in url, don't know;
    # certainly needs to in text, in principle
    return "<a href=\"" + url + "\">" + text + "</a>"

# == test code

def __wiki_url_for_topic(topic, wikiprefix = None):
    wikiprefix = wikiprefix or \
                 "http://www.nanoengineer-1.net/mediawiki/index.php?title="
                # note: this is a hardcoded version of the default value
                # of wiki_prefix(); maybe it should be a named constant
    topic1 = topic[:1].upper() + topic[1:]
    topic1 = topic1.replace(" ", "_") # assume no additional url-encoding is needed
    url = wikiprefix + topic1
    return url

def __wikiPageHtmlLink(topic, text = None, wikiprefix = None):
    url = __wiki_url_for_topic(topic, wikiprefix = wikiprefix)
    if text is None:
        text = topic
    return HTML_link(url, text)

def __testWikiHelpBrowser():
    import sys
    app = QApplication(sys.argv)
    w = WikiHelpBrowser("Here is a wiki page about " +
                        __wikiPageHtmlLink("QWhatsThis and web links") +
                        " to click.")
    w.show()
    app.connect(app, SIGNAL("lastWindowClosed()"),
                app, SLOT("quit()"))
    app.exec_()

if __name__ == "__main__":
    __testWikiHelpBrowser()

# end