dialogs.py 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844
  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, GetDbEncoding
  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, 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 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 = GetDbEncoding()
  284. for sql in self.GetSQLString(updateValues=True):
  285. if not sql:
  286. close = False
  287. continue
  288. sql = sql.encode(enc)
  289. driver, database = self.mapDBInfo.GetDbSettings(layer)
  290. Debug.msg(1, "SQL: %s" % sql)
  291. RunCommand('db.execute',
  292. parent=self,
  293. quiet=True,
  294. input='-',
  295. stdin=sql,
  296. driver=driver,
  297. database=database)
  298. layer += 1
  299. if close and self.closeDialog.IsChecked():
  300. self.OnClose(event)
  301. def OnFeature(self, event):
  302. self.fid = int(event.GetString())
  303. self.UpdateDialog(cats=self.cats, fid=self.fid)
  304. def GetCats(self):
  305. """Get id of selected vector object or 'None' if nothing selected
  306. :param id: if true return ids otherwise cats
  307. """
  308. if self.fid < 0:
  309. return None
  310. return self.cats[self.fid]
  311. def GetFid(self):
  312. """Get selected feature id"""
  313. return self.fid
  314. def UpdateDialog(self, map=None, query=None, cats=None, fid=-1,
  315. action=None):
  316. """Update dialog
  317. :param map: name of vector map
  318. :param query:
  319. :param cats:
  320. :param fid: feature id
  321. :param action: add, update, display or None
  322. :return: True if updated
  323. :return: False
  324. """
  325. if action:
  326. self.action = action
  327. if action == 'display':
  328. enabled = False
  329. else:
  330. enabled = True
  331. self.closeDialog.Enable(enabled)
  332. self.FindWindowById(wx.ID_OK).Enable(enabled)
  333. if map:
  334. self.map = map
  335. # get layer/table/column information
  336. self.mapDBInfo = VectorDBInfo(self.map)
  337. if not self.mapDBInfo:
  338. return False
  339. self.mapDBInfo.Reset()
  340. layers = self.mapDBInfo.layers.keys() # get available layers
  341. # id of selected line
  342. if query: # select by position
  343. data = self.mapDBInfo.SelectByPoint(query[0],
  344. query[1])
  345. self.cats = {}
  346. if data and 'Layer' in data:
  347. idx = 0
  348. for layer in data['Layer']:
  349. layer = int(layer)
  350. if data['Id'][idx] is not None:
  351. tfid = int(data['Id'][idx])
  352. else:
  353. tfid = 0 # Area / Volume
  354. if not tfid in self.cats:
  355. self.cats[tfid] = {}
  356. if not layer in self.cats[tfid]:
  357. self.cats[tfid][layer] = []
  358. cat = int(data['Category'][idx])
  359. self.cats[tfid][layer].append(cat)
  360. idx += 1
  361. else:
  362. self.cats = cats
  363. if fid > 0:
  364. self.fid = fid
  365. elif len(self.cats.keys()) > 0:
  366. self.fid = list(self.cats.keys())[0]
  367. else:
  368. self.fid = -1
  369. if len(self.cats.keys()) == 1:
  370. self.fidMulti.Show(False)
  371. self.fidText.Show(True)
  372. if self.fid > 0:
  373. self.fidText.SetLabel("%d" % self.fid)
  374. else:
  375. self.fidText.SetLabel(_("Unknown"))
  376. else:
  377. self.fidMulti.Show(True)
  378. self.fidText.Show(False)
  379. choices = []
  380. for tfid in self.cats.keys():
  381. choices.append(str(tfid))
  382. self.fidMulti.SetItems(choices)
  383. self.fidMulti.SetStringSelection(str(self.fid))
  384. # reset notebook
  385. self.notebook.DeleteAllPages()
  386. for layer in layers: # for each layer
  387. if not query: # select by layer/cat
  388. if self.fid > 0 and layer in self.cats[self.fid]:
  389. for cat in self.cats[self.fid][layer]:
  390. nselected = self.mapDBInfo.SelectFromTable(
  391. layer, where="%s=%d" %
  392. (self.mapDBInfo.layers[layer]['key'], cat))
  393. else:
  394. nselected = 0
  395. # if nselected <= 0 and self.action != "add":
  396. # continue # nothing selected ...
  397. if self.action == "add":
  398. if nselected <= 0:
  399. if layer in self.cats[self.fid]:
  400. table = self.mapDBInfo.layers[layer]["table"]
  401. key = self.mapDBInfo.layers[layer]["key"]
  402. columns = self.mapDBInfo.tables[table]
  403. for name in columns.keys():
  404. if name == key:
  405. for cat in self.cats[self.fid][layer]:
  406. self.mapDBInfo.tables[table][
  407. name]['values'].append(cat)
  408. else:
  409. self.mapDBInfo.tables[table][
  410. name]['values'].append(None)
  411. else: # change status 'add' -> 'update'
  412. self.action = "update"
  413. table = self.mapDBInfo.layers[layer]["table"]
  414. key = self.mapDBInfo.layers[layer]["key"]
  415. columns = self.mapDBInfo.tables[table]
  416. for idx in range(len(columns[key]['values'])):
  417. for name in columns.keys():
  418. if name == key:
  419. cat = int(columns[name]['values'][idx])
  420. break
  421. # use scrolled panel instead (and fix initial max height of the
  422. # window to 480px)
  423. panel = scrolled.ScrolledPanel(
  424. parent=self.notebook, id=wx.ID_ANY, size=(-1, 150))
  425. panel.SetupScrolling(scroll_x=False)
  426. self.notebook.AddPage(
  427. page=panel, text=" %s %d / %s %d" %
  428. (_("Layer"), layer, _("Category"), cat))
  429. # notebook body
  430. border = wx.BoxSizer(wx.VERTICAL)
  431. flexSizer = wx.FlexGridSizer(cols=3, hgap=3, vgap=3)
  432. flexSizer.AddGrowableCol(2)
  433. # columns (sorted by index)
  434. names = [''] * len(columns.keys())
  435. for name in columns.keys():
  436. names[columns[name]['index']] = name
  437. for name in names:
  438. if name == key: # skip key column (category)
  439. continue
  440. vtype = columns[name]['type'].lower()
  441. ctype = columns[name]['ctype']
  442. if columns[name]['values'][idx] is not None:
  443. if not isinstance(columns[name]['ctype'], six.string_types):
  444. value = str(columns[name]['values'][idx])
  445. else:
  446. value = columns[name]['values'][idx]
  447. else:
  448. value = ''
  449. colName = StaticText(parent=panel, id=wx.ID_ANY,
  450. label=name)
  451. colType = StaticText(parent=panel, id=wx.ID_ANY,
  452. label="[%s]:" % vtype)
  453. colValue = TextCtrl(
  454. parent=panel, id=wx.ID_ANY, value=value)
  455. colValue.SetName(name)
  456. if ctype == int:
  457. colValue.SetValidator(IntegerValidator())
  458. elif ctype == float:
  459. colValue.SetValidator(FloatValidator())
  460. self.Bind(wx.EVT_TEXT, self.OnSQLStatement, colValue)
  461. if self.action == 'display':
  462. colValue.SetWindowStyle(wx.TE_READONLY)
  463. flexSizer.Add(colName, proportion=0,
  464. flag=wx.ALIGN_CENTER_VERTICAL)
  465. flexSizer.Add(
  466. colType,
  467. proportion=0,
  468. flag=wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_RIGHT)
  469. flexSizer.Add(colValue, proportion=1,
  470. flag=wx.EXPAND | wx.ALIGN_CENTER_VERTICAL)
  471. # add widget reference to self.columns
  472. columns[name]['ids'].append(
  473. colValue.GetId()) # name, type, values, id
  474. # for each attribute (including category) END
  475. border.Add(
  476. flexSizer,
  477. proportion=1,
  478. flag=wx.ALL | wx.EXPAND,
  479. border=5)
  480. panel.SetSizer(border)
  481. # for each category END
  482. # for each layer END
  483. if self.notebook.GetPageCount() == 0:
  484. self.noFoundMsg.Show(True)
  485. else:
  486. self.noFoundMsg.Show(False)
  487. self.Layout()
  488. return True
  489. def SetColumnValue(self, layer, column, value):
  490. """Set attrbute value
  491. :param column: column name
  492. :param value: value
  493. """
  494. table = self.mapDBInfo.GetTable(layer)
  495. columns = self.mapDBInfo.GetTableDesc(table)
  496. for key, col in six.iteritems(columns):
  497. if key == column:
  498. col['values'] = [col['ctype'](value), ]
  499. break
  500. class ModifyTableRecord(wx.Dialog):
  501. def __init__(self, parent, title, data, keyEditable=(-1, True),
  502. id=wx.ID_ANY, style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER):
  503. """Dialog for inserting/updating table record
  504. :param data: a list: [(column, value)]
  505. :param keyEditable: (id, editable?) indicates if textarea for
  506. key column is editable(True) or not
  507. """
  508. # parent -> VDigitWindow
  509. wx.Dialog.__init__(self, parent, id, title, style=style)
  510. self.CenterOnParent()
  511. self.keyId = keyEditable[0]
  512. box = StaticBox(parent=self, id=wx.ID_ANY)
  513. box.Hide()
  514. self.dataPanel = scrolled.ScrolledPanel(parent=self, id=wx.ID_ANY,
  515. style=wx.TAB_TRAVERSAL)
  516. self.dataPanel.SetupScrolling(scroll_x=False)
  517. # buttons
  518. self.btnCancel = Button(self, wx.ID_CANCEL)
  519. self.btnSubmit = Button(self, wx.ID_OK, _("&Submit"))
  520. self.btnSubmit.SetDefault()
  521. # data area
  522. self.widgets = []
  523. cId = 0
  524. self.usebox = False
  525. self.cat = None
  526. winFocus = False
  527. for column, ctype, ctypeStr, value in data:
  528. if self.keyId == cId:
  529. self.cat = int(value)
  530. if not keyEditable[1]:
  531. self.usebox = True
  532. box.SetLabel(" %s %d " % (_("Category"), self.cat))
  533. box.Show()
  534. self.boxSizer = wx.StaticBoxSizer(box, wx.VERTICAL)
  535. cId += 1
  536. continue
  537. else:
  538. valueWin = SpinCtrl(
  539. parent=self.dataPanel, id=wx.ID_ANY, value=value,
  540. min=-1e9, max=1e9, size=(250, -1))
  541. else:
  542. valueWin = TextCtrl(parent=self.dataPanel, id=wx.ID_ANY,
  543. value=value, size=(250, -1))
  544. if ctype == int:
  545. valueWin.SetValidator(IntegerValidator())
  546. elif ctype == float:
  547. valueWin.SetValidator(FloatValidator())
  548. if not winFocus:
  549. wx.CallAfter(valueWin.SetFocus)
  550. winFocus = True
  551. label = StaticText(parent=self.dataPanel, id=wx.ID_ANY,
  552. label=column)
  553. ctype = StaticText(parent=self.dataPanel, id=wx.ID_ANY,
  554. label="[%s]:" % ctypeStr.lower())
  555. self.widgets.append(
  556. (label.GetId(), ctype.GetId(), valueWin.GetId()))
  557. cId += 1
  558. self._layout()
  559. def _layout(self):
  560. """Do layout"""
  561. sizer = wx.BoxSizer(wx.VERTICAL)
  562. # data area
  563. dataSizer = wx.FlexGridSizer(cols=3, hgap=3, vgap=3)
  564. dataSizer.AddGrowableCol(2)
  565. for labelId, ctypeId, valueId in self.widgets:
  566. label = self.FindWindowById(labelId)
  567. ctype = self.FindWindowById(ctypeId)
  568. value = self.FindWindowById(valueId)
  569. dataSizer.Add(label, proportion=0,
  570. flag=wx.ALIGN_CENTER_VERTICAL)
  571. dataSizer.Add(ctype, proportion=0,
  572. flag=wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_RIGHT)
  573. dataSizer.Add(value, proportion=0,
  574. flag=wx.EXPAND | wx.ALIGN_CENTER_VERTICAL)
  575. self.dataPanel.SetAutoLayout(True)
  576. self.dataPanel.SetSizer(dataSizer)
  577. dataSizer.Fit(self.dataPanel)
  578. if self.usebox:
  579. self.boxSizer.Add(self.dataPanel, proportion=1,
  580. flag=wx.EXPAND | wx.ALL, border=5)
  581. # buttons
  582. btnSizer = wx.StdDialogButtonSizer()
  583. btnSizer.AddButton(self.btnCancel)
  584. btnSizer.AddButton(self.btnSubmit)
  585. btnSizer.Realize()
  586. if not self.usebox:
  587. sizer.Add(self.dataPanel, proportion=1,
  588. flag=wx.EXPAND | wx.ALL, border=5)
  589. else:
  590. sizer.Add(self.boxSizer, proportion=1,
  591. flag=wx.EXPAND | wx.ALL, border=5)
  592. sizer.Add(btnSizer, proportion=0,
  593. flag=wx.EXPAND | wx.ALL, border=5)
  594. framewidth = self.GetBestSize()[0] + 25
  595. self.SetMinSize((framewidth, 250))
  596. self.SetAutoLayout(True)
  597. self.SetSizer(sizer)
  598. sizer.Fit(self)
  599. self.Layout()
  600. def GetValues(self, columns=None):
  601. """Return list of values (casted to string).
  602. If columns is given (list), return only values of given columns.
  603. """
  604. valueList = list()
  605. for labelId, ctypeId, valueId in self.widgets:
  606. column = self.FindWindowById(labelId).GetLabel()
  607. if columns is None or column in columns:
  608. value = GetUnicodeValue(
  609. self.FindWindowById(valueId).GetValue())
  610. valueList.append(value)
  611. # add key value
  612. if self.usebox:
  613. valueList.insert(self.keyId, GetUnicodeValue(str(self.cat)))
  614. return valueList
  615. class AddColumnDialog(wx.Dialog):
  616. def __init__(self, parent, title, id=wx.ID_ANY,
  617. style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER):
  618. """Dialog for adding column into table
  619. """
  620. wx.Dialog.__init__(self, parent, id, title, style=style)
  621. self.CenterOnParent()
  622. self.data = {}
  623. self.data['addColName'] = TextCtrl(
  624. parent=self, id=wx.ID_ANY, value='', size=(
  625. 150, -1), style=wx.TE_PROCESS_ENTER)
  626. self.data['addColType'] = wx.Choice(parent=self, id=wx.ID_ANY,
  627. choices=["integer",
  628. "double",
  629. "varchar",
  630. "date"]) # FIXME
  631. self.data['addColType'].SetSelection(0)
  632. self.data['addColType'].Bind(wx.EVT_CHOICE, self.OnTableChangeType)
  633. self.data['addColLength'] = SpinCtrl(
  634. parent=self, id=wx.ID_ANY, size=(
  635. 65, -1), initial=250, min=1, max=1e6)
  636. self.data['addColLength'].Enable(False)
  637. # buttons
  638. self.btnCancel = Button(self, wx.ID_CANCEL)
  639. self.btnOk = Button(self, wx.ID_OK)
  640. self.btnOk.SetDefault()
  641. self._layout()
  642. def _layout(self):
  643. sizer = wx.BoxSizer(wx.VERTICAL)
  644. addSizer = wx.BoxSizer(wx.HORIZONTAL)
  645. addSizer.Add(
  646. StaticText(
  647. parent=self,
  648. id=wx.ID_ANY,
  649. label=_("Column")),
  650. flag=wx.ALIGN_CENTER_VERTICAL | wx.LEFT | wx.RIGHT,
  651. border=5)
  652. addSizer.Add(self.data['addColName'], proportion=1,
  653. flag=wx.ALIGN_CENTER_VERTICAL | wx.LEFT | wx.RIGHT,
  654. border=5)
  655. addSizer.Add(
  656. StaticText(
  657. parent=self,
  658. id=wx.ID_ANY,
  659. label=_("Type")),
  660. flag=wx.ALIGN_CENTER_VERTICAL | wx.LEFT | wx.RIGHT,
  661. border=5)
  662. addSizer.Add(self.data['addColType'],
  663. flag=wx.ALIGN_CENTER_VERTICAL | wx.LEFT | wx.RIGHT,
  664. border=5)
  665. addSizer.Add(
  666. StaticText(
  667. parent=self,
  668. id=wx.ID_ANY,
  669. label=_("Length")),
  670. flag=wx.ALIGN_CENTER_VERTICAL | wx.LEFT | wx.RIGHT,
  671. border=5)
  672. addSizer.Add(self.data['addColLength'],
  673. flag=wx.ALIGN_CENTER_VERTICAL | wx.LEFT | wx.RIGHT,
  674. border=5)
  675. sizer.Add(addSizer, proportion=0,
  676. flag=wx.ALIGN_RIGHT | wx.ALL, border=5)
  677. btnSizer = wx.StdDialogButtonSizer()
  678. btnSizer.AddButton(self.btnCancel)
  679. btnSizer.AddButton(self.btnOk)
  680. btnSizer.Realize()
  681. sizer.Add(btnSizer, proportion=0,
  682. flag=wx.ALIGN_RIGHT | wx.ALL, border=5)
  683. self.SetSizer(sizer)
  684. self.Fit()
  685. def GetData(self):
  686. """Get inserted data from dialog's widgets"""
  687. values = {}
  688. values['name'] = self.data['addColName'].GetValue()
  689. values['ctype'] = self.data['addColType'].GetStringSelection()
  690. values['length'] = int(self.data['addColLength'].GetValue())
  691. return values
  692. def OnTableChangeType(self, event):
  693. """Data type for new column changed. Enable or disable
  694. data length widget"""
  695. if event.GetString() == "varchar":
  696. self.data['addColLength'].Enable(True)
  697. else:
  698. self.data['addColLength'].Enable(False)