base.py 142 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519
  1. """!
  2. @package dbmgr.base
  3. @brief GRASS Attribute Table Manager base classes
  4. List of classes:
  5. - base::Log
  6. - base::VirtualAttributeList
  7. - base::DbMgrBase
  8. - base::DbMgrNotebookBase
  9. - base::DbMgrBrowsePage
  10. - base::DbMgrTablesPage
  11. - base::DbMgrLayersPage
  12. - base::TableListCtrl
  13. - base::LayerListCtrl
  14. - base::LayerBook
  15. - base::FieldStatistics
  16. @todo Implement giface class
  17. (C) 2007-2014 by the GRASS Development Team
  18. This program is free software under the GNU General Public License
  19. (>=v2). Read the file COPYING that comes with GRASS for details.
  20. @author Jachym Cepicky <jachym.cepicky gmail.com>
  21. @author Martin Landa <landa.martin gmail.com>
  22. @author Refactoring by Stepan Turek <stepan.turek seznam.cz> (GSoC 2012, mentor: Martin Landa)
  23. """
  24. import sys
  25. import os
  26. import locale
  27. import tempfile
  28. import copy
  29. import types
  30. import math
  31. from core import globalvar
  32. import wx
  33. import wx.lib.mixins.listctrl as listmix
  34. import wx.lib.flatnotebook as FN
  35. import wx.lib.scrolledpanel as scrolled
  36. import grass.script as grass
  37. from dbmgr.sqlbuilder import SQLBuilderSelect, SQLBuilderUpdate
  38. from core.gcmd import RunCommand, GException, GError, GMessage, GWarning
  39. from core.utils import ListOfCatsToRange, _
  40. from gui_core.dialogs import CreateNewVector
  41. from dbmgr.vinfo import VectorDBInfo, GetUnicodeValue, CreateDbInfoDesc
  42. from core.debug import Debug
  43. from dbmgr.dialogs import ModifyTableRecord, AddColumnDialog
  44. from core.settings import UserSettings
  45. class Log:
  46. """!The log output SQL is redirected to the status bar of the
  47. containing frame.
  48. """
  49. def __init__(self, parent):
  50. self.parent = parent
  51. def write(self, text_string):
  52. """!Update status bar"""
  53. if self.parent:
  54. self.parent.SetStatusText(text_string.strip())
  55. class VirtualAttributeList(wx.ListCtrl,
  56. listmix.ListCtrlAutoWidthMixin,
  57. listmix.ColumnSorterMixin):
  58. """!Support virtual list class for Attribute Table Manager (browse page)
  59. """
  60. def __init__(self, parent, log, dbMgrData, layer, pages):
  61. # initialize variables
  62. self.parent = parent
  63. self.log = log
  64. self.dbMgrData = dbMgrData
  65. self.mapDBInfo = self.dbMgrData['mapDBInfo']
  66. self.layer = layer
  67. self.pages = pages
  68. self.fieldCalc = None
  69. self.fieldStats = None
  70. self.columns = {} # <- LoadData()
  71. self.sqlFilter = {}
  72. wx.ListCtrl.__init__(self, parent = parent, id = wx.ID_ANY,
  73. style = wx.LC_REPORT | wx.LC_HRULES |
  74. wx.LC_VRULES | wx.LC_VIRTUAL | wx.LC_SORT_ASCENDING)
  75. try:
  76. keyColumn = self.LoadData(layer)
  77. except GException as e:
  78. GError(parent = self,
  79. message = e.value)
  80. return
  81. # add some attributes (colourful background for each item rows)
  82. self.attr1 = wx.ListItemAttr()
  83. self.attr1.SetBackgroundColour(wx.Colour(238,238,238))
  84. self.attr2 = wx.ListItemAttr()
  85. self.attr2.SetBackgroundColour("white")
  86. self.il = wx.ImageList(16, 16)
  87. self.sm_up = self.il.Add(wx.ArtProvider_GetBitmap(wx.ART_GO_UP, wx.ART_TOOLBAR,
  88. (16,16)))
  89. self.sm_dn = self.il.Add(wx.ArtProvider_GetBitmap(wx.ART_GO_DOWN, wx.ART_TOOLBAR,
  90. (16,16)))
  91. self.SetImageList(self.il, wx.IMAGE_LIST_SMALL)
  92. # setup mixins
  93. listmix.ListCtrlAutoWidthMixin.__init__(self)
  94. listmix.ColumnSorterMixin.__init__(self, len(self.columns))
  95. # sort item by category (id)
  96. if keyColumn > -1:
  97. self.SortListItems(col = keyColumn, ascending = True)
  98. elif keyColumn:
  99. self.SortListItems(col = 0, ascending = True)
  100. # events
  101. self.Bind(wx.EVT_LIST_ITEM_SELECTED, self.OnItemSelected)
  102. self.Bind(wx.EVT_LIST_ITEM_DESELECTED, self.OnItemDeselected)
  103. self.Bind(wx.EVT_LIST_COL_CLICK, self.OnColumnSort)
  104. self.Bind(wx.EVT_LIST_COL_RIGHT_CLICK, self.OnColumnMenu)
  105. def Update(self, mapDBInfo = None):
  106. """!Update list according new mapDBInfo description"""
  107. if mapDBInfo:
  108. self.mapDBInfo = mapDBInfo
  109. self.LoadData(self.layer)
  110. else:
  111. self.LoadData(self.layer, **self.sqlFilter)
  112. def LoadData(self, layer, columns = None, where = None, sql = None):
  113. """!Load data into list
  114. @param layer layer number
  115. @param columns list of columns for output (-> v.db.select)
  116. @param where where statement (-> v.db.select)
  117. @param sql full sql statement (-> db.select)
  118. @return id of key column
  119. @return -1 if key column is not displayed
  120. """
  121. self.log.write(_("Loading data..."))
  122. tableName = self.mapDBInfo.layers[layer]['table']
  123. keyColumn = self.mapDBInfo.layers[layer]['key']
  124. try:
  125. self.columns = self.mapDBInfo.tables[tableName]
  126. except KeyError:
  127. raise GException(_("Attribute table <%s> not found. "
  128. "For creating the table switch to "
  129. "'Manage layers' tab.") % tableName)
  130. if not columns:
  131. columns = self.mapDBInfo.GetColumns(tableName)
  132. else:
  133. all = self.mapDBInfo.GetColumns(tableName)
  134. for col in columns:
  135. if col not in all:
  136. GError(parent = self,
  137. message = _("Column <%(column)s> not found in "
  138. "in the table <%(table)s>.") % \
  139. { 'column' : col, 'table' : tableName })
  140. return
  141. try:
  142. # for maps connected via v.external
  143. keyId = columns.index(keyColumn)
  144. except:
  145. keyId = -1
  146. # read data
  147. # FIXME: Max. number of rows, while the GUI is still usable
  148. # stdout can be very large, do not use PIPE, redirect to temp file
  149. # TODO: more effective way should be implemented...
  150. # split on field sep breaks if varchar() column contains the
  151. # values, so while sticking with ASCII we make it something
  152. # highly unlikely to exist naturally.
  153. fs = '{_sep_}'
  154. outFile = tempfile.NamedTemporaryFile(mode = 'w+b')
  155. cmdParams = dict(quiet = True,
  156. parent = self,
  157. flags = 'c',
  158. separator = fs)
  159. if sql:
  160. cmdParams.update(dict(sql = sql,
  161. output = outFile.name,
  162. overwrite = True))
  163. ret = RunCommand('db.select',
  164. **cmdParams)
  165. self.sqlFilter = {"sql" : sql}
  166. else:
  167. cmdParams.update(dict(map = self.mapDBInfo.map,
  168. layer = layer,
  169. where = where,
  170. stdout = outFile))
  171. self.sqlFilter = {"where" : where}
  172. if columns:
  173. cmdParams.update(dict(columns = ','.join(columns)))
  174. ret = RunCommand('v.db.select',
  175. **cmdParams)
  176. # These two should probably be passed to init more cleanly
  177. # setting the numbers of items = number of elements in the dictionary
  178. self.itemDataMap = {}
  179. self.itemIndexMap = []
  180. self.itemCatsMap = {}
  181. self.DeleteAllItems()
  182. # self.ClearAll()
  183. for i in range(self.GetColumnCount()):
  184. self.DeleteColumn(0)
  185. i = 0
  186. info = wx.ListItem()
  187. info.m_mask = wx.LIST_MASK_TEXT | wx.LIST_MASK_IMAGE | wx.LIST_MASK_FORMAT
  188. info.m_image = -1
  189. info.m_format = 0
  190. for column in columns:
  191. info.m_text = column
  192. self.InsertColumnInfo(i, info)
  193. i += 1
  194. if i >= 256:
  195. self.log.write(_("Can display only 256 columns."))
  196. i = 0
  197. outFile.seek(0)
  198. while True:
  199. # os.linesep doesn't work here (MSYS)
  200. record = outFile.readline().replace('\n', '')
  201. if not record:
  202. break
  203. record = record.split(fs)
  204. if len(columns) != len(record):
  205. GError(parent = self,
  206. message = _("Inconsistent number of columns "
  207. "in the table <%(table)s>.") % \
  208. {'table' : tableName })
  209. self.columns = {} # because of IsEmpty method
  210. return
  211. self.AddDataRow(i, record, columns, keyId)
  212. i += 1
  213. if i >= 100000:
  214. self.log.write(_("Viewing limit: 100000 records."))
  215. break
  216. self.SetItemCount(i)
  217. if where:
  218. item = -1
  219. while True:
  220. item = self.GetNextItem(item)
  221. if item == -1:
  222. break
  223. self.SetItemState(item, wx.LIST_STATE_SELECTED, wx.LIST_STATE_SELECTED)
  224. i = 0
  225. for col in columns:
  226. width = self.columns[col]['length'] * 6 # FIXME
  227. if width < 60:
  228. width = 60
  229. if width > 300:
  230. width = 300
  231. self.SetColumnWidth(col = i, width = width)
  232. i += 1
  233. self.SendSizeEvent()
  234. self.log.write(_("Number of loaded records: %d") % \
  235. self.GetItemCount())
  236. return keyId
  237. def AddDataRow(self, i, record, columns, keyId):
  238. """!Add row to the data list"""
  239. self.itemDataMap[i] = []
  240. keyColumn = self.mapDBInfo.layers[self.layer]['key']
  241. j = 0
  242. cat = None
  243. if keyColumn == 'OGC_FID':
  244. self.itemDataMap[i].append(i+1)
  245. j += 1
  246. cat = i + 1
  247. for value in record:
  248. if self.columns[columns[j]]['ctype'] != types.StringType:
  249. try:
  250. ### casting disabled (2009/03)
  251. ### self.itemDataMap[i].append(self.columns[columns[j]]['ctype'](value))
  252. self.itemDataMap[i].append(value)
  253. except ValueError:
  254. self.itemDataMap[i].append(_('Unknown value'))
  255. else:
  256. # encode string values
  257. try:
  258. self.itemDataMap[i].append(GetUnicodeValue(value))
  259. except UnicodeDecodeError:
  260. self.itemDataMap[i].append(_("Unable to decode value. "
  261. "Set encoding in GUI preferences ('Attributes')."))
  262. if not cat and keyId > -1 and keyId == j:
  263. try:
  264. cat = self.columns[columns[j]]['ctype'] (value)
  265. except ValueError as e:
  266. cat = -1
  267. GError(parent = self,
  268. message = _("Error loading attribute data. "
  269. "Record number: %(rec)d. Unable to convert value '%(val)s' in "
  270. "key column (%(key)s) to integer.\n\n"
  271. "Details: %(detail)s") % \
  272. { 'rec' : i + 1, 'val' : value,
  273. 'key' : keyColumn, 'detail' : e})
  274. j += 1
  275. self.itemIndexMap.append(i)
  276. if keyId > -1: # load cats only when LoadData() is called first time
  277. self.itemCatsMap[i] = cat
  278. def OnItemSelected(self, event):
  279. """!Item selected. Add item to selected cats..."""
  280. # cat = int(self.GetItemText(event.m_itemIndex))
  281. # if cat not in self.selectedCats:
  282. # self.selectedCats.append(cat)
  283. # self.selectedCats.sort()
  284. event.Skip()
  285. def OnItemDeselected(self, event):
  286. """!Item deselected. Remove item from selected cats..."""
  287. # cat = int(self.GetItemText(event.m_itemIndex))
  288. # if cat in self.selectedCats:
  289. # self.selectedCats.remove(cat)
  290. # self.selectedCats.sort()
  291. event.Skip()
  292. def GetSelectedItems(self):
  293. """!Return list of selected items (category numbers)"""
  294. cats = []
  295. item = self.GetFirstSelected()
  296. while item != -1:
  297. cats.append(self.GetItemText(item))
  298. item = self.GetNextSelected(item)
  299. return cats
  300. def GetItems(self):
  301. """!Return list of items (category numbers)"""
  302. cats = []
  303. for item in range(self.GetItemCount()):
  304. cats.append(self.GetItemText(item))
  305. return cats
  306. def GetColumnText(self, index, col):
  307. """!Return column text"""
  308. item = self.GetItem(index, col)
  309. return item.GetText()
  310. def GetListCtrl(self):
  311. """!Returt list"""
  312. return self
  313. def OnGetItemText(self, item, col):
  314. """!Get item text"""
  315. index = self.itemIndexMap[item]
  316. s = self.itemDataMap[index][col]
  317. return s
  318. def OnGetItemAttr(self, item):
  319. """!Get item attributes"""
  320. if ( item % 2) == 0:
  321. return self.attr2
  322. else:
  323. return self.attr1
  324. def OnColumnMenu(self, event):
  325. """!Column heading right mouse button -> pop-up menu"""
  326. self._col = event.GetColumn()
  327. popupMenu = wx.Menu()
  328. if not hasattr (self, "popupID"):
  329. self.popupId = { 'sortAsc' : wx.NewId(),
  330. 'sortDesc' : wx.NewId(),
  331. 'calculate' : wx.NewId(),
  332. 'area' : wx.NewId(),
  333. 'length' : wx.NewId(),
  334. 'compact' : wx.NewId(),
  335. 'fractal' : wx.NewId(),
  336. 'perimeter' : wx.NewId(),
  337. 'ncats' : wx.NewId(),
  338. 'slope' : wx.NewId(),
  339. 'lsin' : wx.NewId(),
  340. 'lazimuth' : wx.NewId(),
  341. 'calculator' : wx.NewId(),
  342. 'stats' : wx.NewId() }
  343. popupMenu.Append(self.popupId['sortAsc'], text = _("Sort ascending"))
  344. popupMenu.Append(self.popupId['sortDesc'], text = _("Sort descending"))
  345. popupMenu.AppendSeparator()
  346. subMenu = wx.Menu()
  347. popupMenu.AppendMenu(self.popupId['calculate'], _("Calculate (only numeric columns)"),
  348. subMenu)
  349. popupMenu.Append(self.popupId['calculator'], text = _("Field calculator"))
  350. popupMenu.AppendSeparator()
  351. popupMenu.Append(self.popupId['stats'], text = _("Statistics"))
  352. if not self.pages['manageTable']:
  353. popupMenu.AppendSeparator()
  354. self.popupId['addCol'] = wx.NewId()
  355. popupMenu.Append(self.popupId['addCol'], text = _("Add column"))
  356. if not self.dbMgrData['editable']:
  357. popupMenu.Enable(self.popupId['addCol'], False)
  358. if not self.dbMgrData['editable']:
  359. popupMenu.Enable(self.popupId['calculator'], False)
  360. if not self.dbMgrData['editable'] or \
  361. self.columns[self.GetColumn(self._col).GetText()]['ctype'] not in (types.IntType, types.FloatType):
  362. popupMenu.Enable(self.popupId['calculate'], False)
  363. subMenu.Append(self.popupId['area'], text = _("Area size"))
  364. subMenu.Append(self.popupId['length'], text = _("Line length"))
  365. subMenu.Append(self.popupId['compact'], text = _("Compactness of an area"))
  366. subMenu.Append(self.popupId['fractal'], text = _("Fractal dimension of boundary defining a polygon"))
  367. subMenu.Append(self.popupId['perimeter'], text = _("Perimeter length of an area"))
  368. subMenu.Append(self.popupId['ncats'], text = _("Number of features for each category"))
  369. subMenu.Append(self.popupId['slope'], text = _("Slope steepness of 3D line"))
  370. subMenu.Append(self.popupId['lsin'], text = _("Line sinuousity"))
  371. subMenu.Append(self.popupId['lazimuth'], text = _("Line azimuth"))
  372. self.Bind (wx.EVT_MENU, self.OnColumnSortAsc, id = self.popupId['sortAsc'])
  373. self.Bind (wx.EVT_MENU, self.OnColumnSortDesc, id = self.popupId['sortDesc'])
  374. self.Bind(wx.EVT_MENU, self.OnFieldCalculator, id = self.popupId['calculator'])
  375. self.Bind(wx.EVT_MENU, self.OnFieldStatistics, id = self.popupId['stats'])
  376. if not self.pages['manageTable']:
  377. self.Bind(wx.EVT_MENU, self.OnAddColumn, id = self.popupId['addCol'])
  378. for id in (self.popupId['area'], self.popupId['length'], self.popupId['compact'],
  379. self.popupId['fractal'], self.popupId['perimeter'], self.popupId['ncats'],
  380. self.popupId['slope'], self.popupId['lsin'], self.popupId['lazimuth']):
  381. self.Bind(wx.EVT_MENU, self.OnColumnCompute, id = id)
  382. self.PopupMenu(popupMenu)
  383. popupMenu.Destroy()
  384. def OnColumnSort(self, event):
  385. """!Column heading left mouse button -> sorting"""
  386. self._col = event.GetColumn()
  387. self.ColumnSort()
  388. event.Skip()
  389. def OnColumnSortAsc(self, event):
  390. """!Sort values of selected column (ascending)"""
  391. self.SortListItems(col = self._col, ascending = True)
  392. event.Skip()
  393. def OnColumnSortDesc(self, event):
  394. """!Sort values of selected column (descending)"""
  395. self.SortListItems(col = self._col, ascending = False)
  396. event.Skip()
  397. def OnColumnCompute(self, event):
  398. """!Compute values of selected column"""
  399. id = event.GetId()
  400. option = None
  401. if id == self.popupId['area']:
  402. option = 'area'
  403. elif id == self.popupId['length']:
  404. option = 'length'
  405. elif id == self.popupId['compact']:
  406. option = 'compact'
  407. elif id == self.popupId['fractal']:
  408. option = 'fd'
  409. elif id == self.popupId['perimeter']:
  410. option = 'perimeter'
  411. elif id == self.popupId['ncats']:
  412. option = 'count'
  413. elif id == self.popupId['slope']:
  414. option = 'slope'
  415. elif id == self.popupId['lsin']:
  416. option = 'sinuous'
  417. elif id == self.popupId['lazimuth']:
  418. option = 'azimuth'
  419. if not option:
  420. return
  421. RunCommand('v.to.db',
  422. parent = self.parent,
  423. map = self.mapDBInfo.map,
  424. layer = self.layer,
  425. option = option,
  426. columns = self.GetColumn(self._col).GetText())
  427. self.LoadData(self.layer)
  428. def ColumnSort(self):
  429. """!Sort values of selected column (self._col)"""
  430. # remove duplicated arrow symbol from column header
  431. # FIXME: should be done automatically
  432. info = wx.ListItem()
  433. info.m_mask = wx.LIST_MASK_TEXT | wx.LIST_MASK_IMAGE
  434. info.m_image = -1
  435. for column in range(self.GetColumnCount()):
  436. info.m_text = self.GetColumn(column).GetText()
  437. self.SetColumn(column, info)
  438. def OnFieldCalculator(self, event):
  439. """!Calls SQLBuilderUpdate instance"""
  440. if not self.fieldCalc:
  441. self.fieldCalc = SQLBuilderUpdate(parent = self, id = wx.ID_ANY,
  442. vectmap = self.dbMgrData['vectName'],
  443. layer = self.layer,
  444. column = self.GetColumn(self._col).GetText())
  445. self.fieldCalc.Show()
  446. else:
  447. self.fieldCalc.Raise()
  448. def OnFieldStatistics(self, event):
  449. """Calls FieldStatistics instance"""
  450. if not self.fieldStats:
  451. self.fieldStats = FieldStatistics(parent = self, id = wx.ID_ANY)
  452. self.fieldStats.Show()
  453. else:
  454. self.fieldStats.Raise()
  455. selLayer = self.dbMgrData['mapDBInfo'].layers[self.layer]
  456. self.fieldStats.Update(driver = selLayer['driver'],
  457. database = selLayer['database'],
  458. table = selLayer['table'],
  459. column = self.GetColumn(self._col).GetText())
  460. def OnAddColumn(self, event):
  461. """!Add column into table"""
  462. table = self.dbMgrData['mapDBInfo'].layers[self.layer]['table']
  463. dlg = AddColumnDialog(parent = self, title = _('Add column to table <%s>') % table)
  464. if not dlg:
  465. return
  466. if dlg.ShowModal() == wx.ID_OK:
  467. data = dlg.GetData()
  468. self.pages['browse'].AddColumn(name = data['name'],
  469. ctype = data['ctype'],
  470. length = data['length'])
  471. dlg.Destroy()
  472. def SortItems(self, sorter = cmp):
  473. """!Sort items"""
  474. items = list(self.itemDataMap.keys())
  475. items.sort(self.Sorter)
  476. self.itemIndexMap = items
  477. # redraw the list
  478. self.Refresh()
  479. def Sorter(self, key1, key2):
  480. colName = self.GetColumn(self._col).GetText()
  481. ascending = self._colSortFlag[self._col]
  482. try:
  483. item1 = self.columns[colName]["ctype"](self.itemDataMap[key1][self._col])
  484. item2 = self.columns[colName]["ctype"](self.itemDataMap[key2][self._col])
  485. except ValueError:
  486. item1 = self.itemDataMap[key1][self._col]
  487. item2 = self.itemDataMap[key2][self._col]
  488. if type(item1) == types.StringType or type(item2) == types.StringTypes:
  489. cmpVal = locale.strcoll(str(item1), str(item2))
  490. else:
  491. cmpVal = cmp(item1, item2)
  492. # If the items are equal then pick something else to make the sort value unique
  493. if cmpVal == 0:
  494. cmpVal = apply(cmp, self.GetSecondarySortValues(self._col, key1, key2))
  495. if ascending:
  496. return cmpVal
  497. else:
  498. return -cmpVal
  499. def GetSortImages(self):
  500. """!Used by the ColumnSorterMixin, see wx/lib/mixins/listctrl.py"""
  501. return (self.sm_dn, self.sm_up)
  502. def IsEmpty(self):
  503. """!Check if list if empty"""
  504. if self.columns:
  505. return False
  506. return True
  507. class DbMgrBase:
  508. def __init__(self, id = wx.ID_ANY, mapdisplay = None,
  509. vectorName = None, item = None, giface = None,
  510. statusbar = None,
  511. **kwargs):
  512. """!Base class, which enables usage of separate pages of Attribute Table Manager
  513. @param id window id
  514. @param mapdisplay MapFrame instance
  515. @param vetorName name of vector map
  516. @param item item from Layer Tree
  517. @param log log window
  518. @param statusbar widget with statusbar
  519. @param kwagrs other wx.Frame's arguments
  520. """
  521. # stores all data, which are shared by pages
  522. self.dbMgrData = {}
  523. self.dbMgrData['vectName'] = vectorName
  524. self.dbMgrData['treeItem'] = item # item in layer tree
  525. self.mapdisplay = mapdisplay
  526. if self.mapdisplay:
  527. self.map = mapdisplay.Map
  528. else:
  529. self.map = None
  530. if not self.mapdisplay:
  531. pass
  532. elif self.mapdisplay.tree and \
  533. self.dbMgrData['treeItem'] and not self.dbMgrData['vectName']:
  534. maptree = self.mapdisplay.tree
  535. name = maptree.GetLayerInfo(self.dbMgrData['treeItem'], key = 'maplayer').GetName()
  536. self.dbMgrData['vectName'] = name
  537. # vector attributes can be changed only if vector map is in
  538. # the current mapset
  539. mapInfo = None
  540. if self.dbMgrData['vectName']:
  541. mapInfo = grass.find_file(name = self.dbMgrData['vectName'], element = 'vector')
  542. if not mapInfo or mapInfo['mapset'] != grass.gisenv()['MAPSET']:
  543. self.dbMgrData['editable'] = False
  544. else:
  545. self.dbMgrData['editable'] = True
  546. self.giface = giface
  547. # status bar log class
  548. self.log = Log(statusbar) # -> statusbar
  549. # -> layers / tables description
  550. self.dbMgrData['mapDBInfo'] = VectorDBInfo(self.dbMgrData['vectName'])
  551. # store information, which pages were initialized
  552. self.pages = {
  553. 'browse' : None,
  554. 'manageTable' : None,
  555. 'manageLayer' : None
  556. }
  557. def ChangeVectorMap(self, vectorName):
  558. """!Change of vector map
  559. Does not import layers of new vector map into pages.
  560. For the import use methods addLayer in DbMgrBrowsePage and DbMgrTablesPage
  561. """
  562. if self.pages['browse']:
  563. self.pages['browse'].DeleteAllPages()
  564. if self.pages['manageTable']:
  565. self.pages['manageTable'].DeleteAllPages()
  566. self.dbMgrData['vectName'] = vectorName
  567. # fetch fresh db info
  568. self.dbMgrData['mapDBInfo'] = VectorDBInfo(self.dbMgrData['vectName'])
  569. # vector attributes can be changed only if vector map is in
  570. # the current mapset
  571. mapInfo = grass.find_file(name = self.dbMgrData['vectName'], element = 'vector')
  572. if not mapInfo or mapInfo['mapset'] != grass.gisenv()['MAPSET']:
  573. self.dbMgrData['editable'] = False
  574. else:
  575. self.dbMgrData['editable'] = True
  576. # 'manage layers page
  577. if self.pages['manageLayer']:
  578. self.pages['manageLayer'].UpdatePage()
  579. def CreateDbMgrPage(self, parent, pageName, onlyLayer = -1):
  580. """!Creates chosen page
  581. @param pageName can be 'browse' or 'manageTable' or 'manageLayer' which corresponds with pages in
  582. Attribute Table Manager
  583. @return created instance of page - if the page has been already created returns the previously created instance
  584. @return None if wrong identifier was passed
  585. """
  586. if pageName == 'browse':
  587. if not self.pages['browse']:
  588. self.pages[pageName] = DbMgrBrowsePage(parent = parent, parentDbMgrBase = self,
  589. onlyLayer = onlyLayer)
  590. return self.pages[pageName]
  591. if pageName == 'manageTable':
  592. if not self.pages['manageTable']:
  593. self.pages[pageName] = DbMgrTablesPage(parent = parent, parentDbMgrBase = self,
  594. onlyLayer = onlyLayer)
  595. return self.pages[pageName]
  596. if pageName == 'manageLayer':
  597. if not self.pages['manageLayer']:
  598. self.pages[pageName] = DbMgrLayersPage(parent = parent, parentDbMgrBase = self)
  599. return self.pages[pageName]
  600. return None
  601. def UpdateDialog(self, layer):
  602. """!Updates dialog layout for given layer"""
  603. # delete page
  604. if layer in self.dbMgrData['mapDBInfo'].layers.keys():
  605. # delete page
  606. # draging pages disallowed
  607. # if self.browsePage.GetPageText(page).replace('Layer ', '').strip() == str(layer):
  608. # self.browsePage.DeletePage(page)
  609. # break
  610. if self.pages['browse']:
  611. self.pages['browse'].DeletePage(layer)
  612. if self.pages['manageTable']:
  613. self.pages['manageTable'].DeletePage(layer)
  614. # fetch fresh db info
  615. self.dbMgrData['mapDBInfo'] = VectorDBInfo(self.dbMgrData['vectName'])
  616. #
  617. # add new page
  618. #
  619. if layer in self.dbMgrData['mapDBInfo'].layers.keys():
  620. # 'browse data' page
  621. if self.pages['browse']:
  622. self.pages['browse'].AddLayer(layer)
  623. # 'manage tables' page
  624. if self.pages['manageTable']:
  625. self.pages['manageTable'].AddLayer(layer)
  626. # manage layers page
  627. if self.pages['manageLayer']:
  628. self.pages['manageLayer'].UpdatePage()
  629. def GetVectorName(self):
  630. """!Get vector name"""
  631. return self.dbMgrData['vectName']
  632. def GetVectorLayers(self):
  633. """!Get layers of vector map which have table"""
  634. return self.dbMgrData['mapDBInfo'].layers.keys()
  635. class DbMgrNotebookBase(FN.FlatNotebook):
  636. def __init__(self, parent, parentDbMgrBase):
  637. """!Base class for notebook with attribute tables in tabs
  638. @param parent GUI parent
  639. @param parentDbMgrBase instance of DbMgrBase class
  640. """
  641. self.parent = parent
  642. self.parentDbMgrBase = parentDbMgrBase
  643. self.log = self.parentDbMgrBase.log
  644. self.giface = self.parentDbMgrBase.giface
  645. self.map = self.parentDbMgrBase.map
  646. self.mapdisplay = self.parentDbMgrBase.mapdisplay
  647. #TODO no need to have it in class scope make it local?
  648. self.listOfCommands = []
  649. self.listOfSQLStatements = []
  650. #initializet pages
  651. self.pages = self.parentDbMgrBase.pages
  652. # shared data among pages
  653. self.dbMgrData = self.parentDbMgrBase.dbMgrData
  654. # set up virtual lists (each layer)
  655. ### {layer: list, widgets...}
  656. self.layerPage = {}
  657. # currently selected layer
  658. self.selLayer = None
  659. # list which represents layers numbers in order of tabs
  660. self.layers = []
  661. if globalvar.hasAgw:
  662. dbmStyle = { 'agwStyle' : globalvar.FNPageStyle }
  663. else:
  664. dbmStyle = { 'style' : globalvar.FNPageStyle }
  665. FN.FlatNotebook.__init__(self,parent = self.parent, id = wx.ID_ANY,
  666. **dbmStyle)
  667. self.Bind(FN.EVT_FLATNOTEBOOK_PAGE_CHANGED, self.OnLayerPageChanged)
  668. def OnLayerPageChanged(self, event):
  669. """!Layer tab changed"""
  670. # because of SQL Query notebook
  671. if event.GetEventObject() != self:
  672. return
  673. pageNum = self.GetSelection()
  674. self.selLayer = self.layers[pageNum]
  675. try:
  676. idCol = self.layerPage[self.selLayer]['whereColumn']
  677. except KeyError:
  678. idCol = None
  679. try:
  680. # update statusbar
  681. self.log.write(_("Number of loaded records: %d") % \
  682. self.FindWindowById(self.layerPage[self.selLayer]['data']).\
  683. GetItemCount())
  684. except:
  685. pass
  686. if idCol:
  687. winCol = self.FindWindowById(idCol)
  688. table = self.dbMgrData['mapDBInfo'].layers[self.selLayer]["table"]
  689. self.dbMgrData['mapDBInfo'].GetColumns(table)
  690. def ApplyCommands(self, listOfCommands, listOfSQLStatements):
  691. """!Apply changes
  692. @todo: this part should be _completely_ redesigned
  693. """
  694. # perform GRASS commands (e.g. v.db.addcolumn)
  695. wx.BeginBusyCursor()
  696. if len(listOfCommands) > 0:
  697. for cmd in listOfCommands:
  698. RunCommand(prog = cmd[0],
  699. quiet = True,
  700. parent = self,
  701. **cmd[1])
  702. self.dbMgrData['mapDBInfo'] = VectorDBInfo(self.dbMgrData['vectName'])
  703. if self.pages['manageTable']:
  704. self.pages['manageTable'].UpdatePage(self.selLayer)
  705. if self.pages['browse']:
  706. self.pages['browse'].UpdatePage(self.selLayer)
  707. # reset list of commands
  708. listOfCommands = []
  709. # perform SQL non-select statements (e.g. 'delete from table where cat=1')
  710. if len(listOfSQLStatements) > 0:
  711. fd, sqlFilePath = tempfile.mkstemp(text=True)
  712. sqlFile = open(sqlFilePath, 'w')
  713. for sql in listOfSQLStatements:
  714. enc = UserSettings.Get(group = 'atm', key = 'encoding', subkey = 'value')
  715. if not enc and 'GRASS_DB_ENCODING' in os.environ:
  716. enc = os.environ['GRASS_DB_ENCODING']
  717. if enc:
  718. sqlFile.write(sql.encode(enc) + ';')
  719. else:
  720. sqlFile.write(sql.encode('utf-8') + ';')
  721. sqlFile.write(os.linesep)
  722. sqlFile.close()
  723. driver = self.dbMgrData['mapDBInfo'].layers[self.selLayer]["driver"]
  724. database = self.dbMgrData['mapDBInfo'].layers[self.selLayer]["database"]
  725. Debug.msg(3, 'AttributeManger.ApplyCommands(): %s' %
  726. ';'.join(["%s" % s for s in listOfSQLStatements]))
  727. RunCommand('db.execute',
  728. parent = self,
  729. input = sqlFilePath,
  730. driver = driver,
  731. database = database)
  732. os.close(fd)
  733. os.remove(sqlFilePath)
  734. # reset list of statements
  735. self.listOfSQLStatements = []
  736. wx.EndBusyCursor()
  737. def DeletePage(self, layer):
  738. """!Removes layer page"""
  739. if layer not in self.layers:
  740. return False
  741. FN.FlatNotebook.DeletePage(self, self.layers.index(layer))
  742. self.layers.remove(layer)
  743. del self.layerPage[layer]
  744. if self.GetSelection() >= 0:
  745. self.selLayer = self.layers[self.GetSelection()]
  746. else:
  747. self.selLayer = None
  748. return True
  749. def DeleteAllPages(self):
  750. """!Removes all layer pages"""
  751. FN.FlatNotebook.DeleteAllPages(self)
  752. self.layerPage = {}
  753. self.layers = []
  754. self.selLayer = None
  755. def AddColumn(self, name, ctype, length):
  756. """!Add new column to the table"""
  757. table = self.dbMgrData['mapDBInfo'].layers[self.selLayer]['table']
  758. if not name:
  759. GError(parent = self,
  760. message = _("Unable to add column to the table. "
  761. "No column name defined."))
  762. return False
  763. # cast type if needed
  764. if ctype == 'double':
  765. ctype = 'double precision'
  766. if ctype != 'varchar':
  767. length = '' # FIXME
  768. # check for duplicate items
  769. if name in self.dbMgrData['mapDBInfo'].GetColumns(table):
  770. GError(parent = self,
  771. message = _("Column <%(column)s> already exists in table <%(table)s>.") % \
  772. {'column' : name, 'table' : self.dbMgrData['mapDBInfo'].layers[self.selLayer]["table"]}
  773. )
  774. return False
  775. # add v.db.addcolumn command to the list
  776. if ctype == 'varchar':
  777. ctype += ' (%d)' % length
  778. self.listOfCommands.append(('v.db.addcolumn',
  779. { 'map' : self.dbMgrData['vectName'],
  780. 'layer' : self.selLayer,
  781. 'columns' : '%s %s' % (name, ctype) }
  782. ))
  783. # apply changes
  784. self.ApplyCommands(self.listOfCommands, self.listOfSQLStatements)
  785. return True
  786. def GetAddedLayers(self):
  787. """!Get list of added layers"""
  788. return self.layers[:]
  789. class DbMgrBrowsePage(DbMgrNotebookBase):
  790. def __init__(self, parent, parentDbMgrBase, onlyLayer = -1):
  791. """!Browse page class
  792. @param parent GUI parent
  793. @param parentDbMgrBase instance of DbMgrBase class
  794. @param onlyLayer create only tab of given layer, if -1 creates tabs of all layers
  795. """
  796. DbMgrNotebookBase.__init__(self, parent = parent,
  797. parentDbMgrBase = parentDbMgrBase)
  798. # for Sql Query notebook adaptation on current width
  799. self.sqlBestSize = None
  800. for layer in self.dbMgrData['mapDBInfo'].layers.keys():
  801. if onlyLayer > 0 and layer != onlyLayer:
  802. continue
  803. self.AddLayer(layer)
  804. if self.layers:
  805. self.SetSelection(0)
  806. self.selLayer = self.layers[0]
  807. self.log.write(_("Number of loaded records: %d") % \
  808. self.FindWindowById(self.layerPage[self.selLayer]['data']).GetItemCount())
  809. # query map layer (if parent (GMFrame) is given)
  810. self.qlayer = None
  811. # sqlbuilder
  812. self.builder = None
  813. def AddLayer(self, layer, pos = -1):
  814. """!Adds tab which represents table and enables browse it
  815. @param layer vector map layer conntected to table
  816. @param pos position of tab, if -1 it is added to end
  817. @return True if layer was added
  818. @return False if layer was not added - layer has been already added or has empty table or does not exist
  819. """
  820. if layer in self.layers or \
  821. layer not in self.parentDbMgrBase.GetVectorLayers():
  822. return False
  823. panel = wx.Panel(parent = self, id = wx.ID_ANY)
  824. #IMPORTANT NOTE: wx.StaticBox MUST be defined BEFORE any of the
  825. # controls that are placed IN the wx.StaticBox, or it will freeze
  826. # on the Mac
  827. listBox = wx.StaticBox(parent = panel, id = wx.ID_ANY,
  828. label = " %s " % _("Attribute data - right-click to edit/manage records"))
  829. listSizer = wx.StaticBoxSizer(listBox, wx.VERTICAL)
  830. win = VirtualAttributeList(panel, self.log,
  831. self.dbMgrData, layer, self.pages)
  832. if win.IsEmpty():
  833. panel.Destroy()
  834. return False
  835. self.layers.append(layer)
  836. win.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self.OnDataItemActivated)
  837. self.layerPage[layer] = {'browsePage': panel.GetId()}
  838. label = _("Table")
  839. if not self.dbMgrData['editable']:
  840. label += _(" (readonly)")
  841. if pos == -1:
  842. pos = self.GetPageCount()
  843. self.InsertPage(pos, page = panel,
  844. text = " %d / %s %s" % \
  845. (layer, label, self.dbMgrData['mapDBInfo'].layers[layer]['table']))
  846. pageSizer = wx.BoxSizer(wx.VERTICAL)
  847. sqlQueryPanel = wx.Panel(parent = panel, id = wx.ID_ANY)
  848. # attribute data
  849. sqlBox = wx.StaticBox(parent = sqlQueryPanel, id = wx.ID_ANY,
  850. label = " %s " % _("SQL Query"))
  851. sqlSizer = wx.StaticBoxSizer(sqlBox, wx.VERTICAL)
  852. win.Bind(wx.EVT_COMMAND_RIGHT_CLICK, self.OnDataRightUp) #wxMSW
  853. win.Bind(wx.EVT_RIGHT_UP, self.OnDataRightUp) #wxGTK
  854. if UserSettings.Get(group = 'atm', key = 'leftDbClick', subkey = 'selection') == 0:
  855. win.Bind(wx.EVT_LEFT_DCLICK, self.OnDataItemEdit)
  856. win.Bind(wx.EVT_COMMAND_LEFT_DCLICK, self.OnDataItemEdit)
  857. else:
  858. win.Bind(wx.EVT_LEFT_DCLICK, self.OnDataDrawSelected)
  859. win.Bind(wx.EVT_COMMAND_LEFT_DCLICK, self.OnDataDrawSelected)
  860. listSizer.Add(item = win, proportion = 1,
  861. flag = wx.EXPAND | wx.ALL,
  862. border = 3)
  863. # sql statement box
  864. FNPageStyle = FN.FNB_NO_NAV_BUTTONS | \
  865. FN.FNB_NO_X_BUTTON
  866. if globalvar.hasAgw:
  867. dbmStyle = { 'agwStyle' : FNPageStyle }
  868. else:
  869. dbmStyle = { 'style' : FNPageStyle }
  870. sqlNtb = FN.FlatNotebook(parent = sqlQueryPanel, id = wx.ID_ANY,
  871. **dbmStyle)
  872. # Simple tab
  873. simpleSqlPanel = wx.Panel(parent = sqlNtb, id = wx.ID_ANY)
  874. sqlNtb.AddPage(page = simpleSqlPanel,
  875. text = _('Simple'))
  876. btnApply = wx.Button(parent = simpleSqlPanel, id = wx.ID_APPLY, name = 'btnApply')
  877. btnApply.SetToolTipString(_("Apply SELECT statement and reload data records"))
  878. btnApply.Bind(wx.EVT_BUTTON, self.OnApplySqlStatement)
  879. whereSimpleSqlPanel = wx.Panel(parent = simpleSqlPanel, id = wx.ID_ANY, name = 'wherePanel')
  880. sqlWhereColumn = wx.ComboBox(parent = whereSimpleSqlPanel, id = wx.ID_ANY,
  881. size = (150,-1),
  882. style = wx.CB_SIMPLE | wx.CB_READONLY,
  883. choices = self.dbMgrData['mapDBInfo'].GetColumns(self.dbMgrData['mapDBInfo'].layers[layer]['table']))
  884. sqlWhereColumn.SetSelection(0)
  885. sqlWhereCond = wx.Choice(parent = whereSimpleSqlPanel, id = wx.ID_ANY,
  886. size = (55,-1),
  887. choices = ['=', '!=', '<', '<=', '>', '>='])
  888. sqlWhereValue = wx.TextCtrl(parent = whereSimpleSqlPanel, id = wx.ID_ANY, value = "",
  889. style = wx.TE_PROCESS_ENTER)
  890. sqlWhereValue.SetToolTipString(_("Example: %s") % "MULTILANE = 'no' AND OBJECTID < 10")
  891. sqlLabel = wx.StaticText(parent = simpleSqlPanel, id = wx.ID_ANY,
  892. label = "SELECT * FROM %s WHERE " % \
  893. self.dbMgrData['mapDBInfo'].layers[layer]['table'])
  894. # Advanced tab
  895. advancedSqlPanel = wx.Panel(parent = sqlNtb, id = wx.ID_ANY)
  896. sqlNtb.AddPage(page = advancedSqlPanel,
  897. text = _('Builder'))
  898. btnSqlBuilder = wx.Button(parent = advancedSqlPanel, id = wx.ID_ANY, label = _("SQL Builder"))
  899. btnSqlBuilder.Bind(wx.EVT_BUTTON, self.OnBuilder)
  900. sqlStatement = wx.TextCtrl(parent = advancedSqlPanel, id = wx.ID_ANY,
  901. value = "SELECT * FROM %s" % \
  902. self.dbMgrData['mapDBInfo'].layers[layer]['table'],
  903. style = wx.TE_PROCESS_ENTER)
  904. sqlStatement.SetToolTipString(_("Example: %s") % "SELECT * FROM roadsmajor WHERE MULTILANE = 'no' AND OBJECTID < 10")
  905. sqlWhereValue.Bind(wx.EVT_TEXT_ENTER, self.OnApplySqlStatement)
  906. sqlStatement.Bind(wx.EVT_TEXT_ENTER, self.OnApplySqlStatement)
  907. # Simple tab layout
  908. simpleSqlSizer = wx.GridBagSizer (hgap = 5, vgap = 5)
  909. sqlSimpleWhereSizer= wx.BoxSizer(wx.HORIZONTAL)
  910. sqlSimpleWhereSizer.Add(item = sqlWhereColumn,
  911. flag = wx.EXPAND | wx.ALIGN_CENTER_VERTICAL| wx.LEFT,
  912. border = 3)
  913. sqlSimpleWhereSizer.Add(item = sqlWhereCond,
  914. flag = wx.EXPAND | wx.ALIGN_CENTER_VERTICAL | wx.LEFT,
  915. border = 3)
  916. sqlSimpleWhereSizer.Add(item = sqlWhereValue, proportion = 1,
  917. flag = wx.EXPAND | wx.ALIGN_CENTER_VERTICAL | wx.LEFT,
  918. border = 3)
  919. whereSimpleSqlPanel.SetSizer(sqlSimpleWhereSizer)
  920. simpleSqlSizer.Add(item = sqlLabel, border = 5, pos = (0, 0),
  921. flag = wx.ALIGN_CENTER_VERTICAL | wx.TOP | wx.LEFT)
  922. simpleSqlSizer.Add(item = whereSimpleSqlPanel, border = 5, pos = (0, 1),
  923. flag = wx.ALIGN_CENTER_VERTICAL | wx.TOP | wx.EXPAND)
  924. simpleSqlSizer.Add(item = btnApply, border = 5, pos = (0, 2),
  925. flag = wx.ALIGN_CENTER_VERTICAL | wx.TOP)
  926. simpleSqlSizer.AddGrowableCol(1)
  927. simpleSqlPanel.SetSizer(simpleSqlSizer)
  928. # Advanced tab layout
  929. advancedSqlSizer = wx.FlexGridSizer (cols = 2, hgap = 5, vgap = 5)
  930. advancedSqlSizer.AddGrowableCol(0)
  931. advancedSqlSizer.Add(item = sqlStatement,
  932. flag = wx.EXPAND | wx.ALL, border = 5)
  933. advancedSqlSizer.Add(item = btnSqlBuilder,
  934. flag = wx.ALIGN_RIGHT | wx.TOP | wx.RIGHT | wx.BOTTOM, border = 5)
  935. sqlSizer.Add(item = sqlNtb,
  936. flag = wx.ALL | wx.EXPAND,
  937. border = 3)
  938. advancedSqlPanel.SetSizer(advancedSqlSizer)
  939. pageSizer.Add(item = listSizer,
  940. proportion = 1,
  941. flag = wx.ALL | wx.EXPAND,
  942. border = 5)
  943. sqlQueryPanel.SetSizer(sqlSizer)
  944. pageSizer.Add(item = sqlQueryPanel,
  945. proportion = 0,
  946. flag = wx.BOTTOM | wx.LEFT | wx.RIGHT | wx.EXPAND,
  947. border = 5)
  948. panel.SetSizer(pageSizer)
  949. sqlNtb.Bind(wx.EVT_SIZE, self.OnSqlQuerySizeWrap(layer))
  950. self.layerPage[layer]['data'] = win.GetId()
  951. self.layerPage[layer]['sqlNtb'] = sqlNtb.GetId()
  952. self.layerPage[layer]['whereColumn'] = sqlWhereColumn.GetId()
  953. self.layerPage[layer]['whereOperator'] = sqlWhereCond.GetId()
  954. self.layerPage[layer]['where'] = sqlWhereValue.GetId()
  955. self.layerPage[layer]['builder'] = btnSqlBuilder.GetId()
  956. self.layerPage[layer]['statement'] = sqlStatement.GetId()
  957. self.layerPage[layer]['sqlIsReduced'] = False # for SQL Query adaptation on width
  958. return True
  959. def OnSqlQuerySizeWrap(self, layer):
  960. """!Helper function"""
  961. return lambda event : self.OnSqlQuerySize(event, layer)
  962. def OnSqlQuerySize(self, event, layer):
  963. """!Adapts SQL Query Simple tab on current width"""
  964. sqlNtb = event.GetEventObject()
  965. if not self.sqlBestSize:
  966. self.sqlBestSize = sqlNtb.GetBestSize()
  967. size = sqlNtb.GetSize()
  968. sqlReduce = self.sqlBestSize[0] > size[0]
  969. if (sqlReduce and self.layerPage[layer]['sqlIsReduced']) or \
  970. (not sqlReduce and not self.layerPage[layer]['sqlIsReduced']):
  971. event.Skip()
  972. return
  973. wherePanel = sqlNtb.FindWindowByName('wherePanel')
  974. btnApply = sqlNtb.FindWindowByName('btnApply')
  975. sqlSimpleSizer = btnApply.GetContainingSizer()
  976. if sqlReduce:
  977. self.layerPage[layer]['sqlIsReduced'] = True
  978. sqlSimpleSizer.AddGrowableCol(0)
  979. sqlSimpleSizer.RemoveGrowableCol(1)
  980. sqlSimpleSizer.SetItemPosition(wherePanel, (1, 0))
  981. sqlSimpleSizer.SetItemPosition(btnApply, (1, 1))
  982. else:
  983. self.layerPage[layer]['sqlIsReduced'] = False
  984. sqlSimpleSizer.AddGrowableCol(1)
  985. sqlSimpleSizer.RemoveGrowableCol(0)
  986. sqlSimpleSizer.SetItemPosition(wherePanel, (0, 1))
  987. sqlSimpleSizer.SetItemPosition(btnApply, (0, 2))
  988. event.Skip()
  989. def OnDataItemActivated(self, event):
  990. """!Item activated, highlight selected item"""
  991. self.OnDataDrawSelected(event)
  992. event.Skip()
  993. def OnDataRightUp(self, event):
  994. """!Table description area, context menu"""
  995. if not hasattr(self, "popupDataID1"):
  996. self.popupDataID1 = wx.NewId()
  997. self.popupDataID2 = wx.NewId()
  998. self.popupDataID3 = wx.NewId()
  999. self.popupDataID4 = wx.NewId()
  1000. self.popupDataID5 = wx.NewId()
  1001. self.popupDataID6 = wx.NewId()
  1002. self.popupDataID7 = wx.NewId()
  1003. self.popupDataID8 = wx.NewId()
  1004. self.popupDataID9 = wx.NewId()
  1005. self.popupDataID10 = wx.NewId()
  1006. self.popupDataID11 = wx.NewId()
  1007. self.Bind(wx.EVT_MENU, self.OnDataItemEdit, id = self.popupDataID1)
  1008. self.Bind(wx.EVT_MENU, self.OnDataItemAdd, id = self.popupDataID2)
  1009. self.Bind(wx.EVT_MENU, self.OnDataItemDelete, id = self.popupDataID3)
  1010. self.Bind(wx.EVT_MENU, self.OnDataItemDeleteAll, id = self.popupDataID4)
  1011. self.Bind(wx.EVT_MENU, self.OnDataSelectAll, id = self.popupDataID5)
  1012. self.Bind(wx.EVT_MENU, self.OnDataSelectNone, id = self.popupDataID6)
  1013. self.Bind(wx.EVT_MENU, self.OnDataDrawSelected, id = self.popupDataID7)
  1014. self.Bind(wx.EVT_MENU, self.OnDataDrawSelectedZoom, id = self.popupDataID8)
  1015. self.Bind(wx.EVT_MENU, self.OnExtractSelected, id = self.popupDataID9)
  1016. self.Bind(wx.EVT_MENU, self.OnDeleteSelected, id = self.popupDataID11)
  1017. self.Bind(wx.EVT_MENU, self.OnDataReload, id = self.popupDataID10)
  1018. tlist = self.FindWindowById(self.layerPage[self.selLayer]['data'])
  1019. # generate popup-menu
  1020. menu = wx.Menu()
  1021. menu.Append(self.popupDataID1, _("Edit selected record"))
  1022. selected = tlist.GetFirstSelected()
  1023. if not self.dbMgrData['editable'] or selected == -1 or tlist.GetNextSelected(selected) != -1:
  1024. menu.Enable(self.popupDataID1, False)
  1025. menu.Append(self.popupDataID2, _("Insert new record"))
  1026. menu.Append(self.popupDataID3, _("Delete selected record(s)"))
  1027. menu.Append(self.popupDataID4, _("Delete all records"))
  1028. if not self.dbMgrData['editable']:
  1029. menu.Enable(self.popupDataID2, False)
  1030. menu.Enable(self.popupDataID3, False)
  1031. menu.Enable(self.popupDataID4, False)
  1032. menu.AppendSeparator()
  1033. menu.Append(self.popupDataID5, _("Select all"))
  1034. menu.Append(self.popupDataID6, _("Deselect all"))
  1035. menu.AppendSeparator()
  1036. menu.Append(self.popupDataID7, _("Highlight selected features"))
  1037. menu.Append(self.popupDataID8, _("Highlight selected features and zoom"))
  1038. if not self.map or len(tlist.GetSelectedItems()) == 0:
  1039. menu.Enable(self.popupDataID7, False)
  1040. menu.Enable(self.popupDataID8, False)
  1041. menu.Append(self.popupDataID9, _("Extract selected features"))
  1042. menu.Append(self.popupDataID11, _("Delete selected features"))
  1043. if not self.dbMgrData['editable']:
  1044. menu.Enable(self.popupDataID11, False)
  1045. if tlist.GetFirstSelected() == -1:
  1046. menu.Enable(self.popupDataID3, False)
  1047. menu.Enable(self.popupDataID9, False)
  1048. menu.Enable(self.popupDataID11, False)
  1049. menu.AppendSeparator()
  1050. menu.Append(self.popupDataID10, _("Reload"))
  1051. self.PopupMenu(menu)
  1052. menu.Destroy()
  1053. # update statusbar
  1054. self.log.write(_("Number of loaded records: %d") % \
  1055. tlist.GetItemCount())
  1056. def OnDataItemEdit(self, event):
  1057. """!Edit selected record of the attribute table"""
  1058. tlist = self.FindWindowById(self.layerPage[self.selLayer]['data'])
  1059. item = tlist.GetFirstSelected()
  1060. if item == -1:
  1061. return
  1062. table = self.dbMgrData['mapDBInfo'].layers[self.selLayer]['table']
  1063. keyColumn = self.dbMgrData['mapDBInfo'].layers[self.selLayer]['key']
  1064. cat = tlist.itemCatsMap[tlist.itemIndexMap[item]]
  1065. # (column name, value)
  1066. data = []
  1067. # collect names of all visible columns
  1068. columnName = []
  1069. for i in range(tlist.GetColumnCount()):
  1070. columnName.append(tlist.GetColumn(i).GetText())
  1071. # key column must be always presented
  1072. if keyColumn not in columnName:
  1073. columnName.insert(0, keyColumn) # insert key column on first position
  1074. data.append((keyColumn, str(cat)))
  1075. keyId = 0
  1076. missingKey = True
  1077. else:
  1078. missingKey = False
  1079. # add other visible columns
  1080. for i in range(len(columnName)):
  1081. ctype = self.dbMgrData['mapDBInfo'].tables[table][columnName[i]]['ctype']
  1082. ctypeStr = self.dbMgrData['mapDBInfo'].tables[table][columnName[i]]['type']
  1083. if columnName[i] == keyColumn: # key
  1084. if missingKey is False:
  1085. data.append((columnName[i], ctype, ctypeStr, str(cat)))
  1086. keyId = i
  1087. else:
  1088. if missingKey is True:
  1089. value = tlist.GetItem(item, i-1).GetText()
  1090. else:
  1091. value = tlist.GetItem(item, i).GetText()
  1092. data.append((columnName[i], ctype, ctypeStr, value))
  1093. dlg = ModifyTableRecord(parent = self,
  1094. title = _("Update existing record"),
  1095. data = data, keyEditable = (keyId, False))
  1096. if dlg.ShowModal() == wx.ID_OK:
  1097. values = dlg.GetValues() # string
  1098. updateList = list()
  1099. try:
  1100. for i in range(len(values)):
  1101. if i == keyId: # skip key column
  1102. continue
  1103. if tlist.GetItem(item, i).GetText() == values[i]:
  1104. continue # no change
  1105. column = tlist.columns[columnName[i]]
  1106. if len(values[i]) > 0:
  1107. try:
  1108. if missingKey is True:
  1109. idx = i - 1
  1110. else:
  1111. idx = i
  1112. if column['ctype'] != types.StringType:
  1113. tlist.itemDataMap[item][idx] = column['ctype'] (values[i])
  1114. else: # -> string
  1115. tlist.itemDataMap[item][idx] = values[i]
  1116. except ValueError:
  1117. raise ValueError(_("Value '%(value)s' needs to be entered as %(type)s.") % \
  1118. {'value' : str(values[i]),
  1119. 'type' : column['type']})
  1120. if column['ctype'] == types.StringType:
  1121. if "'" in values[i]: # replace "'" -> "''"
  1122. values[i] = values[i].replace("'", "''")
  1123. updateList.append("%s='%s'" % (columnName[i], values[i]))
  1124. else:
  1125. updateList.append("%s=%s" % (columnName[i], values[i]))
  1126. else: # -> NULL
  1127. updateList.append("%s=NULL" % (columnName[i]))
  1128. except ValueError as err:
  1129. GError(parent = self,
  1130. message = _("Unable to update existing record.\n%s") % err,
  1131. showTraceback = False)
  1132. self.OnDataItemEdit(event)
  1133. return
  1134. if updateList:
  1135. self.listOfSQLStatements.append('UPDATE %s SET %s WHERE %s=%d' % \
  1136. (table, ','.join(updateList),
  1137. keyColumn, cat))
  1138. self.ApplyCommands(self.listOfCommands, self.listOfSQLStatements)
  1139. tlist.Update()
  1140. def OnDataItemAdd(self, event):
  1141. """!Add new record to the attribute table"""
  1142. tlist = self.FindWindowById(self.layerPage[self.selLayer]['data'])
  1143. table = self.dbMgrData['mapDBInfo'].layers[self.selLayer]['table']
  1144. keyColumn = self.dbMgrData['mapDBInfo'].layers[self.selLayer]['key']
  1145. # (column name, value)
  1146. data = []
  1147. # collect names of all visible columns
  1148. columnName = []
  1149. for i in range(tlist.GetColumnCount()):
  1150. columnName.append(tlist.GetColumn(i).GetText())
  1151. # maximal category number
  1152. if len(tlist.itemCatsMap.values()) > 0:
  1153. maxCat = max(tlist.itemCatsMap.values())
  1154. else:
  1155. maxCat = 0 # starting category '1'
  1156. # key column must be always presented
  1157. if keyColumn not in columnName:
  1158. columnName.insert(0, keyColumn) # insert key column on first position
  1159. data.append((keyColumn, str(maxCat + 1)))
  1160. missingKey = True
  1161. else:
  1162. missingKey = False
  1163. # add other visible columns
  1164. colIdx = 0
  1165. keyId = -1
  1166. for col in columnName:
  1167. ctype = self.dbMgrData['mapDBInfo'].tables[table][col]['ctype']
  1168. ctypeStr = self.dbMgrData['mapDBInfo'].tables[table][col]['type']
  1169. if col == keyColumn: # key
  1170. if missingKey is False:
  1171. data.append((col, ctype, ctypeStr, str(maxCat + 1)))
  1172. keyId = colIdx
  1173. else:
  1174. data.append((col, ctype, ctypeStr, ''))
  1175. colIdx += 1
  1176. dlg = ModifyTableRecord(parent = self,
  1177. title = _("Insert new record"),
  1178. data = data, keyEditable = (keyId, True))
  1179. if dlg.ShowModal() == wx.ID_OK:
  1180. try: # get category number
  1181. cat = int(dlg.GetValues(columns = [keyColumn])[0])
  1182. except:
  1183. cat = -1
  1184. try:
  1185. if cat in tlist.itemCatsMap.values():
  1186. raise ValueError(_("Record with category number %d "
  1187. "already exists in the table.") % cat)
  1188. values = dlg.GetValues() # values (need to be casted)
  1189. columnsString = ''
  1190. valuesString = ''
  1191. for i in range(len(values)):
  1192. if len(values[i]) == 0: # NULL
  1193. if columnName[i] == keyColumn:
  1194. raise ValueError(_("Category number (column %s)"
  1195. " is missing.") % keyColumn)
  1196. else:
  1197. continue
  1198. try:
  1199. if tlist.columns[columnName[i]]['ctype'] == int:
  1200. # values[i] is stored as text.
  1201. values[i] = int(float(values[i]))
  1202. elif tlist.columns[columnName[i]]['ctype'] == float:
  1203. values[i] = float(values[i])
  1204. except:
  1205. raise ValueError(_("Value '%(value)s' needs to be entered as %(type)s.") %
  1206. {'value': values[i],
  1207. 'type': tlist.columns[columnName[i]]['type']})
  1208. columnsString += '%s,' % columnName[i]
  1209. if tlist.columns[columnName[i]]['ctype'] == str:
  1210. valuesString += "'%s'," % values[i]
  1211. else:
  1212. valuesString += "%s," % values[i]
  1213. except ValueError as err:
  1214. GError(parent = self,
  1215. message = _("Unable to insert new record.\n%s") % err,
  1216. showTraceback = False)
  1217. self.OnDataItemAdd(event)
  1218. return
  1219. # remove category if need
  1220. if missingKey is True:
  1221. del values[0]
  1222. # add new item to the tlist
  1223. if len(tlist.itemIndexMap) > 0:
  1224. index = max(tlist.itemIndexMap) + 1
  1225. else:
  1226. index = 0
  1227. tlist.itemIndexMap.append(index)
  1228. tlist.itemDataMap[index] = values
  1229. tlist.itemCatsMap[index] = cat
  1230. tlist.SetItemCount(tlist.GetItemCount() + 1)
  1231. self.listOfSQLStatements.append('INSERT INTO %s (%s) VALUES(%s)' % \
  1232. (table,
  1233. columnsString.rstrip(','),
  1234. valuesString.rstrip(',')))
  1235. self.ApplyCommands(self.listOfCommands, self.listOfSQLStatements)
  1236. def OnDataItemDelete(self, event):
  1237. """!Delete selected item(s) from the tlist (layer/category pair)"""
  1238. dlist = self.FindWindowById(self.layerPage[self.selLayer]['data'])
  1239. item = dlist.GetFirstSelected()
  1240. table = self.dbMgrData['mapDBInfo'].layers[self.selLayer]["table"]
  1241. key = self.dbMgrData['mapDBInfo'].layers[self.selLayer]["key"]
  1242. indeces = []
  1243. # collect SQL statements
  1244. while item != -1:
  1245. index = dlist.itemIndexMap[item]
  1246. indeces.append(index)
  1247. cat = dlist.itemCatsMap[index]
  1248. self.listOfSQLStatements.append('DELETE FROM %s WHERE %s=%d' % \
  1249. (table, key, cat))
  1250. item = dlist.GetNextSelected(item)
  1251. if UserSettings.Get(group = 'atm', key = 'askOnDeleteRec', subkey = 'enabled'):
  1252. deleteDialog = wx.MessageBox(parent = self,
  1253. message = _("Selected data records (%d) will be permanently deleted "
  1254. "from table. Do you want to delete them?") % \
  1255. (len(self.listOfSQLStatements)),
  1256. caption = _("Delete records"),
  1257. style = wx.YES_NO | wx.CENTRE)
  1258. if deleteDialog != wx.YES:
  1259. self.listOfSQLStatements = []
  1260. return False
  1261. # restore maps
  1262. i = 0
  1263. indexTemp = copy.copy(dlist.itemIndexMap)
  1264. dlist.itemIndexMap = []
  1265. dataTemp = copy.deepcopy(dlist.itemDataMap)
  1266. dlist.itemDataMap = {}
  1267. catsTemp = copy.deepcopy(dlist.itemCatsMap)
  1268. dlist.itemCatsMap = {}
  1269. i = 0
  1270. for index in indexTemp:
  1271. if index in indeces:
  1272. continue
  1273. dlist.itemIndexMap.append(i)
  1274. dlist.itemDataMap[i] = dataTemp[index]
  1275. dlist.itemCatsMap[i] = catsTemp[index]
  1276. i += 1
  1277. dlist.SetItemCount(len(dlist.itemIndexMap))
  1278. # deselect items
  1279. item = dlist.GetFirstSelected()
  1280. while item != -1:
  1281. dlist.SetItemState(item, 0, wx.LIST_STATE_SELECTED | wx.LIST_STATE_FOCUSED)
  1282. item = dlist.GetNextSelected(item)
  1283. # submit SQL statements
  1284. self.ApplyCommands(self.listOfCommands, self.listOfSQLStatements)
  1285. return True
  1286. def OnDataItemDeleteAll(self, event):
  1287. """!Delete all items from the list"""
  1288. dlist = self.FindWindowById(self.layerPage[self.selLayer]['data'])
  1289. if UserSettings.Get(group = 'atm', key = 'askOnDeleteRec', subkey = 'enabled'):
  1290. deleteDialog = wx.MessageBox(parent = self,
  1291. message = _("All data records (%d) will be permanently deleted "
  1292. "from table. Do you want to delete them?") % \
  1293. (len(dlist.itemIndexMap)),
  1294. caption = _("Delete records"),
  1295. style = wx.YES_NO | wx.CENTRE)
  1296. if deleteDialog != wx.YES:
  1297. return
  1298. dlist.DeleteAllItems()
  1299. dlist.itemDataMap = {}
  1300. dlist.itemIndexMap = []
  1301. dlist.SetItemCount(0)
  1302. table = self.dbMgrData['mapDBInfo'].layers[self.selLayer]["table"]
  1303. self.listOfSQLStatements.append('DELETE FROM %s' % table)
  1304. self.ApplyCommands(self.listOfCommands, self.listOfSQLStatements)
  1305. event.Skip()
  1306. def _drawSelected(self, zoom, selectedOnly=True):
  1307. """!Highlight selected features"""
  1308. if not self.map or not self.mapdisplay:
  1309. return
  1310. tlist = self.FindWindowById(self.layerPage[self.selLayer]['data'])
  1311. if selectedOnly:
  1312. fn = tlist.GetSelectedItems
  1313. else:
  1314. fn = tlist.GetItems
  1315. cats = map(int, fn())
  1316. digitToolbar = None
  1317. if 'vdigit' in self.mapdisplay.toolbars:
  1318. digitToolbar = self.mapdisplay.toolbars['vdigit']
  1319. if digitToolbar and digitToolbar.GetLayer() and \
  1320. digitToolbar.GetLayer().GetName() == self.dbMgrData['vectName']:
  1321. display = self.mapdisplay.GetMapWindow().GetDisplay()
  1322. display.SetSelected(cats, layer = self.selLayer)
  1323. if zoom:
  1324. n, s, w, e = display.GetRegionSelected()
  1325. self.mapdisplay.Map.GetRegion(n = n, s = s, w = w, e = e,
  1326. update = True)
  1327. else:
  1328. # add map layer with higlighted vector features
  1329. self.AddQueryMapLayer(selectedOnly) # -> self.qlayer
  1330. # set opacity based on queried layer
  1331. if self.parent and self.mapdisplay.tree and \
  1332. self.dbMgrData['treeItem']:
  1333. maptree = self.mapdisplay.tree # TODO: giface
  1334. opacity = maptree.GetLayerInfo(self.dbMgrData['treeItem'], key = 'maplayer').GetOpacity()
  1335. self.qlayer.SetOpacity(opacity)
  1336. if zoom:
  1337. keyColumn = self.dbMgrData['mapDBInfo'].layers[self.selLayer]['key']
  1338. where = ''
  1339. for range in ListOfCatsToRange(cats).split(','):
  1340. if '-' in range:
  1341. min, max = range.split('-')
  1342. where += '%s >= %d and %s <= %d or ' % \
  1343. (keyColumn, int(min),
  1344. keyColumn, int(max))
  1345. else:
  1346. where += '%s = %d or ' % (keyColumn, int(range))
  1347. where = where.rstrip('or ')
  1348. select = RunCommand('v.db.select',
  1349. parent = self,
  1350. read = True,
  1351. quiet = True,
  1352. flags = 'r',
  1353. map = self.dbMgrData['mapDBInfo'].map,
  1354. layer = int(self.selLayer),
  1355. where = where)
  1356. region = {}
  1357. for line in select.splitlines():
  1358. key, value = line.split('=')
  1359. region[key.strip()] = float(value.strip())
  1360. nsdist = ewdist = 0
  1361. renderer = self.mapdisplay.GetMap()
  1362. nsdist = 10 * ((renderer.GetCurrentRegion()['n'] - renderer.GetCurrentRegion()['s']) /
  1363. renderer.height)
  1364. ewdist = 10 * ((renderer.GetCurrentRegion()['e'] - renderer.GetCurrentRegion()['w']) /
  1365. renderer.width)
  1366. north = region['n'] + nsdist
  1367. south = region['s'] - nsdist
  1368. west = region['w'] - ewdist
  1369. east = region['e'] + ewdist
  1370. renderer.GetRegion(n = north, s = south, w = west, e = east, update = True)
  1371. self.mapdisplay.GetMapWindow().ZoomHistory(n = north, s = south, w = west, e = east)
  1372. if zoom:
  1373. self.mapdisplay.Map.AdjustRegion() # adjust resolution
  1374. self.mapdisplay.Map.AlignExtentFromDisplay() # adjust extent
  1375. self.mapdisplay.MapWindow.UpdateMap(render = True, renderVector = True)
  1376. else:
  1377. self.mapdisplay.MapWindow.UpdateMap(render = False, renderVector = True)
  1378. def AddQueryMapLayer(self, selectedOnly = True):
  1379. """!Redraw a map
  1380. Return True if map has been redrawn, False if no map is given
  1381. """
  1382. tlist = self.FindWindowById(self.layerPage[self.selLayer]['data'])
  1383. if selectedOnly:
  1384. fn = tlist.GetSelectedItems
  1385. else:
  1386. fn = tlist.GetItems
  1387. cats = { self.selLayer : fn() }
  1388. if self.mapdisplay.Map.GetLayerIndex(self.qlayer) < 0:
  1389. self.qlayer = None
  1390. if self.qlayer:
  1391. self.qlayer.SetCmd(self.mapdisplay.AddTmpVectorMapLayer(self.dbMgrData['vectName'], cats, addLayer = False))
  1392. else:
  1393. self.qlayer = self.mapdisplay.AddTmpVectorMapLayer(self.dbMgrData['vectName'], cats)
  1394. return self.qlayer
  1395. def OnDataReload(self, event):
  1396. """!Reload tlist of records"""
  1397. self.OnApplySqlStatement(None)
  1398. self.listOfSQLStatements = []
  1399. def OnDataSelectAll(self, event):
  1400. """!Select all items"""
  1401. tlist = self.FindWindowById(self.layerPage[self.selLayer]['data'])
  1402. item = -1
  1403. while True:
  1404. item = tlist.GetNextItem(item)
  1405. if item == -1:
  1406. break
  1407. tlist.SetItemState(item, wx.LIST_STATE_SELECTED, wx.LIST_STATE_SELECTED)
  1408. event.Skip()
  1409. def OnDataSelectNone(self, event):
  1410. """!Deselect items"""
  1411. tlist = self.FindWindowById(self.layerPage[self.selLayer]['data'])
  1412. item = -1
  1413. while True:
  1414. item = tlist.GetNextItem(item, wx.LIST_STATE_SELECTED)
  1415. if item == -1:
  1416. break
  1417. tlist.SetItemState(item, 0, wx.LIST_STATE_SELECTED | wx.LIST_STATE_FOCUSED)
  1418. event.Skip()
  1419. def OnDataDrawSelected(self, event):
  1420. """!Reload table description"""
  1421. self._drawSelected(zoom = False)
  1422. event.Skip()
  1423. def OnDataDrawSelectedZoom(self, event):
  1424. self._drawSelected(zoom = True)
  1425. event.Skip()
  1426. def OnExtractSelected(self, event):
  1427. """!Extract vector objects selected in attribute browse window
  1428. to new vector map
  1429. """
  1430. tlist = self.FindWindowById(self.layerPage[self.selLayer]['data'])
  1431. # cats = tlist.selectedCats[:]
  1432. cats = tlist.GetSelectedItems()
  1433. if len(cats) == 0:
  1434. GMessage(parent = self,
  1435. message = _('Nothing to extract.'))
  1436. return
  1437. else:
  1438. # dialog to get file name
  1439. dlg = CreateNewVector(parent=self, title=_('Extract selected features'),
  1440. giface=self.giface,
  1441. cmd=(('v.extract',
  1442. {'input': self.dbMgrData['vectName'],
  1443. 'cats': ListOfCatsToRange(cats)},
  1444. 'output')),
  1445. disableTable=True)
  1446. if not dlg:
  1447. return
  1448. name = dlg.GetName(full = True)
  1449. if not self.mapdisplay and self.mapdisplay.tree:
  1450. pass
  1451. elif name and dlg.IsChecked('add'):
  1452. # add layer to map layer tree
  1453. self.mapdisplay.tree.AddLayer(ltype = 'vector',
  1454. lname = name,
  1455. lcmd = ['d.vect', 'map=%s' % name])
  1456. dlg.Destroy()
  1457. def OnDeleteSelected(self, event):
  1458. """!Delete vector objects selected in attribute browse window
  1459. (attribures and geometry)
  1460. """
  1461. tlist = self.FindWindowById(self.layerPage[self.selLayer]['data'])
  1462. cats = tlist.GetSelectedItems()
  1463. if len(cats) == 0:
  1464. GMessage(parent = self,
  1465. message = _('Nothing to delete.'))
  1466. return
  1467. display = None
  1468. if not self.mapdisplay:
  1469. pass
  1470. elif 'vdigit' in self.mapdisplay.toolbars:
  1471. digitToolbar = self.mapdisplay.toolbars['vdigit']
  1472. if digitToolbar and digitToolbar.GetLayer() and \
  1473. digitToolbar.GetLayer().GetName() == self.dbMgrData['vectName']:
  1474. display = self.mapdisplay.GetMapWindow().GetDisplay()
  1475. display.SetSelected(map(int, cats), layer = self.selLayer)
  1476. self.mapdisplay.MapWindow.UpdateMap(render = True, renderVector = True)
  1477. if self.OnDataItemDelete(None) and self.mapdisplay:
  1478. if display:
  1479. self.mapdisplay.GetMapWindow().digit.DeleteSelectedLines()
  1480. else:
  1481. RunCommand('v.edit',
  1482. parent = self,
  1483. quiet = True,
  1484. map = self.dbMgrData['vectName'],
  1485. tool = 'delete',
  1486. cats = ListOfCatsToRange(cats))
  1487. self.mapdisplay.MapWindow.UpdateMap(render = True, renderVector = True)
  1488. def OnApplySqlStatement(self, event):
  1489. """!Apply simple/advanced sql statement"""
  1490. keyColumn = -1 # index of key column
  1491. listWin = self.FindWindowById(self.layerPage[self.selLayer]['data'])
  1492. sql = None
  1493. win = self.FindWindowById(self.layerPage[self.selLayer]['sqlNtb'])
  1494. if not win:
  1495. return
  1496. showSelected = False
  1497. wx.BeginBusyCursor()
  1498. if win.GetSelection() == 0:
  1499. # simple sql statement
  1500. whereCol = self.FindWindowById(self.layerPage[self.selLayer]['whereColumn']).GetStringSelection()
  1501. whereOpe = self.FindWindowById(self.layerPage[self.selLayer]['whereOperator']).GetStringSelection()
  1502. whereVal = self.FindWindowById(self.layerPage[self.selLayer]['where']).GetValue().strip()
  1503. try:
  1504. if len(whereVal) > 0:
  1505. showSelected = True
  1506. keyColumn = listWin.LoadData(self.selLayer, where = whereCol + whereOpe + whereVal)
  1507. else:
  1508. keyColumn = listWin.LoadData(self.selLayer)
  1509. except GException as e:
  1510. GError(parent = self,
  1511. message = _("Loading attribute data failed.\n\n%s") % e.value)
  1512. self.FindWindowById(self.layerPage[self.selLayer]['where']).SetValue('')
  1513. else:
  1514. # advanced sql statement
  1515. win = self.FindWindowById(self.layerPage[self.selLayer]['statement'])
  1516. try:
  1517. cols, where = self.ValidateSelectStatement(win.GetValue())
  1518. if cols is None and where is None:
  1519. sql = win.GetValue()
  1520. if where:
  1521. showSelected = True
  1522. except TypeError:
  1523. GError(parent = self,
  1524. message = _("Loading attribute data failed.\n"
  1525. "Invalid SQL select statement.\n\n%s") % win.GetValue())
  1526. win.SetValue("SELECT * FROM %s" % self.dbMgrData['mapDBInfo'].layers[self.selLayer]['table'])
  1527. cols = None
  1528. where = None
  1529. if cols or where or sql:
  1530. try:
  1531. keyColumn = listWin.LoadData(self.selLayer, columns = cols,
  1532. where = where, sql = sql)
  1533. except GException as e:
  1534. GError(parent = self,
  1535. message = _("Loading attribute data failed.\n\n%s") % e.value)
  1536. win.SetValue("SELECT * FROM %s" % self.dbMgrData['mapDBInfo'].layers[self.selLayer]['table'])
  1537. # sort by key column
  1538. if sql and 'order by' in sql.lower():
  1539. pass # don't order by key column
  1540. else:
  1541. if keyColumn > -1:
  1542. listWin.SortListItems(col = keyColumn, ascending = True)
  1543. else:
  1544. listWin.SortListItems(col = 0, ascending = True)
  1545. wx.EndBusyCursor()
  1546. # update statusbar
  1547. self.log.write(_("Number of loaded records: %d") % \
  1548. self.FindWindowById(self.layerPage[self.selLayer]['data']).GetItemCount())
  1549. # update map display if needed
  1550. if self.mapdisplay and \
  1551. UserSettings.Get(group = 'atm', key = 'highlight', subkey = 'auto'):
  1552. # TODO: replace by signals
  1553. if showSelected:
  1554. self._drawSelected(zoom=False, selectedOnly=False)
  1555. else:
  1556. self.mapdisplay.RemoveQueryLayer()
  1557. self.mapdisplay.MapWindow.UpdateMap(render=False) # TODO: replace by signals
  1558. def OnBuilder(self,event):
  1559. """!SQL Builder button pressed -> show the SQLBuilder dialog"""
  1560. if not self.builder:
  1561. self.builder = SQLBuilderSelect(parent = self, id = wx.ID_ANY,
  1562. vectmap = self.dbMgrData['vectName'],
  1563. layer = self.selLayer,
  1564. evtHandler = self.OnBuilderEvt)
  1565. self.builder.Show()
  1566. else:
  1567. self.builder.Raise()
  1568. def OnBuilderEvt(self, event):
  1569. if event == 'apply':
  1570. sqlstr = self.builder.GetSQLStatement()
  1571. self.FindWindowById(self.layerPage[self.selLayer]['statement']).SetValue(sqlstr)
  1572. # apply query
  1573. #self.listOfSQLStatements.append(sqlstr) #TODO probably it was bug
  1574. self.OnApplySqlStatement(None)
  1575. # close builder on apply
  1576. if self.builder.CloseOnApply():
  1577. self.builder = None
  1578. elif event == 'close':
  1579. self.builder = None
  1580. def ValidateSelectStatement(self, statement):
  1581. """!Validate SQL select statement
  1582. @return (columns, where)
  1583. @return None on error
  1584. """
  1585. if statement[0:7].lower() != 'select ':
  1586. return None
  1587. cols = ''
  1588. index = 7
  1589. for c in statement[index:]:
  1590. if c == ' ':
  1591. break
  1592. cols += c
  1593. index += 1
  1594. if cols == '*':
  1595. cols = None
  1596. else:
  1597. cols = cols.split(',')
  1598. tablelen = len(self.dbMgrData['mapDBInfo'].layers[self.selLayer]['table'])
  1599. if statement[index+1:index+6].lower() != 'from ' or \
  1600. statement[index+6:index+6+tablelen] != '%s' % \
  1601. (self.dbMgrData['mapDBInfo'].layers[self.selLayer]['table']):
  1602. return None
  1603. if len(statement[index+7+tablelen:]) > 0:
  1604. index = statement.lower().find('where ')
  1605. if index > -1:
  1606. where = statement[index+6:]
  1607. else:
  1608. where = None
  1609. else:
  1610. where = None
  1611. return (cols, where)
  1612. def LoadData(self, layer, columns = None, where = None, sql = None):
  1613. """!Load data into list
  1614. @param layer layer number
  1615. @param columns list of columns for output
  1616. @param where where statement
  1617. @param sql full sql statement
  1618. @return id of key column
  1619. @return -1 if key column is not displayed
  1620. """
  1621. listWin = self.FindWindowById(self.layerPage[layer]['data'])
  1622. return listWin.LoadData(layer, columns, where, sql)
  1623. def UpdatePage(self, layer):
  1624. # update data tlist
  1625. if layer in self.layerPage.keys():
  1626. tlist = self.FindWindowById(self.layerPage[layer]['data'])
  1627. tlist.Update(self.dbMgrData['mapDBInfo'])
  1628. class DbMgrTablesPage(DbMgrNotebookBase):
  1629. def __init__(self, parent, parentDbMgrBase, onlyLayer = -1):
  1630. """!Page for managing tables
  1631. @param parent GUI parent
  1632. @param parentDbMgrBase instance of DbMgrBase class
  1633. @param onlyLayer create only tab of given layer, if -1 creates tabs of all layers
  1634. """
  1635. DbMgrNotebookBase.__init__(self, parent = parent,
  1636. parentDbMgrBase = parentDbMgrBase)
  1637. for layer in self.dbMgrData['mapDBInfo'].layers.keys():
  1638. if onlyLayer > 0 and layer != onlyLayer:
  1639. continue
  1640. self.AddLayer(layer)
  1641. if self.layers:
  1642. self.SetSelection(0) # select first layer
  1643. self.selLayer = self.layers[0]
  1644. def AddLayer(self, layer, pos = -1):
  1645. """!Adds tab which represents table
  1646. @param layer vector map layer connected to table
  1647. @param pos position of tab, if -1 it is added to end
  1648. @return True if layer was added
  1649. @return False if layer was not added - layer has been already added or does not exist
  1650. """
  1651. if layer in self.layers or \
  1652. layer not in self.parentDbMgrBase.GetVectorLayers():
  1653. return False
  1654. self.layers.append(layer)
  1655. self.layerPage[layer] = {}
  1656. panel = wx.Panel(parent = self, id = wx.ID_ANY)
  1657. self.layerPage[layer]['tablePage'] = panel.GetId()
  1658. label = _("Table")
  1659. if not self.dbMgrData['editable']:
  1660. label += _(" (readonly)")
  1661. if pos == -1:
  1662. pos = self.GetPageCount()
  1663. self.InsertPage(pos, page = panel,
  1664. text = " %d / %s %s" % (layer, label,
  1665. self.dbMgrData['mapDBInfo'].layers[layer]['table']))
  1666. pageSizer = wx.BoxSizer(wx.VERTICAL)
  1667. #
  1668. # dbInfo
  1669. #
  1670. dbBox = wx.StaticBox(parent = panel, id = wx.ID_ANY,
  1671. label = " %s " % _("Database connection"))
  1672. dbSizer = wx.StaticBoxSizer(dbBox, wx.VERTICAL)
  1673. dbSizer.Add(item = CreateDbInfoDesc(panel, self.dbMgrData['mapDBInfo'], layer),
  1674. proportion = 1,
  1675. flag = wx.EXPAND | wx.ALL,
  1676. border = 3)
  1677. #
  1678. # table description
  1679. #
  1680. table = self.dbMgrData['mapDBInfo'].layers[layer]['table']
  1681. tableBox = wx.StaticBox(parent = panel, id = wx.ID_ANY,
  1682. label = " %s " % _("Table <%s> - right-click to delete column(s)") % table)
  1683. tableSizer = wx.StaticBoxSizer(tableBox, wx.VERTICAL)
  1684. tlist = self._createTableDesc(panel, table)
  1685. tlist.Bind(wx.EVT_COMMAND_RIGHT_CLICK, self.OnTableRightUp) #wxMSW
  1686. tlist.Bind(wx.EVT_RIGHT_UP, self.OnTableRightUp) #wxGTK
  1687. self.layerPage[layer]['tableData'] = tlist.GetId()
  1688. # manage columns (add)
  1689. addBox = wx.StaticBox(parent = panel, id = wx.ID_ANY,
  1690. label = " %s " % _("Add column"))
  1691. addSizer = wx.StaticBoxSizer(addBox, wx.HORIZONTAL)
  1692. column = wx.TextCtrl(parent = panel, id = wx.ID_ANY, value = '',
  1693. size = (150, -1), style = wx.TE_PROCESS_ENTER)
  1694. column.Bind(wx.EVT_TEXT, self.OnTableAddColumnName)
  1695. column.Bind(wx.EVT_TEXT_ENTER, self.OnTableItemAdd)
  1696. self.layerPage[layer]['addColName'] = column.GetId()
  1697. addSizer.Add(item = wx.StaticText(parent = panel, id = wx.ID_ANY, label = _("Column")),
  1698. flag = wx.ALIGN_CENTER_VERTICAL | wx.LEFT | wx.RIGHT,
  1699. border = 5)
  1700. addSizer.Add(item = column, proportion = 1,
  1701. flag = wx.ALIGN_CENTER_VERTICAL | wx.LEFT | wx.RIGHT,
  1702. border = 5)
  1703. ctype = wx.Choice (parent = panel, id = wx.ID_ANY,
  1704. choices = ["integer",
  1705. "double",
  1706. "varchar",
  1707. "date"]) # FIXME
  1708. ctype.SetSelection(0)
  1709. ctype.Bind(wx.EVT_CHOICE, self.OnTableChangeType)
  1710. self.layerPage[layer]['addColType'] = ctype.GetId()
  1711. addSizer.Add(item = wx.StaticText(parent = panel, id = wx.ID_ANY, label = _("Type")),
  1712. flag = wx.ALIGN_CENTER_VERTICAL | wx.LEFT | wx.RIGHT,
  1713. border = 5)
  1714. addSizer.Add(item = ctype,
  1715. flag = wx.ALIGN_CENTER_VERTICAL | wx.LEFT | wx.RIGHT,
  1716. border = 5)
  1717. length = wx.SpinCtrl(parent = panel, id = wx.ID_ANY, size = (65, -1),
  1718. initial = 250,
  1719. min = 1, max = 1e6)
  1720. length.Enable(False)
  1721. self.layerPage[layer]['addColLength'] = length.GetId()
  1722. addSizer.Add(item = wx.StaticText(parent = panel, id = wx.ID_ANY, label = _("Length")),
  1723. flag = wx.ALIGN_CENTER_VERTICAL | wx.LEFT | wx.RIGHT,
  1724. border = 5)
  1725. addSizer.Add(item = length,
  1726. flag = wx.ALIGN_CENTER_VERTICAL | wx.LEFT | wx.RIGHT,
  1727. border = 5)
  1728. btnAddCol = wx.Button(parent = panel, id = wx.ID_ANY, label = _("Add"))
  1729. btnAddCol.Bind(wx.EVT_BUTTON, self.OnTableItemAdd)
  1730. btnAddCol.Enable(False)
  1731. self.layerPage[layer]['addColButton'] = btnAddCol.GetId()
  1732. addSizer.Add(item = btnAddCol, flag = wx.ALL | wx.ALIGN_RIGHT | wx.EXPAND,
  1733. border = 3)
  1734. # manage columns (rename)
  1735. renameBox = wx.StaticBox(parent = panel, id = wx.ID_ANY,
  1736. label = " %s " % _("Rename column"))
  1737. renameSizer = wx.StaticBoxSizer(renameBox, wx.HORIZONTAL)
  1738. columnFrom = wx.ComboBox(parent = panel, id = wx.ID_ANY, size = (150, -1),
  1739. style = wx.CB_SIMPLE | wx.CB_READONLY,
  1740. choices = self.dbMgrData['mapDBInfo'].GetColumns(table))
  1741. columnFrom.SetSelection(0)
  1742. self.layerPage[layer]['renameCol'] = columnFrom.GetId()
  1743. renameSizer.Add(item = wx.StaticText(parent = panel, id = wx.ID_ANY, label = _("Column")),
  1744. flag = wx.ALIGN_CENTER_VERTICAL | wx.LEFT | wx.RIGHT,
  1745. border = 5)
  1746. renameSizer.Add(item = columnFrom, proportion = 1,
  1747. flag = wx.ALIGN_CENTER_VERTICAL | wx.LEFT | wx.RIGHT,
  1748. border = 5)
  1749. columnTo = wx.TextCtrl(parent = panel, id = wx.ID_ANY, value = '',
  1750. size = (150, -1), style = wx.TE_PROCESS_ENTER)
  1751. columnTo.Bind(wx.EVT_TEXT, self.OnTableRenameColumnName)
  1752. columnTo.Bind(wx.EVT_TEXT_ENTER, self.OnTableItemChange)
  1753. self.layerPage[layer]['renameColTo'] = columnTo.GetId()
  1754. renameSizer.Add(item = wx.StaticText(parent = panel, id = wx.ID_ANY, label = _("To")),
  1755. flag = wx.ALIGN_CENTER_VERTICAL | wx.LEFT | wx.RIGHT,
  1756. border = 5)
  1757. renameSizer.Add(item = columnTo, proportion = 1,
  1758. flag = wx.ALIGN_CENTER_VERTICAL | wx.LEFT | wx.RIGHT,
  1759. border = 5)
  1760. btnRenameCol = wx.Button(parent = panel, id = wx.ID_ANY, label = _("&Rename"))
  1761. btnRenameCol.Bind(wx.EVT_BUTTON, self.OnTableItemChange)
  1762. btnRenameCol.Enable(False)
  1763. self.layerPage[layer]['renameColButton'] = btnRenameCol.GetId()
  1764. renameSizer.Add(item = btnRenameCol, flag = wx.ALL | wx.ALIGN_RIGHT | wx.EXPAND,
  1765. border = 3)
  1766. tableSizer.Add(item = tlist,
  1767. flag = wx.ALL | wx.EXPAND,
  1768. proportion = 1,
  1769. border = 3)
  1770. pageSizer.Add(item=dbSizer,
  1771. flag = wx.ALL | wx.EXPAND,
  1772. proportion = 0,
  1773. border = 3)
  1774. pageSizer.Add(item = tableSizer,
  1775. flag = wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.EXPAND,
  1776. proportion = 1,
  1777. border = 3)
  1778. pageSizer.Add(item = addSizer,
  1779. flag = wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.EXPAND,
  1780. proportion = 0,
  1781. border = 3)
  1782. pageSizer.Add(item = renameSizer,
  1783. flag = wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.EXPAND,
  1784. proportion = 0,
  1785. border = 3)
  1786. panel.SetSizer(pageSizer)
  1787. if not self.dbMgrData['editable']:
  1788. for widget in [columnTo, columnFrom, length, ctype,
  1789. column, btnAddCol, btnRenameCol]:
  1790. widget.Enable(False)
  1791. return True
  1792. def _createTableDesc(self, parent, table):
  1793. """!Create list with table description"""
  1794. tlist = TableListCtrl(parent = parent, id = wx.ID_ANY,
  1795. table = self.dbMgrData['mapDBInfo'].tables[table],
  1796. columns = self.dbMgrData['mapDBInfo'].GetColumns(table))
  1797. tlist.Populate()
  1798. # sorter
  1799. # itemDataMap = list.Populate()
  1800. # listmix.ColumnSorterMixin.__init__(self, 2)
  1801. return tlist
  1802. def OnTableChangeType(self, event):
  1803. """!Data type for new column changed. Enable or disable
  1804. data length widget"""
  1805. win = self.FindWindowById(self.layerPage[self.selLayer]['addColLength'])
  1806. if event.GetString() == "varchar":
  1807. win.Enable(True)
  1808. else:
  1809. win.Enable(False)
  1810. def OnTableRenameColumnName(self, event):
  1811. """!Editing column name to be added to the table"""
  1812. btn = self.FindWindowById(self.layerPage[self.selLayer]['renameColButton'])
  1813. col = self.FindWindowById(self.layerPage[self.selLayer]['renameCol'])
  1814. colTo = self.FindWindowById(self.layerPage[self.selLayer]['renameColTo'])
  1815. if len(col.GetValue()) > 0 and len(colTo.GetValue()) > 0:
  1816. btn.Enable(True)
  1817. else:
  1818. btn.Enable(False)
  1819. event.Skip()
  1820. def OnTableAddColumnName(self, event):
  1821. """!Editing column name to be added to the table"""
  1822. btn = self.FindWindowById(self.layerPage[self.selLayer]['addColButton'])
  1823. if len(event.GetString()) > 0:
  1824. btn.Enable(True)
  1825. else:
  1826. btn.Enable(False)
  1827. event.Skip()
  1828. def OnTableItemChange(self, event):
  1829. """!Rename column in the table"""
  1830. tlist = self.FindWindowById(self.layerPage[self.selLayer]['tableData'])
  1831. name = self.FindWindowById(self.layerPage[self.selLayer]['renameCol']).GetValue()
  1832. nameTo = self.FindWindowById(self.layerPage[self.selLayer]['renameColTo']).GetValue()
  1833. table = self.dbMgrData['mapDBInfo'].layers[self.selLayer]["table"]
  1834. if not name or not nameTo:
  1835. GError(parent = self,
  1836. message = _("Unable to rename column. "
  1837. "No column name defined."))
  1838. return
  1839. else:
  1840. item = tlist.FindItem(start = -1, str = name)
  1841. if item > -1:
  1842. if tlist.FindItem(start = -1, str = nameTo) > -1:
  1843. GError(parent = self,
  1844. message = _("Unable to rename column <%(column)s> to "
  1845. "<%(columnTo)s>. Column already exists "
  1846. "in the table <%(table)s>.") % \
  1847. {'column' : name, 'columnTo' : nameTo,
  1848. 'table' : table})
  1849. return
  1850. else:
  1851. tlist.SetItemText(item, nameTo)
  1852. self.listOfCommands.append(('v.db.renamecolumn',
  1853. { 'map' : self.dbMgrData['vectName'],
  1854. 'layer' : self.selLayer,
  1855. 'column' : '%s,%s' % (name, nameTo) }
  1856. ))
  1857. else:
  1858. GError(parent = self,
  1859. message = _("Unable to rename column. "
  1860. "Column <%(column)s> doesn't exist in the table <%(table)s>.") %
  1861. {'column' : name, 'table' : table})
  1862. return
  1863. # apply changes
  1864. self.ApplyCommands(self.listOfCommands, self.listOfSQLStatements)
  1865. # update widgets
  1866. self.FindWindowById(self.layerPage[self.selLayer]['renameCol']).SetItems(self.dbMgrData['mapDBInfo'].GetColumns(table))
  1867. self.FindWindowById(self.layerPage[self.selLayer]['renameCol']).SetSelection(0)
  1868. self.FindWindowById(self.layerPage[self.selLayer]['renameColTo']).SetValue('')
  1869. event.Skip()
  1870. def OnTableRightUp(self, event):
  1871. """!Table description area, context menu"""
  1872. if not hasattr(self, "popupTableID"):
  1873. self.popupTableID1 = wx.NewId()
  1874. self.popupTableID2 = wx.NewId()
  1875. self.popupTableID3 = wx.NewId()
  1876. self.Bind(wx.EVT_MENU, self.OnTableItemDelete, id = self.popupTableID1)
  1877. self.Bind(wx.EVT_MENU, self.OnTableItemDeleteAll, id = self.popupTableID2)
  1878. self.Bind(wx.EVT_MENU, self.OnTableReload, id = self.popupTableID3)
  1879. # generate popup-menu
  1880. menu = wx.Menu()
  1881. menu.Append(self.popupTableID1, _("Drop selected column"))
  1882. if self.FindWindowById(self.layerPage[self.selLayer]['tableData']).GetFirstSelected() == -1:
  1883. menu.Enable(self.popupTableID1, False)
  1884. menu.Append(self.popupTableID2, _("Drop all columns"))
  1885. menu.AppendSeparator()
  1886. menu.Append(self.popupTableID3, _("Reload"))
  1887. if not self.dbMgrData['editable']:
  1888. menu.Enable(self.popupTableID1, False)
  1889. menu.Enable(self.popupTableID2, False)
  1890. self.PopupMenu(menu)
  1891. menu.Destroy()
  1892. def OnTableItemDelete(self, event):
  1893. """!Delete selected item(s) from the list"""
  1894. tlist = self.FindWindowById(self.layerPage[self.selLayer]['tableData'])
  1895. item = tlist.GetFirstSelected()
  1896. countSelected = tlist.GetSelectedItemCount()
  1897. if UserSettings.Get(group = 'atm', key = 'askOnDeleteRec', subkey = 'enabled'):
  1898. # if the user select more columns to delete, all the columns name
  1899. # will appear the the warning dialog
  1900. if tlist.GetSelectedItemCount() > 1:
  1901. deleteColumns = "columns '%s'" % tlist.GetItemText(item)
  1902. while item != -1:
  1903. item = tlist.GetNextSelected(item)
  1904. if item != -1:
  1905. deleteColumns += ", '%s'" % tlist.GetItemText(item)
  1906. else:
  1907. deleteColumns = "column '%s'" % tlist.GetItemText(item)
  1908. deleteDialog = wx.MessageBox(parent = self,
  1909. message = _("Selected %s will PERMANENTLY removed "
  1910. "from table. Do you want to drop the column?") % \
  1911. (deleteColumns),
  1912. caption = _("Drop column(s)"),
  1913. style = wx.YES_NO | wx.CENTRE)
  1914. if deleteDialog != wx.YES:
  1915. return False
  1916. item = tlist.GetFirstSelected()
  1917. while item != -1:
  1918. self.listOfCommands.append(('v.db.dropcolumn',
  1919. { 'map' : self.dbMgrData['vectName'],
  1920. 'layer' : self.selLayer,
  1921. 'column' : tlist.GetItemText(item) }
  1922. ))
  1923. tlist.DeleteItem(item)
  1924. item = tlist.GetFirstSelected()
  1925. # apply changes
  1926. self.ApplyCommands(self.listOfCommands, self.listOfSQLStatements)
  1927. # update widgets
  1928. table = self.dbMgrData['mapDBInfo'].layers[self.selLayer]['table']
  1929. self.FindWindowById(self.layerPage[self.selLayer]['renameCol']).SetItems(self.dbMgrData['mapDBInfo'].GetColumns(table))
  1930. self.FindWindowById(self.layerPage[self.selLayer]['renameCol']).SetSelection(0)
  1931. event.Skip()
  1932. def OnTableItemDeleteAll(self, event):
  1933. """!Delete all items from the list"""
  1934. table = self.dbMgrData['mapDBInfo'].layers[self.selLayer]['table']
  1935. cols = self.dbMgrData['mapDBInfo'].GetColumns(table)
  1936. keyColumn = self.dbMgrData['mapDBInfo'].layers[self.selLayer]['key']
  1937. if keyColumn in cols:
  1938. cols.remove(keyColumn)
  1939. if UserSettings.Get(group = 'atm', key = 'askOnDeleteRec', subkey = 'enabled'):
  1940. deleteDialog = wx.MessageBox(parent = self,
  1941. message = _("Selected columns\n%s\nwill PERMANENTLY removed "
  1942. "from table. Do you want to drop the columns?") % \
  1943. ('\n'.join(cols)),
  1944. caption = _("Drop column(s)"),
  1945. style = wx.YES_NO | wx.CENTRE)
  1946. if deleteDialog != wx.YES:
  1947. return False
  1948. for col in cols:
  1949. self.listOfCommands.append(('v.db.dropcolumn',
  1950. { 'map' : self.dbMgrData['vectName'],
  1951. 'layer' : self.selLayer,
  1952. 'column' : col }
  1953. ))
  1954. self.FindWindowById(self.layerPage[self.selLayer]['tableData']).DeleteAllItems()
  1955. # apply changes
  1956. self.ApplyCommands(self.listOfCommands, self.listOfSQLStatements)
  1957. # update widgets
  1958. table = self.dbMgrData['mapDBInfo'].layers[self.selLayer]['table']
  1959. self.FindWindowById(self.layerPage[self.selLayer]['renameCol']).SetItems(self.dbMgrData['mapDBInfo'].GetColumns(table))
  1960. self.FindWindowById(self.layerPage[self.selLayer]['renameCol']).SetSelection(0)
  1961. event.Skip()
  1962. def OnTableReload(self, event = None):
  1963. """!Reload table description"""
  1964. self.FindWindowById(self.layerPage[self.selLayer]['tableData']).Populate(update = True)
  1965. self.listOfCommands = []
  1966. def OnTableItemAdd(self, event):
  1967. """!Add new column to the table"""
  1968. name = self.FindWindowById(self.layerPage[self.selLayer]['addColName']).GetValue()
  1969. ctype = self.FindWindowById(self.layerPage[self.selLayer]['addColType']). \
  1970. GetStringSelection()
  1971. length = int(self.FindWindowById(self.layerPage[self.selLayer]['addColLength']). \
  1972. GetValue())
  1973. # add item to the list of table columns
  1974. tlist = self.FindWindowById(self.layerPage[self.selLayer]['tableData'])
  1975. index = tlist.InsertStringItem(sys.maxint, str(name))
  1976. tlist.SetStringItem(index, 0, str(name))
  1977. tlist.SetStringItem(index, 1, str(ctype))
  1978. tlist.SetStringItem(index, 2, str(length))
  1979. self.AddColumn(name, ctype, length)
  1980. # update widgets
  1981. table = self.dbMgrData['mapDBInfo'].layers[self.selLayer]['table']
  1982. self.FindWindowById(self.layerPage[self.selLayer]['addColName']).SetValue('')
  1983. self.FindWindowById(self.layerPage[self.selLayer]['renameCol']).SetItems(self.dbMgrData['mapDBInfo'].GetColumns(table))
  1984. self.FindWindowById(self.layerPage[self.selLayer]['renameCol']).SetSelection(0)
  1985. event.Skip()
  1986. def UpdatePage(self, layer):
  1987. if layer in self.layerPage.keys():
  1988. table = self.dbMgrData['mapDBInfo'].layers[layer]['table']
  1989. # update table description
  1990. tlist = self.FindWindowById(self.layerPage[layer]['tableData'])
  1991. tlist.Update(table = self.dbMgrData['mapDBInfo'].tables[table],
  1992. columns = self.dbMgrData['mapDBInfo'].GetColumns(table))
  1993. self.OnTableReload(None)
  1994. class DbMgrLayersPage(wx.Panel):
  1995. def __init__(self, parent, parentDbMgrBase):
  1996. """!Create layer manage page"""
  1997. self.parentDbMgrBase = parentDbMgrBase
  1998. self.dbMgrData = self.parentDbMgrBase.dbMgrData
  1999. wx.Panel.__init__(self, parent = parent)
  2000. splitterWin = wx.SplitterWindow(parent = self, id = wx.ID_ANY)
  2001. splitterWin.SetMinimumPaneSize(100)
  2002. #
  2003. # list of layers
  2004. #
  2005. panelList = wx.Panel(parent = splitterWin, id = wx.ID_ANY)
  2006. panelListSizer = wx.BoxSizer(wx.VERTICAL)
  2007. layerBox = wx.StaticBox(parent = panelList, id = wx.ID_ANY,
  2008. label = " %s " % _("List of layers"))
  2009. layerSizer = wx.StaticBoxSizer(layerBox, wx.VERTICAL)
  2010. self.layerList = self._createLayerDesc(panelList)
  2011. self.layerList.Bind(wx.EVT_COMMAND_RIGHT_CLICK, self.OnLayerRightUp) #wxMSW
  2012. self.layerList.Bind(wx.EVT_RIGHT_UP, self.OnLayerRightUp) #wxGTK
  2013. layerSizer.Add(item = self.layerList,
  2014. flag = wx.ALL | wx.EXPAND,
  2015. proportion = 1,
  2016. border = 3)
  2017. panelListSizer.Add(item = layerSizer,
  2018. flag = wx.ALL | wx.EXPAND,
  2019. proportion = 1,
  2020. border = 3)
  2021. panelList.SetSizer(panelListSizer)
  2022. #
  2023. # manage part
  2024. #
  2025. panelManage = wx.Panel(parent = splitterWin, id = wx.ID_ANY)
  2026. manageSizer = wx.BoxSizer(wx.VERTICAL)
  2027. self.manageLayerBook = LayerBook(parent = panelManage, id = wx.ID_ANY,
  2028. parentDialog = self)
  2029. if not self.dbMgrData['editable']:
  2030. self.manageLayerBook.Enable(False)
  2031. manageSizer.Add(item = self.manageLayerBook,
  2032. proportion = 1,
  2033. flag = wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.EXPAND,
  2034. border = 5)
  2035. panelSizer = wx.BoxSizer(wx.VERTICAL)
  2036. panelSizer.Add(item = splitterWin,
  2037. proportion = 1,
  2038. flag = wx.EXPAND)
  2039. panelManage.SetSizer(manageSizer)
  2040. splitterWin.SplitHorizontally(panelList, panelManage, 100)
  2041. splitterWin.Fit()
  2042. self.SetSizer(panelSizer)
  2043. def _createLayerDesc(self, parent):
  2044. """!Create list of linked layers"""
  2045. tlist = LayerListCtrl(parent = parent, id = wx.ID_ANY,
  2046. layers = self.dbMgrData['mapDBInfo'].layers)
  2047. tlist.Populate()
  2048. # sorter
  2049. # itemDataMap = list.Populate()
  2050. # listmix.ColumnSorterMixin.__init__(self, 2)
  2051. return tlist
  2052. def UpdatePage(self):
  2053. #
  2054. # 'manage layers' page
  2055. #
  2056. # update list of layers
  2057. #self.dbMgrData['mapDBInfo'] = VectorDBInfo(self.dbMgrData['vectName'])
  2058. self.layerList.Update(self.dbMgrData['mapDBInfo'].layers)
  2059. self.layerList.Populate(update = True)
  2060. # update selected widgets
  2061. listOfLayers = map(str, self.dbMgrData['mapDBInfo'].layers.keys())
  2062. ### delete layer page
  2063. self.manageLayerBook.deleteLayer.SetItems(listOfLayers)
  2064. if len(listOfLayers) > 0:
  2065. self.manageLayerBook.deleteLayer.SetStringSelection(listOfLayers[0])
  2066. tableName = self.dbMgrData['mapDBInfo'].layers[int(listOfLayers[0])]['table']
  2067. maxLayer = max(self.dbMgrData['mapDBInfo'].layers.keys())
  2068. else:
  2069. tableName = ''
  2070. maxLayer = 0
  2071. self.manageLayerBook.deleteTable.SetLabel( \
  2072. _('Drop also linked attribute table (%s)') % \
  2073. tableName)
  2074. ### add layer page
  2075. self.manageLayerBook.addLayerWidgets['layer'][1].SetValue(\
  2076. maxLayer+1)
  2077. ### modify layer
  2078. self.manageLayerBook.modifyLayerWidgets['layer'][1].SetItems(listOfLayers)
  2079. self.manageLayerBook.OnChangeLayer(event = None)
  2080. def OnLayerRightUp(self, event):
  2081. """!Layer description area, context menu"""
  2082. pass
  2083. class TableListCtrl(wx.ListCtrl,
  2084. listmix.ListCtrlAutoWidthMixin):
  2085. # listmix.TextEditMixin):
  2086. """!Table description list"""
  2087. def __init__(self, parent, id, table, columns, pos = wx.DefaultPosition,
  2088. size = wx.DefaultSize):
  2089. self.parent = parent
  2090. self.table = table
  2091. self.columns = columns
  2092. wx.ListCtrl.__init__(self, parent, id, pos, size,
  2093. style = wx.LC_REPORT | wx.LC_HRULES | wx.LC_VRULES |
  2094. wx.BORDER_NONE)
  2095. listmix.ListCtrlAutoWidthMixin.__init__(self)
  2096. # listmix.TextEditMixin.__init__(self)
  2097. def Update(self, table, columns):
  2098. """!Update column description"""
  2099. self.table = table
  2100. self.columns = columns
  2101. def Populate(self, update = False):
  2102. """!Populate the list"""
  2103. itemData = {} # requested by sorter
  2104. if not update:
  2105. headings = [_("Column name"), _("Data type"), _("Data length")]
  2106. i = 0
  2107. for h in headings:
  2108. self.InsertColumn(col = i, heading = h)
  2109. i += 1
  2110. self.SetColumnWidth(col = 0, width = 350)
  2111. self.SetColumnWidth(col = 1, width = 175)
  2112. else:
  2113. self.DeleteAllItems()
  2114. i = 0
  2115. for column in self.columns:
  2116. index = self.InsertStringItem(sys.maxint, str(column))
  2117. self.SetStringItem(index, 0, str(column))
  2118. self.SetStringItem(index, 1, str(self.table[column]['type']))
  2119. self.SetStringItem(index, 2, str(self.table[column]['length']))
  2120. self.SetItemData(index, i)
  2121. itemData[i] = (str(column),
  2122. str(self.table[column]['type']),
  2123. int(self.table[column]['length']))
  2124. i = i + 1
  2125. self.SendSizeEvent()
  2126. return itemData
  2127. class LayerListCtrl(wx.ListCtrl,
  2128. listmix.ListCtrlAutoWidthMixin):
  2129. # listmix.ColumnSorterMixin):
  2130. # listmix.TextEditMixin):
  2131. """!Layer description list"""
  2132. def __init__(self, parent, id, layers,
  2133. pos = wx.DefaultPosition,
  2134. size = wx.DefaultSize):
  2135. self.parent = parent
  2136. self.layers = layers
  2137. wx.ListCtrl.__init__(self, parent, id, pos, size,
  2138. style = wx.LC_REPORT | wx.LC_HRULES | wx.LC_VRULES |
  2139. wx.BORDER_NONE)
  2140. listmix.ListCtrlAutoWidthMixin.__init__(self)
  2141. # listmix.TextEditMixin.__init__(self)
  2142. def Update(self, layers):
  2143. """!Update description"""
  2144. self.layers = layers
  2145. def Populate(self, update = False):
  2146. """!Populate the list"""
  2147. itemData = {} # requested by sorter
  2148. if not update:
  2149. headings = [_("Layer"), _("Driver"), _("Database"), _("Table"), _("Key")]
  2150. i = 0
  2151. for h in headings:
  2152. self.InsertColumn(col = i, heading = h)
  2153. i += 1
  2154. else:
  2155. self.DeleteAllItems()
  2156. i = 0
  2157. for layer in self.layers.keys():
  2158. index = self.InsertStringItem(sys.maxint, str(layer))
  2159. self.SetStringItem(index, 0, str(layer))
  2160. database = str(self.layers[layer]['database'])
  2161. driver = str(self.layers[layer]['driver'])
  2162. table = str(self.layers[layer]['table'])
  2163. key = str(self.layers[layer]['key'])
  2164. self.SetStringItem(index, 1, driver)
  2165. self.SetStringItem(index, 2, database)
  2166. self.SetStringItem(index, 3, table)
  2167. self.SetStringItem(index, 4, key)
  2168. self.SetItemData(index, i)
  2169. itemData[i] = (str(layer),
  2170. driver,
  2171. database,
  2172. table,
  2173. key)
  2174. i += 1
  2175. for i in range(self.GetColumnCount()):
  2176. self.SetColumnWidth(col = i, width = wx.LIST_AUTOSIZE)
  2177. if self.GetColumnWidth(col = i) < 60:
  2178. self.SetColumnWidth(col = i, width = 60)
  2179. self.SendSizeEvent()
  2180. return itemData
  2181. class LayerBook(wx.Notebook):
  2182. """!Manage layers (add, delete, modify)"""
  2183. def __init__(self, parent, id,
  2184. parentDialog,
  2185. style = wx.BK_DEFAULT):
  2186. wx.Notebook.__init__(self, parent, id, style = style)
  2187. self.parent = parent
  2188. self.parentDialog = parentDialog
  2189. self.mapDBInfo = self.parentDialog.dbMgrData['mapDBInfo']
  2190. #
  2191. # drivers
  2192. #
  2193. drivers = RunCommand('db.drivers',
  2194. quiet = True,
  2195. read = True,
  2196. flags = 'p')
  2197. self.listOfDrivers = []
  2198. for drv in drivers.splitlines():
  2199. self.listOfDrivers.append(drv.strip())
  2200. #
  2201. # get default values
  2202. #
  2203. self.defaultConnect = {}
  2204. connect = RunCommand('db.connect',
  2205. flags = 'p',
  2206. read = True,
  2207. quiet = True)
  2208. for line in connect.splitlines():
  2209. item, value = line.split(':', 1)
  2210. self.defaultConnect[item.strip()] = value.strip()
  2211. ### really needed?
  2212. # if len(self.defaultConnect['driver']) == 0 or \
  2213. # len(self.defaultConnect['database']) == 0:
  2214. # GWarning(parent = self.parent,
  2215. # message = _("Unknown default DB connection. "
  2216. # "Please define DB connection using db.connect module."))
  2217. self.defaultTables = self._getTables(self.defaultConnect['driver'],
  2218. self.defaultConnect['database'])
  2219. try:
  2220. self.defaultColumns = self._getColumns(self.defaultConnect['driver'],
  2221. self.defaultConnect['database'],
  2222. self.defaultTables[0])
  2223. except IndexError:
  2224. self.defaultColumns = []
  2225. self._createAddPage()
  2226. self._createDeletePage()
  2227. self._createModifyPage()
  2228. def _createAddPage(self):
  2229. """!Add new layer"""
  2230. self.addPanel = wx.Panel(parent = self, id = wx.ID_ANY)
  2231. self.AddPage(page = self.addPanel, text = _("Add layer"))
  2232. try:
  2233. maxLayer = max(self.mapDBInfo.layers.keys())
  2234. except ValueError:
  2235. maxLayer = 0
  2236. # layer description
  2237. layerBox = wx.StaticBox (parent = self.addPanel, id = wx.ID_ANY,
  2238. label = " %s " % (_("Layer description")))
  2239. layerSizer = wx.StaticBoxSizer(layerBox, wx.VERTICAL)
  2240. #
  2241. # list of layer widgets (label, value)
  2242. #
  2243. self.addLayerWidgets = {'layer':
  2244. (wx.StaticText(parent = self.addPanel, id = wx.ID_ANY,
  2245. label = '%s:' % _("Layer")),
  2246. wx.SpinCtrl(parent = self.addPanel, id = wx.ID_ANY, size = (65, -1),
  2247. initial = maxLayer+1,
  2248. min = 1, max = 1e6)),
  2249. 'driver':
  2250. (wx.StaticText(parent = self.addPanel, id = wx.ID_ANY,
  2251. label = '%s:' % _("Driver")),
  2252. wx.Choice(parent = self.addPanel, id = wx.ID_ANY, size = (200, -1),
  2253. choices = self.listOfDrivers)),
  2254. 'database':
  2255. (wx.StaticText(parent = self.addPanel, id = wx.ID_ANY,
  2256. label = '%s:' % _("Database")),
  2257. wx.TextCtrl(parent = self.addPanel, id = wx.ID_ANY,
  2258. value = '',
  2259. style = wx.TE_PROCESS_ENTER)),
  2260. 'table':
  2261. (wx.StaticText(parent = self.addPanel, id = wx.ID_ANY,
  2262. label = '%s:' % _("Table")),
  2263. wx.Choice(parent = self.addPanel, id = wx.ID_ANY, size = (200, -1),
  2264. choices = self.defaultTables)),
  2265. 'key':
  2266. (wx.StaticText(parent = self.addPanel, id = wx.ID_ANY,
  2267. label = '%s:' % _("Key column")),
  2268. wx.Choice(parent = self.addPanel, id = wx.ID_ANY, size = (200, -1),
  2269. choices = self.defaultColumns)),
  2270. 'addCat':
  2271. (wx.CheckBox(parent = self.addPanel, id = wx.ID_ANY,
  2272. label = _("Insert record for each category into table")),
  2273. None),
  2274. }
  2275. # set default values for widgets
  2276. self.addLayerWidgets['driver'][1].SetStringSelection(self.defaultConnect['driver'])
  2277. self.addLayerWidgets['database'][1].SetValue(self.defaultConnect['database'])
  2278. self.addLayerWidgets['table'][1].SetSelection(0)
  2279. self.addLayerWidgets['key'][1].SetSelection(0)
  2280. # events
  2281. self.addLayerWidgets['driver'][1].Bind(wx.EVT_CHOICE, self.OnDriverChanged)
  2282. self.addLayerWidgets['database'][1].Bind(wx.EVT_TEXT_ENTER, self.OnDatabaseChanged)
  2283. self.addLayerWidgets['table'][1].Bind(wx.EVT_CHOICE, self.OnTableChanged)
  2284. # tooltips
  2285. self.addLayerWidgets['addCat'][0].SetToolTipString(_("You need to add categories "
  2286. "by v.category module."))
  2287. # table description
  2288. tableBox = wx.StaticBox (parent = self.addPanel, id = wx.ID_ANY,
  2289. label = " %s " % (_("Table description")))
  2290. tableSizer = wx.StaticBoxSizer(tableBox, wx.VERTICAL)
  2291. #
  2292. # list of table widgets
  2293. #
  2294. keyCol = UserSettings.Get(group = 'atm', key = 'keycolumn', subkey = 'value')
  2295. self.tableWidgets = {'table': (wx.StaticText(parent = self.addPanel, id = wx.ID_ANY,
  2296. label = '%s:' % _("Table name")),
  2297. wx.TextCtrl(parent = self.addPanel, id = wx.ID_ANY,
  2298. value = '',
  2299. style = wx.TE_PROCESS_ENTER)),
  2300. 'key': (wx.StaticText(parent = self.addPanel, id = wx.ID_ANY,
  2301. label = '%s:' % _("Key column")),
  2302. wx.TextCtrl(parent = self.addPanel, id = wx.ID_ANY,
  2303. value = keyCol,
  2304. style = wx.TE_PROCESS_ENTER))}
  2305. # events
  2306. self.tableWidgets['table'][1].Bind(wx.EVT_TEXT_ENTER, self.OnCreateTable)
  2307. self.tableWidgets['key'][1].Bind(wx.EVT_TEXT_ENTER, self.OnCreateTable)
  2308. btnTable = wx.Button(self.addPanel, wx.ID_ANY, _("&Create table"),
  2309. size = (125,-1))
  2310. btnTable.Bind(wx.EVT_BUTTON, self.OnCreateTable)
  2311. btnLayer = wx.Button(self.addPanel, wx.ID_ANY, _("&Add layer"),
  2312. size = (125,-1))
  2313. btnLayer.Bind(wx.EVT_BUTTON, self.OnAddLayer)
  2314. btnDefault = wx.Button(self.addPanel, wx.ID_ANY, _("&Set default"),
  2315. size = (125,-1))
  2316. btnDefault.Bind(wx.EVT_BUTTON, self.OnSetDefault)
  2317. # do layout
  2318. pageSizer = wx.BoxSizer(wx.HORIZONTAL)
  2319. # data area
  2320. dataSizer = wx.GridBagSizer(hgap = 5, vgap = 5)
  2321. row = 0
  2322. for key in ('layer', 'driver', 'database', 'table', 'key', 'addCat'):
  2323. label, value = self.addLayerWidgets[key]
  2324. if not value:
  2325. span = (1, 2)
  2326. else:
  2327. span = (1, 1)
  2328. dataSizer.Add(item = label,
  2329. flag = wx.ALIGN_CENTER_VERTICAL, pos = (row, 0),
  2330. span = span)
  2331. if not value:
  2332. row += 1
  2333. continue
  2334. if key == 'layer':
  2335. style = wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_LEFT
  2336. else:
  2337. style = wx.ALIGN_CENTER_VERTICAL | wx.EXPAND
  2338. dataSizer.Add(item = value,
  2339. flag = style, pos = (row, 1))
  2340. row += 1
  2341. dataSizer.AddGrowableCol(1)
  2342. layerSizer.Add(item = dataSizer,
  2343. proportion = 1,
  2344. flag = wx.ALL | wx.EXPAND,
  2345. border = 5)
  2346. btnSizer = wx.BoxSizer(wx.HORIZONTAL)
  2347. btnSizer.Add(item = btnDefault,
  2348. proportion = 0,
  2349. flag = wx.ALL | wx.ALIGN_LEFT,
  2350. border = 5)
  2351. btnSizer.Add(item = (5, 5),
  2352. proportion = 1,
  2353. flag = wx.ALL | wx.EXPAND,
  2354. border = 5)
  2355. btnSizer.Add(item = btnLayer,
  2356. proportion = 0,
  2357. flag = wx.ALL | wx.ALIGN_RIGHT,
  2358. border = 5)
  2359. layerSizer.Add(item = btnSizer,
  2360. proportion = 0,
  2361. flag = wx.ALL | wx.EXPAND,
  2362. border = 0)
  2363. # data area
  2364. dataSizer = wx.FlexGridSizer(cols = 2, hgap = 5, vgap = 5)
  2365. dataSizer.AddGrowableCol(1)
  2366. for key in ['table', 'key']:
  2367. label, value = self.tableWidgets[key]
  2368. dataSizer.Add(item = label,
  2369. flag = wx.ALIGN_CENTER_VERTICAL)
  2370. dataSizer.Add(item = value,
  2371. flag = wx.ALIGN_CENTER_VERTICAL | wx.EXPAND)
  2372. tableSizer.Add(item = dataSizer,
  2373. proportion = 1,
  2374. flag = wx.ALL | wx.EXPAND,
  2375. border = 5)
  2376. tableSizer.Add(item = btnTable,
  2377. proportion = 0,
  2378. flag = wx.ALL | wx.ALIGN_BOTTOM | wx.ALIGN_RIGHT,
  2379. border = 5)
  2380. pageSizer.Add(item = layerSizer,
  2381. proportion = 3,
  2382. flag = wx.ALL | wx.EXPAND,
  2383. border = 3)
  2384. pageSizer.Add(item = tableSizer,
  2385. proportion = 2,
  2386. flag = wx.TOP | wx.BOTTOM | wx.RIGHT | wx.EXPAND,
  2387. border = 3)
  2388. layerSizer.SetVirtualSizeHints(self.addPanel)
  2389. self.addPanel.SetAutoLayout(True)
  2390. self.addPanel.SetSizer(pageSizer)
  2391. pageSizer.Fit(self.addPanel)
  2392. def _createDeletePage(self):
  2393. """!Delete layer"""
  2394. self.deletePanel = wx.Panel(parent = self, id = wx.ID_ANY)
  2395. self.AddPage(page = self.deletePanel, text = _("Remove layer"))
  2396. label = wx.StaticText(parent = self.deletePanel, id = wx.ID_ANY,
  2397. label = '%s:' % _("Layer to remove"))
  2398. self.deleteLayer = wx.ComboBox(parent = self.deletePanel, id = wx.ID_ANY, size = (100, -1),
  2399. style = wx.CB_SIMPLE | wx.CB_READONLY,
  2400. choices = map(str, self.mapDBInfo.layers.keys()))
  2401. self.deleteLayer.SetSelection(0)
  2402. self.deleteLayer.Bind(wx.EVT_COMBOBOX, self.OnChangeLayer)
  2403. try:
  2404. tableName = self.mapDBInfo.layers[int(self.deleteLayer.GetStringSelection())]['table']
  2405. except ValueError:
  2406. tableName = ''
  2407. self.deleteTable = wx.CheckBox(parent = self.deletePanel, id = wx.ID_ANY,
  2408. label = _('Drop also linked attribute table (%s)') % \
  2409. tableName)
  2410. if tableName == '':
  2411. self.deleteLayer.Enable(False)
  2412. self.deleteTable.Enable(False)
  2413. btnDelete = wx.Button(self.deletePanel, wx.ID_DELETE, _("&Remove layer"),
  2414. size = (125,-1))
  2415. btnDelete.Bind(wx.EVT_BUTTON, self.OnDeleteLayer)
  2416. #
  2417. # do layout
  2418. #
  2419. pageSizer = wx.BoxSizer(wx.VERTICAL)
  2420. dataSizer = wx.BoxSizer(wx.VERTICAL)
  2421. flexSizer = wx.FlexGridSizer(cols = 2, hgap = 5, vgap = 5)
  2422. flexSizer.Add(item = label,
  2423. flag = wx.ALIGN_CENTER_VERTICAL)
  2424. flexSizer.Add(item = self.deleteLayer,
  2425. flag = wx.ALIGN_CENTER_VERTICAL)
  2426. dataSizer.Add(item = flexSizer,
  2427. proportion = 0,
  2428. flag = wx.ALL | wx.EXPAND,
  2429. border = 1)
  2430. dataSizer.Add(item = self.deleteTable,
  2431. proportion = 0,
  2432. flag = wx.ALL | wx.EXPAND,
  2433. border = 1)
  2434. pageSizer.Add(item = dataSizer,
  2435. proportion = 1,
  2436. flag = wx.ALL | wx.EXPAND,
  2437. border = 5)
  2438. pageSizer.Add(item = btnDelete,
  2439. proportion = 0,
  2440. flag = wx.ALL | wx.ALIGN_RIGHT,
  2441. border = 5)
  2442. self.deletePanel.SetSizer(pageSizer)
  2443. def _createModifyPage(self):
  2444. """!Modify layer"""
  2445. self.modifyPanel = wx.Panel(parent = self, id = wx.ID_ANY)
  2446. self.AddPage(page = self.modifyPanel, text = _("Modify layer"))
  2447. #
  2448. # list of layer widgets (label, value)
  2449. #
  2450. self.modifyLayerWidgets = {'layer':
  2451. (wx.StaticText(parent = self.modifyPanel, id = wx.ID_ANY,
  2452. label = '%s:' % _("Layer")),
  2453. wx.ComboBox(parent = self.modifyPanel, id = wx.ID_ANY,
  2454. size = (100, -1),
  2455. style = wx.CB_SIMPLE | wx.CB_READONLY,
  2456. choices = map(str,
  2457. self.mapDBInfo.layers.keys()))),
  2458. 'driver':
  2459. (wx.StaticText(parent = self.modifyPanel, id = wx.ID_ANY,
  2460. label = '%s:' % _("Driver")),
  2461. wx.Choice(parent = self.modifyPanel, id = wx.ID_ANY,
  2462. size = (200, -1),
  2463. choices = self.listOfDrivers)),
  2464. 'database':
  2465. (wx.StaticText(parent = self.modifyPanel, id = wx.ID_ANY,
  2466. label = '%s:' % _("Database")),
  2467. wx.TextCtrl(parent = self.modifyPanel, id = wx.ID_ANY,
  2468. value = '', size = (350, -1),
  2469. style = wx.TE_PROCESS_ENTER)),
  2470. 'table':
  2471. (wx.StaticText(parent = self.modifyPanel, id = wx.ID_ANY,
  2472. label = '%s:' % _("Table")),
  2473. wx.Choice(parent = self.modifyPanel, id = wx.ID_ANY,
  2474. size = (200, -1),
  2475. choices = self.defaultTables)),
  2476. 'key':
  2477. (wx.StaticText(parent = self.modifyPanel, id = wx.ID_ANY,
  2478. label = '%s:' % _("Key column")),
  2479. wx.Choice(parent = self.modifyPanel, id = wx.ID_ANY,
  2480. size = (200, -1),
  2481. choices = self.defaultColumns))}
  2482. # set default values for widgets
  2483. self.modifyLayerWidgets['layer'][1].SetSelection(0)
  2484. try:
  2485. layer = int(self.modifyLayerWidgets['layer'][1].GetStringSelection())
  2486. except ValueError:
  2487. layer = None
  2488. for label in self.modifyLayerWidgets.keys():
  2489. self.modifyLayerWidgets[label][1].Enable(False)
  2490. if layer:
  2491. driver = self.mapDBInfo.layers[layer]['driver']
  2492. database = self.mapDBInfo.layers[layer]['database']
  2493. table = self.mapDBInfo.layers[layer]['table']
  2494. listOfColumns = self._getColumns(driver, database, table)
  2495. self.modifyLayerWidgets['driver'][1].SetStringSelection(driver)
  2496. self.modifyLayerWidgets['database'][1].SetValue(database)
  2497. if table in self.modifyLayerWidgets['table'][1].GetItems():
  2498. self.modifyLayerWidgets['table'][1].SetStringSelection(table)
  2499. else:
  2500. if self.defaultConnect['schema'] != '':
  2501. table = self.defaultConnect['schema'] + table # try with default schema
  2502. else:
  2503. table = 'public.' + table # try with 'public' schema
  2504. self.modifyLayerWidgets['table'][1].SetStringSelection(table)
  2505. self.modifyLayerWidgets['key'][1].SetItems(listOfColumns)
  2506. self.modifyLayerWidgets['key'][1].SetSelection(0)
  2507. # events
  2508. self.modifyLayerWidgets['layer'][1].Bind(wx.EVT_COMBOBOX, self.OnChangeLayer)
  2509. # self.modifyLayerWidgets['driver'][1].Bind(wx.EVT_CHOICE, self.OnDriverChanged)
  2510. # self.modifyLayerWidgets['database'][1].Bind(wx.EVT_TEXT_ENTER, self.OnDatabaseChanged)
  2511. # self.modifyLayerWidgets['table'][1].Bind(wx.EVT_CHOICE, self.OnTableChanged)
  2512. btnModify = wx.Button(self.modifyPanel, wx.ID_DELETE, _("&Modify layer"),
  2513. size = (125,-1))
  2514. btnModify.Bind(wx.EVT_BUTTON, self.OnModifyLayer)
  2515. #
  2516. # do layout
  2517. #
  2518. pageSizer = wx.BoxSizer(wx.VERTICAL)
  2519. # data area
  2520. dataSizer = wx.FlexGridSizer(cols = 2, hgap = 5, vgap = 5)
  2521. dataSizer.AddGrowableCol(1)
  2522. for key in ('layer', 'driver', 'database', 'table', 'key'):
  2523. label, value = self.modifyLayerWidgets[key]
  2524. dataSizer.Add(item = label,
  2525. flag = wx.ALIGN_CENTER_VERTICAL)
  2526. if key == 'layer':
  2527. dataSizer.Add(item = value,
  2528. flag = wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_LEFT)
  2529. else:
  2530. dataSizer.Add(item = value,
  2531. flag = wx.ALIGN_CENTER_VERTICAL)
  2532. pageSizer.Add(item = dataSizer,
  2533. proportion = 1,
  2534. flag = wx.ALL | wx.EXPAND,
  2535. border = 5)
  2536. pageSizer.Add(item = btnModify,
  2537. proportion = 0,
  2538. flag = wx.ALL | wx.ALIGN_RIGHT,
  2539. border = 5)
  2540. self.modifyPanel.SetSizer(pageSizer)
  2541. def _getTables(self, driver, database):
  2542. """!Get list of tables for given driver and database"""
  2543. tables = []
  2544. ret = RunCommand('db.tables',
  2545. parent = self,
  2546. read = True,
  2547. flags = 'p',
  2548. driver = driver,
  2549. database = database)
  2550. if ret is None:
  2551. GError(parent = self,
  2552. message = _("Unable to get list of tables.\n"
  2553. "Please use db.connect to set database parameters."))
  2554. return tables
  2555. for table in ret.splitlines():
  2556. tables.append(table)
  2557. return tables
  2558. def _getColumns(self, driver, database, table):
  2559. """!Get list of column of given table"""
  2560. columns = []
  2561. ret = RunCommand('db.columns',
  2562. parent = self,
  2563. quiet = True,
  2564. read = True,
  2565. driver = driver,
  2566. database = database,
  2567. table = table)
  2568. if ret == None:
  2569. return columns
  2570. for column in ret.splitlines():
  2571. columns.append(column)
  2572. return columns
  2573. def OnDriverChanged(self, event):
  2574. """!Driver selection changed, update list of tables"""
  2575. driver = event.GetString()
  2576. database = self.addLayerWidgets['database'][1].GetValue()
  2577. winTable = self.addLayerWidgets['table'][1]
  2578. winKey = self.addLayerWidgets['key'][1]
  2579. tables = self._getTables(driver, database)
  2580. winTable.SetItems(tables)
  2581. winTable.SetSelection(0)
  2582. if len(tables) == 0:
  2583. winKey.SetItems([])
  2584. event.Skip()
  2585. def OnDatabaseChanged(self, event):
  2586. """!Database selection changed, update list of tables"""
  2587. event.Skip()
  2588. def OnTableChanged(self, event):
  2589. """!Table name changed, update list of columns"""
  2590. driver = self.addLayerWidgets['driver'][1].GetStringSelection()
  2591. database = self.addLayerWidgets['database'][1].GetValue()
  2592. table = event.GetString()
  2593. win = self.addLayerWidgets['key'][1]
  2594. cols = self._getColumns(driver, database, table)
  2595. win.SetItems(cols)
  2596. win.SetSelection(0)
  2597. event.Skip()
  2598. def OnSetDefault(self, event):
  2599. """!Set default values"""
  2600. driver = self.addLayerWidgets['driver'][1]
  2601. database = self.addLayerWidgets['database'][1]
  2602. table = self.addLayerWidgets['table'][1]
  2603. key = self.addLayerWidgets['key'][1]
  2604. driver.SetStringSelection(self.defaultConnect['driver'])
  2605. database.SetValue(self.defaultConnect['database'])
  2606. tables = self._getTables(self.defaultConnect['driver'],
  2607. self.defaultConnect['database'])
  2608. table.SetItems(tables)
  2609. table.SetSelection(0)
  2610. if len(tables) == 0:
  2611. key.SetItems([])
  2612. else:
  2613. cols = self._getColumns(self.defaultConnect['driver'],
  2614. self.defaultConnect['database'],
  2615. tables[0])
  2616. key.SetItems(cols)
  2617. key.SetSelection(0)
  2618. event.Skip()
  2619. def OnCreateTable(self, event):
  2620. """!Create new table (name and key column given)"""
  2621. driver = self.addLayerWidgets['driver'][1].GetStringSelection()
  2622. database = self.addLayerWidgets['database'][1].GetValue()
  2623. table = self.tableWidgets['table'][1].GetValue()
  2624. key = self.tableWidgets['key'][1].GetValue()
  2625. if not table or not key:
  2626. GError(parent = self,
  2627. message = _("Unable to create new table. "
  2628. "Table name or key column name is missing."))
  2629. return
  2630. if table in self.addLayerWidgets['table'][1].GetItems():
  2631. GError(parent = self,
  2632. message = _("Unable to create new table. "
  2633. "Table <%s> already exists in the database.") % table)
  2634. return
  2635. # create table
  2636. sql = 'CREATE TABLE %s (%s INTEGER)' % (table, key)
  2637. RunCommand('db.execute',
  2638. quiet = True,
  2639. parent = self,
  2640. stdin = sql,
  2641. input = '-',
  2642. driver = driver,
  2643. database = database)
  2644. # update list of tables
  2645. tableList = self.addLayerWidgets['table'][1]
  2646. tableList.SetItems(self._getTables(driver, database))
  2647. tableList.SetStringSelection(table)
  2648. # update key column selection
  2649. keyList = self.addLayerWidgets['key'][1]
  2650. keyList.SetItems(self._getColumns(driver, database, table))
  2651. keyList.SetStringSelection(key)
  2652. event.Skip()
  2653. def OnAddLayer(self, event):
  2654. """!Add new layer to vector map"""
  2655. layer = int(self.addLayerWidgets['layer'][1].GetValue())
  2656. layerWin = self.addLayerWidgets['layer'][1]
  2657. driver = self.addLayerWidgets['driver'][1].GetStringSelection()
  2658. database = self.addLayerWidgets['database'][1].GetValue()
  2659. table = self.addLayerWidgets['table'][1].GetStringSelection()
  2660. key = self.addLayerWidgets['key'][1].GetStringSelection()
  2661. if layer in self.mapDBInfo.layers.keys():
  2662. GError(parent = self,
  2663. message = _("Unable to add new layer to vector map <%(vector)s>. "
  2664. "Layer %(layer)d already exists.") % \
  2665. {'vector' : self.mapDBInfo.map, 'layer' : layer})
  2666. return
  2667. # add new layer
  2668. ret = RunCommand('v.db.connect',
  2669. parent = self,
  2670. quiet = True,
  2671. map = self.mapDBInfo.map,
  2672. driver = driver,
  2673. database = database,
  2674. table = table,
  2675. key = key,
  2676. layer = layer)
  2677. # insert records into table if required
  2678. if self.addLayerWidgets['addCat'][0].IsChecked():
  2679. RunCommand('v.to.db',
  2680. parent = self,
  2681. quiet = True,
  2682. map = self.mapDBInfo.map,
  2683. layer = layer,
  2684. qlayer = layer,
  2685. option = 'cat',
  2686. columns = key)
  2687. if ret == 0:
  2688. # update dialog (only for new layer)
  2689. self.parentDialog.parentDbMgrBase.UpdateDialog(layer = layer)
  2690. # update db info
  2691. self.mapDBInfo = self.parentDialog.dbMgrData['mapDBInfo']
  2692. # increase layer number
  2693. layerWin.SetValue(layer+1)
  2694. if len(self.mapDBInfo.layers.keys()) == 1:
  2695. # first layer add --- enable previously disabled widgets
  2696. self.deleteLayer.Enable()
  2697. self.deleteTable.Enable()
  2698. for label in self.modifyLayerWidgets.keys():
  2699. self.modifyLayerWidgets[label][1].Enable()
  2700. def OnDeleteLayer(self, event):
  2701. """!Delete layer"""
  2702. try:
  2703. layer = int(self.deleteLayer.GetValue())
  2704. except:
  2705. return
  2706. RunCommand('v.db.connect',
  2707. parent = self,
  2708. flags = 'd',
  2709. map = self.mapDBInfo.map,
  2710. layer = layer)
  2711. # drop also table linked to layer which is deleted
  2712. if self.deleteTable.IsChecked():
  2713. driver = self.addLayerWidgets['driver'][1].GetStringSelection()
  2714. database = self.addLayerWidgets['database'][1].GetValue()
  2715. table = self.mapDBInfo.layers[layer]['table']
  2716. sql = 'DROP TABLE %s' % (table)
  2717. RunCommand('db.execute',
  2718. parent = self,
  2719. stdin = sql,
  2720. quiet = True,
  2721. driver = driver,
  2722. database = database)
  2723. # update list of tables
  2724. tableList = self.addLayerWidgets['table'][1]
  2725. tableList.SetItems(self._getTables(driver, database))
  2726. tableList.SetStringSelection(table)
  2727. # update dialog
  2728. self.parentDialog.parentDbMgrBase.UpdateDialog(layer = layer)
  2729. # update db info
  2730. self.mapDBInfo = self.parentDialog.dbMgrData['mapDBInfo']
  2731. if len(self.mapDBInfo.layers.keys()) == 0:
  2732. # disable selected widgets
  2733. self.deleteLayer.Enable(False)
  2734. self.deleteTable.Enable(False)
  2735. for label in self.modifyLayerWidgets.keys():
  2736. self.modifyLayerWidgets[label][1].Enable(False)
  2737. event.Skip()
  2738. def OnChangeLayer(self, event):
  2739. """!Layer number of layer to be deleted is changed"""
  2740. try:
  2741. layer = int(event.GetString())
  2742. except:
  2743. try:
  2744. layer = self.mapDBInfo.layers.keys()[0]
  2745. except:
  2746. return
  2747. if self.GetCurrentPage() == self.modifyPanel:
  2748. driver = self.mapDBInfo.layers[layer]['driver']
  2749. database = self.mapDBInfo.layers[layer]['database']
  2750. table = self.mapDBInfo.layers[layer]['table']
  2751. listOfColumns = self._getColumns(driver, database, table)
  2752. self.modifyLayerWidgets['driver'][1].SetStringSelection(driver)
  2753. self.modifyLayerWidgets['database'][1].SetValue(database)
  2754. self.modifyLayerWidgets['table'][1].SetStringSelection(table)
  2755. self.modifyLayerWidgets['key'][1].SetItems(listOfColumns)
  2756. self.modifyLayerWidgets['key'][1].SetSelection(0)
  2757. else:
  2758. self.deleteTable.SetLabel(_('Drop also linked attribute table (%s)') % \
  2759. self.mapDBInfo.layers[layer]['table'])
  2760. if event:
  2761. event.Skip()
  2762. def OnModifyLayer(self, event):
  2763. """!Modify layer connection settings"""
  2764. layer = int(self.modifyLayerWidgets['layer'][1].GetStringSelection())
  2765. modify = False
  2766. if self.modifyLayerWidgets['driver'][1].GetStringSelection() != \
  2767. self.mapDBInfo.layers[layer]['driver'] or \
  2768. self.modifyLayerWidgets['database'][1].GetStringSelection() != \
  2769. self.mapDBInfo.layers[layer]['database'] or \
  2770. self.modifyLayerWidgets['table'][1].GetStringSelection() != \
  2771. self.mapDBInfo.layers[layer]['table'] or \
  2772. self.modifyLayerWidgets['key'][1].GetStringSelection() != \
  2773. self.mapDBInfo.layers[layer]['key']:
  2774. modify = True
  2775. if modify:
  2776. # delete layer
  2777. RunCommand('v.db.connect',
  2778. parent = self,
  2779. quiet = True,
  2780. flags = 'd',
  2781. map = self.mapDBInfo.map,
  2782. layer = layer)
  2783. # add modified layer
  2784. RunCommand('v.db.connect',
  2785. quiet = True,
  2786. map = self.mapDBInfo.map,
  2787. driver = self.modifyLayerWidgets['driver'][1].GetStringSelection(),
  2788. database = self.modifyLayerWidgets['database'][1].GetValue(),
  2789. table = self.modifyLayerWidgets['table'][1].GetStringSelection(),
  2790. key = self.modifyLayerWidgets['key'][1].GetStringSelection(),
  2791. layer = int(layer))
  2792. # update dialog (only for new layer)
  2793. self.parentDialog.parentDbMgrBase.UpdateDialog(layer = layer)
  2794. # update db info
  2795. self.mapDBInfo = self.parentDialog.dbMgrData['mapDBInfo']
  2796. event.Skip()
  2797. class FieldStatistics(wx.Frame):
  2798. def __init__(self, parent, id=wx.ID_ANY,
  2799. style = wx.DEFAULT_FRAME_STYLE, **kwargs):
  2800. """Dialog to display and save statistics of field stats
  2801. """
  2802. self.parent = parent
  2803. wx.Frame.__init__(self, parent, id, style = style, **kwargs)
  2804. self.SetTitle(_("Field statistics"))
  2805. self.SetIcon(wx.Icon(os.path.join(globalvar.ICONDIR, 'grass_sql.ico'), wx.BITMAP_TYPE_ICO))
  2806. self.panel = wx.Panel(parent = self, id = wx.ID_ANY)
  2807. self.sp = scrolled.ScrolledPanel(parent=self.panel, id=wx.ID_ANY, size=(250, 150),
  2808. style = wx.TAB_TRAVERSAL|wx.SUNKEN_BORDER, name="Statistics" )
  2809. self.text = wx.TextCtrl(parent=self.sp, id=wx.ID_ANY, style=wx.TE_MULTILINE|wx.TE_READONLY)
  2810. self.text.SetBackgroundColour("white")
  2811. # buttons
  2812. self.btnClipboard = wx.Button(parent=self.panel, id = wx.ID_COPY)
  2813. self.btnClipboard.SetToolTipString(_("Copy statistics the clipboard (Ctrl+C)"))
  2814. self.btnCancel = wx.Button(parent=self.panel, id=wx.ID_CLOSE)
  2815. self.btnCancel.SetDefault()
  2816. # bindings
  2817. self.btnCancel.Bind(wx.EVT_BUTTON, self.OnClose)
  2818. self.btnClipboard.Bind(wx.EVT_BUTTON, self.OnCopy)
  2819. self._layout()
  2820. def _layout(self):
  2821. sizer = wx.BoxSizer(wx.VERTICAL)
  2822. txtSizer = wx.BoxSizer(wx.VERTICAL)
  2823. btnSizer = wx.BoxSizer(wx.HORIZONTAL)
  2824. txtSizer.Add(item = self.text, proportion = 1,
  2825. flag = wx.EXPAND|wx.ALIGN_CENTER_VERTICAL|wx.ALL, border = 5)
  2826. self.sp.SetSizer(txtSizer)
  2827. self.sp.SetAutoLayout(True)
  2828. self.sp.SetupScrolling()
  2829. sizer.Add(item = self.sp, proportion = 1, flag = wx.GROW |
  2830. wx.LEFT | wx.RIGHT | wx.BOTTOM, border = 3)
  2831. line = wx.StaticLine(parent = self.panel, id = wx.ID_ANY,
  2832. size = (20, -1), style = wx.LI_HORIZONTAL)
  2833. sizer.Add(item = line, proportion = 0,
  2834. flag = wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.LEFT|wx.RIGHT, border = 3)
  2835. # buttons
  2836. btnSizer.Add(item = self.btnClipboard, proportion = 0,
  2837. flag = wx.ALIGN_LEFT | wx.ALL, border = 5)
  2838. btnSizer.Add(item = self.btnCancel, proportion = 0,
  2839. flag = wx.ALIGN_RIGHT | wx.ALL, border = 5)
  2840. sizer.Add(item = btnSizer, proportion = 0, flag = wx.ALIGN_RIGHT | wx.ALL, border = 5)
  2841. self.panel.SetSizer(sizer)
  2842. sizer.Fit(self.panel)
  2843. def OnCopy(self, event):
  2844. """!Copy the statistics to the clipboard
  2845. """
  2846. stats = self.text.GetValue()
  2847. rdata = wx.TextDataObject()
  2848. rdata.SetText(stats)
  2849. if wx.TheClipboard.Open():
  2850. wx.TheClipboard.SetData(rdata)
  2851. wx.TheClipboard.Close()
  2852. def OnClose(self, event):
  2853. """!Button 'Close' pressed
  2854. """
  2855. self.Close(True)
  2856. def Update(self, driver, database, table, column):
  2857. """!Update statistics for given column
  2858. :param: column column name
  2859. """
  2860. if driver == 'dbf':
  2861. GError(parent=self,
  2862. message=_("Statistics is not support for DBF tables."))
  2863. self.Close()
  2864. return
  2865. fd, sqlFilePath = tempfile.mkstemp(text=True)
  2866. sqlFile = open(sqlFilePath, 'w')
  2867. stats = ['count', 'min', 'max', 'avg', 'sum', 'null']
  2868. for fn in stats:
  2869. if fn == 'null':
  2870. sqlFile.write('select count(*) from %s where %s is null;%s' % (table, column, os.linesep))
  2871. else:
  2872. sqlFile.write('select %s(%s) from %s;%s' % (fn, column, table, os.linesep))
  2873. sqlFile.close()
  2874. dataStr = RunCommand('db.select',
  2875. parent = self.parent,
  2876. read = True,
  2877. flags='c',
  2878. input = sqlFilePath,
  2879. driver = driver,
  2880. database = database)
  2881. if not dataStr:
  2882. GError(parent = self.parent,
  2883. message = _("Unable to calculte statistics."))
  2884. self.Close()
  2885. return
  2886. dataLines = dataStr.splitlines()
  2887. if len(dataLines) != len(stats):
  2888. GError(parent = self.parent,
  2889. message = _("Unable to calculte statistics. "
  2890. "Invalid number of lines %d (should be %d).") % (len(dataLines), len(stats)))
  2891. self.Close()
  2892. return
  2893. # calculate stddev
  2894. avg = float(dataLines[stats.index('avg')])
  2895. count = float(dataLines[stats.index('count')])
  2896. sql = "select (%(column)s - %(avg)f)*(%(column)s - %(avg)f) from %(table)s" % { 'column' : column, 'avg' : avg, 'table' : table }
  2897. dataVar = RunCommand('db.select',
  2898. parent = self.parent,
  2899. read = True,
  2900. flags='c',
  2901. sql = sql,
  2902. driver = driver,
  2903. database = database)
  2904. if not dataVar:
  2905. GWarning(parent = self.parent,
  2906. message = _("Unable to calculte standard deviation."))
  2907. varSum = 0
  2908. for var in dataVar.splitlines():
  2909. varSum += float(var)
  2910. stddev = math.sqrt(varSum/count)
  2911. self.SetTitle(_("Field statistics <%s>") % column)
  2912. self.text.Clear()
  2913. for idx in range(len(stats)):
  2914. self.text.AppendText('%s: %s\n' % (stats[idx], dataLines[idx]))
  2915. self.text.AppendText('stddev: %f\n' % stddev)