dialogs.py 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849
  1. """
  2. @package dbmgr.dialogs
  3. @brief DBM-related dialogs
  4. List of classes:
  5. - dialogs::DisplayAttributesDialog
  6. - dialogs::ModifyTableRecord
  7. - dialogs::AddColumnDialog
  8. (C) 2007-2013 by the GRASS Development Team
  9. This program is free software under the GNU General Public License
  10. (>=v2). Read the file COPYING that comes with GRASS for details.
  11. @author Martin Landa <landa.martin gmail.com>
  12. @author Refactoring by Stepan Turek <stepan.turek seznam.cz> (GSoC 2012, mentor: Martin Landa)
  13. """
  14. import os
  15. import types
  16. import six
  17. from core import globalvar
  18. import wx
  19. import wx.lib.scrolledpanel as scrolled
  20. from core.gcmd import RunCommand, GError
  21. from core.debug import Debug
  22. from core.settings import UserSettings
  23. from dbmgr.vinfo import VectorDBInfo, GetUnicodeValue
  24. from gui_core.widgets import IntegerValidator, FloatValidator
  25. from gui_core.wrap import SpinCtrl, Button, StaticText, StaticBox, \
  26. TextCtrl
  27. class DisplayAttributesDialog(wx.Dialog):
  28. def __init__(self, parent, map,
  29. query=None, cats=None, line=None,
  30. style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER,
  31. pos=wx.DefaultPosition,
  32. action="add", ignoreError=False):
  33. """Standard dialog used to add/update/display attributes linked
  34. to the vector map.
  35. Attribute data can be selected based on layer and category number
  36. or coordinates.
  37. :param parent:
  38. :param map: vector map
  39. :param query: query coordinates and distance (used for v.edit)
  40. :param cats: {layer: cats}
  41. :param line: feature id (requested for cats)
  42. :param style:
  43. :param pos:
  44. :param action: (add, update, display)
  45. :param ignoreError: True to ignore errors
  46. """
  47. self.parent = parent # mapdisplay.BufferedWindow
  48. self.map = map
  49. self.action = action
  50. # ids/cats of selected features
  51. # fid : {layer : cats}
  52. self.cats = {}
  53. self.fid = -1 # feature id
  54. # get layer/table/column information
  55. self.mapDBInfo = VectorDBInfo(self.map)
  56. layers = self.mapDBInfo.layers.keys() # get available layers
  57. # check if db connection / layer exists
  58. if len(layers) <= 0:
  59. if not ignoreError:
  60. dlg = wx.MessageDialog(
  61. parent=self.parent,
  62. message=_(
  63. "No attribute table found.\n\n"
  64. "Do you want to create a new attribute table "
  65. "and defined a link to vector map <%s>?") %
  66. self.map,
  67. caption=_("Create table?"),
  68. style=wx.YES_NO | wx.NO_DEFAULT | wx.ICON_QUESTION)
  69. if dlg.ShowModal() == wx.ID_YES:
  70. lmgr = self.parent.lmgr
  71. lmgr.OnShowAttributeTable(event=None, selection='layers')
  72. dlg.Destroy()
  73. self.mapDBInfo = None
  74. wx.Dialog.__init__(self, parent=self.parent, id=wx.ID_ANY,
  75. title="", style=style, pos=pos)
  76. # dialog body
  77. mainSizer = wx.BoxSizer(wx.VERTICAL)
  78. # notebook
  79. self.notebook = wx.Notebook(
  80. parent=self, id=wx.ID_ANY, style=wx.BK_DEFAULT)
  81. self.closeDialog = wx.CheckBox(parent=self, id=wx.ID_ANY,
  82. label=_("Close dialog on submit"))
  83. self.closeDialog.SetValue(True)
  84. if self.action == 'display':
  85. self.closeDialog.Enable(False)
  86. # feature id (text/choice for duplicates)
  87. self.fidMulti = wx.Choice(parent=self, id=wx.ID_ANY,
  88. size=(150, -1))
  89. self.fidMulti.Bind(wx.EVT_CHOICE, self.OnFeature)
  90. self.fidText = StaticText(parent=self, id=wx.ID_ANY)
  91. self.noFoundMsg = StaticText(parent=self, id=wx.ID_ANY,
  92. label=_("No attributes found"))
  93. self.UpdateDialog(query=query, cats=cats)
  94. # set title
  95. if self.action == "update":
  96. self.SetTitle(_("Update attributes"))
  97. elif self.action == "add":
  98. self.SetTitle(_("Define attributes"))
  99. else:
  100. self.SetTitle(_("Display attributes"))
  101. # buttons
  102. btnCancel = Button(self, wx.ID_CANCEL)
  103. btnReset = Button(self, wx.ID_UNDO, _("&Reload"))
  104. btnSubmit = Button(self, wx.ID_OK, _("&Submit"))
  105. if self.action == 'display':
  106. btnSubmit.Enable(False)
  107. btnSizer = wx.StdDialogButtonSizer()
  108. btnSizer.AddButton(btnCancel)
  109. btnSizer.AddButton(btnReset)
  110. btnSizer.SetNegativeButton(btnReset)
  111. btnSubmit.SetDefault()
  112. btnSizer.AddButton(btnSubmit)
  113. btnSizer.Realize()
  114. mainSizer.Add(self.noFoundMsg, proportion=0,
  115. flag=wx.EXPAND | wx.ALL, border=5)
  116. mainSizer.Add(self.notebook, proportion=1,
  117. flag=wx.EXPAND | wx.ALL, border=5)
  118. fidSizer = wx.BoxSizer(wx.HORIZONTAL)
  119. fidSizer.Add(StaticText(parent=self, id=wx.ID_ANY,
  120. label=_("Feature id:")),
  121. proportion=0, border=5,
  122. flag=wx.ALIGN_CENTER_VERTICAL)
  123. fidSizer.Add(self.fidMulti, proportion=0,
  124. flag=wx.EXPAND | wx.ALL, border=5)
  125. fidSizer.Add(self.fidText, proportion=0,
  126. flag=wx.EXPAND | wx.ALL, border=5)
  127. mainSizer.Add(fidSizer, proportion=0,
  128. flag=wx.EXPAND | wx.LEFT | wx.RIGHT, border=5)
  129. mainSizer.Add(
  130. self.closeDialog,
  131. proportion=0,
  132. flag=wx.EXPAND | wx.LEFT | wx.RIGHT,
  133. border=5)
  134. mainSizer.Add(btnSizer, proportion=0,
  135. flag=wx.EXPAND | wx.ALL | wx.ALIGN_CENTER, border=5)
  136. # bindigs
  137. btnReset.Bind(wx.EVT_BUTTON, self.OnReset)
  138. btnSubmit.Bind(wx.EVT_BUTTON, self.OnSubmit)
  139. btnCancel.Bind(wx.EVT_BUTTON, self.OnClose)
  140. self.Bind(wx.EVT_CLOSE, self.OnClose)
  141. self.SetSizer(mainSizer)
  142. mainSizer.Fit(self)
  143. # set min size for dialog
  144. w, h = self.GetBestSize()
  145. w += 50
  146. if h < 200:
  147. self.SetMinSize((w, 200))
  148. else:
  149. self.SetMinSize((w, h))
  150. if self.notebook.GetPageCount() == 0:
  151. Debug.msg(2, "DisplayAttributesDialog(): Nothing found!")
  152. ### self.mapDBInfo = None
  153. def OnSQLStatement(self, event):
  154. """Update SQL statement"""
  155. pass
  156. def IsFound(self):
  157. """Check for status
  158. :return: True on attributes found
  159. :return: False attributes not found
  160. """
  161. return bool(self.mapDBInfo and self.notebook.GetPageCount() > 0)
  162. def GetSQLString(self, updateValues=False):
  163. """Create SQL statement string based on self.sqlStatement
  164. Show error message when invalid values are entered.
  165. If updateValues is True, update dataFrame according to values
  166. in textfields.
  167. """
  168. sqlCommands = []
  169. # find updated values for each layer/category
  170. for layer in self.mapDBInfo.layers.keys(): # for each layer
  171. table = self.mapDBInfo.GetTable(layer)
  172. key = self.mapDBInfo.GetKeyColumn(layer)
  173. columns = self.mapDBInfo.GetTableDesc(table)
  174. for idx in range(len(columns[key]['values'])): # for each category
  175. updatedColumns = []
  176. updatedValues = []
  177. for name in columns.keys():
  178. if name == key:
  179. cat = columns[name]['values'][idx]
  180. continue
  181. ctype = columns[name]['ctype']
  182. value = columns[name]['values'][idx]
  183. id = columns[name]['ids'][idx]
  184. try:
  185. newvalue = self.FindWindowById(id).GetValue()
  186. except:
  187. newvalue = self.FindWindowById(id).GetLabel()
  188. if newvalue:
  189. try:
  190. if ctype == int:
  191. newvalue = int(newvalue)
  192. elif ctype == float:
  193. newvalue = float(newvalue)
  194. except ValueError:
  195. GError(
  196. parent=self,
  197. message=_("Column <%(col)s>: Value '%(value)s' needs to be entered as %(type)s.") % {
  198. 'col': name,
  199. 'value': str(newvalue),
  200. 'type': columns[name]['type'].lower()},
  201. showTraceback=False)
  202. sqlCommands.append(None)
  203. continue
  204. else:
  205. if self.action == 'add':
  206. continue
  207. if newvalue != value:
  208. updatedColumns.append(name)
  209. if not newvalue:
  210. updatedValues.append('NULL')
  211. else:
  212. if ctype != str:
  213. updatedValues.append(str(newvalue))
  214. else:
  215. updatedValues.append("'" + newvalue.replace("'", "''") + "'")
  216. columns[name]['values'][idx] = newvalue
  217. if self.action != "add" and len(updatedValues) == 0:
  218. continue
  219. if self.action == "add":
  220. sqlString = "INSERT INTO %s (%s," % (table, key)
  221. else:
  222. sqlString = "UPDATE %s SET " % table
  223. for idx in range(len(updatedColumns)):
  224. name = updatedColumns[idx]
  225. if self.action == "add":
  226. sqlString += name + ","
  227. else:
  228. sqlString += name + "=" + updatedValues[idx] + ","
  229. sqlString = sqlString[:-1] # remove last comma
  230. if self.action == "add":
  231. sqlString += ") VALUES (%s," % cat
  232. for value in updatedValues:
  233. sqlString += value + ","
  234. sqlString = sqlString[:-1] # remove last comma
  235. sqlString += ")"
  236. else:
  237. sqlString += " WHERE %s=%s" % (key, cat)
  238. sqlCommands.append(sqlString)
  239. # for each category
  240. # for each layer END
  241. Debug.msg(
  242. 3, "DisplayAttributesDialog.GetSQLString(): %s" %
  243. sqlCommands)
  244. return sqlCommands
  245. def OnReset(self, event=None):
  246. """Reset form"""
  247. for layer in self.mapDBInfo.layers.keys():
  248. table = self.mapDBInfo.layers[layer]["table"]
  249. key = self.mapDBInfo.layers[layer]["key"]
  250. columns = self.mapDBInfo.tables[table]
  251. for idx in range(len(columns[key]['values'])):
  252. for name in columns.keys():
  253. type = columns[name]['type']
  254. value = columns[name]['values'][idx]
  255. if value is None:
  256. value = ''
  257. try:
  258. id = columns[name]['ids'][idx]
  259. except IndexError:
  260. id = wx.NOT_FOUND
  261. if name != key and id != wx.NOT_FOUND:
  262. self.FindWindowById(id).SetValue(str(value))
  263. def OnClose(self, event):
  264. """Closes dialog and removes query layer.
  265. """
  266. frame = self.parent.parent
  267. frame.dialogs['attributes'] = None
  268. if hasattr(self, "digit"):
  269. self.parent.digit.GetDisplay().SetSelected([])
  270. if frame.IsAutoRendered():
  271. self.parent.UpdateMap(render=False)
  272. elif frame.IsAutoRendered():
  273. frame.RemoveQueryLayer()
  274. self.parent.UpdateMap(render=True)
  275. if self.IsModal():
  276. self.EndModal(wx.ID_OK)
  277. else:
  278. self.Destroy()
  279. def OnSubmit(self, event):
  280. """Submit records"""
  281. layer = 1
  282. close = True
  283. enc = UserSettings.Get(group='atm', key='encoding', subkey='value')
  284. if not enc and 'GRASS_DB_ENCODING' in os.environ:
  285. enc = os.environ['GRASS_DB_ENCODING']
  286. for sql in self.GetSQLString(updateValues=True):
  287. if not sql:
  288. close = False
  289. continue
  290. if enc:
  291. sql = sql.encode(enc)
  292. else:
  293. sql = sql.encode('utf-8')
  294. driver, database = self.mapDBInfo.GetDbSettings(layer)
  295. Debug.msg(1, "SQL: %s" % sql)
  296. RunCommand('db.execute',
  297. parent=self,
  298. quiet=True,
  299. input='-',
  300. stdin=sql,
  301. driver=driver,
  302. database=database)
  303. layer += 1
  304. if close and self.closeDialog.IsChecked():
  305. self.OnClose(event)
  306. def OnFeature(self, event):
  307. self.fid = int(event.GetString())
  308. self.UpdateDialog(cats=self.cats, fid=self.fid)
  309. def GetCats(self):
  310. """Get id of selected vector object or 'None' if nothing selected
  311. :param id: if true return ids otherwise cats
  312. """
  313. if self.fid < 0:
  314. return None
  315. return self.cats[self.fid]
  316. def GetFid(self):
  317. """Get selected feature id"""
  318. return self.fid
  319. def UpdateDialog(self, map=None, query=None, cats=None, fid=-1,
  320. action=None):
  321. """Update dialog
  322. :param map: name of vector map
  323. :param query:
  324. :param cats:
  325. :param fid: feature id
  326. :param action: add, update, display or None
  327. :return: True if updated
  328. :return: False
  329. """
  330. if action:
  331. self.action = action
  332. if action == 'display':
  333. enabled = False
  334. else:
  335. enabled = True
  336. self.closeDialog.Enable(enabled)
  337. self.FindWindowById(wx.ID_OK).Enable(enabled)
  338. if map:
  339. self.map = map
  340. # get layer/table/column information
  341. self.mapDBInfo = VectorDBInfo(self.map)
  342. if not self.mapDBInfo:
  343. return False
  344. self.mapDBInfo.Reset()
  345. layers = self.mapDBInfo.layers.keys() # get available layers
  346. # id of selected line
  347. if query: # select by position
  348. data = self.mapDBInfo.SelectByPoint(query[0],
  349. query[1])
  350. self.cats = {}
  351. if data and 'Layer' in data:
  352. idx = 0
  353. for layer in data['Layer']:
  354. layer = int(layer)
  355. if data['Id'][idx] is not None:
  356. tfid = int(data['Id'][idx])
  357. else:
  358. tfid = 0 # Area / Volume
  359. if not tfid in self.cats:
  360. self.cats[tfid] = {}
  361. if not layer in self.cats[tfid]:
  362. self.cats[tfid][layer] = []
  363. cat = int(data['Category'][idx])
  364. self.cats[tfid][layer].append(cat)
  365. idx += 1
  366. else:
  367. self.cats = cats
  368. if fid > 0:
  369. self.fid = fid
  370. elif len(self.cats.keys()) > 0:
  371. self.fid = self.cats.keys()[0]
  372. else:
  373. self.fid = -1
  374. if len(self.cats.keys()) == 1:
  375. self.fidMulti.Show(False)
  376. self.fidText.Show(True)
  377. if self.fid > 0:
  378. self.fidText.SetLabel("%d" % self.fid)
  379. else:
  380. self.fidText.SetLabel(_("Unknown"))
  381. else:
  382. self.fidMulti.Show(True)
  383. self.fidText.Show(False)
  384. choices = []
  385. for tfid in self.cats.keys():
  386. choices.append(str(tfid))
  387. self.fidMulti.SetItems(choices)
  388. self.fidMulti.SetStringSelection(str(self.fid))
  389. # reset notebook
  390. self.notebook.DeleteAllPages()
  391. for layer in layers: # for each layer
  392. if not query: # select by layer/cat
  393. if self.fid > 0 and layer in self.cats[self.fid]:
  394. for cat in self.cats[self.fid][layer]:
  395. nselected = self.mapDBInfo.SelectFromTable(
  396. layer, where="%s=%d" %
  397. (self.mapDBInfo.layers[layer]['key'], cat))
  398. else:
  399. nselected = 0
  400. # if nselected <= 0 and self.action != "add":
  401. # continue # nothing selected ...
  402. if self.action == "add":
  403. if nselected <= 0:
  404. if layer in self.cats[self.fid]:
  405. table = self.mapDBInfo.layers[layer]["table"]
  406. key = self.mapDBInfo.layers[layer]["key"]
  407. columns = self.mapDBInfo.tables[table]
  408. for name in columns.keys():
  409. if name == key:
  410. for cat in self.cats[self.fid][layer]:
  411. self.mapDBInfo.tables[table][
  412. name]['values'].append(cat)
  413. else:
  414. self.mapDBInfo.tables[table][
  415. name]['values'].append(None)
  416. else: # change status 'add' -> 'update'
  417. self.action = "update"
  418. table = self.mapDBInfo.layers[layer]["table"]
  419. key = self.mapDBInfo.layers[layer]["key"]
  420. columns = self.mapDBInfo.tables[table]
  421. for idx in range(len(columns[key]['values'])):
  422. for name in columns.keys():
  423. if name == key:
  424. cat = int(columns[name]['values'][idx])
  425. break
  426. # use scrolled panel instead (and fix initial max height of the
  427. # window to 480px)
  428. panel = scrolled.ScrolledPanel(
  429. parent=self.notebook, id=wx.ID_ANY, size=(-1, 150))
  430. panel.SetupScrolling(scroll_x=False)
  431. self.notebook.AddPage(
  432. page=panel, text=" %s %d / %s %d" %
  433. (_("Layer"), layer, _("Category"), cat))
  434. # notebook body
  435. border = wx.BoxSizer(wx.VERTICAL)
  436. flexSizer = wx.FlexGridSizer(cols=3, hgap=3, vgap=3)
  437. flexSizer.AddGrowableCol(2)
  438. # columns (sorted by index)
  439. names = [''] * len(columns.keys())
  440. for name in columns.keys():
  441. names[columns[name]['index']] = name
  442. for name in names:
  443. if name == key: # skip key column (category)
  444. continue
  445. vtype = columns[name]['type'].lower()
  446. ctype = columns[name]['ctype']
  447. if columns[name]['values'][idx] is not None:
  448. if columns[name]['ctype'] != types.StringType:
  449. value = str(columns[name]['values'][idx])
  450. else:
  451. value = columns[name]['values'][idx]
  452. else:
  453. value = ''
  454. colName = StaticText(parent=panel, id=wx.ID_ANY,
  455. label=name)
  456. colType = StaticText(parent=panel, id=wx.ID_ANY,
  457. label="[%s]:" % vtype)
  458. colValue = TextCtrl(
  459. parent=panel, id=wx.ID_ANY, value=value)
  460. colValue.SetName(name)
  461. if ctype == int:
  462. colValue.SetValidator(IntegerValidator())
  463. elif ctype == float:
  464. colValue.SetValidator(FloatValidator())
  465. self.Bind(wx.EVT_TEXT, self.OnSQLStatement, colValue)
  466. if self.action == 'display':
  467. colValue.SetWindowStyle(wx.TE_READONLY)
  468. flexSizer.Add(colName, proportion=0,
  469. flag=wx.ALIGN_CENTER_VERTICAL)
  470. flexSizer.Add(
  471. colType,
  472. proportion=0,
  473. flag=wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_RIGHT)
  474. flexSizer.Add(colValue, proportion=1,
  475. flag=wx.EXPAND | wx.ALIGN_CENTER_VERTICAL)
  476. # add widget reference to self.columns
  477. columns[name]['ids'].append(
  478. colValue.GetId()) # name, type, values, id
  479. # for each attribute (including category) END
  480. border.Add(
  481. flexSizer,
  482. proportion=1,
  483. flag=wx.ALL | wx.EXPAND,
  484. border=5)
  485. panel.SetSizer(border)
  486. # for each category END
  487. # for each layer END
  488. if self.notebook.GetPageCount() == 0:
  489. self.noFoundMsg.Show(True)
  490. else:
  491. self.noFoundMsg.Show(False)
  492. self.Layout()
  493. return True
  494. def SetColumnValue(self, layer, column, value):
  495. """Set attrbute value
  496. :param column: column name
  497. :param value: value
  498. """
  499. table = self.mapDBInfo.GetTable(layer)
  500. columns = self.mapDBInfo.GetTableDesc(table)
  501. for key, col in six.iteritems(columns):
  502. if key == column:
  503. col['values'] = [col['ctype'](value), ]
  504. break
  505. class ModifyTableRecord(wx.Dialog):
  506. def __init__(self, parent, title, data, keyEditable=(-1, True),
  507. id=wx.ID_ANY, style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER):
  508. """Dialog for inserting/updating table record
  509. :param data: a list: [(column, value)]
  510. :param keyEditable: (id, editable?) indicates if textarea for
  511. key column is editable(True) or not
  512. """
  513. # parent -> VDigitWindow
  514. wx.Dialog.__init__(self, parent, id, title, style=style)
  515. self.CenterOnParent()
  516. self.keyId = keyEditable[0]
  517. box = StaticBox(parent=self, id=wx.ID_ANY)
  518. box.Hide()
  519. self.dataPanel = scrolled.ScrolledPanel(parent=self, id=wx.ID_ANY,
  520. style=wx.TAB_TRAVERSAL)
  521. self.dataPanel.SetupScrolling(scroll_x=False)
  522. # buttons
  523. self.btnCancel = Button(self, wx.ID_CANCEL)
  524. self.btnSubmit = Button(self, wx.ID_OK, _("&Submit"))
  525. self.btnSubmit.SetDefault()
  526. # data area
  527. self.widgets = []
  528. cId = 0
  529. self.usebox = False
  530. self.cat = None
  531. winFocus = False
  532. for column, ctype, ctypeStr, value in data:
  533. if self.keyId == cId:
  534. self.cat = int(value)
  535. if not keyEditable[1]:
  536. self.usebox = True
  537. box.SetLabel(" %s %d " % (_("Category"), self.cat))
  538. box.Show()
  539. self.boxSizer = wx.StaticBoxSizer(box, wx.VERTICAL)
  540. cId += 1
  541. continue
  542. else:
  543. valueWin = SpinCtrl(
  544. parent=self.dataPanel, id=wx.ID_ANY, value=value,
  545. min=-1e9, max=1e9, size=(250, -1))
  546. else:
  547. valueWin = TextCtrl(parent=self.dataPanel, id=wx.ID_ANY,
  548. value=value, size=(250, -1))
  549. if ctype == int:
  550. valueWin.SetValidator(IntegerValidator())
  551. elif ctype == float:
  552. valueWin.SetValidator(FloatValidator())
  553. if not winFocus:
  554. wx.CallAfter(valueWin.SetFocus)
  555. winFocus = True
  556. label = StaticText(parent=self.dataPanel, id=wx.ID_ANY,
  557. label=column)
  558. ctype = StaticText(parent=self.dataPanel, id=wx.ID_ANY,
  559. label="[%s]:" % ctypeStr.lower())
  560. self.widgets.append(
  561. (label.GetId(), ctype.GetId(), valueWin.GetId()))
  562. cId += 1
  563. self._layout()
  564. def _layout(self):
  565. """Do layout"""
  566. sizer = wx.BoxSizer(wx.VERTICAL)
  567. # data area
  568. dataSizer = wx.FlexGridSizer(cols=3, hgap=3, vgap=3)
  569. dataSizer.AddGrowableCol(2)
  570. for labelId, ctypeId, valueId in self.widgets:
  571. label = self.FindWindowById(labelId)
  572. ctype = self.FindWindowById(ctypeId)
  573. value = self.FindWindowById(valueId)
  574. dataSizer.Add(label, proportion=0,
  575. flag=wx.ALIGN_CENTER_VERTICAL)
  576. dataSizer.Add(ctype, proportion=0,
  577. flag=wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_RIGHT)
  578. dataSizer.Add(value, proportion=0,
  579. flag=wx.EXPAND | wx.ALIGN_CENTER_VERTICAL)
  580. self.dataPanel.SetAutoLayout(True)
  581. self.dataPanel.SetSizer(dataSizer)
  582. dataSizer.Fit(self.dataPanel)
  583. if self.usebox:
  584. self.boxSizer.Add(self.dataPanel, proportion=1,
  585. flag=wx.EXPAND | wx.ALL, border=5)
  586. # buttons
  587. btnSizer = wx.StdDialogButtonSizer()
  588. btnSizer.AddButton(self.btnCancel)
  589. btnSizer.AddButton(self.btnSubmit)
  590. btnSizer.Realize()
  591. if not self.usebox:
  592. sizer.Add(self.dataPanel, proportion=1,
  593. flag=wx.EXPAND | wx.ALL, border=5)
  594. else:
  595. sizer.Add(self.boxSizer, proportion=1,
  596. flag=wx.EXPAND | wx.ALL, border=5)
  597. sizer.Add(btnSizer, proportion=0,
  598. flag=wx.EXPAND | wx.ALL, border=5)
  599. framewidth = self.GetBestSize()[0] + 25
  600. self.SetMinSize((framewidth, 250))
  601. self.SetAutoLayout(True)
  602. self.SetSizer(sizer)
  603. sizer.Fit(self)
  604. self.Layout()
  605. def GetValues(self, columns=None):
  606. """Return list of values (casted to string).
  607. If columns is given (list), return only values of given columns.
  608. """
  609. valueList = list()
  610. for labelId, ctypeId, valueId in self.widgets:
  611. column = self.FindWindowById(labelId).GetLabel()
  612. if columns is None or column in columns:
  613. value = GetUnicodeValue(
  614. self.FindWindowById(valueId).GetValue())
  615. valueList.append(value)
  616. # add key value
  617. if self.usebox:
  618. valueList.insert(self.keyId, GetUnicodeValue(str(self.cat)))
  619. return valueList
  620. class AddColumnDialog(wx.Dialog):
  621. def __init__(self, parent, title, id=wx.ID_ANY,
  622. style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER):
  623. """Dialog for adding column into table
  624. """
  625. wx.Dialog.__init__(self, parent, id, title, style=style)
  626. self.CenterOnParent()
  627. self.data = {}
  628. self.data['addColName'] = TextCtrl(
  629. parent=self, id=wx.ID_ANY, value='', size=(
  630. 150, -1), style=wx.TE_PROCESS_ENTER)
  631. self.data['addColType'] = wx.Choice(parent=self, id=wx.ID_ANY,
  632. choices=["integer",
  633. "double",
  634. "varchar",
  635. "date"]) # FIXME
  636. self.data['addColType'].SetSelection(0)
  637. self.data['addColType'].Bind(wx.EVT_CHOICE, self.OnTableChangeType)
  638. self.data['addColLength'] = SpinCtrl(
  639. parent=self, id=wx.ID_ANY, size=(
  640. 65, -1), initial=250, min=1, max=1e6)
  641. self.data['addColLength'].Enable(False)
  642. # buttons
  643. self.btnCancel = Button(self, wx.ID_CANCEL)
  644. self.btnOk = Button(self, wx.ID_OK)
  645. self.btnOk.SetDefault()
  646. self._layout()
  647. def _layout(self):
  648. sizer = wx.BoxSizer(wx.VERTICAL)
  649. addSizer = wx.BoxSizer(wx.HORIZONTAL)
  650. addSizer.Add(
  651. StaticText(
  652. parent=self,
  653. id=wx.ID_ANY,
  654. label=_("Column")),
  655. flag=wx.ALIGN_CENTER_VERTICAL | wx.LEFT | wx.RIGHT,
  656. border=5)
  657. addSizer.Add(self.data['addColName'], proportion=1,
  658. flag=wx.ALIGN_CENTER_VERTICAL | wx.LEFT | wx.RIGHT,
  659. border=5)
  660. addSizer.Add(
  661. StaticText(
  662. parent=self,
  663. id=wx.ID_ANY,
  664. label=_("Type")),
  665. flag=wx.ALIGN_CENTER_VERTICAL | wx.LEFT | wx.RIGHT,
  666. border=5)
  667. addSizer.Add(self.data['addColType'],
  668. flag=wx.ALIGN_CENTER_VERTICAL | wx.LEFT | wx.RIGHT,
  669. border=5)
  670. addSizer.Add(
  671. StaticText(
  672. parent=self,
  673. id=wx.ID_ANY,
  674. label=_("Length")),
  675. flag=wx.ALIGN_CENTER_VERTICAL | wx.LEFT | wx.RIGHT,
  676. border=5)
  677. addSizer.Add(self.data['addColLength'],
  678. flag=wx.ALIGN_CENTER_VERTICAL | wx.LEFT | wx.RIGHT,
  679. border=5)
  680. sizer.Add(addSizer, proportion=0,
  681. flag=wx.ALIGN_RIGHT | wx.ALL, border=5)
  682. btnSizer = wx.StdDialogButtonSizer()
  683. btnSizer.AddButton(self.btnCancel)
  684. btnSizer.AddButton(self.btnOk)
  685. btnSizer.Realize()
  686. sizer.Add(btnSizer, proportion=0,
  687. flag=wx.ALIGN_RIGHT | wx.ALL, border=5)
  688. self.SetSizer(sizer)
  689. self.Fit()
  690. def GetData(self):
  691. """Get inserted data from dialog's widgets"""
  692. values = {}
  693. values['name'] = self.data['addColName'].GetValue()
  694. values['ctype'] = self.data['addColType'].GetStringSelection()
  695. values['length'] = int(self.data['addColLength'].GetValue())
  696. return values
  697. def OnTableChangeType(self, event):
  698. """Data type for new column changed. Enable or disable
  699. data length widget"""
  700. if event.GetString() == "varchar":
  701. self.data['addColLength'].Enable(True)
  702. else:
  703. self.data['addColLength'].Enable(False)