File: searchexplorer.py

package info (click to toggle)
pythoncard 0.8.2-2
  • links: PTS
  • area: main
  • in suites: wheezy
  • size: 8,452 kB
  • sloc: python: 56,787; makefile: 56; sh: 22
file content (243 lines) | stat: -rw-r--r-- 9,695 bytes parent folder | download | duplicates (3)
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
#!/usr/bin/python

"""
App Name: SearchExplorer
Created: 2001-07-21
Description: The first useful application using the PythonCard prototype.

List widget needs to support selections by number rather than just by string
List widget needs to support more methods for inserting, appending, clearing, etc.

Need support for a default button in the resource format...

If you are familiar with OOP design you'll notice that PythonCard does not
implement a Model View Controller. However, unlike HyperCard I'm
not using widgets themselves as data storage or storing data needed across
methods in global variables, both of which were common techniques in
HyperCard. Instead, I'm using instance variables. Where data is stored and
how it is read and saved transparently is one of the issues we have to explore.

I'm playing with naming conventions. When I make a reference to an actual
widget object I'm prefixing the variable name with 'w' followed by the actual widget
unique name. Visual Basic recommended a convention of three letter abbreviations
for each widget type, but these abbreviations were both hard to remember except
for the simplest ones (btn, lbl, fld, chk, mnu) and the convention doesn't scale
well as the list of widgets grows. So, I'm also trying a verbose naming
convention where I prefix the name with the widget class (listSites). This makes
for much more typing, but it has an appeal.

Naming conventions are just that, conventions, not hard rules or something enforced
by Python or the PythonCard framework, but if everyone follows the same conventions
then it will make reading and modifying PythonCard user code much easier.

No attempt has been made to modularize the logic into appropriate classes and
methods. I wanted to try some Rapid Application Development (RAD)
without much planning, just sticking code into the event handlers as I went.

There isn't much in the way of error checking either.

The initial definition of the widgets for the resource file wasn't too bad, but
using a GUI to create, resize, and set the properties of the widgets would have
been much quicker, so creating even a simple GUI layout tool is a high priority
for me once a little bit more of the prototype framework is in place.

Until we have an IDE that manages both the source code and the resource file
and has a way of showing the valid widgets in context, then getting widget names
and method names out of sync will be a source of problems. Consequently, it might
help when coding to keep a copy of the valid widgets for a given card or background
at the top of the source code or class such as the list below. This is particularly
nice if your editor supports split windows.

fldSearch
btnSearch
lblSites
lblPastSearches
listSites
listPastSearches
chkExitAfterSearch
btnRemoveSearch

"""
__version__ = "$Revision: 1.35 $"
__date__ = "$Date: 2005/12/13 11:13:25 $"
__author__ = "Kevin Altis <altis@semi-retired.com>"

import urllib
import webbrowser
import pprint

from PythonCard import configuration, dialog, model, util, log
import os, sys
import shutil

FAVORITES_FILE = 'favorites.py'

