ยปCore Development>Code coverage>Tools/demo/ss1.py

Python code coverage for Tools/demo/ss1.py

#countcontent
1n/a#!/usr/bin/env python3
2n/a
3n/a"""
4n/aSS1 -- a spreadsheet-like application.
5n/a"""
6n/a
7n/aimport os
8n/aimport re
9n/aimport sys
10n/afrom xml.parsers import expat
11n/afrom xml.sax.saxutils import escape
12n/a
13n/aLEFT, CENTER, RIGHT = "LEFT", "CENTER", "RIGHT"
14n/a
15n/adef ljust(x, n):
16n/a return x.ljust(n)
17n/adef center(x, n):
18n/a return x.center(n)
19n/adef rjust(x, n):
20n/a return x.rjust(n)
21n/aalign2action = {LEFT: ljust, CENTER: center, RIGHT: rjust}
22n/a
23n/aalign2xml = {LEFT: "left", CENTER: "center", RIGHT: "right"}
24n/axml2align = {"left": LEFT, "center": CENTER, "right": RIGHT}
25n/a
26n/aalign2anchor = {LEFT: "w", CENTER: "center", RIGHT: "e"}
27n/a
28n/adef sum(seq):
29n/a total = 0
30n/a for x in seq:
31n/a if x is not None:
32n/a total += x
33n/a return total
34n/a
35n/aclass Sheet:
36n/a
37n/a def __init__(self):
38n/a self.cells = {} # {(x, y): cell, ...}
39n/a self.ns = dict(
40n/a cell = self.cellvalue,
41n/a cells = self.multicellvalue,
42n/a sum = sum,
43n/a )
44n/a
45n/a def cellvalue(self, x, y):
46n/a cell = self.getcell(x, y)
47n/a if hasattr(cell, 'recalc'):
48n/a return cell.recalc(self.ns)
49n/a else:
50n/a return cell
51n/a
52n/a def multicellvalue(self, x1, y1, x2, y2):
53n/a if x1 > x2:
54n/a x1, x2 = x2, x1
55n/a if y1 > y2:
56n/a y1, y2 = y2, y1
57n/a seq = []
58n/a for y in range(y1, y2+1):
59n/a for x in range(x1, x2+1):
60n/a seq.append(self.cellvalue(x, y))
61n/a return seq
62n/a
63n/a def getcell(self, x, y):
64n/a return self.cells.get((x, y))
65n/a
66n/a def setcell(self, x, y, cell):
67n/a assert x > 0 and y > 0
68n/a assert isinstance(cell, BaseCell)
69n/a self.cells[x, y] = cell
70n/a
71n/a def clearcell(self, x, y):
72n/a try:
73n/a del self.cells[x, y]
74n/a except KeyError:
75n/a pass
76n/a
77n/a def clearcells(self, x1, y1, x2, y2):
78n/a for xy in self.selectcells(x1, y1, x2, y2):
79n/a del self.cells[xy]
80n/a
81n/a def clearrows(self, y1, y2):
82n/a self.clearcells(0, y1, sys.maxsize, y2)
83n/a
84n/a def clearcolumns(self, x1, x2):
85n/a self.clearcells(x1, 0, x2, sys.maxsize)
86n/a
87n/a def selectcells(self, x1, y1, x2, y2):
88n/a if x1 > x2:
89n/a x1, x2 = x2, x1
90n/a if y1 > y2:
91n/a y1, y2 = y2, y1
92n/a return [(x, y) for x, y in self.cells
93n/a if x1 <= x <= x2 and y1 <= y <= y2]
94n/a
95n/a def movecells(self, x1, y1, x2, y2, dx, dy):
96n/a if dx == 0 and dy == 0:
97n/a return
98n/a if x1 > x2:
99n/a x1, x2 = x2, x1
100n/a if y1 > y2:
101n/a y1, y2 = y2, y1
102n/a assert x1+dx > 0 and y1+dy > 0
103n/a new = {}
104n/a for x, y in self.cells:
105n/a cell = self.cells[x, y]
106n/a if hasattr(cell, 'renumber'):
107n/a cell = cell.renumber(x1, y1, x2, y2, dx, dy)
108n/a if x1 <= x <= x2 and y1 <= y <= y2:
109n/a x += dx
110n/a y += dy
111n/a new[x, y] = cell
112n/a self.cells = new
113n/a
114n/a def insertrows(self, y, n):
115n/a assert n > 0
116n/a self.movecells(0, y, sys.maxsize, sys.maxsize, 0, n)
117n/a
118n/a def deleterows(self, y1, y2):
119n/a if y1 > y2:
120n/a y1, y2 = y2, y1
121n/a self.clearrows(y1, y2)
122n/a self.movecells(0, y2+1, sys.maxsize, sys.maxsize, 0, y1-y2-1)
123n/a
124n/a def insertcolumns(self, x, n):
125n/a assert n > 0
126n/a self.movecells(x, 0, sys.maxsize, sys.maxsize, n, 0)
127n/a
128n/a def deletecolumns(self, x1, x2):
129n/a if x1 > x2:
130n/a x1, x2 = x2, x1
131n/a self.clearcells(x1, x2)
132n/a self.movecells(x2+1, 0, sys.maxsize, sys.maxsize, x1-x2-1, 0)
133n/a
134n/a def getsize(self):
135n/a maxx = maxy = 0
136n/a for x, y in self.cells:
137n/a maxx = max(maxx, x)
138n/a maxy = max(maxy, y)
139n/a return maxx, maxy
140n/a
141n/a def reset(self):
142n/a for cell in self.cells.values():
143n/a if hasattr(cell, 'reset'):
144n/a cell.reset()
145n/a
146n/a def recalc(self):
147n/a self.reset()
148n/a for cell in self.cells.values():
149n/a if hasattr(cell, 'recalc'):
150n/a cell.recalc(self.ns)
151n/a
152n/a def display(self):
153n/a maxx, maxy = self.getsize()
154n/a width, height = maxx+1, maxy+1
155n/a colwidth = [1] * width
156n/a full = {}
157n/a # Add column heading labels in row 0
158n/a for x in range(1, width):
159n/a full[x, 0] = text, alignment = colnum2name(x), RIGHT
160n/a colwidth[x] = max(colwidth[x], len(text))
161n/a # Add row labels in column 0
162n/a for y in range(1, height):
163n/a full[0, y] = text, alignment = str(y), RIGHT
164n/a colwidth[0] = max(colwidth[0], len(text))
165n/a # Add sheet cells in columns with x>0 and y>0
166n/a for (x, y), cell in self.cells.items():
167n/a if x <= 0 or y <= 0:
168n/a continue
169n/a if hasattr(cell, 'recalc'):
170n/a cell.recalc(self.ns)
171n/a if hasattr(cell, 'format'):
172n/a text, alignment = cell.format()
173n/a assert isinstance(text, str)
174n/a assert alignment in (LEFT, CENTER, RIGHT)
175n/a else:
176n/a text = str(cell)
177n/a if isinstance(cell, str):
178n/a alignment = LEFT
179n/a else:
180n/a alignment = RIGHT
181n/a full[x, y] = (text, alignment)
182n/a colwidth[x] = max(colwidth[x], len(text))
183n/a # Calculate the horizontal separator line (dashes and dots)
184n/a sep = ""
185n/a for x in range(width):
186n/a if sep:
187n/a sep += "+"
188n/a sep += "-"*colwidth[x]
189n/a # Now print The full grid
190n/a for y in range(height):
191n/a line = ""
192n/a for x in range(width):
193n/a text, alignment = full.get((x, y)) or ("", LEFT)
194n/a text = align2action[alignment](text, colwidth[x])
195n/a if line:
196n/a line += '|'
197n/a line += text
198n/a print(line)
199n/a if y == 0:
200n/a print(sep)
201n/a
202n/a def xml(self):
203n/a out = ['<spreadsheet>']
204n/a for (x, y), cell in self.cells.items():
205n/a if hasattr(cell, 'xml'):
206n/a cellxml = cell.xml()
207n/a else:
208n/a cellxml = '<value>%s</value>' % escape(cell)
209n/a out.append('<cell row="%s" col="%s">\n %s\n</cell>' %
210n/a (y, x, cellxml))
211n/a out.append('</spreadsheet>')
212n/a return '\n'.join(out)
213n/a
214n/a def save(self, filename):
215n/a text = self.xml()
216n/a with open(filename, "w", encoding='utf-8') as f:
217n/a f.write(text)
218n/a if text and not text.endswith('\n'):
219n/a f.write('\n')
220n/a
221n/a def load(self, filename):
222n/a with open(filename, 'rb') as f:
223n/a SheetParser(self).parsefile(f)
224n/a
225n/aclass SheetParser:
226n/a
227n/a def __init__(self, sheet):
228n/a self.sheet = sheet
229n/a
230n/a def parsefile(self, f):
231n/a parser = expat.ParserCreate()
232n/a parser.StartElementHandler = self.startelement
233n/a parser.EndElementHandler = self.endelement
234n/a parser.CharacterDataHandler = self.data
235n/a parser.ParseFile(f)
236n/a
237n/a def startelement(self, tag, attrs):
238n/a method = getattr(self, 'start_'+tag, None)
239n/a if method:
240n/a method(attrs)
241n/a self.texts = []
242n/a
243n/a def data(self, text):
244n/a self.texts.append(text)
245n/a
246n/a def endelement(self, tag):
247n/a method = getattr(self, 'end_'+tag, None)
248n/a if method:
249n/a method("".join(self.texts))
250n/a
251n/a def start_cell(self, attrs):
252n/a self.y = int(attrs.get("row"))
253n/a self.x = int(attrs.get("col"))
254n/a
255n/a def start_value(self, attrs):
256n/a self.fmt = attrs.get('format')
257n/a self.alignment = xml2align.get(attrs.get('align'))
258n/a
259n/a start_formula = start_value
260n/a
261n/a def end_int(self, text):
262n/a try:
263n/a self.value = int(text)
264n/a except (TypeError, ValueError):
265n/a self.value = None
266n/a
267n/a end_long = end_int
268n/a
269n/a def end_double(self, text):
270n/a try:
271n/a self.value = float(text)
272n/a except (TypeError, ValueError):
273n/a self.value = None
274n/a
275n/a def end_complex(self, text):
276n/a try:
277n/a self.value = complex(text)
278n/a except (TypeError, ValueError):
279n/a self.value = None
280n/a
281n/a def end_string(self, text):
282n/a self.value = text
283n/a
284n/a def end_value(self, text):
285n/a if isinstance(self.value, BaseCell):
286n/a self.cell = self.value
287n/a elif isinstance(self.value, str):
288n/a self.cell = StringCell(self.value,
289n/a self.fmt or "%s",
290n/a self.alignment or LEFT)
291n/a else:
292n/a self.cell = NumericCell(self.value,
293n/a self.fmt or "%s",
294n/a self.alignment or RIGHT)
295n/a
296n/a def end_formula(self, text):
297n/a self.cell = FormulaCell(text,
298n/a self.fmt or "%s",
299n/a self.alignment or RIGHT)
300n/a
301n/a def end_cell(self, text):
302n/a self.sheet.setcell(self.x, self.y, self.cell)
303n/a
304n/aclass BaseCell:
305n/a __init__ = None # Must provide
306n/a """Abstract base class for sheet cells.
307n/a
308n/a Subclasses may but needn't provide the following APIs:
309n/a
310n/a cell.reset() -- prepare for recalculation
311n/a cell.recalc(ns) -> value -- recalculate formula
312n/a cell.format() -> (value, alignment) -- return formatted value
313n/a cell.xml() -> string -- return XML
314n/a """
315n/a
316n/aclass NumericCell(BaseCell):
317n/a
318n/a def __init__(self, value, fmt="%s", alignment=RIGHT):
319n/a assert isinstance(value, (int, float, complex))
320n/a assert alignment in (LEFT, CENTER, RIGHT)
321n/a self.value = value
322n/a self.fmt = fmt
323n/a self.alignment = alignment
324n/a
325n/a def recalc(self, ns):
326n/a return self.value
327n/a
328n/a def format(self):
329n/a try:
330n/a text = self.fmt % self.value
331n/a except:
332n/a text = str(self.value)
333n/a return text, self.alignment
334n/a
335n/a def xml(self):
336n/a method = getattr(self, '_xml_' + type(self.value).__name__)
337n/a return '<value align="%s" format="%s">%s</value>' % (
338n/a align2xml[self.alignment],
339n/a self.fmt,
340n/a method())
341n/a
342n/a def _xml_int(self):
343n/a if -2**31 <= self.value < 2**31:
344n/a return '<int>%s</int>' % self.value
345n/a else:
346n/a return '<long>%s</long>' % self.value
347n/a
348n/a def _xml_float(self):
349n/a return '<double>%r</double>' % self.value
350n/a
351n/a def _xml_complex(self):
352n/a return '<complex>%r</complex>' % self.value
353n/a
354n/aclass StringCell(BaseCell):
355n/a
356n/a def __init__(self, text, fmt="%s", alignment=LEFT):
357n/a assert isinstance(text, str)
358n/a assert alignment in (LEFT, CENTER, RIGHT)
359n/a self.text = text
360n/a self.fmt = fmt
361n/a self.alignment = alignment
362n/a
363n/a def recalc(self, ns):
364n/a return self.text
365n/a
366n/a def format(self):
367n/a return self.text, self.alignment
368n/a
369n/a def xml(self):
370n/a s = '<value align="%s" format="%s"><string>%s</string></value>'
371n/a return s % (
372n/a align2xml[self.alignment],
373n/a self.fmt,
374n/a escape(self.text))
375n/a
376n/aclass FormulaCell(BaseCell):
377n/a
378n/a def __init__(self, formula, fmt="%s", alignment=RIGHT):
379n/a assert alignment in (LEFT, CENTER, RIGHT)
380n/a self.formula = formula
381n/a self.translated = translate(self.formula)
382n/a self.fmt = fmt
383n/a self.alignment = alignment
384n/a self.reset()
385n/a
386n/a def reset(self):
387n/a self.value = None
388n/a
389n/a def recalc(self, ns):
390n/a if self.value is None:
391n/a try:
392n/a self.value = eval(self.translated, ns)
393n/a except:
394n/a exc = sys.exc_info()[0]
395n/a if hasattr(exc, "__name__"):
396n/a self.value = exc.__name__
397n/a else:
398n/a self.value = str(exc)
399n/a return self.value
400n/a
401n/a def format(self):
402n/a try:
403n/a text = self.fmt % self.value
404n/a except:
405n/a text = str(self.value)
406n/a return text, self.alignment
407n/a
408n/a def xml(self):
409n/a return '<formula align="%s" format="%s">%s</formula>' % (
410n/a align2xml[self.alignment],
411n/a self.fmt,
412n/a escape(self.formula))
413n/a
414n/a def renumber(self, x1, y1, x2, y2, dx, dy):
415n/a out = []
416n/a for part in re.split(r'(\w+)', self.formula):
417n/a m = re.match('^([A-Z]+)([1-9][0-9]*)$', part)
418n/a if m is not None:
419n/a sx, sy = m.groups()
420n/a x = colname2num(sx)
421n/a y = int(sy)
422n/a if x1 <= x <= x2 and y1 <= y <= y2:
423n/a part = cellname(x+dx, y+dy)
424n/a out.append(part)
425n/a return FormulaCell("".join(out), self.fmt, self.alignment)
426n/a
427n/adef translate(formula):
428n/a """Translate a formula containing fancy cell names to valid Python code.
429n/a
430n/a Examples:
431n/a B4 -> cell(2, 4)
432n/a B4:Z100 -> cells(2, 4, 26, 100)
433n/a """
434n/a out = []
435n/a for part in re.split(r"(\w+(?::\w+)?)", formula):
436n/a m = re.match(r"^([A-Z]+)([1-9][0-9]*)(?::([A-Z]+)([1-9][0-9]*))?$", part)
437n/a if m is None:
438n/a out.append(part)
439n/a else:
440n/a x1, y1, x2, y2 = m.groups()
441n/a x1 = colname2num(x1)
442n/a if x2 is None:
443n/a s = "cell(%s, %s)" % (x1, y1)
444n/a else:
445n/a x2 = colname2num(x2)
446n/a s = "cells(%s, %s, %s, %s)" % (x1, y1, x2, y2)
447n/a out.append(s)
448n/a return "".join(out)
449n/a
450n/adef cellname(x, y):
451n/a "Translate a cell coordinate to a fancy cell name (e.g. (1, 1)->'A1')."
452n/a assert x > 0 # Column 0 has an empty name, so can't use that
453n/a return colnum2name(x) + str(y)
454n/a
455n/adef colname2num(s):
456n/a "Translate a column name to number (e.g. 'A'->1, 'Z'->26, 'AA'->27)."
457n/a s = s.upper()
458n/a n = 0
459n/a for c in s:
460n/a assert 'A' <= c <= 'Z'
461n/a n = n*26 + ord(c) - ord('A') + 1
462n/a return n
463n/a
464n/adef colnum2name(n):
465n/a "Translate a column number to name (e.g. 1->'A', etc.)."
466n/a assert n > 0
467n/a s = ""
468n/a while n:
469n/a n, m = divmod(n-1, 26)
470n/a s = chr(m+ord('A')) + s
471n/a return s
472n/a
473n/aimport tkinter as Tk
474n/a
475n/aclass SheetGUI:
476n/a
477n/a """Beginnings of a GUI for a spreadsheet.
478n/a
479n/a TO DO:
480n/a - clear multiple cells
481n/a - Insert, clear, remove rows or columns
482n/a - Show new contents while typing
483n/a - Scroll bars
484n/a - Grow grid when window is grown
485n/a - Proper menus
486n/a - Undo, redo
487n/a - Cut, copy and paste
488n/a - Formatting and alignment
489n/a """
490n/a
491n/a def __init__(self, filename="sheet1.xml", rows=10, columns=5):
492n/a """Constructor.
493n/a
494n/a Load the sheet from the filename argument.
495n/a Set up the Tk widget tree.
496n/a """
497n/a # Create and load the sheet
498n/a self.filename = filename
499n/a self.sheet = Sheet()
500n/a if os.path.isfile(filename):
501n/a self.sheet.load(filename)
502n/a # Calculate the needed grid size
503n/a maxx, maxy = self.sheet.getsize()
504n/a rows = max(rows, maxy)
505n/a columns = max(columns, maxx)
506n/a # Create the widgets
507n/a self.root = Tk.Tk()
508n/a self.root.wm_title("Spreadsheet: %s" % self.filename)
509n/a self.beacon = Tk.Label(self.root, text="A1",
510n/a font=('helvetica', 16, 'bold'))
511n/a self.entry = Tk.Entry(self.root)
512n/a self.savebutton = Tk.Button(self.root, text="Save",
513n/a command=self.save)
514n/a self.cellgrid = Tk.Frame(self.root)
515n/a # Configure the widget lay-out
516n/a self.cellgrid.pack(side="bottom", expand=1, fill="both")
517n/a self.beacon.pack(side="left")
518n/a self.savebutton.pack(side="right")
519n/a self.entry.pack(side="left", expand=1, fill="x")
520n/a # Bind some events
521n/a self.entry.bind("<Return>", self.return_event)
522n/a self.entry.bind("<Shift-Return>", self.shift_return_event)
523n/a self.entry.bind("<Tab>", self.tab_event)
524n/a self.entry.bind("<Shift-Tab>", self.shift_tab_event)
525n/a self.entry.bind("<Delete>", self.delete_event)
526n/a self.entry.bind("<Escape>", self.escape_event)
527n/a # Now create the cell grid
528n/a self.makegrid(rows, columns)
529n/a # Select the top-left cell
530n/a self.currentxy = None
531n/a self.cornerxy = None
532n/a self.setcurrent(1, 1)
533n/a # Copy the sheet cells to the GUI cells
534n/a self.sync()
535n/a
536n/a def delete_event(self, event):
537n/a if self.cornerxy != self.currentxy and self.cornerxy is not None:
538n/a self.sheet.clearcells(*(self.currentxy + self.cornerxy))
539n/a else:
540n/a self.sheet.clearcell(*self.currentxy)
541n/a self.sync()
542n/a self.entry.delete(0, 'end')
543n/a return "break"
544n/a
545n/a def escape_event(self, event):
546n/a x, y = self.currentxy
547n/a self.load_entry(x, y)
548n/a
549n/a def load_entry(self, x, y):
550n/a cell = self.sheet.getcell(x, y)
551n/a if cell is None:
552n/a text = ""
553n/a elif isinstance(cell, FormulaCell):
554n/a text = '=' + cell.formula
555n/a else:
556n/a text, alignment = cell.format()
557n/a self.entry.delete(0, 'end')
558n/a self.entry.insert(0, text)
559n/a self.entry.selection_range(0, 'end')
560n/a
561n/a def makegrid(self, rows, columns):
562n/a """Helper to create the grid of GUI cells.
563n/a
564n/a The edge (x==0 or y==0) is filled with labels; the rest is real cells.
565n/a """
566n/a self.rows = rows
567n/a self.columns = columns
568n/a self.gridcells = {}
569n/a # Create the top left corner cell (which selects all)
570n/a cell = Tk.Label(self.cellgrid, relief='raised')
571n/a cell.grid_configure(column=0, row=0, sticky='NSWE')
572n/a cell.bind("<ButtonPress-1>", self.selectall)
573n/a # Create the top row of labels, and configure the grid columns
574n/a for x in range(1, columns+1):
575n/a self.cellgrid.grid_columnconfigure(x, minsize=64)
576n/a cell = Tk.Label(self.cellgrid, text=colnum2name(x), relief='raised')
577n/a cell.grid_configure(column=x, row=0, sticky='WE')
578n/a self.gridcells[x, 0] = cell
579n/a cell.__x = x
580n/a cell.__y = 0
581n/a cell.bind("<ButtonPress-1>", self.selectcolumn)
582n/a cell.bind("<B1-Motion>", self.extendcolumn)
583n/a cell.bind("<ButtonRelease-1>", self.extendcolumn)
584n/a cell.bind("<Shift-Button-1>", self.extendcolumn)
585n/a # Create the leftmost column of labels
586n/a for y in range(1, rows+1):
587n/a cell = Tk.Label(self.cellgrid, text=str(y), relief='raised')
588n/a cell.grid_configure(column=0, row=y, sticky='WE')
589n/a self.gridcells[0, y] = cell
590n/a cell.__x = 0
591n/a cell.__y = y
592n/a cell.bind("<ButtonPress-1>", self.selectrow)
593n/a cell.bind("<B1-Motion>", self.extendrow)
594n/a cell.bind("<ButtonRelease-1>", self.extendrow)
595n/a cell.bind("<Shift-Button-1>", self.extendrow)
596n/a # Create the real cells
597n/a for x in range(1, columns+1):
598n/a for y in range(1, rows+1):
599n/a cell = Tk.Label(self.cellgrid, relief='sunken',
600n/a bg='white', fg='black')
601n/a cell.grid_configure(column=x, row=y, sticky='NSWE')
602n/a self.gridcells[x, y] = cell
603n/a cell.__x = x
604n/a cell.__y = y
605n/a # Bind mouse events
606n/a cell.bind("<ButtonPress-1>", self.press)
607n/a cell.bind("<B1-Motion>", self.motion)
608n/a cell.bind("<ButtonRelease-1>", self.release)
609n/a cell.bind("<Shift-Button-1>", self.release)
610n/a
611n/a def selectall(self, event):
612n/a self.setcurrent(1, 1)
613n/a self.setcorner(sys.maxsize, sys.maxsize)
614n/a
615n/a def selectcolumn(self, event):
616n/a x, y = self.whichxy(event)
617n/a self.setcurrent(x, 1)
618n/a self.setcorner(x, sys.maxsize)
619n/a
620n/a def extendcolumn(self, event):
621n/a x, y = self.whichxy(event)
622n/a if x > 0:
623n/a self.setcurrent(self.currentxy[0], 1)
624n/a self.setcorner(x, sys.maxsize)
625n/a
626n/a def selectrow(self, event):
627n/a x, y = self.whichxy(event)
628n/a self.setcurrent(1, y)
629n/a self.setcorner(sys.maxsize, y)
630n/a
631n/a def extendrow(self, event):
632n/a x, y = self.whichxy(event)
633n/a if y > 0:
634n/a self.setcurrent(1, self.currentxy[1])
635n/a self.setcorner(sys.maxsize, y)
636n/a
637n/a def press(self, event):
638n/a x, y = self.whichxy(event)
639n/a if x > 0 and y > 0:
640n/a self.setcurrent(x, y)
641n/a
642n/a def motion(self, event):
643n/a x, y = self.whichxy(event)
644n/a if x > 0 and y > 0:
645n/a self.setcorner(x, y)
646n/a
647n/a release = motion
648n/a
649n/a def whichxy(self, event):
650n/a w = self.cellgrid.winfo_containing(event.x_root, event.y_root)
651n/a if w is not None and isinstance(w, Tk.Label):
652n/a try:
653n/a return w.__x, w.__y
654n/a except AttributeError:
655n/a pass
656n/a return 0, 0
657n/a
658n/a def save(self):
659n/a self.sheet.save(self.filename)
660n/a
661n/a def setcurrent(self, x, y):
662n/a "Make (x, y) the current cell."
663n/a if self.currentxy is not None:
664n/a self.change_cell()
665n/a self.clearfocus()
666n/a self.beacon['text'] = cellname(x, y)
667n/a self.load_entry(x, y)
668n/a self.entry.focus_set()
669n/a self.currentxy = x, y
670n/a self.cornerxy = None
671n/a gridcell = self.gridcells.get(self.currentxy)
672n/a if gridcell is not None:
673n/a gridcell['bg'] = 'yellow'
674n/a
675n/a def setcorner(self, x, y):
676n/a if self.currentxy is None or self.currentxy == (x, y):
677n/a self.setcurrent(x, y)
678n/a return
679n/a self.clearfocus()
680n/a self.cornerxy = x, y
681n/a x1, y1 = self.currentxy
682n/a x2, y2 = self.cornerxy or self.currentxy
683n/a if x1 > x2:
684n/a x1, x2 = x2, x1
685n/a if y1 > y2:
686n/a y1, y2 = y2, y1
687n/a for (x, y), cell in self.gridcells.items():
688n/a if x1 <= x <= x2 and y1 <= y <= y2:
689n/a cell['bg'] = 'lightBlue'
690n/a gridcell = self.gridcells.get(self.currentxy)
691n/a if gridcell is not None:
692n/a gridcell['bg'] = 'yellow'
693n/a self.setbeacon(x1, y1, x2, y2)
694n/a
695n/a def setbeacon(self, x1, y1, x2, y2):
696n/a if x1 == y1 == 1 and x2 == y2 == sys.maxsize:
697n/a name = ":"
698n/a elif (x1, x2) == (1, sys.maxsize):
699n/a if y1 == y2:
700n/a name = "%d" % y1
701n/a else:
702n/a name = "%d:%d" % (y1, y2)
703n/a elif (y1, y2) == (1, sys.maxsize):
704n/a if x1 == x2:
705n/a name = "%s" % colnum2name(x1)
706n/a else:
707n/a name = "%s:%s" % (colnum2name(x1), colnum2name(x2))
708n/a else:
709n/a name1 = cellname(*self.currentxy)
710n/a name2 = cellname(*self.cornerxy)
711n/a name = "%s:%s" % (name1, name2)
712n/a self.beacon['text'] = name
713n/a
714n/a
715n/a def clearfocus(self):
716n/a if self.currentxy is not None:
717n/a x1, y1 = self.currentxy
718n/a x2, y2 = self.cornerxy or self.currentxy
719n/a if x1 > x2:
720n/a x1, x2 = x2, x1
721n/a if y1 > y2:
722n/a y1, y2 = y2, y1
723n/a for (x, y), cell in self.gridcells.items():
724n/a if x1 <= x <= x2 and y1 <= y <= y2:
725n/a cell['bg'] = 'white'
726n/a
727n/a def return_event(self, event):
728n/a "Callback for the Return key."
729n/a self.change_cell()
730n/a x, y = self.currentxy
731n/a self.setcurrent(x, y+1)
732n/a return "break"
733n/a
734n/a def shift_return_event(self, event):
735n/a "Callback for the Return key with Shift modifier."
736n/a self.change_cell()
737n/a x, y = self.currentxy
738n/a self.setcurrent(x, max(1, y-1))
739n/a return "break"
740n/a
741n/a def tab_event(self, event):
742n/a "Callback for the Tab key."
743n/a self.change_cell()
744n/a x, y = self.currentxy
745n/a self.setcurrent(x+1, y)
746n/a return "break"
747n/a
748n/a def shift_tab_event(self, event):
749n/a "Callback for the Tab key with Shift modifier."
750n/a self.change_cell()
751n/a x, y = self.currentxy
752n/a self.setcurrent(max(1, x-1), y)
753n/a return "break"
754n/a
755n/a def change_cell(self):
756n/a "Set the current cell from the entry widget."
757n/a x, y = self.currentxy
758n/a text = self.entry.get()
759n/a cell = None
760n/a if text.startswith('='):
761n/a cell = FormulaCell(text[1:])
762n/a else:
763n/a for cls in int, float, complex:
764n/a try:
765n/a value = cls(text)
766n/a except (TypeError, ValueError):
767n/a continue
768n/a else:
769n/a cell = NumericCell(value)
770n/a break
771n/a if cell is None and text:
772n/a cell = StringCell(text)
773n/a if cell is None:
774n/a self.sheet.clearcell(x, y)
775n/a else:
776n/a self.sheet.setcell(x, y, cell)
777n/a self.sync()
778n/a
779n/a def sync(self):
780n/a "Fill the GUI cells from the sheet cells."
781n/a self.sheet.recalc()
782n/a for (x, y), gridcell in self.gridcells.items():
783n/a if x == 0 or y == 0:
784n/a continue
785n/a cell = self.sheet.getcell(x, y)
786n/a if cell is None:
787n/a gridcell['text'] = ""
788n/a else:
789n/a if hasattr(cell, 'format'):
790n/a text, alignment = cell.format()
791n/a else:
792n/a text, alignment = str(cell), LEFT
793n/a gridcell['text'] = text
794n/a gridcell['anchor'] = align2anchor[alignment]
795n/a
796n/a
797n/adef test_basic():
798n/a "Basic non-gui self-test."
799n/a a = Sheet()
800n/a for x in range(1, 11):
801n/a for y in range(1, 11):
802n/a if x == 1:
803n/a cell = NumericCell(y)
804n/a elif y == 1:
805n/a cell = NumericCell(x)
806n/a else:
807n/a c1 = cellname(x, 1)
808n/a c2 = cellname(1, y)
809n/a formula = "%s*%s" % (c1, c2)
810n/a cell = FormulaCell(formula)
811n/a a.setcell(x, y, cell)
812n/a## if os.path.isfile("sheet1.xml"):
813n/a## print "Loading from sheet1.xml"
814n/a## a.load("sheet1.xml")
815n/a a.display()
816n/a a.save("sheet1.xml")
817n/a
818n/adef test_gui():
819n/a "GUI test."
820n/a if sys.argv[1:]:
821n/a filename = sys.argv[1]
822n/a else:
823n/a filename = "sheet1.xml"
824n/a g = SheetGUI(filename)
825n/a g.root.mainloop()
826n/a
827n/aif __name__ == '__main__':
828n/a #test_basic()
829n/a test_gui()