class SearchExplorer(model.Background):

    def on_initialize(self, event):
        self.changed = 0
        wFldSearch = self.components.fldSearch
        # copy the clipboard to fldSearch when the app starts
        if wFldSearch.canPaste():
            wFldSearch.paste()
        else:
            wFldSearch.text = ''
        #wFldSearch.setSelection(0, len(wFldSearch.text))
        # below is a simpler way of selecting all of the text in a field
        wFldSearch.setSelection(-1, -1)

        self.configPath = os.path.join(configuration.homedir, 'searchexplorer')
        if not os.path.exists(self.configPath):
            os.mkdir(self.configPath)
        basePath = self.application.applicationDirectory
        self.sfFilename = os.path.join(self.configPath, FAVORITES_FILE)
        log.debug('Favourites are in %s' % self.sfFilename)
        if not os.path.exists(self.sfFilename):
            try:
                shutil.copy2(os.path.join(basePath, FAVORITES_FILE), self.sfFilename)
            except IOError:
                dialog.messageDialog(self, 'Unable to load ' + self.sfFilename, 'SearchExplorer exiting')
                sys.exit()
            self.sitesDict = util.readAndEvalFile(self.sfFilename)

        defaultSite = 'Google'
        sites = self.sitesDict.keys()
        # need to implement a case-insensitive sort and use that everywhere
        sites.sort()
        pastSearches = self.sitesDict[defaultSite]['pastSearches']
        pastSearches.sort()

        wListSites = self.components.listSites
        wListSites.items = sites
        wListSites.stringSelection = defaultSite

        self.components.listPastSearches.items = pastSearches

    # *** these methods won't be called if the window close button is used ***
    # *** so any searches made will be lost until that event is hooked up  ***
    # *** in the framework, unless the Exit on search checkbox is checked***
    # *** and the Search button is pressed to do a search/launch a browser ***
    # this "event" is a native wxPython event, not our Event class
    def on_close(self, event):
        self.doExit()
        event.skip()

    def OnExit(self):
        print "OnExit"
        self.doExit()

    def doExit(self):
        # always save the current dictionary of search favorites
        # to simulate transparent saves
        if self.changed:
            f = open(self.sfFilename, "w")
            #s = repr(self.sitesDict)
            #f.write(s)
            pprint.pprint(self.sitesDict, f)
            f.close()

    def doLaunch(self):
        wFldSearch = self.components.fldSearch
        s = wFldSearch.text
        if s != "":
            # update the past searches
            # site = self.find('listSites').getSelection()
            # KEA old version above, getSelection now returns a position
            site = self.components.listSites.stringSelection
            pastSearches = self.sitesDict[site]['pastSearches']
            if s not in pastSearches:
                self.changed = 1
                pastSearches.append(s)
                pastSearches.sort()
                self.components.listPastSearches.items = pastSearches

            encodedText = urllib.quote_plus(s)
            url = self.sitesDict[site]['preURL'] + encodedText + self.sitesDict[site]['postURL']
            
            # launch a new browser window and autoraise it
            # there appears to be a bug in webbrowser.py because
            # if a window already exists, a new one isn't being created ?!
            webbrowser.open(url, 1, 1)

            if self.components.chkExitAfterSearch.checked:
                self.close()

    # pressing return when the search button has focus or double-clicking
    # a past search will launch a browser window
    # just pressing return will work once default buttons are supported
    def on_btnSearch_mouseClick(self, event):
        self.doLaunch()

    #def on_fldSearch_textEnter(self, event):
    #    self.doLaunch()

    def on_listPastSearches_mouseDoubleClick(self, event):
        self.doLaunch()

    def on_listSites_select(self, event):
        pastSearches = self.sitesDict[event.target.stringSelection]['pastSearches']
        wListPastSearches = self.components.listPastSearches
        if len(pastSearches) > 1:
            pastSearches.sort()
        wListPastSearches.items = pastSearches

    def on_listPastSearches_select(self, event):
        self.components.fldSearch.text = event.target.stringSelection

    def on_btnRemoveSearch_mouseClick(self, event):
        wListPastSearches = self.components.listPastSearches
        doomed = wListPastSearches.stringSelection
        if doomed != "":
            site = self.components.listSites.stringSelection
            pastSearches = self.sitesDict[site]['pastSearches']
            pastSearches.remove(doomed) # update self.sitesDict[site]['pastSearches']
            self.changed = 1
            wListPastSearches.items = pastSearches


    def on_menuEditUndo_select(self, event):
        widget = self.findFocus()
        if hasattr(widget, 'editable') and widget.canUndo():
            widget.undo()

    def on_menuEditRedo_select(self, event):
        widget = self.findFocus()
        if hasattr(widget, 'editable') and widget.canRedo():
            widget.redo()

    def on_menuEditCut_select(self, event):
        widget = self.findFocus()
        if hasattr(widget, 'editable') and widget.canCut():
            widget.cut()

    def on_menuEditCopy_select(self, event):
        widget = self.findFocus()
        if hasattr(widget, 'editable') and widget.canCopy():
            widget.copy()

    def on_menuEditPaste_select(self, event):
        widget = self.findFocus()
        if hasattr(widget, 'editable') and widget.canPaste():
            widget.paste()
        
    def on_menuEditClear_select(self, event):
        widget = self.findFocus()
        if hasattr(widget, 'editable'):
            if widget.canCut():
                # delete the current selection,
                # if we can't do a Cut we shouldn't be able to delete either
                # which is why i used the test above
                sel = widget.replaceSelection('')
            else:
                ins = widget.getInsertionPoint()
                widget.replace(ins, ins + 1, '')

    def on_menuEditSelectAll_select(self, event):
        widget = self.findFocus()
        if hasattr(widget, 'editable'):
            widget.setSelection(0, widget.getLastPosition())


if __name__ == '__main__':
    app = model.Application(SearchExplorer)
    app.MainLoop()